1660 lines
36 KiB
Go
1660 lines
36 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 with optimized AST generation
|
|
type Parser struct {
|
|
lexer *Lexer
|
|
|
|
curToken Token
|
|
peekToken Token
|
|
|
|
prefixParseFns map[TokenType]func() Expression
|
|
infixParseFns map[TokenType]func(Expression) Expression
|
|
|
|
errors []ParseError
|
|
|
|
// Scope tracking with slot management
|
|
scopes []map[string]*Variable
|
|
scopeTypes []string
|
|
scopeSlots []int // Next available slot per scope
|
|
currentDepth int
|
|
|
|
// Struct tracking with ID mapping
|
|
structs map[string]*StructStatement
|
|
structIDs map[uint16]*StructStatement
|
|
nextID uint16
|
|
}
|
|
|
|
// Variable represents a resolved variable in scope
|
|
type Variable struct {
|
|
Name string
|
|
ScopeDepth int
|
|
SlotIndex int
|
|
TypeHint TypeInfo
|
|
}
|
|
|
|
// NewParser creates a new parser instance
|
|
func NewParser(lexer *Lexer) *Parser {
|
|
p := &Parser{
|
|
lexer: lexer,
|
|
errors: []ParseError{},
|
|
scopes: []map[string]*Variable{make(map[string]*Variable)},
|
|
scopeTypes: []string{"global"},
|
|
scopeSlots: []int{0},
|
|
currentDepth: 0,
|
|
structs: make(map[string]*StructStatement),
|
|
structIDs: make(map[uint16]*StructStatement),
|
|
nextID: 1, // 0 reserved for non-struct types
|
|
}
|
|
|
|
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(MOD, 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)
|
|
|
|
p.nextToken()
|
|
p.nextToken()
|
|
|
|
return p
|
|
}
|
|
|
|
// Helper to create position from token
|
|
func (p *Parser) pos() Position {
|
|
return Position{Line: p.curToken.Line, Column: p.curToken.Column}
|
|
}
|
|
|
|
// Struct management
|
|
func (p *Parser) registerStruct(stmt *StructStatement) {
|
|
stmt.ID = p.nextID
|
|
p.nextID++
|
|
p.structs[stmt.Name] = stmt
|
|
p.structIDs[stmt.ID] = stmt
|
|
}
|
|
|
|
func (p *Parser) getStructByName(name string) *StructStatement {
|
|
return p.structs[name]
|
|
}
|
|
|
|
func (p *Parser) isStructDefined(name string) bool {
|
|
_, exists := p.structs[name]
|
|
return exists
|
|
}
|
|
|
|
// Scope management with slot allocation
|
|
func (p *Parser) enterScope(scopeType string) {
|
|
p.scopes = append(p.scopes, make(map[string]*Variable))
|
|
p.scopeTypes = append(p.scopeTypes, scopeType)
|
|
p.scopeSlots = append(p.scopeSlots, 0)
|
|
p.currentDepth++
|
|
}
|
|
|
|
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]
|
|
p.scopeSlots = p.scopeSlots[:len(p.scopeSlots)-1]
|
|
p.currentDepth--
|
|
}
|
|
}
|
|
|
|
func (p *Parser) currentVariableScope() map[string]*Variable {
|
|
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 _, exists := p.scopes[i][name]; exists {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *Parser) declareVariable(name string, typeHint TypeInfo) *Variable {
|
|
scope := p.currentVariableScope()
|
|
scopeIdx := len(p.scopes) - 1
|
|
if len(p.scopeTypes) > 1 && p.scopeTypes[len(p.scopeTypes)-1] == "loop" {
|
|
scopeIdx--
|
|
}
|
|
|
|
variable := &Variable{
|
|
Name: name,
|
|
ScopeDepth: p.currentDepth,
|
|
SlotIndex: p.scopeSlots[scopeIdx],
|
|
TypeHint: typeHint,
|
|
}
|
|
|
|
scope[name] = variable
|
|
p.scopeSlots[scopeIdx]++
|
|
return variable
|
|
}
|
|
|
|
func (p *Parser) declareLoopVariable(name string, typeHint TypeInfo) *Variable {
|
|
scope := p.scopes[len(p.scopes)-1]
|
|
scopeIdx := len(p.scopes) - 1
|
|
|
|
variable := &Variable{
|
|
Name: name,
|
|
ScopeDepth: p.currentDepth,
|
|
SlotIndex: p.scopeSlots[scopeIdx],
|
|
TypeHint: typeHint,
|
|
}
|
|
|
|
scope[name] = variable
|
|
p.scopeSlots[scopeIdx]++
|
|
return variable
|
|
}
|
|
|
|
func (p *Parser) lookupVariable(name string) *Variable {
|
|
for i := len(p.scopes) - 1; i >= 0; i-- {
|
|
if variable, exists := p.scopes[i][name]; exists {
|
|
return variable
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseTypeHint parses optional type hint after colon, returns by value
|
|
func (p *Parser) parseTypeHint() TypeInfo {
|
|
if !p.peekTokenIs(COLON) {
|
|
return UnknownType
|
|
}
|
|
|
|
p.nextToken() // consume ':'
|
|
|
|
if !p.expectPeekIdent() {
|
|
p.addError("expected type name after ':'")
|
|
return UnknownType
|
|
}
|
|
|
|
typeName := p.curToken.Literal
|
|
|
|
// Check built-in types
|
|
switch typeName {
|
|
case "number":
|
|
return TypeInfo{Type: TypeNumber, Inferred: false}
|
|
case "string":
|
|
return TypeInfo{Type: TypeString, Inferred: false}
|
|
case "bool":
|
|
return TypeInfo{Type: TypeBool, Inferred: false}
|
|
case "nil":
|
|
return TypeInfo{Type: TypeNil, Inferred: false}
|
|
case "table":
|
|
return TypeInfo{Type: TypeTable, Inferred: false}
|
|
case "function":
|
|
return TypeInfo{Type: TypeFunction, Inferred: false}
|
|
case "any":
|
|
return TypeInfo{Type: TypeAny, Inferred: false}
|
|
default:
|
|
// Check if it's a struct type
|
|
if structDef := p.getStructByName(typeName); structDef != nil {
|
|
return TypeInfo{Type: TypeStruct, StructID: structDef.ID, Inferred: false}
|
|
}
|
|
p.addError(fmt.Sprintf("invalid type name '%s'", typeName))
|
|
return UnknownType
|
|
}
|
|
}
|
|
|
|
// registerPrefix/registerInfix
|
|
func (p *Parser) registerPrefix(tokenType TokenType, fn func() Expression) {
|
|
p.prefixParseFns[tokenType] = fn
|
|
}
|
|
|
|
func (p *Parser) registerInfix(tokenType TokenType, fn func(Expression) Expression) {
|
|
p.infixParseFns[tokenType] = fn
|
|
}
|
|
|
|
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{Position: p.pos()}
|
|
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{Position: p.pos()}
|
|
|
|
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.registerStruct(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, Position: p.pos()}
|
|
|
|
// Parse required type hint
|
|
field.TypeHint = p.parseTypeHint()
|
|
if field.TypeHint.Type == TypeUnknown {
|
|
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.registerStruct(stmt)
|
|
return stmt
|
|
}
|
|
|
|
// parseFunctionStatement handles both regular functions and methods
|
|
func (p *Parser) parseFunctionStatement() Statement {
|
|
pos := p.pos()
|
|
|
|
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
|
|
|
|
// Get struct ID
|
|
structDef := p.getStructByName(funcName)
|
|
if structDef == nil {
|
|
p.addError(fmt.Sprintf("method defined on undefined struct '%s'", funcName))
|
|
return nil
|
|
}
|
|
|
|
if !p.expectPeek(LPAREN) {
|
|
p.addError("expected '(' after method name")
|
|
return nil
|
|
}
|
|
|
|
// Parse the function literal
|
|
funcLit := &FunctionLiteral{Position: p.pos()}
|
|
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.enterScope("function")
|
|
for _, param := range funcLit.Parameters {
|
|
p.declareVariable(param.Name, param.TypeHint)
|
|
}
|
|
funcLit.Body = p.parseBlockStatements(END)
|
|
p.exitScope()
|
|
|
|
if !p.curTokenIs(END) {
|
|
p.addError("expected 'end' to close function")
|
|
return nil
|
|
}
|
|
|
|
return &MethodDefinition{
|
|
StructID: structDef.ID,
|
|
MethodName: methodName,
|
|
Function: funcLit,
|
|
Position: pos,
|
|
}
|
|
}
|
|
|
|
// Regular function - handle as function literal expression statement
|
|
funcLit := p.parseFunctionLiteral()
|
|
if funcLit == nil {
|
|
return nil
|
|
}
|
|
|
|
return &ExpressionStatement{Expression: funcLit, Position: pos}
|
|
}
|
|
|
|
// parseIdentifierStatement handles assignments and expression statements
|
|
func (p *Parser) parseIdentifierStatement() Statement {
|
|
pos := p.pos()
|
|
|
|
// 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 = UnknownType
|
|
if _, ok := expr.(*Identifier); ok {
|
|
typeHint = p.parseTypeHint()
|
|
}
|
|
|
|
// Check if this is an assignment
|
|
if p.peekTokenIs(ASSIGN) {
|
|
// Create unified assignment
|
|
assignment := &Assignment{
|
|
Target: expr,
|
|
TypeHint: typeHint,
|
|
Position: pos,
|
|
}
|
|
|
|
// Validate assignment target and check if it's a declaration
|
|
switch target := expr.(type) {
|
|
case *Identifier:
|
|
if variable := p.lookupVariable(target.Value); variable != nil {
|
|
// Existing variable - resolve it
|
|
target.ScopeDepth = variable.ScopeDepth
|
|
target.SlotIndex = variable.SlotIndex
|
|
assignment.IsDeclaration = false
|
|
} else {
|
|
// New variable declaration - create and resolve
|
|
variable := p.declareVariable(target.Value, typeHint)
|
|
target.ScopeDepth = variable.ScopeDepth
|
|
target.SlotIndex = variable.SlotIndex
|
|
assignment.IsDeclaration = true
|
|
}
|
|
case *DotExpression, *IndexExpression:
|
|
assignment.IsDeclaration = false
|
|
default:
|
|
p.addError("invalid assignment target")
|
|
return nil
|
|
}
|
|
|
|
if !p.expectPeek(ASSIGN) {
|
|
return nil
|
|
}
|
|
|
|
p.nextToken()
|
|
|
|
assignment.Value = p.ParseExpression(LOWEST)
|
|
if assignment.Value == nil {
|
|
p.addError("expected expression after assignment operator")
|
|
return nil
|
|
}
|
|
|
|
return assignment
|
|
} else {
|
|
// This is an expression statement
|
|
return &ExpressionStatement{Expression: expr, Position: pos}
|
|
}
|
|
}
|
|
|
|
// parseEchoStatement parses echo statements
|
|
func (p *Parser) parseEchoStatement() *EchoStatement {
|
|
stmt := &EchoStatement{Position: p.pos()}
|
|
|
|
p.nextToken() // move past 'echo'
|
|
|
|
stmt.Value = p.ParseExpression(LOWEST)
|
|
if stmt.Value == nil {
|
|
p.addError("expected expression after 'echo'")
|
|
return nil
|
|
}
|
|
|
|
return stmt
|
|
}
|
|
|
|
// Simple statement parsers
|
|
func (p *Parser) parseBreakStatement() *BreakStatement {
|
|
if p.peekTokenIs(IDENT) {
|
|
p.addError("unexpected identifier")
|
|
return nil
|
|
}
|
|
return &BreakStatement{Position: p.pos()}
|
|
}
|
|
|
|
func (p *Parser) parseExitStatement() *ExitStatement {
|
|
stmt := &ExitStatement{Position: p.pos()}
|
|
|
|
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
|
|
}
|
|
|
|
func (p *Parser) parseReturnStatement() *ReturnStatement {
|
|
stmt := &ReturnStatement{Position: p.pos()}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
// Loop statement parsers
|
|
func (p *Parser) parseWhileStatement() *WhileStatement {
|
|
stmt := &WhileStatement{Position: p.pos()}
|
|
|
|
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()
|
|
|
|
stmt.Body = p.parseBlockStatements(END)
|
|
|
|
if !p.curTokenIs(END) {
|
|
p.addError("expected 'end' to close while loop")
|
|
return nil
|
|
}
|
|
|
|
return stmt
|
|
}
|
|
|
|
func (p *Parser) parseForStatement() Statement {
|
|
pos := p.pos()
|
|
p.nextToken()
|
|
|
|
if !p.curTokenIs(IDENT) {
|
|
p.addError("expected identifier after 'for'")
|
|
return nil
|
|
}
|
|
|
|
firstVar := &Identifier{Value: p.curToken.Literal, Position: p.pos()}
|
|
|
|
if p.peekTokenIs(ASSIGN) {
|
|
return p.parseNumericForStatement(firstVar, pos)
|
|
} else if p.peekTokenIs(COMMA) || p.peekTokenIs(IN) {
|
|
return p.parseForInStatement(firstVar, pos)
|
|
} else {
|
|
p.addError("expected '=', ',' or 'in' after for loop variable")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseNumericForStatement(variable *Identifier, pos Position) *ForStatement {
|
|
stmt := &ForStatement{Variable: variable, Position: pos}
|
|
|
|
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.enterScope("loop")
|
|
// Declare and resolve loop variable
|
|
loopVar := p.declareLoopVariable(variable.Value, NumberType)
|
|
variable.ScopeDepth = loopVar.ScopeDepth
|
|
variable.SlotIndex = loopVar.SlotIndex
|
|
variable.typeInfo = NumberType
|
|
|
|
stmt.Body = p.parseBlockStatements(END)
|
|
p.exitScope()
|
|
|
|
if !p.curTokenIs(END) {
|
|
p.addError("expected 'end' to close for loop")
|
|
return nil
|
|
}
|
|
|
|
return stmt
|
|
}
|
|
|
|
func (p *Parser) parseForInStatement(firstVar *Identifier, pos Position) *ForInStatement {
|
|
stmt := &ForInStatement{Position: pos}
|
|
|
|
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, Position: p.pos()}
|
|
} 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.enterScope("loop")
|
|
|
|
// Declare and resolve loop variables
|
|
if stmt.Key != nil {
|
|
keyVar := p.declareLoopVariable(stmt.Key.Value, AnyType)
|
|
stmt.Key.ScopeDepth = keyVar.ScopeDepth
|
|
stmt.Key.SlotIndex = keyVar.SlotIndex
|
|
stmt.Key.typeInfo = AnyType
|
|
}
|
|
|
|
valueVar := p.declareLoopVariable(stmt.Value.Value, AnyType)
|
|
stmt.Value.ScopeDepth = valueVar.ScopeDepth
|
|
stmt.Value.SlotIndex = valueVar.SlotIndex
|
|
stmt.Value.typeInfo = AnyType
|
|
|
|
stmt.Body = p.parseBlockStatements(END)
|
|
p.exitScope()
|
|
|
|
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{Position: p.pos()}
|
|
|
|
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
|
|
}
|
|
|
|
stmt.Body = p.parseBlockStatements(ELSEIF, ELSE, END)
|
|
|
|
for p.curTokenIs(ELSEIF) {
|
|
elseif := ElseIfClause{Position: p.pos()}
|
|
|
|
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()
|
|
|
|
elseif.Body = p.parseBlockStatements(ELSEIF, ELSE, END)
|
|
stmt.ElseIfs = append(stmt.ElseIfs, elseif)
|
|
}
|
|
|
|
if p.curTokenIs(ELSE) {
|
|
p.nextToken()
|
|
stmt.Else = p.parseBlockStatements(END)
|
|
}
|
|
|
|
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 := make([]Statement, 0, 8) // Pre-allocate for performance
|
|
|
|
for !p.curTokenIs(EOF) && !p.isTerminator(terminators...) {
|
|
stmt := p.parseStatement()
|
|
if stmt != nil {
|
|
statements = append(statements, stmt)
|
|
}
|
|
p.nextToken()
|
|
}
|
|
|
|
return statements
|
|
}
|
|
|
|
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 {
|
|
ident := &Identifier{
|
|
Value: p.curToken.Literal,
|
|
ScopeDepth: -1,
|
|
SlotIndex: -1,
|
|
Position: p.pos(),
|
|
}
|
|
|
|
// Resolve variable if it exists
|
|
if variable := p.lookupVariable(ident.Value); variable != nil {
|
|
ident.ScopeDepth = variable.ScopeDepth
|
|
ident.SlotIndex = variable.SlotIndex
|
|
ident.typeInfo = variable.TypeHint
|
|
}
|
|
|
|
return ident
|
|
}
|
|
|
|
func (p *Parser) parseNumberLiteral() Expression {
|
|
lit := &NumberLiteral{Position: p.pos()}
|
|
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, Position: p.pos()}
|
|
}
|
|
|
|
func (p *Parser) parseBooleanLiteral() Expression {
|
|
return &BooleanLiteral{Value: p.curTokenIs(TRUE), Position: p.pos()}
|
|
}
|
|
|
|
func (p *Parser) parseNilLiteral() Expression {
|
|
return &NilLiteral{Position: p.pos()}
|
|
}
|
|
|
|
func (p *Parser) parsePrefixExpression() Expression {
|
|
expression := &PrefixExpression{
|
|
Operator: p.curToken.Literal,
|
|
Position: p.pos(),
|
|
}
|
|
|
|
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 {
|
|
pos := p.pos()
|
|
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 := &Assignment{
|
|
Target: target,
|
|
Value: value,
|
|
IsExpression: true,
|
|
Position: pos,
|
|
}
|
|
|
|
// Handle variable declaration for assignment expressions
|
|
if ident, ok := target.(*Identifier); ok {
|
|
if variable := p.lookupVariable(ident.Value); variable != nil {
|
|
// Existing variable
|
|
ident.ScopeDepth = variable.ScopeDepth
|
|
ident.SlotIndex = variable.SlotIndex
|
|
assignExpr.IsDeclaration = false
|
|
} else {
|
|
// New variable declaration
|
|
newVar := p.declareVariable(ident.Value, UnknownType)
|
|
ident.ScopeDepth = newVar.ScopeDepth
|
|
ident.SlotIndex = newVar.SlotIndex
|
|
assignExpr.IsDeclaration = true
|
|
}
|
|
}
|
|
|
|
return assignExpr
|
|
}
|
|
|
|
func (p *Parser) parseFunctionLiteral() Expression {
|
|
fn := &FunctionLiteral{Position: p.pos()}
|
|
|
|
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.enterScope("function")
|
|
|
|
// Declare function parameters as resolved variables
|
|
for _, param := range fn.Parameters {
|
|
p.declareVariable(param.Name, param.TypeHint)
|
|
param.Position = Position{Line: p.curToken.Line, Column: p.curToken.Column}
|
|
}
|
|
|
|
fn.Body = p.parseBlockStatements(END)
|
|
|
|
// Calculate function metadata before exiting scope
|
|
scopeIdx := len(p.scopes) - 1
|
|
fn.LocalCount = p.scopeSlots[scopeIdx]
|
|
fn.UpvalueCount = 0 // TODO: Calculate captured variables
|
|
fn.MaxStackDepth = 0 // TODO: Calculate max expression depth
|
|
|
|
p.exitScope()
|
|
|
|
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, Position: p.pos()}
|
|
|
|
// 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{Position: p.pos()}
|
|
table.Pairs = make([]TablePair, 0, 4) // Pre-allocate
|
|
|
|
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{Position: p.pos()}
|
|
|
|
if (p.curTokenIs(IDENT) || p.curTokenIs(STRING)) && p.peekTokenIs(ASSIGN) {
|
|
if p.curTokenIs(IDENT) {
|
|
pair.Key = &Identifier{Value: p.curToken.Literal, Position: p.pos()}
|
|
} else {
|
|
pair.Key = &StringLiteral{Value: p.curToken.Literal, Position: p.pos()}
|
|
}
|
|
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
|
|
func (p *Parser) parseStructConstructor(left Expression) Expression {
|
|
pos := p.pos()
|
|
|
|
ident, ok := left.(*Identifier)
|
|
if !ok {
|
|
return p.parseTableLiteralFromBrace()
|
|
}
|
|
|
|
structName := ident.Value
|
|
structDef := p.getStructByName(structName)
|
|
if structDef == nil {
|
|
// Not a struct, parse as table literal
|
|
return p.parseTableLiteralFromBrace()
|
|
}
|
|
|
|
constructor := &StructConstructor{
|
|
StructID: structDef.ID,
|
|
Fields: make([]TablePair, 0, 4),
|
|
typeInfo: TypeInfo{Type: TypeStruct, StructID: structDef.ID, Inferred: true},
|
|
Position: pos,
|
|
}
|
|
|
|
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{Position: p.pos()}
|
|
|
|
if (p.curTokenIs(IDENT) || p.curTokenIs(STRING)) && p.peekTokenIs(ASSIGN) {
|
|
if p.curTokenIs(IDENT) {
|
|
pair.Key = &Identifier{Value: p.curToken.Literal, Position: p.pos()}
|
|
} else {
|
|
pair.Key = &StringLiteral{Value: p.curToken.Literal, Position: p.pos()}
|
|
}
|
|
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 {
|
|
table := &TableLiteral{Position: p.pos()}
|
|
table.Pairs = make([]TablePair, 0, 4)
|
|
|
|
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{Position: p.pos()}
|
|
|
|
if (p.curTokenIs(IDENT) || p.curTokenIs(STRING)) && p.peekTokenIs(ASSIGN) {
|
|
if p.curTokenIs(IDENT) {
|
|
pair.Key = &Identifier{Value: p.curToken.Literal, Position: p.pos()}
|
|
} else {
|
|
pair.Key = &StringLiteral{Value: p.curToken.Literal, Position: p.pos()}
|
|
}
|
|
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,
|
|
Position: p.pos(),
|
|
}
|
|
|
|
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 {
|
|
pos := p.pos()
|
|
|
|
if !p.expectPeekIdent() {
|
|
p.addError("expected identifier after '.'")
|
|
return nil
|
|
}
|
|
|
|
return &DotExpression{
|
|
Left: left,
|
|
Key: p.curToken.Literal,
|
|
Position: pos,
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseCallExpression(fn Expression) Expression {
|
|
call := &CallExpression{Function: fn, Position: p.pos()}
|
|
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 {
|
|
pos := p.pos()
|
|
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,
|
|
Position: pos,
|
|
}
|
|
}
|
|
|
|
// 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, MOD:
|
|
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
|
|
}
|
|
|
|
// Error reporting
|
|
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 MOD:
|
|
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"
|
|
}
|
|
}
|