Created
October 10, 2024 17:50
-
-
Save jbweber/2e0fba853f52900b560d70d63959600d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"bytes" | |
"errors" | |
"fmt" | |
"io" | |
"strings" | |
) | |
type Token int | |
const ( | |
// | |
ILLEGAL Token = iota | |
EOF | |
WS | |
// LITERALS | |
IDENT | |
// | |
LEFT_BRACE | |
RIGHT_BRACE | |
QUOTE | |
MODULE | |
) | |
var eof = rune(0) | |
func isLetter(ch rune) bool { | |
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') | |
} | |
func isWhitespace(ch rune) bool { | |
return ch == ' ' || ch == '\t' || ch == '\n' | |
} | |
type Scanner struct { | |
r *bufio.Reader | |
} | |
func NewScanner(r io.Reader) *Scanner { | |
return &Scanner{bufio.NewReader(r)} | |
} | |
func (s *Scanner) read() rune { | |
ch, _, err := s.r.ReadRune() | |
if err != nil { | |
return eof | |
} | |
return ch | |
} | |
func (s *Scanner) unread() { _ = s.r.UnreadRune() } | |
func (s *Scanner) Scan() (tok Token, lit string) { | |
ch := s.read() | |
if isWhitespace(ch) { | |
s.unread() | |
return s.scanWhitespace() | |
} else if isLetter(ch) { | |
s.unread() | |
return s.scanIdent() | |
} | |
switch ch { | |
case eof: | |
return EOF, "" | |
case '{': | |
return LEFT_BRACE, string(ch) | |
case '}': | |
return RIGHT_BRACE, string(ch) | |
case '"': | |
return QUOTE, string(ch) | |
} | |
return ILLEGAL, string(ch) | |
} | |
func (s *Scanner) scanIdent() (tok Token, lit string) { | |
var buf bytes.Buffer | |
buf.WriteRune(s.read()) | |
for { | |
if ch := s.read(); ch == eof { | |
break | |
} else if !isLetter(ch) { | |
s.unread() | |
break | |
} else { | |
_, _ = buf.WriteRune(ch) | |
} | |
} | |
switch buf.String() { | |
case "module": | |
return MODULE, buf.String() | |
} | |
return IDENT, buf.String() | |
} | |
func (s *Scanner) scanWhitespace() (tok Token, lit string) { | |
var buf bytes.Buffer | |
buf.WriteRune(s.read()) | |
for { | |
if ch := s.read(); ch == eof { | |
break | |
} else if !isWhitespace(ch) { | |
s.unread() | |
break | |
} else { | |
buf.WriteRune(ch) | |
} | |
} | |
return WS, buf.String() | |
} | |
type Parser struct { | |
s *Scanner | |
buf struct { | |
tok Token // last read token | |
lit string // last read literal | |
n int // buffer size | |
} | |
} | |
func NewParser(r io.Reader) *Parser { | |
return &Parser{s: NewScanner(r)} | |
} | |
func (p *Parser) scan() (tok Token, lit string) { | |
// if we have a token return it | |
if p.buf.n != 0 { | |
p.buf.n = 0 | |
return p.buf.tok, p.buf.lit | |
} | |
tok, lit = p.s.Scan() | |
p.buf.tok, p.buf.lit = tok, lit | |
return | |
} | |
func (p *Parser) unscan() { | |
p.buf.n = 1 | |
} | |
func (p *Parser) scanIgnoreWhitespace() (tok Token, lit string) { | |
tok, lit = p.scan() | |
if tok == WS { | |
tok, lit = p.scan() | |
} | |
return | |
} | |
func (p *Parser) Parse() (*Module, error) { | |
module := &Module{} | |
// find module | |
for { | |
tok, _ := p.scanIgnoreWhitespace() | |
if tok == EOF { | |
return nil, nil | |
} | |
if tok == MODULE { | |
break | |
} | |
} | |
// find quote around name | |
tok, lit := p.scanIgnoreWhitespace() | |
if tok != QUOTE { | |
return nil, fmt.Errorf("expected '\"' but got '%s'", lit) | |
} | |
// find name | |
tok, lit = p.scanIgnoreWhitespace() | |
if tok != IDENT { | |
return nil, fmt.Errorf("expected identifier but got '%s'", lit) | |
} | |
module.name = lit | |
// find quote | |
tok, lit = p.scanIgnoreWhitespace() | |
if tok != QUOTE { | |
return nil, errors.New("expected '}' but got '}'") | |
} | |
return module, nil | |
} | |
type Module struct { | |
name string | |
} | |
func main() { | |
r := strings.NewReader(` ejje module "hello" { module "xyz"`) | |
parser := NewParser(r) | |
m, err := parser.Parse() | |
if err != nil { | |
fmt.Println(err) | |
} | |
fmt.Println(m) | |
n, err := parser.Parse() | |
if err != nil { | |
fmt.Println(err) | |
} | |
fmt.Println(n) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment