assignment expressions

This commit is contained in:
Sky Johnson 2025-06-11 11:17:22 -05:00
parent 90eaf081a7
commit fc439b6d5a
4 changed files with 222 additions and 0 deletions

View File

@ -66,6 +66,21 @@ func (as *AssignStatement) String() string {
return fmt.Sprintf("%s%s = %s", prefix, nameStr, as.Value.String())
}
// AssignExpression represents assignment as an expression (only in parentheses)
type AssignExpression struct {
Name Expression // Target (identifier, dot, or index expression)
Value Expression // Value to assign
IsDeclaration bool // true if this declares a new variable
typeInfo *TypeInfo // type of the expression (same as assigned value)
}
func (ae *AssignExpression) expressionNode() {}
func (ae *AssignExpression) String() string {
return fmt.Sprintf("(%s = %s)", ae.Name.String(), ae.Value.String())
}
func (ae *AssignExpression) GetType() *TypeInfo { return ae.typeInfo }
func (ae *AssignExpression) SetType(t *TypeInfo) { ae.typeInfo = t }
// ExpressionStatement represents expressions used as statements
type ExpressionStatement struct {
Expression Expression

View File

@ -739,9 +739,16 @@ func (p *Parser) parsePrefixExpression() Expression {
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
@ -754,6 +761,46 @@ func (p *Parser) parseGroupedExpression() Expression {
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{}

View File

@ -328,3 +328,149 @@ func TestComplexExpressionsWithComparisons(t *testing.T) {
})
}
}
func TestAssignmentExpressions(t *testing.T) {
tests := []struct {
input string
targetName string
value any
isDeclaration bool
desc string
}{
{"(x = 5)", "x", 5.0, true, "simple assignment"},
{"(y = true)", "y", true, true, "boolean assignment"},
{"(name = \"test\")", "name", "test", true, "string assignment"},
{"(count = nil)", "count", nil, true, "nil assignment"},
{"(result = x + 1)", "result", "(x + 1.00)", true, "expression assignment"},
{"(flag = not active)", "flag", "(not active)", true, "prefix expression assignment"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
l := parser.NewLexer(tt.input)
p := parser.NewParser(l)
expr := p.ParseExpression(parser.LOWEST)
checkParserErrors(t, p)
assignExpr, ok := expr.(*parser.AssignExpression)
if !ok {
t.Fatalf("expected AssignExpression, got %T", expr)
}
// Test target name
ident, ok := assignExpr.Name.(*parser.Identifier)
if !ok {
t.Fatalf("expected Identifier for assignment target, got %T", assignExpr.Name)
}
if ident.Value != tt.targetName {
t.Errorf("expected target name %s, got %s", tt.targetName, ident.Value)
}
// Test assignment value
switch expected := tt.value.(type) {
case float64:
testNumberLiteral(t, assignExpr.Value, expected)
case string:
if expected == "test" {
testStringLiteral(t, assignExpr.Value, expected)
} else {
// It's an expression string
if assignExpr.Value.String() != expected {
t.Errorf("expected value %s, got %s", expected, assignExpr.Value.String())
}
}
case bool:
testBooleanLiteral(t, assignExpr.Value, expected)
case nil:
testNilLiteral(t, assignExpr.Value)
}
// Test declaration flag
if assignExpr.IsDeclaration != tt.isDeclaration {
t.Errorf("expected IsDeclaration %v, got %v", tt.isDeclaration, assignExpr.IsDeclaration)
}
})
}
}
func TestAssignmentExpressionWithComplexExpressions(t *testing.T) {
tests := []struct {
input string
desc string
}{
{"(result = func(a, b))", "function call assignment"},
{"(value = table[key])", "index expression assignment"},
{"(prop = obj.field)", "dot expression assignment"},
{"(sum = a + b * c)", "complex arithmetic assignment"},
{"(valid = x > 0 and y < 10)", "logical expression assignment"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
l := parser.NewLexer(tt.input)
p := parser.NewParser(l)
expr := p.ParseExpression(parser.LOWEST)
checkParserErrors(t, p)
assignExpr, ok := expr.(*parser.AssignExpression)
if !ok {
t.Fatalf("expected AssignExpression, got %T", expr)
}
if assignExpr.Name == nil {
t.Error("expected non-nil assignment target")
}
if assignExpr.Value == nil {
t.Error("expected non-nil assignment value")
}
// Verify string representation
result := assignExpr.String()
if result == "" {
t.Error("expected non-empty string representation")
}
})
}
}
func TestAssignmentExpressionErrors(t *testing.T) {
tests := []struct {
input string
expectedErr string
desc string
}{
{"(x =)", "expected expression after assignment operator", "missing value"},
{"(= 5)", "unexpected assignment operator", "missing target"},
{"(x = )", "expected expression after assignment operator", "empty value"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
l := parser.NewLexer(tt.input)
p := parser.NewParser(l)
expr := p.ParseExpression(parser.LOWEST)
if p.HasErrors() {
errors := p.ErrorStrings()
found := false
for _, err := range errors {
if containsSubstring(err, tt.expectedErr) {
found = true
break
}
}
if !found {
t.Errorf("expected error containing '%s', got %v", tt.expectedErr, errors)
}
} else {
t.Errorf("expected parse error for input '%s'", tt.input)
}
if expr != nil {
t.Errorf("expected nil expression for invalid input, got %T", expr)
}
})
}
}

View File

@ -102,3 +102,17 @@ func checkParserErrors(t *testing.T, p *parser.Parser) {
}
t.FailNow()
}
func containsSubstring(s, substr string) bool {
return len(s) >= len(substr) && s[:len(substr)] == substr ||
len(s) > len(substr) && findSubstring(s, substr)
}
func findSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}