550 lines
11 KiB
Go
550 lines
11 KiB
Go
package parser
|
|
|
|
import (
|
|
"git.sharkk.net/Sharkk/Mako/scanner"
|
|
"git.sharkk.net/Sharkk/Mako/types"
|
|
)
|
|
|
|
// Parser manages the state needed for parsing
|
|
type Parser struct {
|
|
scanner *scanner.Scanner
|
|
tokens []types.Token // Token buffer for lookahead
|
|
current int // Index into tokens
|
|
hadError bool
|
|
panicMode bool
|
|
}
|
|
|
|
// New creates a new parser for the given source
|
|
func New(source string) *Parser {
|
|
p := &Parser{
|
|
scanner: scanner.New(source),
|
|
}
|
|
|
|
// Fill the token buffer with at least 2 tokens
|
|
p.tokens = append(p.tokens, p.scanner.NextToken())
|
|
p.tokens = append(p.tokens, p.scanner.NextToken())
|
|
|
|
return p
|
|
}
|
|
|
|
// Parse parses the source and returns the statements
|
|
func (p *Parser) Parse() []types.Statement {
|
|
var statements []types.Statement
|
|
|
|
for !p.check(types.EOF) {
|
|
statements = append(statements, p.declaration())
|
|
}
|
|
|
|
return statements
|
|
}
|
|
|
|
// advance moves to the next token
|
|
func (p *Parser) advance() {
|
|
p.current++
|
|
|
|
// Ensure we always have at least 2 tokens in the buffer
|
|
if p.current >= len(p.tokens)-1 {
|
|
p.tokens = append(p.tokens, p.scanner.NextToken())
|
|
}
|
|
|
|
// Skip error tokens
|
|
for p.currentToken().Type == types.ERROR {
|
|
p.error(p.currentToken().Lexeme)
|
|
p.current++
|
|
|
|
// Ensure we have enough tokens
|
|
if p.current >= len(p.tokens)-1 {
|
|
p.tokens = append(p.tokens, p.scanner.NextToken())
|
|
}
|
|
}
|
|
}
|
|
|
|
// currentToken returns the current token
|
|
func (p *Parser) currentToken() types.Token {
|
|
return p.tokens[p.current]
|
|
}
|
|
|
|
// previousToken returns the previous token
|
|
func (p *Parser) previousToken() types.Token {
|
|
return p.tokens[p.current-1]
|
|
}
|
|
|
|
// peek returns the next token without consuming it
|
|
func (p *Parser) peek() types.Token {
|
|
return p.tokens[p.current+1]
|
|
}
|
|
|
|
// check checks if the current token is of the given type
|
|
func (p *Parser) check(t types.TokenType) bool {
|
|
return p.currentToken().Type == t
|
|
}
|
|
|
|
// match checks if the current token is of the given type
|
|
// If it is, it consumes the token and returns true
|
|
func (p *Parser) match(types ...types.TokenType) bool {
|
|
for _, t := range types {
|
|
if p.check(t) {
|
|
p.advance()
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// consume consumes the current token if it matches the given type
|
|
// Otherwise, it reports an error
|
|
func (p *Parser) consume(t types.TokenType, message string) types.Token {
|
|
if p.check(t) {
|
|
token := p.currentToken()
|
|
p.advance()
|
|
return token
|
|
}
|
|
|
|
p.error(message)
|
|
return p.currentToken()
|
|
}
|
|
|
|
// error reports a parse error
|
|
func (p *Parser) error(message string) *types.MakoError {
|
|
p.hadError = true
|
|
|
|
if p.panicMode {
|
|
return nil
|
|
}
|
|
|
|
p.panicMode = true
|
|
|
|
token := p.currentToken()
|
|
return types.NewError(message, token.Line, token.Column)
|
|
}
|
|
|
|
// synchronize skips tokens until it finds a statement boundary
|
|
func (p *Parser) synchronize() {
|
|
p.panicMode = false
|
|
|
|
for !p.check(types.EOF) {
|
|
if p.previousToken().Type == types.END {
|
|
return
|
|
}
|
|
|
|
switch p.currentToken().Type {
|
|
case types.FN, types.IF, types.RETURN, types.ECHO:
|
|
return
|
|
}
|
|
|
|
p.advance()
|
|
}
|
|
}
|
|
|
|
// declaration parses a declaration
|
|
func (p *Parser) declaration() types.Statement {
|
|
// Only treat it as a function declaration if there's an identifier after 'fn'
|
|
if p.check(types.FN) && p.peek().Type == types.IDENTIFIER {
|
|
p.advance() // Consume the FN token
|
|
return p.function()
|
|
}
|
|
|
|
stmt := p.statement()
|
|
|
|
if p.hadError {
|
|
p.synchronize()
|
|
}
|
|
|
|
return stmt
|
|
}
|
|
|
|
// function parses a function declaration
|
|
func (p *Parser) function() types.Statement {
|
|
name := p.consume(types.IDENTIFIER, "Expected function name.")
|
|
|
|
p.consume(types.LEFT_PAREN, "Expected '(' after function name.")
|
|
|
|
var params []types.Token
|
|
isVariadic := false
|
|
|
|
// Parse parameter list
|
|
if !p.check(types.RIGHT_PAREN) {
|
|
for {
|
|
if p.match(types.ELLIPSIS) {
|
|
isVariadic = true
|
|
break
|
|
}
|
|
|
|
param := p.consume(types.IDENTIFIER, "Expected parameter name.")
|
|
params = append(params, param)
|
|
|
|
if !p.match(types.COMMA) {
|
|
break
|
|
}
|
|
|
|
if p.match(types.ELLIPSIS) {
|
|
isVariadic = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
p.consume(types.RIGHT_PAREN, "Expected ')' after parameters.")
|
|
|
|
// Parse function body
|
|
var body []types.Statement
|
|
for !p.check(types.END) && !p.check(types.EOF) {
|
|
body = append(body, p.declaration())
|
|
}
|
|
|
|
p.consume(types.END, "Expected 'end' after function body.")
|
|
|
|
return types.FunctionStmt{
|
|
Name: name,
|
|
Params: params,
|
|
IsVariadic: isVariadic,
|
|
Body: body,
|
|
}
|
|
}
|
|
|
|
// statement parses a statement
|
|
func (p *Parser) statement() types.Statement {
|
|
if p.match(types.IF) {
|
|
return p.ifStatement()
|
|
}
|
|
|
|
if p.match(types.RETURN) {
|
|
return p.returnStatement()
|
|
}
|
|
|
|
if p.match(types.ECHO) {
|
|
return p.echoStatement()
|
|
}
|
|
|
|
// Check for assignment
|
|
if p.check(types.IDENTIFIER) && p.peek().Type == types.EQUAL {
|
|
name := p.currentToken()
|
|
p.advance() // Consume the identifier
|
|
p.advance() // Consume the equals sign
|
|
|
|
value := p.expression()
|
|
|
|
return types.AssignStmt{
|
|
Name: name,
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
return p.expressionStatement()
|
|
}
|
|
|
|
// ifStatement parses an if statement
|
|
func (p *Parser) ifStatement() types.Statement {
|
|
condition := p.expression()
|
|
|
|
p.consume(types.THEN, "Expected 'then' after if condition.")
|
|
|
|
var thenBranch []types.Statement
|
|
for !p.check(types.END) && !p.check(types.ELSEIF) && !p.check(types.ELSE) && !p.check(types.EOF) {
|
|
thenBranch = append(thenBranch, p.declaration())
|
|
}
|
|
|
|
var elseIfs []struct {
|
|
Condition types.Expression
|
|
Body []types.Statement
|
|
}
|
|
|
|
// Parse 'elseif' branches
|
|
for p.match(types.ELSEIF) {
|
|
elseifCondition := p.expression()
|
|
p.consume(types.THEN, "Expected 'then' after elseif condition.")
|
|
|
|
var body []types.Statement
|
|
for !p.check(types.END) && !p.check(types.ELSEIF) && !p.check(types.ELSE) && !p.check(types.EOF) {
|
|
body = append(body, p.declaration())
|
|
}
|
|
|
|
elseIfs = append(elseIfs, struct {
|
|
Condition types.Expression
|
|
Body []types.Statement
|
|
}{
|
|
Condition: elseifCondition,
|
|
Body: body,
|
|
})
|
|
}
|
|
|
|
var elseBranch []types.Statement
|
|
|
|
// Parse 'else' branch
|
|
if p.match(types.ELSE) {
|
|
for !p.check(types.END) && !p.check(types.EOF) {
|
|
elseBranch = append(elseBranch, p.declaration())
|
|
}
|
|
}
|
|
|
|
p.consume(types.END, "Expected 'end' after if statement.")
|
|
|
|
return types.IfStmt{
|
|
Condition: condition,
|
|
ThenBranch: thenBranch,
|
|
ElseIfs: elseIfs,
|
|
ElseBranch: elseBranch,
|
|
}
|
|
}
|
|
|
|
// returnStatement parses a return statement
|
|
func (p *Parser) returnStatement() types.Statement {
|
|
keyword := p.previousToken()
|
|
|
|
var value types.Expression
|
|
if !p.check(types.END) && !p.check(types.EOF) {
|
|
value = p.expression()
|
|
}
|
|
|
|
return types.ReturnStmt{
|
|
Keyword: keyword,
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
// echoStatement parses an echo statement
|
|
func (p *Parser) echoStatement() types.Statement {
|
|
keyword := p.previousToken()
|
|
value := p.expression()
|
|
|
|
return types.EchoStmt{
|
|
Keyword: keyword,
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
// expressionStatement parses an expression statement
|
|
func (p *Parser) expressionStatement() types.Statement {
|
|
expr := p.expression()
|
|
return types.ExpressionStmt{Expression: expr}
|
|
}
|
|
|
|
// expression parses an expression
|
|
func (p *Parser) expression() types.Expression {
|
|
return p.or()
|
|
}
|
|
|
|
// or parses a logical OR expression
|
|
func (p *Parser) or() types.Expression {
|
|
expr := p.and()
|
|
|
|
for p.match(types.OR) {
|
|
operator := p.previousToken()
|
|
right := p.and()
|
|
expr = types.BinaryExpr{
|
|
Left: expr,
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// and parses a logical AND expression
|
|
func (p *Parser) and() types.Expression {
|
|
expr := p.equality()
|
|
|
|
for p.match(types.AND) {
|
|
operator := p.previousToken()
|
|
right := p.equality()
|
|
expr = types.BinaryExpr{
|
|
Left: expr,
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// equality parses an equality expression
|
|
func (p *Parser) equality() types.Expression {
|
|
expr := p.comparison()
|
|
|
|
for p.match(types.EQUAL_EQUAL, types.BANG_EQUAL) {
|
|
operator := p.previousToken()
|
|
right := p.comparison()
|
|
expr = types.BinaryExpr{
|
|
Left: expr,
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// comparison parses a comparison expression
|
|
func (p *Parser) comparison() types.Expression {
|
|
expr := p.term()
|
|
|
|
for p.match(types.LESS, types.LESS_EQUAL, types.GREATER, types.GREATER_EQUAL) {
|
|
operator := p.previousToken()
|
|
right := p.term()
|
|
expr = types.BinaryExpr{
|
|
Left: expr,
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// term parses a term expression
|
|
func (p *Parser) term() types.Expression {
|
|
expr := p.factor()
|
|
|
|
for p.match(types.PLUS, types.MINUS) {
|
|
operator := p.previousToken()
|
|
right := p.factor()
|
|
expr = types.BinaryExpr{
|
|
Left: expr,
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// factor parses a factor expression
|
|
func (p *Parser) factor() types.Expression {
|
|
expr := p.unary()
|
|
|
|
for p.match(types.STAR, types.SLASH) {
|
|
operator := p.previousToken()
|
|
right := p.unary()
|
|
expr = types.BinaryExpr{
|
|
Left: expr,
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// unary parses a unary expression
|
|
func (p *Parser) unary() types.Expression {
|
|
if p.match(types.MINUS) {
|
|
operator := p.previousToken()
|
|
right := p.unary()
|
|
return types.UnaryExpr{
|
|
Operator: operator,
|
|
Right: right,
|
|
}
|
|
}
|
|
|
|
return p.call()
|
|
}
|
|
|
|
// call parses a function call
|
|
func (p *Parser) call() types.Expression {
|
|
expr := p.primary()
|
|
|
|
for p.match(types.LEFT_PAREN) {
|
|
var arguments []types.Expression
|
|
|
|
if !p.check(types.RIGHT_PAREN) {
|
|
for {
|
|
arguments = append(arguments, p.expression())
|
|
|
|
if !p.match(types.COMMA) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
paren := p.consume(types.RIGHT_PAREN, "Expected ')' after arguments.")
|
|
|
|
expr = types.CallExpr{
|
|
Callee: expr,
|
|
Paren: paren,
|
|
Arguments: arguments,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// primary parses a primary expression
|
|
func (p *Parser) primary() types.Expression {
|
|
if p.match(types.NIL) {
|
|
return types.LiteralExpr{Value: nil}
|
|
}
|
|
|
|
if p.match(types.TRUE) {
|
|
return types.LiteralExpr{Value: true}
|
|
}
|
|
|
|
if p.match(types.FALSE) {
|
|
return types.LiteralExpr{Value: false}
|
|
}
|
|
|
|
if p.match(types.NUMBER, types.STRING) {
|
|
return types.LiteralExpr{Value: p.previousToken().Literal}
|
|
}
|
|
|
|
if p.match(types.IDENTIFIER) {
|
|
return types.VariableExpr{Name: p.previousToken()}
|
|
}
|
|
|
|
if p.match(types.LEFT_PAREN) {
|
|
expr := p.expression()
|
|
p.consume(types.RIGHT_PAREN, "Expected ')' after expression.")
|
|
return expr
|
|
}
|
|
|
|
// Anonymous function expression
|
|
if p.match(types.FN) {
|
|
p.consume(types.LEFT_PAREN, "Expected '(' after 'fn'.")
|
|
|
|
var params []types.Token
|
|
isVariadic := false
|
|
|
|
// Parse parameter list
|
|
if !p.check(types.RIGHT_PAREN) {
|
|
for {
|
|
if p.match(types.ELLIPSIS) {
|
|
isVariadic = true
|
|
break
|
|
}
|
|
|
|
param := p.consume(types.IDENTIFIER, "Expected parameter name.")
|
|
params = append(params, param)
|
|
|
|
if !p.match(types.COMMA) {
|
|
break
|
|
}
|
|
|
|
if p.match(types.ELLIPSIS) {
|
|
isVariadic = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
p.consume(types.RIGHT_PAREN, "Expected ')' after parameters.")
|
|
|
|
// Parse function body
|
|
var body []types.Statement
|
|
for !p.check(types.END) && !p.check(types.EOF) {
|
|
body = append(body, p.declaration())
|
|
}
|
|
|
|
p.consume(types.END, "Expected 'end' after function body.")
|
|
|
|
return types.FunctionExpr{
|
|
Params: params,
|
|
IsVariadic: isVariadic,
|
|
Body: body,
|
|
}
|
|
}
|
|
|
|
p.error("Expected expression.")
|
|
return nil
|
|
}
|