Added the first days
This commit is contained in:
parent
7ebc2cd003
commit
6ed18289c8
26 changed files with 6168 additions and 0 deletions
239
2021/05/line/line.go
Normal file
239
2021/05/line/line.go
Normal file
|
@ -0,0 +1,239 @@
|
|||
package line
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Line struct {
|
||||
X1, Y1, X2, Y2 int
|
||||
}
|
||||
|
||||
// Token represents a lexical token.
|
||||
type Token int
|
||||
|
||||
const (
|
||||
// Special tokens
|
||||
ILLEGAL Token = iota
|
||||
EOF
|
||||
WHITESPACE
|
||||
|
||||
// Literals
|
||||
INT // integers
|
||||
|
||||
// Misc characters
|
||||
COMMA // ,
|
||||
ARROW // ->
|
||||
)
|
||||
|
||||
func isWhitespace(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || ch == '\n'
|
||||
}
|
||||
|
||||
func isDigit(ch rune) bool {
|
||||
return (ch >= '0' && ch <= '9')
|
||||
}
|
||||
|
||||
var eof = rune(0)
|
||||
|
||||
// Scanner represents a lexical scanner.
|
||||
type Scanner struct {
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
// NewScanner returns a new instance of Scanner.
|
||||
func NewScanner(r io.Reader) *Scanner {
|
||||
return &Scanner{r: bufio.NewReader(r)}
|
||||
}
|
||||
|
||||
// read reads the next rune from the bufferred reader.
|
||||
// Returns the rune(0) if an error occurs (or io.EOF is returned).
|
||||
func (s *Scanner) read() rune {
|
||||
ch, _, err := s.r.ReadRune()
|
||||
if err != nil {
|
||||
return eof
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
// unread places the previously read rune back on the reader.
|
||||
func (s *Scanner) unread() { _ = s.r.UnreadRune() }
|
||||
|
||||
// Scan returns the next token and literal value.
|
||||
func (s *Scanner) Scan() (tok Token, lit string) {
|
||||
// Read the next rune.
|
||||
ch := s.read()
|
||||
|
||||
// If we see whitespace then consume all contiguous whitespace.
|
||||
// If we see a digit then consume as an int.
|
||||
if isWhitespace(ch) {
|
||||
return s.scanWhitespace(ch)
|
||||
} else if isDigit(ch) {
|
||||
return s.scanInt(ch)
|
||||
}
|
||||
|
||||
// Otherwise read the individual character.
|
||||
switch ch {
|
||||
case eof:
|
||||
return EOF, ""
|
||||
case ',':
|
||||
return COMMA, string(ch)
|
||||
case '-':
|
||||
return s.scanArrow(ch)
|
||||
}
|
||||
|
||||
return ILLEGAL, string(ch)
|
||||
}
|
||||
|
||||
// scanWhitespace consumes the current rune and all contiguous whitespace.
|
||||
func (s *Scanner) scanWhitespace(read rune) (tok Token, lit string) {
|
||||
// Create a buffer and read the current character into it.
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune(read)
|
||||
|
||||
// Read every subsequent whitespace character into the buffer.
|
||||
// Non-whitespace characters and EOF will cause the loop to exit.
|
||||
for {
|
||||
if ch := s.read(); ch == eof {
|
||||
break
|
||||
} else if !isWhitespace(ch) {
|
||||
s.unread()
|
||||
break
|
||||
} else {
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return WHITESPACE, buf.String()
|
||||
}
|
||||
|
||||
// scanInt consumes the current rune and all contiguous digit runes.
|
||||
func (s *Scanner) scanInt(read rune) (tok Token, lit string) {
|
||||
// Create a buffer and read the current character into it.
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune(read)
|
||||
|
||||
// Read every subsequent ident character into the buffer.
|
||||
// Non-ident characters and EOF will cause the loop to exit.
|
||||
for {
|
||||
if ch := s.read(); ch == eof {
|
||||
break
|
||||
} else if !isDigit(ch) {
|
||||
s.unread()
|
||||
break
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise return as a regular identifier.
|
||||
return INT, buf.String()
|
||||
}
|
||||
|
||||
func (s *Scanner) scanArrow(read rune) (tok Token, lit string) {
|
||||
// Create a buffer and read the current character into it.
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune(read)
|
||||
|
||||
// Read every subsequent ident character into the buffer.
|
||||
// Non-ident characters and EOF will cause the loop to exit.
|
||||
for {
|
||||
if ch := s.read(); ch == eof {
|
||||
break
|
||||
} else if ch == '>' {
|
||||
_, _ = buf.WriteRune(ch)
|
||||
return ARROW, buf.String()
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise return as a regular identifier.
|
||||
return ILLEGAL, buf.String()
|
||||
}
|
||||
|
||||
// Parser represents a parser.
|
||||
type Parser struct {
|
||||
s *Scanner
|
||||
buf struct {
|
||||
tok Token // last read token
|
||||
lit string // last read literal
|
||||
n int // buffer size (max=1)
|
||||
}
|
||||
}
|
||||
|
||||
// NewParser returns a new instance of Parser.
|
||||
func NewParser(r io.Reader) *Parser {
|
||||
return &Parser{s: NewScanner(r)}
|
||||
}
|
||||
|
||||
// scan returns the next token from the underlying scanner.
|
||||
// If a token has been unscanned then read that instead.
|
||||
func (p *Parser) scan() (tok Token, lit string) {
|
||||
// If we have a token on the buffer, then return it.
|
||||
if p.buf.n != 0 {
|
||||
p.buf.n = 0
|
||||
return p.buf.tok, p.buf.lit
|
||||
}
|
||||
|
||||
// Otherwise read the next token from the scanner.
|
||||
tok, lit = p.s.Scan()
|
||||
|
||||
// Save it to the buffer in case we unscan later.
|
||||
p.buf.tok, p.buf.lit = tok, lit
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// unscan pushes the previously read token back onto the buffer.
|
||||
func (p *Parser) unscan() { p.buf.n = 1 }
|
||||
|
||||
// scanIgnoreWhitespace scans the next non-whitespace token.
|
||||
func (p *Parser) scanIgnoreWhitespace() (tok Token, lit string) {
|
||||
tok, lit = p.scan()
|
||||
if tok == WHITESPACE {
|
||||
tok, lit = p.scan()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) Parse() (*Line, error) {
|
||||
l := &Line{}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != INT {
|
||||
return nil, fmt.Errorf("found %q, expected INT", lit)
|
||||
} else {
|
||||
l.X1, _ = strconv.Atoi(lit)
|
||||
}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != COMMA {
|
||||
return nil, fmt.Errorf("found %q, expected COMMA", lit)
|
||||
}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != INT {
|
||||
return nil, fmt.Errorf("found %q, expected INT", lit)
|
||||
} else {
|
||||
l.Y1, _ = strconv.Atoi(lit)
|
||||
}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != ARROW {
|
||||
return nil, fmt.Errorf("found %q, expected ARROW", lit)
|
||||
}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != INT {
|
||||
return nil, fmt.Errorf("found %q, expected INT", lit)
|
||||
} else {
|
||||
l.X2, _ = strconv.Atoi(lit)
|
||||
}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != COMMA {
|
||||
return nil, fmt.Errorf("found %q, expected COMMA", lit)
|
||||
}
|
||||
if tok, lit := p.scanIgnoreWhitespace(); tok != INT {
|
||||
return nil, fmt.Errorf("found %q, expected INT", lit)
|
||||
} else {
|
||||
l.Y2, _ = strconv.Atoi(lit)
|
||||
}
|
||||
if tok, lit := p.scan(); tok != WHITESPACE {
|
||||
return nil, fmt.Errorf("found %q, expected WHITESPACE", lit)
|
||||
}
|
||||
return l, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue