From 3d07d010dc650bb9eb61c43c3a33f289e72b6153 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 11 Jun 2025 23:08:34 -0500 Subject: [PATCH] modulo operator --- parser/ast.go | 219 +++++++++++++++++++++--------------- parser/lexer.go | 2 + parser/parser.go | 117 +++++++++++-------- parser/tests/parser_test.go | 43 +++++++ parser/token.go | 4 +- parser/types.go | 5 +- 6 files changed, 252 insertions(+), 138 deletions(-) diff --git a/parser/ast.go b/parser/ast.go index 2d1c4ae..0e401f7 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -2,11 +2,16 @@ package parser import "fmt" -// Note: Type definitions moved to types.go for proper separation of concerns +// Position represents source location information +type Position struct { + Line int + Column int +} // Node represents any node in the AST type Node interface { String() string + Pos() Position } // Statement represents statement nodes that can appear at the top level or in blocks @@ -19,14 +24,14 @@ type Statement interface { type Expression interface { Node expressionNode() - TypeInfo() TypeInfo // Returns type by value, not pointer + TypeInfo() TypeInfo } // Program represents the root of the AST containing all top-level statements. -// Tracks exit code for script termination and owns the statement list. type Program struct { Statements []Statement ExitCode int + Position Position } func (p *Program) String() string { @@ -36,24 +41,26 @@ func (p *Program) String() string { } return result } +func (p *Program) Pos() Position { return p.Position } // StructField represents a field definition within a struct. -// Contains field name and required type annotation for compile-time checking. type StructField struct { Name string - TypeHint TypeInfo // Required for struct fields, embeds directly + TypeHint TypeInfo + Position Position } func (sf *StructField) String() string { return fmt.Sprintf("%s: %s", sf.Name, typeToString(sf.TypeHint)) } +func (sf *StructField) Pos() Position { return sf.Position } // StructStatement represents struct type definitions with named fields. -// Defines new types that can be instantiated and used for type checking. type StructStatement struct { - Name string - Fields []StructField - ID uint16 // Unique identifier for fast lookup + Name string + Fields []StructField + ID uint16 + Position Position } func (ss *StructStatement) statementNode() {} @@ -67,26 +74,28 @@ func (ss *StructStatement) String() string { } return fmt.Sprintf("struct %s {\n\t%s\n}", ss.Name, fields) } +func (ss *StructStatement) Pos() Position { return ss.Position } // MethodDefinition represents method definitions attached to struct types. -// Links a function implementation to a specific struct via struct ID. type MethodDefinition struct { - StructID uint16 // Index into struct table for fast lookup + StructID uint16 MethodName string Function *FunctionLiteral + Position Position } func (md *MethodDefinition) statementNode() {} func (md *MethodDefinition) String() string { return fmt.Sprintf("fn .%s%s", md.MethodName, md.Function.String()[2:]) } +func (md *MethodDefinition) Pos() Position { return md.Position } // StructConstructor represents struct instantiation with field initialization. -// Uses struct ID for fast type resolution and validation during parsing. type StructConstructor struct { - StructID uint16 // Index into struct table - Fields []TablePair // Reuses table pair structure for field assignments - typeInfo TypeInfo // Cached type info for this constructor + StructID uint16 + Fields []TablePair + typeInfo TypeInfo + Position Position } func (sc *StructConstructor) expressionNode() {} @@ -98,15 +107,16 @@ func (sc *StructConstructor) String() string { return fmt.Sprintf("{%s}", joinStrings(pairs, ", ")) } func (sc *StructConstructor) TypeInfo() TypeInfo { return sc.typeInfo } +func (sc *StructConstructor) Pos() Position { return sc.Position } // Assignment represents both variable assignment statements and assignment expressions. -// Unified design reduces AST node count and simplifies type checking logic. type Assignment struct { - Target Expression // Target (identifier, dot, or index expression) - Value Expression // Value being assigned - TypeHint TypeInfo // Optional explicit type hint, embeds directly - IsDeclaration bool // True if declaring new variable in current scope - IsExpression bool // True if used as expression (wrapped in parentheses) + Target Expression + Value Expression + TypeHint TypeInfo + IsDeclaration bool + IsExpression bool + Position Position } func (a *Assignment) statementNode() {} @@ -131,40 +141,45 @@ func (a *Assignment) String() string { return result } func (a *Assignment) TypeInfo() TypeInfo { return a.Value.TypeInfo() } +func (a *Assignment) Pos() Position { return a.Position } // ExpressionStatement wraps expressions used as statements. -// Allows function calls and other expressions at statement level. type ExpressionStatement struct { Expression Expression + Position Position } func (es *ExpressionStatement) statementNode() {} func (es *ExpressionStatement) String() string { return es.Expression.String() } +func (es *ExpressionStatement) Pos() Position { return es.Position } // EchoStatement represents output statements for displaying values. -// Simple debugging and output mechanism built into the language. type EchoStatement struct { - Value Expression + Value Expression + Position Position } func (es *EchoStatement) statementNode() {} func (es *EchoStatement) String() string { return fmt.Sprintf("echo %s", es.Value.String()) } +func (es *EchoStatement) Pos() Position { return es.Position } // BreakStatement represents loop exit statements. -// Simple marker node with no additional data needed. -type BreakStatement struct{} +type BreakStatement struct { + Position Position +} func (bs *BreakStatement) statementNode() {} func (bs *BreakStatement) String() string { return "break" } +func (bs *BreakStatement) Pos() Position { return bs.Position } // ExitStatement represents script termination with optional exit code. -// Value expression is nil for plain "exit", non-nil for "exit ". type ExitStatement struct { - Value Expression // Optional exit code expression + Value Expression + Position Position } func (es *ExitStatement) statementNode() {} @@ -174,11 +189,12 @@ func (es *ExitStatement) String() string { } return fmt.Sprintf("exit %s", es.Value.String()) } +func (es *ExitStatement) Pos() Position { return es.Position } // ReturnStatement represents function return with optional value. -// Value expression is nil for plain "return", non-nil for "return ". type ReturnStatement struct { - Value Expression // Optional return value expression + Value Expression + Position Position } func (rs *ReturnStatement) statementNode() {} @@ -188,12 +204,13 @@ func (rs *ReturnStatement) String() string { } return fmt.Sprintf("return %s", rs.Value.String()) } +func (rs *ReturnStatement) Pos() Position { return rs.Position } // ElseIfClause represents conditional branches in if statements. -// Contains condition expression and body statements for this branch. type ElseIfClause struct { Condition Expression Body []Statement + Position Position } func (eic *ElseIfClause) String() string { @@ -203,14 +220,15 @@ func (eic *ElseIfClause) String() string { } return fmt.Sprintf("elseif %s then\n%s", eic.Condition.String(), body) } +func (eic *ElseIfClause) Pos() Position { return eic.Position } // IfStatement represents conditional execution with optional elseif and else branches. -// Supports multiple elseif clauses and an optional final else clause. type IfStatement struct { - Condition Expression // Main condition - Body []Statement // Statements to execute if condition is true - ElseIfs []ElseIfClause // Optional elseif branches - Else []Statement // Optional else branch + Condition Expression + Body []Statement + ElseIfs []ElseIfClause + Else []Statement + Position Position } func (is *IfStatement) statementNode() {} @@ -236,12 +254,13 @@ func (is *IfStatement) String() string { result += "end" return result } +func (is *IfStatement) Pos() Position { return is.Position } // WhileStatement represents condition-based loops that execute while condition is true. -// Contains condition expression and body statements to repeat. type WhileStatement struct { Condition Expression Body []Statement + Position Position } func (ws *WhileStatement) statementNode() {} @@ -256,15 +275,16 @@ func (ws *WhileStatement) String() string { result += "end" return result } +func (ws *WhileStatement) Pos() Position { return ws.Position } // ForStatement represents numeric for loops with start, end, and optional step. -// Variable is automatically scoped to the loop body. type ForStatement struct { - Variable *Identifier // Loop variable (automatically number type) - Start Expression // Starting value expression - End Expression // Ending value expression - Step Expression // Optional step expression (nil means step of 1) - Body []Statement // Loop body statements + Variable *Identifier + Start Expression + End Expression + Step Expression + Body []Statement + Position Position } func (fs *ForStatement) statementNode() {} @@ -285,14 +305,15 @@ func (fs *ForStatement) String() string { result += "end" return result } +func (fs *ForStatement) Pos() Position { return fs.Position } // ForInStatement represents iterator-based loops over tables, arrays, or other iterables. -// Supports both single variable (for v in iter) and key-value (for k,v in iter) forms. type ForInStatement struct { - Key *Identifier // Optional key variable (nil for single variable iteration) - Value *Identifier // Value variable (required) - Iterable Expression // Expression to iterate over - Body []Statement // Loop body statements + Key *Identifier + Value *Identifier + Iterable Expression + Body []Statement + Position Position } func (fis *ForInStatement) statementNode() {} @@ -313,12 +334,13 @@ func (fis *ForInStatement) String() string { result += "end" return result } +func (fis *ForInStatement) Pos() Position { return fis.Position } // FunctionParameter represents a parameter in function definitions. -// Contains parameter name and optional type hint for type checking. type FunctionParameter struct { Name string - TypeHint TypeInfo // Optional type constraint, embeds directly + TypeHint TypeInfo + Position Position } func (fp *FunctionParameter) String() string { @@ -327,12 +349,13 @@ func (fp *FunctionParameter) String() string { } return fp.Name } +func (fp *FunctionParameter) Pos() Position { return fp.Position } // Identifier represents variable references and names. -// Stores resolved type information for efficient type checking. type Identifier struct { Value string - typeInfo TypeInfo // Resolved type, embeds directly + typeInfo TypeInfo + Position Position } func (i *Identifier) expressionNode() {} @@ -343,31 +366,34 @@ func (i *Identifier) TypeInfo() TypeInfo { } return i.typeInfo } +func (i *Identifier) Pos() Position { return i.Position } // NumberLiteral represents numeric constants including integers, floats, hex, and binary. -// Always has number type, so no additional type storage needed. type NumberLiteral struct { - Value float64 // All numbers stored as float64 for simplicity + Value float64 + Position Position } func (nl *NumberLiteral) expressionNode() {} func (nl *NumberLiteral) String() string { return fmt.Sprintf("%.2f", nl.Value) } func (nl *NumberLiteral) TypeInfo() TypeInfo { return NumberType } +func (nl *NumberLiteral) Pos() Position { return nl.Position } // StringLiteral represents string constants and multiline strings. -// Always has string type, so no additional type storage needed. type StringLiteral struct { - Value string // String content without quotes + Value string + Position Position } func (sl *StringLiteral) expressionNode() {} func (sl *StringLiteral) String() string { return fmt.Sprintf(`"%s"`, sl.Value) } func (sl *StringLiteral) TypeInfo() TypeInfo { return StringType } +func (sl *StringLiteral) Pos() Position { return sl.Position } // BooleanLiteral represents true and false constants. -// Always has bool type, so no additional type storage needed. type BooleanLiteral struct { - Value bool + Value bool + Position Position } func (bl *BooleanLiteral) expressionNode() {} @@ -378,22 +404,25 @@ func (bl *BooleanLiteral) String() string { return "false" } func (bl *BooleanLiteral) TypeInfo() TypeInfo { return BoolType } +func (bl *BooleanLiteral) Pos() Position { return bl.Position } // NilLiteral represents the nil constant value. -// Always has nil type, so no additional type storage needed. -type NilLiteral struct{} +type NilLiteral struct { + Position Position +} func (nl *NilLiteral) expressionNode() {} func (nl *NilLiteral) String() string { return "nil" } func (nl *NilLiteral) TypeInfo() TypeInfo { return NilType } +func (nl *NilLiteral) Pos() Position { return nl.Position } // FunctionLiteral represents function definitions with parameters, body, and optional return type. -// Always has function type, stores additional return type information separately. type FunctionLiteral struct { - Parameters []FunctionParameter // Function parameters with optional types - Body []Statement // Function body statements - ReturnType TypeInfo // Optional return type hint, embeds directly - Variadic bool // True if function accepts variable arguments + Parameters []FunctionParameter + Body []Statement + ReturnType TypeInfo + Variadic bool + Position Position } func (fl *FunctionLiteral) expressionNode() {} @@ -425,13 +454,14 @@ func (fl *FunctionLiteral) String() string { return result } func (fl *FunctionLiteral) TypeInfo() TypeInfo { return FunctionType } +func (fl *FunctionLiteral) Pos() Position { return fl.Position } // CallExpression represents function calls with arguments. -// Stores inferred return type from function signature analysis. type CallExpression struct { - Function Expression // Function expression to call - Arguments []Expression // Argument expressions - typeInfo TypeInfo // Inferred return type, embeds directly + Function Expression + Arguments []Expression + typeInfo TypeInfo + Position Position } func (ce *CallExpression) expressionNode() {} @@ -443,13 +473,14 @@ func (ce *CallExpression) String() string { return fmt.Sprintf("%s(%s)", ce.Function.String(), joinStrings(args, ", ")) } func (ce *CallExpression) TypeInfo() TypeInfo { return ce.typeInfo } +func (ce *CallExpression) Pos() Position { return ce.Position } // PrefixExpression represents unary operations like negation and logical not. -// Stores result type based on operator and operand type analysis. type PrefixExpression struct { - Operator string // Operator symbol ("-", "not") - Right Expression // Operand expression - typeInfo TypeInfo // Result type, embeds directly + Operator string + Right Expression + typeInfo TypeInfo + Position Position } func (pe *PrefixExpression) expressionNode() {} @@ -460,14 +491,15 @@ func (pe *PrefixExpression) String() string { return fmt.Sprintf("(%s%s)", pe.Operator, pe.Right.String()) } func (pe *PrefixExpression) TypeInfo() TypeInfo { return pe.typeInfo } +func (pe *PrefixExpression) Pos() Position { return pe.Position } // InfixExpression represents binary operations between two expressions. -// Stores result type based on operator and operand type compatibility. type InfixExpression struct { - Left Expression // Left operand - Right Expression // Right operand - Operator string // Operator symbol ("+", "-", "==", "and", etc.) - typeInfo TypeInfo // Result type, embeds directly + Left Expression + Right Expression + Operator string + typeInfo TypeInfo + Position Position } func (ie *InfixExpression) expressionNode() {} @@ -475,13 +507,14 @@ func (ie *InfixExpression) String() string { return fmt.Sprintf("(%s %s %s)", ie.Left.String(), ie.Operator, ie.Right.String()) } func (ie *InfixExpression) TypeInfo() TypeInfo { return ie.typeInfo } +func (ie *InfixExpression) Pos() Position { return ie.Position } // IndexExpression represents bracket-based member access (table[key]). -// Stores inferred element type based on container type analysis. type IndexExpression struct { - Left Expression // Container expression - Index Expression // Index/key expression - typeInfo TypeInfo // Element type, embeds directly + Left Expression + Index Expression + typeInfo TypeInfo + Position Position } func (ie *IndexExpression) expressionNode() {} @@ -489,13 +522,14 @@ func (ie *IndexExpression) String() string { return fmt.Sprintf("%s[%s]", ie.Left.String(), ie.Index.String()) } func (ie *IndexExpression) TypeInfo() TypeInfo { return ie.typeInfo } +func (ie *IndexExpression) Pos() Position { return ie.Position } // DotExpression represents dot-based member access (table.key). -// Stores inferred member type based on container type and field analysis. type DotExpression struct { - Left Expression // Container expression - Key string // Member name - typeInfo TypeInfo // Member type, embeds directly + Left Expression + Key string + typeInfo TypeInfo + Position Position } func (de *DotExpression) expressionNode() {} @@ -503,12 +537,13 @@ func (de *DotExpression) String() string { return fmt.Sprintf("%s.%s", de.Left.String(), de.Key) } func (de *DotExpression) TypeInfo() TypeInfo { return de.typeInfo } +func (de *DotExpression) Pos() Position { return de.Position } // TablePair represents key-value pairs in table literals and struct constructors. -// Key is nil for array-style elements, non-nil for object-style elements. type TablePair struct { - Key Expression // Key expression (nil for array elements) - Value Expression // Value expression + Key Expression + Value Expression + Position Position } func (tp *TablePair) String() string { @@ -517,11 +552,12 @@ func (tp *TablePair) String() string { } return fmt.Sprintf("%s = %s", tp.Key.String(), tp.Value.String()) } +func (tp *TablePair) Pos() Position { return tp.Position } // TableLiteral represents table/array/object literals with key-value pairs. -// Always has table type, provides methods to check if it's array-style. type TableLiteral struct { - Pairs []TablePair // Key-value pairs (key nil for array elements) + Pairs []TablePair + Position Position } func (tl *TableLiteral) expressionNode() {} @@ -533,6 +569,7 @@ func (tl *TableLiteral) String() string { return fmt.Sprintf("{%s}", joinStrings(pairs, ", ")) } func (tl *TableLiteral) TypeInfo() TypeInfo { return TableType } +func (tl *TableLiteral) Pos() Position { return tl.Position } // IsArray returns true if this table contains only array-style elements (no explicit keys) func (tl *TableLiteral) IsArray() bool { diff --git a/parser/lexer.go b/parser/lexer.go index 4d263b4..30f2f4c 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -257,6 +257,8 @@ func (l *Lexer) NextToken() Token { tok = Token{Type: STAR, Literal: string(l.ch), Line: l.line, Column: l.column} case '/': tok = Token{Type: SLASH, Literal: string(l.ch), Line: l.line, Column: l.column} + case '%': + tok = Token{Type: MOD, Literal: string(l.ch), Line: l.line, Column: l.column} case ':': tok = Token{Type: COLON, Literal: string(l.ch), Line: l.line, Column: l.column} case '.': diff --git a/parser/parser.go b/parser/parser.go index 15dd89c..351a8dd 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -71,6 +71,7 @@ func NewParser(lexer *Lexer) *Parser { 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) @@ -90,6 +91,11 @@ func NewParser(lexer *Lexer) *Parser { 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 @@ -201,7 +207,7 @@ func (p *Parser) nextToken() { // ParseProgram parses the entire program func (p *Parser) ParseProgram() *Program { - program := &Program{} + program := &Program{Position: p.pos()} program.Statements = []Statement{} for !p.curTokenIs(EOF) { @@ -254,7 +260,7 @@ func (p *Parser) parseStatement() Statement { // parseStructStatement parses struct definitions func (p *Parser) parseStructStatement() *StructStatement { - stmt := &StructStatement{} + stmt := &StructStatement{Position: p.pos()} if !p.expectPeek(IDENT) { p.addError("expected struct name") @@ -289,7 +295,7 @@ func (p *Parser) parseStructStatement() *StructStatement { return nil } - field := StructField{Name: p.curToken.Literal} + field := StructField{Name: p.curToken.Literal, Position: p.pos()} // Parse required type hint field.TypeHint = p.parseTypeHint() @@ -327,6 +333,8 @@ func (p *Parser) parseStructStatement() *StructStatement { // 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 @@ -358,7 +366,7 @@ func (p *Parser) parseFunctionStatement() Statement { } // Parse the function literal - funcLit := &FunctionLiteral{} + funcLit := &FunctionLiteral{Position: p.pos()} funcLit.Parameters, funcLit.Variadic = p.parseFunctionParameters() if !p.expectPeek(RPAREN) { @@ -387,6 +395,7 @@ func (p *Parser) parseFunctionStatement() Statement { StructID: structDef.ID, MethodName: methodName, Function: funcLit, + Position: pos, } } @@ -396,11 +405,13 @@ func (p *Parser) parseFunctionStatement() Statement { return nil } - return &ExpressionStatement{Expression: funcLit} + 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 { @@ -419,6 +430,7 @@ func (p *Parser) parseIdentifierStatement() Statement { assignment := &Assignment{ Target: expr, TypeHint: typeHint, + Position: pos, } // Validate assignment target and check if it's a declaration @@ -450,13 +462,13 @@ func (p *Parser) parseIdentifierStatement() Statement { return assignment } else { // This is an expression statement - return &ExpressionStatement{Expression: expr} + return &ExpressionStatement{Expression: expr, Position: pos} } } // parseEchoStatement parses echo statements func (p *Parser) parseEchoStatement() *EchoStatement { - stmt := &EchoStatement{} + stmt := &EchoStatement{Position: p.pos()} p.nextToken() // move past 'echo' @@ -475,11 +487,11 @@ func (p *Parser) parseBreakStatement() *BreakStatement { p.addError("unexpected identifier") return nil } - return &BreakStatement{} + return &BreakStatement{Position: p.pos()} } func (p *Parser) parseExitStatement() *ExitStatement { - stmt := &ExitStatement{} + stmt := &ExitStatement{Position: p.pos()} if p.canStartExpression(p.peekToken.Type) { p.nextToken() @@ -494,7 +506,7 @@ func (p *Parser) parseExitStatement() *ExitStatement { } func (p *Parser) parseReturnStatement() *ReturnStatement { - stmt := &ReturnStatement{} + stmt := &ReturnStatement{Position: p.pos()} if p.canStartExpression(p.peekToken.Type) { p.nextToken() @@ -519,7 +531,7 @@ func (p *Parser) canStartExpression(tokenType TokenType) bool { // Loop statement parsers func (p *Parser) parseWhileStatement() *WhileStatement { - stmt := &WhileStatement{} + stmt := &WhileStatement{Position: p.pos()} p.nextToken() @@ -547,6 +559,7 @@ func (p *Parser) parseWhileStatement() *WhileStatement { } func (p *Parser) parseForStatement() Statement { + pos := p.pos() p.nextToken() if !p.curTokenIs(IDENT) { @@ -554,20 +567,20 @@ func (p *Parser) parseForStatement() Statement { return nil } - firstVar := &Identifier{Value: p.curToken.Literal} + firstVar := &Identifier{Value: p.curToken.Literal, Position: p.pos()} if p.peekTokenIs(ASSIGN) { - return p.parseNumericForStatement(firstVar) + return p.parseNumericForStatement(firstVar, pos) } else if p.peekTokenIs(COMMA) || p.peekTokenIs(IN) { - return p.parseForInStatement(firstVar) + return p.parseForInStatement(firstVar, pos) } else { p.addError("expected '=', ',' or 'in' after for loop variable") return nil } } -func (p *Parser) parseNumericForStatement(variable *Identifier) *ForStatement { - stmt := &ForStatement{Variable: variable} +func (p *Parser) parseNumericForStatement(variable *Identifier, pos Position) *ForStatement { + stmt := &ForStatement{Variable: variable, Position: pos} if !p.expectPeek(ASSIGN) { return nil @@ -625,8 +638,8 @@ func (p *Parser) parseNumericForStatement(variable *Identifier) *ForStatement { return stmt } -func (p *Parser) parseForInStatement(firstVar *Identifier) *ForInStatement { - stmt := &ForInStatement{} +func (p *Parser) parseForInStatement(firstVar *Identifier, pos Position) *ForInStatement { + stmt := &ForInStatement{Position: pos} if p.peekTokenIs(COMMA) { stmt.Key = firstVar @@ -638,7 +651,7 @@ func (p *Parser) parseForInStatement(firstVar *Identifier) *ForInStatement { return nil } - stmt.Value = &Identifier{Value: p.curToken.Literal} + stmt.Value = &Identifier{Value: p.curToken.Literal, Position: p.pos()} } else { stmt.Value = firstVar } @@ -681,7 +694,7 @@ func (p *Parser) parseForInStatement(firstVar *Identifier) *ForInStatement { // parseIfStatement parses if statements func (p *Parser) parseIfStatement() *IfStatement { - stmt := &IfStatement{} + stmt := &IfStatement{Position: p.pos()} p.nextToken() @@ -705,7 +718,7 @@ func (p *Parser) parseIfStatement() *IfStatement { stmt.Body = p.parseBlockStatements(ELSEIF, ELSE, END) for p.curTokenIs(ELSEIF) { - elseif := ElseIfClause{} + elseif := ElseIfClause{Position: p.pos()} p.nextToken() @@ -793,11 +806,11 @@ func (p *Parser) ParseExpression(precedence Precedence) Expression { // Expression parsing functions func (p *Parser) parseIdentifier() Expression { - return &Identifier{Value: p.curToken.Literal} + return &Identifier{Value: p.curToken.Literal, Position: p.pos()} } func (p *Parser) parseNumberLiteral() Expression { - lit := &NumberLiteral{} + lit := &NumberLiteral{Position: p.pos()} literal := p.curToken.Literal var value float64 @@ -853,20 +866,21 @@ func (p *Parser) parseNumberLiteral() Expression { } func (p *Parser) parseStringLiteral() Expression { - return &StringLiteral{Value: p.curToken.Literal} + return &StringLiteral{Value: p.curToken.Literal, Position: p.pos()} } func (p *Parser) parseBooleanLiteral() Expression { - return &BooleanLiteral{Value: p.curTokenIs(TRUE)} + return &BooleanLiteral{Value: p.curTokenIs(TRUE), Position: p.pos()} } func (p *Parser) parseNilLiteral() Expression { - return &NilLiteral{} + return &NilLiteral{Position: p.pos()} } func (p *Parser) parsePrefixExpression() Expression { expression := &PrefixExpression{ Operator: p.curToken.Literal, + Position: p.pos(), } p.nextToken() @@ -904,6 +918,7 @@ func (p *Parser) parseGroupedExpression() Expression { // parseParenthesizedAssignment parses assignment expressions in parentheses func (p *Parser) parseParenthesizedAssignment() Expression { + pos := p.pos() target := p.parseIdentifier() if !p.expectPeek(ASSIGN) { @@ -927,6 +942,7 @@ func (p *Parser) parseParenthesizedAssignment() Expression { Target: target, Value: value, IsExpression: true, + Position: pos, } // Handle variable declaration for assignment expressions @@ -941,7 +957,7 @@ func (p *Parser) parseParenthesizedAssignment() Expression { } func (p *Parser) parseFunctionLiteral() Expression { - fn := &FunctionLiteral{} + fn := &FunctionLiteral{Position: p.pos()} if !p.expectPeek(LPAREN) { p.addError("expected '(' after 'fn'") @@ -996,7 +1012,7 @@ func (p *Parser) parseFunctionParameters() ([]FunctionParameter, bool) { return nil, false } - param := FunctionParameter{Name: p.curToken.Literal} + param := FunctionParameter{Name: p.curToken.Literal, Position: p.pos()} // Check for type hint param.TypeHint = p.parseTypeHint() @@ -1020,7 +1036,7 @@ func (p *Parser) parseFunctionParameters() ([]FunctionParameter, bool) { } func (p *Parser) parseTableLiteral() Expression { - table := &TableLiteral{} + table := &TableLiteral{Position: p.pos()} table.Pairs = make([]TablePair, 0, 4) // Pre-allocate if p.peekTokenIs(RBRACE) { @@ -1036,13 +1052,13 @@ func (p *Parser) parseTableLiteral() Expression { return nil } - pair := TablePair{} + 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} + pair.Key = &Identifier{Value: p.curToken.Literal, Position: p.pos()} } else { - pair.Key = &StringLiteral{Value: p.curToken.Literal} + pair.Key = &StringLiteral{Value: p.curToken.Literal, Position: p.pos()} } p.nextToken() p.nextToken() @@ -1089,6 +1105,8 @@ func (p *Parser) parseTableLiteral() Expression { // parseStructConstructor handles struct constructor calls func (p *Parser) parseStructConstructor(left Expression) Expression { + pos := p.pos() + ident, ok := left.(*Identifier) if !ok { return p.parseTableLiteralFromBrace() @@ -1105,6 +1123,7 @@ func (p *Parser) parseStructConstructor(left Expression) Expression { StructID: structDef.ID, Fields: make([]TablePair, 0, 4), typeInfo: TypeInfo{Type: TypeStruct, StructID: structDef.ID, Inferred: true}, + Position: pos, } if p.peekTokenIs(RBRACE) { @@ -1120,13 +1139,13 @@ func (p *Parser) parseStructConstructor(left Expression) Expression { return nil } - pair := TablePair{} + 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} + pair.Key = &Identifier{Value: p.curToken.Literal, Position: p.pos()} } else { - pair.Key = &StringLiteral{Value: p.curToken.Literal} + pair.Key = &StringLiteral{Value: p.curToken.Literal, Position: p.pos()} } p.nextToken() p.nextToken() @@ -1172,7 +1191,7 @@ func (p *Parser) parseStructConstructor(left Expression) Expression { } func (p *Parser) parseTableLiteralFromBrace() Expression { - table := &TableLiteral{} + table := &TableLiteral{Position: p.pos()} table.Pairs = make([]TablePair, 0, 4) if p.peekTokenIs(RBRACE) { @@ -1188,13 +1207,13 @@ func (p *Parser) parseTableLiteralFromBrace() Expression { return nil } - pair := TablePair{} + 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} + pair.Key = &Identifier{Value: p.curToken.Literal, Position: p.pos()} } else { - pair.Key = &StringLiteral{Value: p.curToken.Literal} + pair.Key = &StringLiteral{Value: p.curToken.Literal, Position: p.pos()} } p.nextToken() p.nextToken() @@ -1243,6 +1262,7 @@ func (p *Parser) parseInfixExpression(left Expression) Expression { expression := &InfixExpression{ Left: left, Operator: p.curToken.Literal, + Position: p.pos(), } precedence := p.curPrecedence() @@ -1258,19 +1278,22 @@ func (p *Parser) parseInfixExpression(left Expression) 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, + Left: left, + Key: p.curToken.Literal, + Position: pos, } } func (p *Parser) parseCallExpression(fn Expression) Expression { - call := &CallExpression{Function: fn} + call := &CallExpression{Function: fn, Position: p.pos()} call.Arguments = p.parseExpressionList(RPAREN) return call } @@ -1300,6 +1323,7 @@ func (p *Parser) parseExpressionList(end TokenType) []Expression { } func (p *Parser) parseIndexExpression(left Expression) Expression { + pos := p.pos() p.nextToken() index := p.ParseExpression(LOWEST) @@ -1314,8 +1338,9 @@ func (p *Parser) parseIndexExpression(left Expression) Expression { } return &IndexExpression{ - Left: left, - Index: index, + Left: left, + Index: index, + Position: pos, } } @@ -1381,7 +1406,7 @@ func (p *Parser) noPrefixParseFnError(t TokenType) { switch t { case ASSIGN: message = "unexpected assignment operator, missing left-hand side identifier" - case PLUS, MINUS, STAR, SLASH: + case PLUS, MINUS, STAR, SLASH, MOD: message = fmt.Sprintf("unexpected operator '%s', missing left operand", tokenTypeString(t)) case RPAREN: message = "unexpected closing parenthesis" @@ -1446,6 +1471,8 @@ func tokenTypeString(t TokenType) string { return "*" case SLASH: return "/" + case MOD: + return "%" case DOT: return "." case COLON: diff --git a/parser/tests/parser_test.go b/parser/tests/parser_test.go index 55add71..81a83fd 100644 --- a/parser/tests/parser_test.go +++ b/parser/tests/parser_test.go @@ -325,3 +325,46 @@ echo {result = x}` t.Fatalf("expected TableLiteral in echo, got %T", echo2.Value) } } + +func TestModuloOperator(t *testing.T) { + input := `result = 10 % 3` + + l := parser.NewLexer(input) + p := parser.NewParser(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("expected 1 statement, got %d", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*parser.Assignment) + if !ok { + t.Fatalf("expected AssignStatement, got %T", program.Statements[0]) + } + + infix, ok := stmt.Value.(*parser.InfixExpression) + if !ok { + t.Fatalf("expected InfixExpression, got %T", stmt.Value) + } + + if infix.Operator != "%" { + t.Errorf("expected operator '%%', got %s", infix.Operator) + } + + leftLit, ok := infix.Left.(*parser.NumberLiteral) + if !ok { + t.Fatalf("expected NumberLiteral on left, got %T", infix.Left) + } + if leftLit.Value != 10.0 { + t.Errorf("expected left value 10, got %f", leftLit.Value) + } + + rightLit, ok := infix.Right.(*parser.NumberLiteral) + if !ok { + t.Fatalf("expected NumberLiteral on right, got %T", infix.Right) + } + if rightLit.Value != 3.0 { + t.Errorf("expected right value 3, got %f", rightLit.Value) + } +} diff --git a/parser/token.go b/parser/token.go index 3fcdf28..efcbff4 100644 --- a/parser/token.go +++ b/parser/token.go @@ -18,6 +18,7 @@ const ( MINUS // - STAR // * SLASH // / + MOD // % DOT // . // Comparison operators @@ -85,7 +86,7 @@ const ( EQUALS // ==, != LESSGREATER // >, <, >=, <= SUM // +, - - PRODUCT // *, / + PRODUCT // *, /, % PREFIX // -x, not x MEMBER // table[key], table.key CALL // function() @@ -105,6 +106,7 @@ var precedences = map[TokenType]Precedence{ MINUS: SUM, SLASH: PRODUCT, STAR: PRODUCT, + MOD: PRODUCT, DOT: MEMBER, LBRACKET: MEMBER, LPAREN: CALL, diff --git a/parser/types.go b/parser/types.go index 1dc4ea6..fcbc197 100644 --- a/parser/types.go +++ b/parser/types.go @@ -521,7 +521,7 @@ func (ti *TypeInferrer) inferInfixExpression(infix *InfixExpression) TypeInfo { var resultType TypeInfo switch infix.Operator { - case "+", "-", "*", "/": + case "+", "-", "*", "/", "%": if !ti.isNumericType(leftType) || !ti.isNumericType(rightType) { ti.addError(fmt.Sprintf("arithmetic operator '%s' requires numeric operands", infix.Operator), infix) } @@ -636,8 +636,11 @@ func (ti *TypeInferrer) exitScope() { } func (ti *TypeInferrer) addError(message string, node Node) { + pos := node.Pos() ti.errors = append(ti.errors, TypeError{ Message: message, + Line: pos.Line, + Column: pos.Column, Node: node, }) }