assignment expressions
This commit is contained in:
parent
90eaf081a7
commit
fc439b6d5a
@ -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
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user