Skip to content

Instantly share code, notes, and snippets.

@jbweber
Created October 10, 2024 17:50
Show Gist options
  • Save jbweber/2e0fba853f52900b560d70d63959600d to your computer and use it in GitHub Desktop.
Save jbweber/2e0fba853f52900b560d70d63959600d to your computer and use it in GitHub Desktop.
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