diff --git a/parser/ast.go b/parser/ast.go index cb25448..1f21c6c 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -66,6 +66,16 @@ func (as *AssignStatement) String() string { return fmt.Sprintf("%s%s = %s", prefix, nameStr, as.Value.String()) } +// ExpressionStatement represents expressions used as statements +type ExpressionStatement struct { + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) String() string { + return es.Expression.String() +} + // EchoStatement represents echo output statements type EchoStatement struct { Value Expression diff --git a/parser/parser.go b/parser/parser.go index 42c2e52..989e134 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -201,7 +201,7 @@ func (p *Parser) ParseProgram() *Program { func (p *Parser) parseStatement() Statement { switch p.curToken.Type { case IDENT: - return p.parseAssignStatement() + return p.parseIdentifierStatement() case IF: return p.parseIfStatement() case FOR: @@ -230,54 +230,68 @@ func (p *Parser) parseStatement() Statement { } } -// parseAssignStatement parses variable assignment with optional type hint -func (p *Parser) parseAssignStatement() *AssignStatement { - stmt := &AssignStatement{} - - // Parse left-hand side expression - stmt.Name = p.ParseExpression(LOWEST) - if stmt.Name == nil { - p.addError("expected expression for assignment left-hand side") +// 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 on simple identifiers - if _, ok := stmt.Name.(*Identifier); ok { - stmt.TypeHint = p.parseTypeHint() + // Check for type hint (only valid on simple identifiers) + var typeHint *TypeInfo + if _, ok := expr.(*Identifier); ok { + typeHint = p.parseTypeHint() } - // Check if next token is assignment operator - if !p.peekTokenIs(ASSIGN) { - p.addError("unexpected identifier, expected assignment or declaration") - return nil - } - - // Validate assignment target and check if it's a declaration - switch name := stmt.Name.(type) { - case *Identifier: - stmt.IsDeclaration = !p.isVariableDeclared(name.Value) - if stmt.IsDeclaration { - p.declareVariable(name.Value) + // Check if this is an assignment + if p.peekTokenIs(ASSIGN) { + // Convert to assignment statement + stmt := &AssignStatement{ + Name: expr, + TypeHint: typeHint, } - case *DotExpression, *IndexExpression: - stmt.IsDeclaration = false - default: - p.addError("invalid assignment target") + + // 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 } - - 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 } @@ -298,6 +312,11 @@ func (p *Parser) parseEchoStatement() *EchoStatement { // 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{} } diff --git a/parser/tests/errors_test.go b/parser/tests/errors_test.go index 174ed7c..b40c8c5 100644 --- a/parser/tests/errors_test.go +++ b/parser/tests/errors_test.go @@ -20,7 +20,7 @@ func TestParsingErrors(t *testing.T) { {"+ 5", "unexpected operator '+'", 1, 1}, {"1 +", "expected expression after operator '+'", 1, 3}, {"@", "unexpected token '@'", 1, 1}, - {"invalid@", "unexpected identifier", 1, 1}, + {"invalid@", "unexpected token '@'", 1, 1}, {"{1, 2", "expected next token to be }", 1, 6}, {"{a =", "expected expression after assignment operator", 1, 4}, {"{a = 1,", "expected next token to be }", 1, 8},