package parser
import (
"slices"
"github.com/marzeq/qk/shared"
"github.com/marzeq/qk/tokeniser"
)
type Parser struct {
pos int
tokens []tokeniser.Token
}
func NewParser(tokens []tokeniser.Token) *Parser {
return &Parser{
pos: 0,
tokens: tokens,
}
}
func (p *Parser) Peek() tokeniser.Token {
if p.pos >= len(p.tokens) || p.pos < 0 {
return tokeniser.Token{Type: tokeniser.TOKEN_TYPE_EOF}
}
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.TOKEN_TYPE_EOF}
}
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.TokenType) bool {
tok := p.Consume()
ret := tok.Type == expected
return ret
}
func (p *Parser) ExpectGet(expected tokeniser.TokenType) (*tokeniser.Token, bool) {
tok := p.Consume()
if tok.Type != expected {
return nil, false
}
return &tok, true
}
func (p *Parser) Match(ttypes ...tokeniser.TokenType) bool {
ptype := p.Peek().Type
return slices.Contains(ttypes, ptype)
}
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.TOKEN_TYPE_EOF) {
for p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
p.Inc()
}
e := shared.NewError(p.CurrLoc(), "expected function definition, struct definition or import statement")
if !p.Match(tokeniser.TOKEN_TYPE_KEYWORD) {
return nil, e
}
switch p.Peek().Value {
case string(tokeniser.KEYWORD_PUB):
p.Inc()
if p.Peek().Type != tokeniser.TOKEN_TYPE_KEYWORD {
return nil, e
}
switch p.Peek().Value {
case
string(tokeniser.KEYWORD_LET),
string(tokeniser.KEYWORD_EXTERN):
fnDef, err := p.ParseFunctionDefinition()
if err != nil {
return nil, err
}
fnDef.Pub = true
rootNode.Body = append(rootNode.Body, fnDef)
case string(tokeniser.KEYWORD_STRUCT):
str, err := p.ParseStructDefinition()
if err != nil {
return nil, err
}
str.Pub = true
rootNode.Body = append(rootNode.Body, str)
default:
return nil, e
}
case
string(tokeniser.KEYWORD_LET),
string(tokeniser.KEYWORD_EXTERN):
fnDef, err := p.ParseFunctionDefinition()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, fnDef)
case string(tokeniser.KEYWORD_STRUCT):
str, err := p.ParseStructDefinition()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, str)
case string(tokeniser.KEYWORD_IMPORT):
imp, err := p.ParseImport()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, imp)
case string(tokeniser.KEYWORD_MODULE):
mod, err := p.ParseModule()
if err != nil {
return nil, err
}
rootNode.Body = append(rootNode.Body, mod)
default:
return nil, e
}
if p.Match(tokeniser.TOKEN_TYPE_EOF) {
break
}
if !p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
return nil, shared.NewError(p.CurrLoc(), "expected ';' or '\\n'")
}
for p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
p.Inc()
}
}
return rootNode, nil
}