commit 7de8b91547a216dffde3a17898b377d8002214c8
parent 406889154e7971cfc6e9beefa382e8389ac542f5
Author: weetat <wtenison@protonmail.com>
Date: Tue, 25 Feb 2025 22:38:13 +0000
Start recursive descent parser for fmd
Parse keyword:literal and keyword:{block} commands and plain blocks into
a node tree. Literal parsing is still a stub.
Diffstat:
2 files changed, 99 insertions(+), 21 deletions(-)
diff --git a/src/fmd/lexer.go b/src/fmd/lexer.go
@@ -90,22 +90,3 @@ func (l *lexer) Exec() []token {
return l.tokens
}
-
-// func (l *lexer) checkWord() *token {
-// _start := l.pos
-
-// for l.pos < len(l.input) && !strings.Contains(":{} \\", string(l.input[l.pos])) {
-// l.pos++
-// }
-
-// _token := l.input[_start:l.pos]
-// if _token == "" {
-// return nil
-// }
-
-// if slices.Contains(opKeywords, _token) {
-// return &token{TokenKeyword, _token}
-// }
-
-// return &token{TokenLiteral, _token}
-// }
diff --git a/src/fmd/parser.go b/src/fmd/parser.go
@@ -1,5 +1,102 @@
package fmd
-/* Reference source: Recursive descent parser,
-https://en.wikipedia.org/wiki/Recursive_descent_parser */
+import (
+ "fmt"
+)
+type nodeType string
+
+const (
+ nodeFMD nodeType = "fmd"
+ nodeBlock nodeType = "block"
+ nodeCommand nodeType = "command"
+ nodeSpace nodeType = "space"
+)
+
+type node struct {
+ nType nodeType
+ value string
+ children []node
+}
+
+type parser struct {
+ tokens []token
+ pos int
+}
+
+func MakeParser(tokens []token) *parser {
+ return &parser{tokens: tokens, pos: 0}
+}
+
+func (p *parser) Exec() node {
+ _fmd := node{nType: nodeFMD}
+ for p.pos < len(p.tokens) {
+ if p.peekNextN(TokenKeyword, 0) &&
+ p.peekNextN(TokenColon, 1) &&
+ p.peekNextN(TokenLiteral, 2) ||
+ p.peekNextN(TokenLBrace, 2) {
+ _fmd.children = append(_fmd.children, p.parseCommand())
+ } else {
+ _fmd.children = append(_fmd.children, p.parseBlock())
+ }
+ }
+ return _fmd
+}
+
+func (p *parser) parseCommand() node {
+ _command := node{nType: nodeCommand}
+
+ _keyword := p.consume(TokenKeyword)
+ _command.value = _keyword.Value
+
+ p.consume(TokenColon)
+
+ if p.peekNextN(TokenLBrace, 0) {
+ _block := p.parseBlock()
+ _command.children = []node{_block}
+
+ p.consume(TokenRBrace)
+ } else {
+ _literal := p.parseLiteral()
+ _command.children = []node{_literal}
+ }
+
+ return _command
+}
+
+func (p *parser) parseBlock() node {
+ _block := node{nType: nodeBlock}
+
+ for p.pos < len(p.tokens) && p.current().Type != TokenRBrace {
+ if p.peekNextN(TokenKeyword, 0) && p.peekNextN(TokenColon, 1) {
+ _block.children = append(_block.children, p.parseCommand())
+ } else if p.peekNextN(TokenSpace, 0) {
+ _block.children = append(_block.children, node{nType: nodeSpace, value: " "})
+ } else {
+ _block.children = append(_block.children, p.parseLiteral())
+ }
+ }
+
+ return _block
+}
+
+func (p *parser) parseLiteral() node {
+ return node{}
+}
+
+func (p *parser) peekNextN(t tokenType, n int) bool {
+ return p.pos+n < len(p.tokens) && p.tokens[p.pos+n].Type == t
+}
+
+func (p *parser) consume(t tokenType) token {
+ if p.peekNextN(t, 0) {
+ token := p.tokens[p.pos]
+ p.pos++
+ return token
+ }
+ panic(fmt.Sprintf("__PANIC\tInvalid token:%v", t))
+}
+
+func (p *parser) current() token {
+ return p.tokens[p.pos]
+}