miki

Miki: personal wiki with the fmd markup format
git clone https://wtenison.com/repo/miki.git
Log | Files | Refs | README | LICENSE

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:
Msrc/fmd/lexer.go | 19-------------------
Msrc/fmd/parser.go | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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] +}