Mako/parser/parser.go
2025-06-11 16:28:55 -05:00

1547 lines
32 KiB
Go

package parser
import (
"fmt"
"strconv"
"strings"
)
// ParseError represents a parsing error with location information
type ParseError struct {
Message string
Line int
Column int
Token Token
}
func (pe ParseError) Error() string {
return fmt.Sprintf("Parse error at line %d, column %d: %s (near '%s')",
pe.Line, pe.Column, pe.Message, pe.Token.Literal)
}
// Parser implements a recursive descent Pratt parser
type Parser struct {
lexer *Lexer
curToken Token
peekToken Token
prefixParseFns map[TokenType]func() Expression
infixParseFns map[TokenType]func(Expression) Expression
errors []ParseError
// Scope tracking
scopes []map[string]bool // stack of scopes, each tracking declared variables
scopeTypes []string // track what type each scope is: "global", "function", "loop"
// Struct tracking
structs map[string]*StructStatement // track defined structs
}
// NewParser creates a new parser instance
func NewParser(lexer *Lexer) *Parser {
p := &Parser{
lexer: lexer,
errors: []ParseError{},
scopes: []map[string]bool{make(map[string]bool)}, // start with global scope
scopeTypes: []string{"global"}, // start with global scope type
structs: make(map[string]*StructStatement), // track struct definitions
}
p.prefixParseFns = make(map[TokenType]func() Expression)
p.registerPrefix(IDENT, p.parseIdentifier)
p.registerPrefix(NUMBER, p.parseNumberLiteral)
p.registerPrefix(STRING, p.parseStringLiteral)
p.registerPrefix(TRUE, p.parseBooleanLiteral)
p.registerPrefix(FALSE, p.parseBooleanLiteral)
p.registerPrefix(NIL, p.parseNilLiteral)
p.registerPrefix(LPAREN, p.parseGroupedExpression)
p.registerPrefix(LBRACE, p.parseTableLiteral)
p.registerPrefix(MINUS, p.parsePrefixExpression)
p.registerPrefix(NOT, p.parsePrefixExpression)
p.registerPrefix(FN, p.parseFunctionLiteral)
p.infixParseFns = make(map[TokenType]func(Expression) Expression)
p.registerInfix(PLUS, p.parseInfixExpression)
p.registerInfix(MINUS, p.parseInfixExpression)
p.registerInfix(SLASH, p.parseInfixExpression)
p.registerInfix(STAR, p.parseInfixExpression)
p.registerInfix(EQ, p.parseInfixExpression)
p.registerInfix(NOT_EQ, p.parseInfixExpression)
p.registerInfix(LT, p.parseInfixExpression)
p.registerInfix(GT, p.parseInfixExpression)
p.registerInfix(LT_EQ, p.parseInfixExpression)
p.registerInfix(GT_EQ, p.parseInfixExpression)
p.registerInfix(AND, p.parseInfixExpression)
p.registerInfix(OR, p.parseInfixExpression)
p.registerInfix(DOT, p.parseDotExpression)
p.registerInfix(LBRACKET, p.parseIndexExpression)
p.registerInfix(LPAREN, p.parseCallExpression)
p.registerInfix(LBRACE, p.parseStructConstructor) // struct constructor
// Read two tokens, so curToken and peekToken are both set
p.nextToken()
p.nextToken()
return p
}
// Scope management
func (p *Parser) enterScope(scopeType string) {
p.scopes = append(p.scopes, make(map[string]bool))
p.scopeTypes = append(p.scopeTypes, scopeType)
}
func (p *Parser) exitScope() {
if len(p.scopes) > 1 {
p.scopes = p.scopes[:len(p.scopes)-1]
p.scopeTypes = p.scopeTypes[:len(p.scopeTypes)-1]
}
}
func (p *Parser) enterFunctionScope() {
p.enterScope("function")
}
func (p *Parser) exitFunctionScope() {
p.exitScope()
}
func (p *Parser) enterLoopScope() {
p.enterScope("loop")
}
func (p *Parser) exitLoopScope() {
p.exitScope()
}
func (p *Parser) enterBlockScope() {
// Blocks don't create new variable scopes
}
func (p *Parser) exitBlockScope() {
// No-op
}
func (p *Parser) currentVariableScope() map[string]bool {
if len(p.scopeTypes) > 1 && p.scopeTypes[len(p.scopeTypes)-1] == "loop" {
return p.scopes[len(p.scopes)-2]
}
return p.scopes[len(p.scopes)-1]
}
func (p *Parser) isVariableDeclared(name string) bool {
for i := len(p.scopes) - 1; i >= 0; i-- {
if p.scopes[i][name] {
return true
}
}
return false
}
func (p *Parser) declareVariable(name string) {
p.currentVariableScope()[name] = true
}
func (p *Parser) declareLoopVariable(name string) {
p.scopes[len(p.scopes)-1][name] = true
}
// parseTypeHint parses optional type hint after colon
func (p *Parser) parseTypeHint() *TypeInfo {
if !p.peekTokenIs(COLON) {
return nil
}
p.nextToken() // consume ':'
if !p.expectPeekIdent() {
p.addError("expected type name after ':'")
return nil
}
typeName := p.curToken.Literal
if !ValidTypeName(typeName) && !p.isStructDefined(typeName) {
p.addError(fmt.Sprintf("invalid type name '%s'", typeName))
return nil
}
return &TypeInfo{Type: typeName, Inferred: false}
}
// isStructDefined checks if a struct name is defined
func (p *Parser) isStructDefined(name string) bool {
_, exists := p.structs[name]
return exists
}
// registerPrefix registers a prefix parse function
func (p *Parser) registerPrefix(tokenType TokenType, fn func() Expression) {
p.prefixParseFns[tokenType] = fn
}
// registerInfix registers an infix parse function
func (p *Parser) registerInfix(tokenType TokenType, fn func(Expression) Expression) {
p.infixParseFns[tokenType] = fn
}
// nextToken advances to the next token
func (p *Parser) nextToken() {
p.curToken = p.peekToken
p.peekToken = p.lexer.NextToken()
}
// ParseProgram parses the entire program
func (p *Parser) ParseProgram() *Program {
program := &Program{}
program.Statements = []Statement{}
for !p.curTokenIs(EOF) {
stmt := p.parseStatement()
if stmt != nil {
program.Statements = append(program.Statements, stmt)
}
p.nextToken()
}
return program
}
// parseStatement parses a statement
func (p *Parser) parseStatement() Statement {
switch p.curToken.Type {
case STRUCT:
return p.parseStructStatement()
case FN:
return p.parseFunctionStatement()
case IDENT:
return p.parseIdentifierStatement()
case IF:
return p.parseIfStatement()
case FOR:
return p.parseForStatement()
case WHILE:
return p.parseWhileStatement()
case ECHO:
return p.parseEchoStatement()
case BREAK:
return p.parseBreakStatement()
case EXIT:
return p.parseExitStatement()
case RETURN:
return p.parseReturnStatement()
case ASSIGN:
p.addError("assignment operator '=' without left-hand side identifier")
return nil
case ILLEGAL:
p.addError(fmt.Sprintf("unexpected token '%s'", p.curToken.Literal))
return nil
case EOF:
return nil
default:
p.addError(fmt.Sprintf("unexpected token '%s', expected statement", p.curToken.Literal))
return nil
}
}
// parseStructStatement parses struct definitions
func (p *Parser) parseStructStatement() *StructStatement {
stmt := &StructStatement{}
if !p.expectPeek(IDENT) {
p.addError("expected struct name")
return nil
}
stmt.Name = p.curToken.Literal
if !p.expectPeek(LBRACE) {
p.addError("expected '{' after struct name")
return nil
}
stmt.Fields = []StructField{}
if p.peekTokenIs(RBRACE) {
p.nextToken()
p.structs[stmt.Name] = stmt
return stmt
}
p.nextToken()
for {
if p.curTokenIs(EOF) {
p.addError("unexpected end of input, expected }")
return nil
}
if !p.curTokenIs(IDENT) {
p.addError("expected field name")
return nil
}
field := StructField{Name: p.curToken.Literal}
// Parse optional type hint
field.TypeHint = p.parseTypeHint()
if field.TypeHint == nil {
p.addError("struct fields require type annotation")
return nil
}
stmt.Fields = append(stmt.Fields, field)
if !p.peekTokenIs(COMMA) {
break
}
p.nextToken() // consume comma
p.nextToken() // move to next field
if p.curTokenIs(RBRACE) {
break
}
if p.curTokenIs(EOF) {
p.addError("expected next token to be }")
return nil
}
}
if !p.expectPeek(RBRACE) {
return nil
}
p.structs[stmt.Name] = stmt
return stmt
}
// parseFunctionStatement handles both regular functions and methods
func (p *Parser) parseFunctionStatement() Statement {
if !p.expectPeek(IDENT) {
p.addError("expected function name")
return nil
}
funcName := p.curToken.Literal
// Check if this is a method definition (struct.method)
if p.peekTokenIs(DOT) {
p.nextToken() // consume '.'
if !p.expectPeek(IDENT) {
p.addError("expected method name after '.'")
return nil
}
methodName := p.curToken.Literal
if !p.expectPeek(LPAREN) {
p.addError("expected '(' after method name")
return nil
}
// Parse the function literal starting from parameters
funcLit := &FunctionLiteral{}
funcLit.Parameters, funcLit.Variadic = p.parseFunctionParameters()
if !p.expectPeek(RPAREN) {
p.addError("expected ')' after function parameters")
return nil
}
// Check for return type hint
funcLit.ReturnType = p.parseTypeHint()
p.nextToken()
p.enterFunctionScope()
for _, param := range funcLit.Parameters {
p.declareVariable(param.Name)
}
funcLit.Body = p.parseBlockStatements(END)
p.exitFunctionScope()
if !p.curTokenIs(END) {
p.addError("expected 'end' to close function")
return nil
}
return &MethodDefinition{
StructName: funcName,
MethodName: methodName,
Function: funcLit,
}
}
// Regular function - this should be handled as expression statement
// Reset to handle as function literal
funcLit := p.parseFunctionLiteral()
if funcLit == nil {
return nil
}
return &ExpressionStatement{Expression: funcLit}
}
// parseIdentifierStatement handles both assignments and expression statements starting with identifiers
func (p *Parser) parseIdentifierStatement() Statement {
// Parse the left-hand side expression first
expr := p.ParseExpression(LOWEST)
if expr == nil {
return nil
}
// Check for type hint (only valid on simple identifiers)
var typeHint *TypeInfo
if _, ok := expr.(*Identifier); ok {
typeHint = p.parseTypeHint()
}
// Check if this is an assignment
if p.peekTokenIs(ASSIGN) {
// Convert to assignment statement
stmt := &AssignStatement{
Name: expr,
TypeHint: typeHint,
}
// Validate assignment target and check if it's a declaration
switch name := expr.(type) {
case *Identifier:
stmt.IsDeclaration = !p.isVariableDeclared(name.Value)
if stmt.IsDeclaration {
p.declareVariable(name.Value)
}
case *DotExpression, *IndexExpression:
stmt.IsDeclaration = false
default:
p.addError("invalid assignment target")
return nil
}
if !p.expectPeek(ASSIGN) {
return nil
}
p.nextToken()
stmt.Value = p.ParseExpression(LOWEST)
if stmt.Value == nil {
p.addError("expected expression after assignment operator")
return nil
}
return stmt
} else {
// This is an expression statement
return &ExpressionStatement{Expression: expr}
}
}
// parseExpressionStatement parses expressions used as statements
func (p *Parser) parseExpressionStatement() *ExpressionStatement {
stmt := &ExpressionStatement{}
stmt.Expression = p.ParseExpression(LOWEST)
if stmt.Expression == nil {
return nil
}
return stmt
}
// parseEchoStatement parses echo statements
func (p *Parser) parseEchoStatement() *EchoStatement {
stmt := &EchoStatement{}
p.nextToken() // move past 'echo'
stmt.Value = p.ParseExpression(LOWEST)
if stmt.Value == nil {
p.addError("expected expression after 'echo'")
return nil
}
return stmt
}
// parseBreakStatement parses break statements
func (p *Parser) parseBreakStatement() *BreakStatement {
// Check if break is followed by an identifier (invalid)
if p.peekTokenIs(IDENT) {
p.addError("unexpected identifier")
return nil
}
return &BreakStatement{}
}
// parseExitStatement parses exit statements
func (p *Parser) parseExitStatement() *ExitStatement {
stmt := &ExitStatement{}
if p.canStartExpression(p.peekToken.Type) {
p.nextToken()
stmt.Value = p.ParseExpression(LOWEST)
if stmt.Value == nil {
p.addError("expected expression after 'exit'")
return nil
}
}
return stmt
}
// parseReturnStatement parses return statements
func (p *Parser) parseReturnStatement() *ReturnStatement {
stmt := &ReturnStatement{}
if p.canStartExpression(p.peekToken.Type) {
p.nextToken()
stmt.Value = p.ParseExpression(LOWEST)
if stmt.Value == nil {
p.addError("expected expression after 'return'")
return nil
}
}
return stmt
}
// canStartExpression checks if a token type can start an expression
func (p *Parser) canStartExpression(tokenType TokenType) bool {
switch tokenType {
case IDENT, NUMBER, STRING, TRUE, FALSE, NIL, LPAREN, LBRACE, MINUS, NOT, FN:
return true
default:
return false
}
}
// parseWhileStatement parses while loops
func (p *Parser) parseWhileStatement() *WhileStatement {
stmt := &WhileStatement{}
p.nextToken()
stmt.Condition = p.ParseExpression(LOWEST)
if stmt.Condition == nil {
p.addError("expected condition after 'while'")
return nil
}
if !p.expectPeek(DO) {
p.addError("expected 'do' after while condition")
return nil
}
p.nextToken()
p.enterBlockScope()
stmt.Body = p.parseBlockStatements(END)
p.exitBlockScope()
if !p.curTokenIs(END) {
p.addError("expected 'end' to close while loop")
return nil
}
return stmt
}
// parseForStatement parses for loops
func (p *Parser) parseForStatement() Statement {
p.nextToken()
if !p.curTokenIs(IDENT) {
p.addError("expected identifier after 'for'")
return nil
}
firstVar := &Identifier{Value: p.curToken.Literal}
if p.peekTokenIs(ASSIGN) {
return p.parseNumericForStatement(firstVar)
} else if p.peekTokenIs(COMMA) || p.peekTokenIs(IN) {
return p.parseForInStatement(firstVar)
} else {
p.addError("expected '=', ',' or 'in' after for loop variable")
return nil
}
}
// parseNumericForStatement parses numeric for loops
func (p *Parser) parseNumericForStatement(variable *Identifier) *ForStatement {
stmt := &ForStatement{Variable: variable}
if !p.expectPeek(ASSIGN) {
return nil
}
p.nextToken()
stmt.Start = p.ParseExpression(LOWEST)
if stmt.Start == nil {
p.addError("expected start expression in for loop")
return nil
}
if !p.expectPeek(COMMA) {
p.addError("expected ',' after start expression in for loop")
return nil
}
p.nextToken()
stmt.End = p.ParseExpression(LOWEST)
if stmt.End == nil {
p.addError("expected end expression in for loop")
return nil
}
if p.peekTokenIs(COMMA) {
p.nextToken()
p.nextToken()
stmt.Step = p.ParseExpression(LOWEST)
if stmt.Step == nil {
p.addError("expected step expression in for loop")
return nil
}
}
if !p.expectPeek(DO) {
p.addError("expected 'do' after for loop header")
return nil
}
p.nextToken()
p.enterLoopScope()
p.declareLoopVariable(variable.Value)
stmt.Body = p.parseBlockStatements(END)
p.exitLoopScope()
if !p.curTokenIs(END) {
p.addError("expected 'end' to close for loop")
return nil
}
return stmt
}
// parseForInStatement parses for-in loops
func (p *Parser) parseForInStatement(firstVar *Identifier) *ForInStatement {
stmt := &ForInStatement{}
if p.peekTokenIs(COMMA) {
stmt.Key = firstVar
p.nextToken()
p.nextToken()
if !p.curTokenIs(IDENT) {
p.addError("expected identifier after ',' in for loop")
return nil
}
stmt.Value = &Identifier{Value: p.curToken.Literal}
} else {
stmt.Value = firstVar
}
if !p.expectPeek(IN) {
p.addError("expected 'in' in for loop")
return nil
}
p.nextToken()
stmt.Iterable = p.ParseExpression(LOWEST)
if stmt.Iterable == nil {
p.addError("expected expression after 'in' in for loop")
return nil
}
if !p.expectPeek(DO) {
p.addError("expected 'do' after for loop header")
return nil
}
p.nextToken()
p.enterLoopScope()
if stmt.Key != nil {
p.declareLoopVariable(stmt.Key.Value)
}
p.declareLoopVariable(stmt.Value.Value)
stmt.Body = p.parseBlockStatements(END)
p.exitLoopScope()
if !p.curTokenIs(END) {
p.addError("expected 'end' to close for loop")
return nil
}
return stmt
}
// parseIfStatement parses if statements
func (p *Parser) parseIfStatement() *IfStatement {
stmt := &IfStatement{}
p.nextToken()
stmt.Condition = p.ParseExpression(LOWEST)
if stmt.Condition == nil {
p.addError("expected condition after 'if'")
return nil
}
if p.peekTokenIs(THEN) {
p.nextToken()
}
p.nextToken()
if p.curTokenIs(END) {
p.addError("expected 'end' to close if statement")
return nil
}
p.enterBlockScope()
stmt.Body = p.parseBlockStatements(ELSEIF, ELSE, END)
p.exitBlockScope()
for p.curTokenIs(ELSEIF) {
elseif := ElseIfClause{}
p.nextToken()
elseif.Condition = p.ParseExpression(LOWEST)
if elseif.Condition == nil {
p.addError("expected condition after 'elseif'")
return nil
}
if p.peekTokenIs(THEN) {
p.nextToken()
}
p.nextToken()
p.enterBlockScope()
elseif.Body = p.parseBlockStatements(ELSEIF, ELSE, END)
p.exitBlockScope()
stmt.ElseIfs = append(stmt.ElseIfs, elseif)
}
if p.curTokenIs(ELSE) {
p.nextToken()
p.enterBlockScope()
stmt.Else = p.parseBlockStatements(END)
p.exitBlockScope()
}
if !p.curTokenIs(END) {
p.addError("expected 'end' to close if statement")
return nil
}
return stmt
}
// parseBlockStatements parses statements until terminators
func (p *Parser) parseBlockStatements(terminators ...TokenType) []Statement {
statements := []Statement{}
for !p.curTokenIs(EOF) && !p.isTerminator(terminators...) {
stmt := p.parseStatement()
if stmt != nil {
statements = append(statements, stmt)
}
p.nextToken()
}
return statements
}
// isTerminator checks if current token is a terminator
func (p *Parser) isTerminator(terminators ...TokenType) bool {
for _, terminator := range terminators {
if p.curTokenIs(terminator) {
return true
}
}
return false
}
// ParseExpression parses expressions using Pratt parsing
func (p *Parser) ParseExpression(precedence Precedence) Expression {
prefix := p.prefixParseFns[p.curToken.Type]
if prefix == nil {
p.noPrefixParseFnError(p.curToken.Type)
return nil
}
leftExp := prefix()
if leftExp == nil {
return nil
}
for !p.peekTokenIs(EOF) && precedence < p.peekPrecedence() {
infix := p.infixParseFns[p.peekToken.Type]
if infix == nil {
return leftExp
}
p.nextToken()
leftExp = infix(leftExp)
if leftExp == nil {
return nil
}
}
return leftExp
}
// Expression parsing functions
func (p *Parser) parseIdentifier() Expression {
return &Identifier{Value: p.curToken.Literal}
}
func (p *Parser) parseNumberLiteral() Expression {
lit := &NumberLiteral{}
literal := p.curToken.Literal
var value float64
var err error
if strings.HasPrefix(literal, "0x") || strings.HasPrefix(literal, "0X") {
if len(literal) <= 2 {
p.addError(fmt.Sprintf("could not parse '%s' as hexadecimal number", literal))
return nil
}
hexPart := literal[2:]
for _, ch := range hexPart {
if !((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
p.addError(fmt.Sprintf("could not parse '%s' as hexadecimal number", literal))
return nil
}
}
intVal, parseErr := strconv.ParseInt(literal, 0, 64)
if parseErr != nil {
p.addError(fmt.Sprintf("could not parse '%s' as hexadecimal number", literal))
return nil
}
value = float64(intVal)
} else if strings.HasPrefix(literal, "0b") || strings.HasPrefix(literal, "0B") {
if len(literal) <= 2 {
p.addError(fmt.Sprintf("could not parse '%s' as binary number", literal))
return nil
}
binaryPart := literal[2:]
for _, ch := range binaryPart {
if ch != '0' && ch != '1' {
p.addError(fmt.Sprintf("could not parse '%s' as binary number", literal))
return nil
}
}
binaryStr := literal[2:]
intVal, parseErr := strconv.ParseInt(binaryStr, 2, 64)
if parseErr != nil {
p.addError(fmt.Sprintf("could not parse '%s' as binary number", literal))
return nil
}
value = float64(intVal)
} else {
value, err = strconv.ParseFloat(literal, 64)
if err != nil {
p.addError(fmt.Sprintf("could not parse '%s' as number", literal))
return nil
}
}
lit.Value = value
return lit
}
func (p *Parser) parseStringLiteral() Expression {
return &StringLiteral{Value: p.curToken.Literal}
}
func (p *Parser) parseBooleanLiteral() Expression {
return &BooleanLiteral{Value: p.curTokenIs(TRUE)}
}
func (p *Parser) parseNilLiteral() Expression {
return &NilLiteral{}
}
func (p *Parser) parsePrefixExpression() Expression {
expression := &PrefixExpression{
Operator: p.curToken.Literal,
}
p.nextToken()
expression.Right = p.ParseExpression(PREFIX)
if expression.Right == nil {
p.addError(fmt.Sprintf("expected expression after prefix operator '%s'", expression.Operator))
return nil
}
return expression
}
// parseGroupedExpression handles parentheses and assignment expressions
func (p *Parser) parseGroupedExpression() Expression {
p.nextToken()
// Check if this is an assignment expression inside parentheses
if p.curTokenIs(IDENT) && p.peekTokenIs(ASSIGN) {
return p.parseParenthesizedAssignment()
}
// Regular grouped expression
exp := p.ParseExpression(LOWEST)
if exp == nil {
return nil
}
if !p.expectPeek(RPAREN) {
return nil
}
return exp
}
// parseParenthesizedAssignment parses assignment expressions in parentheses
func (p *Parser) parseParenthesizedAssignment() Expression {
// We're at identifier, peek is ASSIGN
target := p.parseIdentifier()
if !p.expectPeek(ASSIGN) {
return nil
}
p.nextToken() // move past =
value := p.ParseExpression(LOWEST)
if value == nil {
p.addError("expected expression after assignment operator")
return nil
}
if !p.expectPeek(RPAREN) {
return nil
}
// Create assignment expression
assignExpr := &AssignExpression{
Name: target,
Value: value,
}
// Handle variable declaration for assignment expressions
if ident, ok := target.(*Identifier); ok {
assignExpr.IsDeclaration = !p.isVariableDeclared(ident.Value)
if assignExpr.IsDeclaration {
p.declareVariable(ident.Value)
}
}
// Assignment expression evaluates to the assigned value
assignExpr.SetType(value.GetType())
return assignExpr
}
func (p *Parser) parseFunctionLiteral() Expression {
fn := &FunctionLiteral{}
if !p.expectPeek(LPAREN) {
p.addError("expected '(' after 'fn'")
return nil
}
fn.Parameters, fn.Variadic = p.parseFunctionParameters()
if !p.expectPeek(RPAREN) {
p.addError("expected ')' after function parameters")
return nil
}
// Check for return type hint
fn.ReturnType = p.parseTypeHint()
p.nextToken()
p.enterFunctionScope()
for _, param := range fn.Parameters {
p.declareVariable(param.Name)
}
fn.Body = p.parseBlockStatements(END)
p.exitFunctionScope()
if !p.curTokenIs(END) {
p.addError("expected 'end' to close function")
return nil
}
return fn
}
func (p *Parser) parseFunctionParameters() ([]FunctionParameter, bool) {
var params []FunctionParameter
var variadic bool
if p.peekTokenIs(RPAREN) {
return params, false
}
p.nextToken()
for {
if p.curTokenIs(ELLIPSIS) {
variadic = true
break
}
if !p.curTokenIs(IDENT) {
p.addError("expected parameter name")
return nil, false
}
param := FunctionParameter{Name: p.curToken.Literal}
// Check for type hint
param.TypeHint = p.parseTypeHint()
params = append(params, param)
if !p.peekTokenIs(COMMA) {
break
}
p.nextToken()
p.nextToken()
if p.curTokenIs(ELLIPSIS) {
variadic = true
break
}
}
return params, variadic
}
func (p *Parser) parseTableLiteral() Expression {
table := &TableLiteral{}
table.Pairs = []TablePair{}
if p.peekTokenIs(RBRACE) {
p.nextToken()
return table
}
p.nextToken()
for {
if p.curTokenIs(EOF) {
p.addError("unexpected end of input, expected }")
return nil
}
pair := TablePair{}
if (p.curTokenIs(IDENT) || p.curTokenIs(STRING)) && p.peekTokenIs(ASSIGN) {
if p.curTokenIs(IDENT) {
pair.Key = &Identifier{Value: p.curToken.Literal}
} else {
pair.Key = &StringLiteral{Value: p.curToken.Literal}
}
p.nextToken()
p.nextToken()
if p.curTokenIs(EOF) {
p.addError("expected expression after assignment operator")
return nil
}
pair.Value = p.ParseExpression(LOWEST)
} else {
pair.Value = p.ParseExpression(LOWEST)
}
if pair.Value == nil {
return nil
}
table.Pairs = append(table.Pairs, pair)
if !p.peekTokenIs(COMMA) {
break
}
p.nextToken()
p.nextToken()
if p.curTokenIs(RBRACE) {
break
}
if p.curTokenIs(EOF) {
p.addError("expected next token to be }")
return nil
}
}
if !p.expectPeek(RBRACE) {
return nil
}
return table
}
// parseStructConstructor handles struct constructor calls like my_type{...}
func (p *Parser) parseStructConstructor(left Expression) Expression {
// left should be an identifier representing the struct name
ident, ok := left.(*Identifier)
if !ok {
// Not an identifier, fall back to table literal parsing
return p.parseTableLiteralFromBrace()
}
structName := ident.Value
// Always try to parse as struct constructor if we have an identifier
// Type checking will catch undefined structs later
constructor := &StructConstructorExpression{
StructName: structName,
Fields: []TablePair{},
}
if p.peekTokenIs(RBRACE) {
p.nextToken()
return constructor
}
p.nextToken()
for {
if p.curTokenIs(EOF) {
p.addError("unexpected end of input, expected }")
return nil
}
pair := TablePair{}
if (p.curTokenIs(IDENT) || p.curTokenIs(STRING)) && p.peekTokenIs(ASSIGN) {
if p.curTokenIs(IDENT) {
pair.Key = &Identifier{Value: p.curToken.Literal}
} else {
pair.Key = &StringLiteral{Value: p.curToken.Literal}
}
p.nextToken()
p.nextToken()
if p.curTokenIs(EOF) {
p.addError("expected expression after assignment operator")
return nil
}
pair.Value = p.ParseExpression(LOWEST)
} else {
pair.Value = p.ParseExpression(LOWEST)
}
if pair.Value == nil {
return nil
}
constructor.Fields = append(constructor.Fields, pair)
if !p.peekTokenIs(COMMA) {
break
}
p.nextToken()
p.nextToken()
if p.curTokenIs(RBRACE) {
break
}
if p.curTokenIs(EOF) {
p.addError("expected next token to be }")
return nil
}
}
if !p.expectPeek(RBRACE) {
return nil
}
return constructor
}
func (p *Parser) parseTableLiteralFromBrace() Expression {
// We're already at the opening brace, so parse as table literal
table := &TableLiteral{}
table.Pairs = []TablePair{}
if p.peekTokenIs(RBRACE) {
p.nextToken()
return table
}
p.nextToken()
for {
if p.curTokenIs(EOF) {
p.addError("unexpected end of input, expected }")
return nil
}
pair := TablePair{}
if (p.curTokenIs(IDENT) || p.curTokenIs(STRING)) && p.peekTokenIs(ASSIGN) {
if p.curTokenIs(IDENT) {
pair.Key = &Identifier{Value: p.curToken.Literal}
} else {
pair.Key = &StringLiteral{Value: p.curToken.Literal}
}
p.nextToken()
p.nextToken()
if p.curTokenIs(EOF) {
p.addError("expected expression after assignment operator")
return nil
}
pair.Value = p.ParseExpression(LOWEST)
} else {
pair.Value = p.ParseExpression(LOWEST)
}
if pair.Value == nil {
return nil
}
table.Pairs = append(table.Pairs, pair)
if !p.peekTokenIs(COMMA) {
break
}
p.nextToken()
p.nextToken()
if p.curTokenIs(RBRACE) {
break
}
if p.curTokenIs(EOF) {
p.addError("expected next token to be }")
return nil
}
}
if !p.expectPeek(RBRACE) {
return nil
}
return table
}
func (p *Parser) parseInfixExpression(left Expression) Expression {
expression := &InfixExpression{
Left: left,
Operator: p.curToken.Literal,
}
precedence := p.curPrecedence()
p.nextToken()
expression.Right = p.ParseExpression(precedence)
if expression.Right == nil {
p.addError(fmt.Sprintf("expected expression after operator '%s'", expression.Operator))
return nil
}
return expression
}
func (p *Parser) parseDotExpression(left Expression) Expression {
if !p.expectPeekIdent() {
p.addError("expected identifier after '.'")
return nil
}
return &DotExpression{
Left: left,
Key: p.curToken.Literal,
}
}
func (p *Parser) parseCallExpression(fn Expression) Expression {
call := &CallExpression{Function: fn}
call.Arguments = p.parseExpressionList(RPAREN)
return call
}
func (p *Parser) parseExpressionList(end TokenType) []Expression {
var args []Expression
if p.peekTokenIs(end) {
p.nextToken()
return args
}
p.nextToken()
args = append(args, p.ParseExpression(LOWEST))
for p.peekTokenIs(COMMA) {
p.nextToken()
p.nextToken()
args = append(args, p.ParseExpression(LOWEST))
}
if !p.expectPeek(end) {
return nil
}
return args
}
func (p *Parser) parseIndexExpression(left Expression) Expression {
p.nextToken()
index := p.ParseExpression(LOWEST)
if index == nil {
p.addError("expected expression inside brackets")
return nil
}
if !p.expectPeek(RBRACKET) {
p.addError("expected ']' after index expression")
return nil
}
return &IndexExpression{
Left: left,
Index: index,
}
}
// Helper methods
func (p *Parser) curTokenIs(t TokenType) bool {
return p.curToken.Type == t
}
func (p *Parser) peekTokenIs(t TokenType) bool {
return p.peekToken.Type == t
}
func (p *Parser) expectPeek(t TokenType) bool {
if p.peekTokenIs(t) {
p.nextToken()
return true
}
p.peekError(t)
return false
}
func (p *Parser) expectPeekIdent() bool {
if p.peekTokenIs(IDENT) || p.isKeyword(p.peekToken.Type) {
p.nextToken()
return true
}
p.peekError(IDENT)
return false
}
func (p *Parser) isKeyword(t TokenType) bool {
switch t {
case TRUE, FALSE, NIL, AND, OR, NOT, IF, THEN, ELSEIF, ELSE, END, ECHO, FOR, WHILE, IN, DO, BREAK, EXIT, FN, RETURN, STRUCT:
return true
default:
return false
}
}
// Error handling
func (p *Parser) addError(message string) {
p.errors = append(p.errors, ParseError{
Message: message,
Line: p.curToken.Line,
Column: p.curToken.Column,
Token: p.curToken,
})
}
func (p *Parser) peekError(t TokenType) {
message := fmt.Sprintf("expected next token to be %s, got %s instead",
tokenTypeString(t), tokenTypeString(p.peekToken.Type))
p.errors = append(p.errors, ParseError{
Message: message,
Line: p.peekToken.Line,
Column: p.peekToken.Column,
Token: p.peekToken,
})
}
func (p *Parser) noPrefixParseFnError(t TokenType) {
var message string
switch t {
case ASSIGN:
message = "unexpected assignment operator, missing left-hand side identifier"
case PLUS, MINUS, STAR, SLASH:
message = fmt.Sprintf("unexpected operator '%s', missing left operand", tokenTypeString(t))
case RPAREN:
message = "unexpected closing parenthesis"
case RBRACE:
message = "unexpected closing brace"
case RBRACKET:
message = "unexpected closing bracket"
case EOF:
message = "unexpected end of input"
default:
message = fmt.Sprintf("unexpected token '%s'", tokenTypeString(t))
}
p.addError(message)
}
func (p *Parser) peekPrecedence() Precedence {
if p, ok := precedences[p.peekToken.Type]; ok {
return p
}
return LOWEST
}
func (p *Parser) curPrecedence() Precedence {
if p, ok := precedences[p.curToken.Type]; ok {
return p
}
return LOWEST
}
// Errors returns all parsing errors
func (p *Parser) Errors() []ParseError {
return p.errors
}
func (p *Parser) HasErrors() bool {
return len(p.errors) > 0
}
func (p *Parser) ErrorStrings() []string {
result := make([]string, len(p.errors))
for i, err := range p.errors {
result[i] = err.Error()
}
return result
}
// tokenTypeString returns human-readable string for token types
func tokenTypeString(t TokenType) string {
switch t {
case IDENT:
return "identifier"
case NUMBER:
return "number"
case STRING:
return "string"
case TRUE, FALSE:
return "boolean"
case NIL:
return "nil"
case ASSIGN:
return "="
case PLUS:
return "+"
case MINUS:
return "-"
case STAR:
return "*"
case SLASH:
return "/"
case DOT:
return "."
case COLON:
return ":"
case EQ:
return "=="
case NOT_EQ:
return "!="
case LT:
return "<"
case GT:
return ">"
case LT_EQ:
return "<="
case GT_EQ:
return ">="
case AND:
return "and"
case OR:
return "or"
case NOT:
return "not"
case LPAREN:
return "("
case RPAREN:
return ")"
case LBRACE:
return "{"
case RBRACE:
return "}"
case LBRACKET:
return "["
case RBRACKET:
return "]"
case COMMA:
return ","
case ELLIPSIS:
return "..."
case IF:
return "if"
case THEN:
return "then"
case ELSEIF:
return "elseif"
case ELSE:
return "else"
case END:
return "end"
case ECHO:
return "echo"
case FOR:
return "for"
case WHILE:
return "while"
case IN:
return "in"
case DO:
return "do"
case BREAK:
return "break"
case EXIT:
return "exit"
case FN:
return "fn"
case RETURN:
return "return"
case STRUCT:
return "struct"
case EOF:
return "end of file"
case ILLEGAL:
return "illegal token"
default:
return "unknown"
}
}