diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-24 11:58:45 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-24 11:58:45 +0300 |
| commit | c705f26cd8f62009cafc1fb064f417926182cccd (patch) | |
| tree | 244d29e3c828a792cce49dea0dbebd12b59d8977 /internal | |
| parent | 60f2d80da7b197be6f280abf254f946572e060ef (diff) | |
docs: add variables.md; tests: comprehensive assignment operator tests
- Document all assignment operators (:=, =:, =) with syntax table
- Document variable management commands (vars, clear, d)
- Document variable lifecycle and practical use cases
- Add table-driven tests for := and =: operators
- Add tests for = with expression continuation
- Add tests for variable reuse in expressions
- Add tests for chained assignments
- Add tests for vars/clear commands
- Add tests for d (delete) operator
- Add tests for variable persistence across expressions
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/rpn/assignment_test.go | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/internal/rpn/assignment_test.go b/internal/rpn/assignment_test.go index 1b3cfff..8c6a4b2 100644 --- a/internal/rpn/assignment_test.go +++ b/internal/rpn/assignment_test.go @@ -4,6 +4,7 @@ package rpn import ( + "strings" "testing" ) @@ -326,3 +327,250 @@ func TestAssignmentAfterEqualEqual(t *testing.T) { t.Errorf("x = %v (exists=%v), want 5", val, exists) } } + +// TestAssignRightOperator tests the := (right assignment) operator via ParseAndEvaluate. +func TestAssignRightOperator(t *testing.T) { + tests := []struct { + name string + expr string + want string + }{ + {"basic right assignment", "x 5 :=", "x = 5"}, + {"right assignment with decimal", "pi 3.14159 :=", "pi = 3.14159"}, + {"right assignment with negative value", "neg -10 :=", "neg = -10"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewRPN(NewVariables()) + result, err := r.ParseAndEvaluate(tt.expr) + if err != nil { + t.Fatalf("ParseAndEvaluate(%q) error = %v", tt.expr, err) + } + if result != tt.want { + t.Errorf("ParseAndEvaluate(%q) = %q, want %q", tt.expr, result, tt.want) + } + }) + } +} + +// TestAssignLeftOperator tests the =: (left assignment) operator via ParseAndEvaluate. +func TestAssignLeftOperator(t *testing.T) { + tests := []struct { + name string + expr string + want string + }{ + {"basic left assignment", "10 y =:", "y = 10"}, + {"left assignment with decimal", "2.71828 e =:", "e = 2.71828"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewRPN(NewVariables()) + result, err := r.ParseAndEvaluate(tt.expr) + if err != nil { + t.Fatalf("ParseAndEvaluate(%q) error = %v", tt.expr, err) + } + if result != tt.want { + t.Errorf("ParseAndEvaluate(%q) = %q, want %q", tt.expr, result, tt.want) + } + }) + } +} + +// TestStandardAssignOperator tests the = (standard assignment) operator via ParseAndEvaluate. +func TestStandardAssignOperator(t *testing.T) { + tests := []struct { + name string + expr string + want string + }{ + {"infix assignment", "x = 5", "x = 5"}, + {"postfix assignment", "x 10 =", "x = 10"}, + {"assignment with expression continuation", "x 10 = x 5 +", "15"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewRPN(NewVariables()) + result, err := r.ParseAndEvaluate(tt.expr) + if err != nil { + t.Fatalf("ParseAndEvaluate(%q) error = %v", tt.expr, err) + } + if result != tt.want { + t.Errorf("ParseAndEvaluate(%q) = %q, want %q", tt.expr, result, tt.want) + } + }) + } +} + +// TestVariableInExpression tests using variables in subsequent expressions. +func TestVariableInExpression(t *testing.T) { + tests := []struct { + name string + setup string + expr string + want string + }{ + {"single variable reuse", "x 5 :=", "x x +", "10"}, + {"multi-variable expression", "a 10 := b 3 :=", "a b +", "13"}, + {"variable in complex expression", "x 5 :=", "x 2 + 3 *", "21"}, + {"variable reassignment", "x 5 :=", "x 10 := x", "10"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewRPN(NewVariables()) + _, err := r.ParseAndEvaluate(tt.setup) + if err != nil { + t.Fatalf("Setup failed for %q: %v", tt.setup, err) + } + result, err := r.ParseAndEvaluate(tt.expr) + if err != nil { + t.Fatalf("ParseAndEvaluate(%q) error = %v", tt.expr, err) + } + if result != tt.want { + t.Errorf("ParseAndEvaluate(%q) = %q, want %q", tt.expr, result, tt.want) + } + }) + } +} + +// TestChainedAssignments tests chaining multiple assignments in one expression. +func TestChainedAssignments(t *testing.T) { + r := NewRPN(NewVariables()) + result, err := r.ParseAndEvaluate("a 10 := b 3 := c 2 :=") + if err != nil { + t.Fatalf("Chained assignment failed: %v", err) + } + // Chained assignments return empty result (side effects) + if result != "" { + t.Errorf("Chained assignment result = %q, want empty", result) + } + + // Verify variables were set + if val, exists := r.vars.GetVariable("a"); !exists || val != 10 { + t.Errorf("Variable a should be 10, got %v (exists=%v)", val, exists) + } + if val, exists := r.vars.GetVariable("b"); !exists || val != 3 { + t.Errorf("Variable b should be 3, got %v (exists=%v)", val, exists) + } + if val, exists := r.vars.GetVariable("c"); !exists || val != 2 { + t.Errorf("Variable c should be 2, got %v (exists=%v)", val, exists) + } + + // Use variables in expression + result, err = r.ParseAndEvaluate("a b + c *") + if err != nil { + t.Fatalf("Expression with variables failed: %v", err) + } + if result != "26" { + t.Errorf("a b + c * = %q, want %q", result, "26") + } +} + +// TestVarsCommand tests the vars command. +func TestVarsCommand(t *testing.T) { + r := NewRPN(NewVariables()) + + // Empty vars + result, err := r.ParseAndEvaluate("vars") + if err != nil { + t.Fatalf("vars failed: %v", err) + } + if !strings.Contains(result, "No variables defined") { + t.Errorf("Empty vars should say 'No variables defined', got: %q", result) + } + + // With variables + r.ParseAndEvaluate("z 3 :=") + r.ParseAndEvaluate("a 1 :=") + r.ParseAndEvaluate("m 2 :=") + + result, err = r.ParseAndEvaluate("vars") + if err != nil { + t.Fatalf("vars with data failed: %v", err) + } + + // Should contain all variable names + if !strings.Contains(result, "a") || !strings.Contains(result, "m") || !strings.Contains(result, "z") { + t.Errorf("vars should contain a, m, z; got: %q", result) + } +} + +// TestClearCommand tests the clear command. +func TestClearCommand(t *testing.T) { + r := NewRPN(NewVariables()) + r.ParseAndEvaluate("x 5 :=") + r.ParseAndEvaluate("y 10 :=") + + result, err := r.ParseAndEvaluate("clear") + if err != nil { + t.Fatalf("clear failed: %v", err) + } + if !strings.Contains(result, "All variables cleared") { + t.Errorf("clear result = %q, want to contain 'All variables cleared'", result) + } + + // Verify variables are gone + if r.vars.Count() != 0 { + t.Errorf("After clear, count = %d, want 0", r.vars.Count()) + } +} + +// TestDeleteVariableOperator tests the d (delete) operator. +func TestDeleteVariableOperator(t *testing.T) { + r := NewRPN(NewVariables()) + + // Create variable + r.ParseAndEvaluate("x 5 :=") + _, exists := r.vars.GetVariable("x") + if !exists { + t.Fatal("Variable x should exist before delete") + } + + // Delete with symbol syntax — d is a side-effect operator that leaves + // the stack empty, so ParseAndEvaluate returns "empty result". The delete + // still succeeds; we verify by checking the variable store directly. + _, _ = r.ParseAndEvaluate(":x d") + + // Verify it's gone + _, exists = r.vars.GetVariable("x") + if exists { + t.Error("Variable x should not exist after delete") + } +} + +// TestDeleteNonExistentVariableOperator tests deleting a variable that doesn't exist. +func TestDeleteNonExistentVariableOperator(t *testing.T) { + r := NewRPN(NewVariables()) + _, err := r.ParseAndEvaluate(":nonexistent d") + if err == nil { + t.Error("Deleting non-existent variable should return error") + } +} + +// TestVariablePersistenceAcrossExpressions tests that variables persist across +// multiple ParseAndEvaluate calls on the same RPN instance. +func TestVariablePersistenceAcrossExpressions(t *testing.T) { + r := NewRPN(NewVariables()) + + r.ParseAndEvaluate("a 10 :=") + r.ParseAndEvaluate("b 3 :=") + + // Variables should persist + result, err := r.ParseAndEvaluate("a b +") + if err != nil { + t.Fatalf("Expression failed: %v", err) + } + if result != "13" { + t.Errorf("a b + = %q, want %q", result, "13") + } + + // Verify vars shows both + result, _ = r.ParseAndEvaluate("vars") + if !strings.Contains(result, "a") || !strings.Contains(result, "b") { + t.Errorf("vars should contain a and b, got: %q", result) + } +} |
