diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-06-14 23:22:02 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-06-15 19:16:58 +0900 |
commit | f16811613aeb79444a3555115e4031f68cd183b9 (patch) | |
tree | 3e4207ddd7f331fdcb11c86e2273bcbe262e9d0f /spec/parser.go | |
parent | Update README (diff) | |
download | cotia-f16811613aeb79444a3555115e4031f68cd183b9.tar.gz cotia-f16811613aeb79444a3555115e4031f68cd183b9.tar.xz |
Add spec parser
Currently, the parser only supports definitions of lexical specification.
Diffstat (limited to 'spec/parser.go')
-rw-r--r-- | spec/parser.go | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/spec/parser.go b/spec/parser.go new file mode 100644 index 0000000..a204cb7 --- /dev/null +++ b/spec/parser.go @@ -0,0 +1,148 @@ +package spec + +import ( + "io" +) + +type RootNode struct { + Productions []*ProductionNode +} + +type ProductionNode struct { + LHS string + RHS []*AlternativeNode +} + +type AlternativeNode struct { + Elements []*ElementNode +} + +type ElementNode struct { + Pattern string +} + +func raiseSyntaxError(synErr *SyntaxError) { + panic(synErr) +} + +func Parse(src io.Reader) (*RootNode, error) { + p, err := newParser(src) + if err != nil { + return nil, err + } + root, err := p.parse() + if err != nil { + return nil, err + } + return root, nil +} + +type parser struct { + lex *lexer + peekedTok *token + lastTok *token +} + +func newParser(src io.Reader) (*parser, error) { + lex, err := newLexer(src) + if err != nil { + return nil, err + } + return &parser{ + lex: lex, + }, nil +} + +func (p *parser) parse() (root *RootNode, retErr error) { + defer func() { + err := recover() + if err != nil { + retErr = err.(error) + return + } + }() + return p.parseRoot(), nil +} + +func (p *parser) parseRoot() *RootNode { + prod := p.parseProduction() + if prod == nil { + raiseSyntaxError(synErrNoProduction) + } + root := &RootNode{ + Productions: []*ProductionNode{prod}, + } + for { + prod := p.parseProduction() + if prod == nil { + break + } + root.Productions = append(root.Productions, prod) + } + return root +} + +func (p *parser) parseProduction() *ProductionNode { + if p.consume(tokenKindEOF) { + return nil + } + if !p.consume(tokenKindID) { + raiseSyntaxError(synErrNoProductionName) + } + lhs := p.lastTok.text + if !p.consume(tokenKindColon) { + raiseSyntaxError(synErrNoColon) + } + alt := p.parseAlternative() + if !p.consume(tokenKindSemicolon) { + raiseSyntaxError(synErrNoSemicolon) + } + return &ProductionNode{ + LHS: lhs, + RHS: []*AlternativeNode{alt}, + } +} + +func (p *parser) parseAlternative() *AlternativeNode { + elem := p.parseElement() + if elem == nil { + raiseSyntaxError(synErrNoElement) + } + return &AlternativeNode{ + Elements: []*ElementNode{elem}, + } +} + +func (p *parser) parseElement() *ElementNode { + if !p.consume(tokenKindTerminalPattern) { + return nil + } + return &ElementNode{ + Pattern: p.lastTok.text, + } +} + +func (p *parser) consume(expected tokenKind) bool { + var tok *token + var err error + if p.peekedTok != nil { + tok = p.peekedTok + p.peekedTok = nil + } else { + tok, err = p.lex.next() + if err != nil { + panic(err) + } + } + p.lastTok = tok + if tok.kind == tokenKindInvalid { + raiseSyntaxError(synErrInvalidToken) + } + if tok.kind == expected { + return true + } + p.peekedTok = tok + p.lastTok = nil + + return false +} |