package parser import ( "errors" "fmt" "slices" "github.com/marzeq/qk/shared" "github.com/marzeq/qk/tokeniser" ) type Parser struct { pos int tokens []tokeniser.Token posStack []int allowTypeCapture bool } func NewParser(tokens []tokeniser.Token) *Parser { return &Parser{ pos: 0, tokens: tokens, } } func (p *Parser) PushPos() { p.posStack = append(p.posStack, p.pos) } func (p *Parser) PopPos() { if len(p.posStack) == 0 { return } last := len(p.posStack) - 1 p.pos = p.posStack[last] p.posStack = p.posStack[:last] } func (p *Parser) CommitPos() { if len(p.posStack) == 0 { return } p.posStack = p.posStack[:len(p.posStack)-1] } func (p *Parser) Peek() tokeniser.Token { if p.pos >= len(p.tokens) || p.pos < 0 { return tokeniser.Token{Type: tokeniser.TokenEof} } return p.tokens[p.pos] } func (p *Parser) Next() tokeniser.Token { pos := p.pos + 1 if pos >= len(p.tokens) || pos < 0 { return tokeniser.Token{Type: tokeniser.TokenEof} } return p.tokens[pos] } func (p *Parser) Inc() *Parser { p.pos++ return p } func (p *Parser) Dec() *Parser { p.pos-- return p } func (p *Parser) PrevLoc() shared.Location { l := p.Dec().Peek().Loc p.Inc() return l } func (p *Parser) CurrLoc() shared.Location { return p.Peek().Loc } func (p *Parser) SpanFrom(start shared.Location) shared.Location { if p.pos == 0 { return start } return start.WithEnd(p.tokens[p.pos-1].Loc) } func (p *Parser) Consume() tokeniser.Token { c := p.Peek() p.Inc() return c } func (p *Parser) Expect(expected tokeniser.TokenKind) bool { tok := p.Consume() ret := tok.Type == expected return ret } func (p *Parser) ExpectGet(expected tokeniser.TokenKind) (*tokeniser.Token, bool) { tok := p.Consume() if tok.Type != expected { return nil, false } return &tok, true } func (p *Parser) Match(ttypes ...tokeniser.TokenKind) bool { ptype := p.Peek().Type return slices.Contains(ttypes, ptype) } func (p *Parser) MatchAdjacentPair(ttype tokeniser.TokenKind) bool { current, next := p.Peek(), p.Next() return current.Type == ttype && next.Type == ttype && current.Loc.EndOffset == next.Loc.Offset } func (p *Parser) MatchBuiltin(name string) bool { return p.Match(tokeniser.TokenAt) && p.Next().Type == tokeniser.TokenIdentifier && p.Next().Value == name } func (p *Parser) ConsumeBuiltin(name string) bool { if !p.MatchBuiltin(name) { return false } p.Inc().Inc() return true } func (p *Parser) DumpCurrent() { fmt.Printf("Current token: %v\n", p.Peek()) } func (p *Parser) Parse() (*RootNode, error) { eofTok := p.tokens[len(p.tokens)-1] rootNode := &RootNode{ Loc: shared.Location{ LC: shared.LineCol{ Line: 1, Col: 1, }, FilePath: eofTok.Loc.FilePath, SourceText: eofTok.Loc.SourceText, EndLC: eofTok.Loc.LC, EndOffset: eofTok.Loc.Offset, }, } var parseErrors []error for !p.Match(tokeniser.TokenEof) { for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) { p.Inc() } if p.Match(tokeniser.TokenEof) { break } stmt, err := p.parseTopLevel() if err != nil { parseErrors = append(parseErrors, err) p.synchroniseTopLevel() continue } rootNode.Body = append(rootNode.Body, stmt) if p.Match(tokeniser.TokenEof) { break } if !p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) { parseErrors = append(parseErrors, shared.NewError(p.CurrLoc(), "expected ';' or '\\n'")) p.synchroniseTopLevel() continue } for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) { p.Inc() } } return rootNode, errors.Join(parseErrors...) } func (p *Parser) parseTopLevel() (Node, error) { startLoc := p.CurrLoc() e := shared.NewError(startLoc, "expected function definition, constant definition, type alias or import statement") if p.MatchBuiltin("compiler_error") || p.MatchBuiltin("compiler_assert") { return p.parseCompilerDirective() } if !p.Match(tokeniser.TokenKeyword) { return nil, e } isPublic := p.Peek().Value == string(tokeniser.KeywordPub) if isPublic { p.Inc() if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordLet) { return nil, e } } var node Node var err error switch p.Peek().Value { case string(tokeniser.KeywordLet): node, _, err = p.ParseStatement() case string(tokeniser.KeywordImport): node, err = p.ParseImport() case string(tokeniser.KeywordModule): node, err = p.ParseModule() case string(tokeniser.KeywordWhen): node, err = p.parseWhen(WhenDeclarations) default: return nil, e } if err != nil { return nil, err } switch n := node.(type) { case *FunctionDefNode: n.Pub = isPublic if isPublic { n.Loc = startLoc.WithEnd(n.Loc) } case *DeclarationNode: n.Pub = isPublic if isPublic { n.Loc = startLoc.WithEnd(n.Loc) } case *TypeAliasNode: n.Pub = isPublic if isPublic { n.Loc = startLoc.WithEnd(n.Loc) } default: if isPublic { return nil, e } } return node, nil } func isTopLevelStart(tok tokeniser.Token) bool { if tok.Type != tokeniser.TokenKeyword { return false } switch tok.Value { case string(tokeniser.KeywordPub), string(tokeniser.KeywordLet), string(tokeniser.KeywordImport), string(tokeniser.KeywordModule): return true default: return false } } func (p *Parser) synchroniseTopLevel() { // Failed speculative parses must not affect the next declaration. p.posStack = nil depth := 0 atBoundary := p.pos == 0 || p.tokens[p.pos-1].Type == tokeniser.TokenNewline || p.tokens[p.pos-1].Type == tokeniser.TokenSemicolon for !p.Match(tokeniser.TokenEof) { if depth == 0 && atBoundary && isTopLevelStart(p.Peek()) { return } tok := p.Consume() switch tok.Type { case tokeniser.TokenOpenCurly: depth++ case tokeniser.TokenCloseCurly: if depth > 0 { depth-- } } atBoundary = tok.Type == tokeniser.TokenNewline || tok.Type == tokeniser.TokenSemicolon } }