package parser
import (
"fmt"
"slices"
"github.com/marzeq/qk/shared"
"github.com/marzeq/qk/tokeniser"
)
type Parser struct {
pos int
tokens []tokeniser.Token
posStack []int
}
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) 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) 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,
},
}
for !p.Match(tokeniser.TokenEof) {
for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
p.Inc()
}
e := shared.NewError(p.CurrLoc(), "expected function definition, constant definition, type alias or import statement")
if !p.Match(tokeniser.TokenKeyword) {
return nil, e
}
switch p.Peek().Value {
case string(tokeniser.KeywordPub):
p.Inc()
if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordLet) && p.Peek().Value != string(tokeniser.KeywordExtern) {
return nil, e
}
stmt, _, err := p.ParseStatement()
if err != nil {
return nil, err
}
switch s := stmt.(type) {
case *FunctionDefNode:
s.Pub = true
case *DeclarationNode:
s.Pub = true
case *TypeAliasNode:
s.Pub = true
default:
return nil, e
}
rootNode.Body = append(rootNode.Body, stmt)
case string(tokeniser.KeywordLet):
stmt, _, err := p.ParseStatement()
if err != nil {
return nil, err
}
switch stmt.(type) {
case *FunctionDefNode, *DeclarationNode, *TypeAliasNode:
rootNode.Body = append(rootNode.Body, stmt)
default:
return nil, e
}
case string(tokeniser.KeywordExtern):
stmt, _, err := p.ParseStatement()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, stmt)
case string(tokeniser.KeywordImport):
imp, err := p.ParseImport()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, imp)
case string(tokeniser.KeywordModule):
mod, err := p.ParseModule()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, mod)
default:
return nil, e
}
if p.Match(tokeniser.TokenEof) {
break
}
if !p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
return nil, shared.NewError(p.CurrLoc(), "expected ';' or '\\n'")
}
for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
p.Inc()
}
}
return rootNode, nil
}