diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-23 22:15:30 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-23 22:15:30 +0200 |
| commit | 0ed3c78acd270c0bd8dec73dedb82f3edc02024f (patch) | |
| tree | 84bc0dbd3d82e9e999d0c8879be103eccb18dcad /internal | |
| parent | b433b4cbd250008020c8d4162e45dd0571d7a6ca (diff) | |
internal/rpn: fix assignment parsing for 'name value =' format
- Update handleAssignment to detect ' =' (space before equals) not just ' = ' (with trailing space)
- Allow assignment without trailing expression: 'x 5 =' instead of 'x 5 = expr'
- This fixes the REPL assignment syntax 'name value =' that was previously failing
The fix also enables command-line usage like 'perc calc x 5 =' to work correctly.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/rpn/rpn.go | 23 | ||||
| -rw-r--r-- | internal/rpn/rpn_test.go | 43 |
2 files changed, 59 insertions, 7 deletions
diff --git a/internal/rpn/rpn.go b/internal/rpn/rpn.go index 5bf7e7e..16e68d0 100644 --- a/internal/rpn/rpn.go +++ b/internal/rpn/rpn.go @@ -344,7 +344,10 @@ func (r *RPN) evaluate(tokens []string) (string, error) { // handleAssignment checks if the input is an assignment format and handles it. // Returns (result string, isAssignment bool, error error). func (r *RPN) handleAssignment(input string) (string, bool, error) { - if !strings.Contains(input, " = ") { + // Check for assignment format (name = value or name value = expression) + // We look for either " = " (with trailing space) or " =" (just space before equals) + hasAssignment := strings.Contains(input, " = ") || strings.Contains(input, " =") + if !hasAssignment { return "", false, nil } @@ -372,10 +375,13 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) { } // Handle assignment with expression: "name value = expression..." - pos := strings.Index(input, " = ") + // Use " =" (space before equals) to find the boundary + pos := strings.Index(input, " =") if pos >= 0 { - before := input[:pos] // "name value" - after := input[pos+3:] // "expr..." + // Extract content before the assignment + before := strings.TrimSpace(input[:pos]) + // Extract content after " =" (may be empty or contain expression) + after := strings.TrimSpace(input[pos+2:]) beforeFields := strings.Fields(before) if len(beforeFields) == 2 { @@ -385,13 +391,16 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) { // Try to parse value as a number val, err := strconv.ParseFloat(valueStr, 64) if err == nil { - // Valid assignment pattern: "name value = expr..." + // Valid assignment pattern: "name value = expr..." or "name value =" if err := r.vars.SetVariable(name, val); err != nil { return "", false, err } - // Evaluate the remaining expression - result, err := r.evaluate(strings.Fields(strings.TrimSpace(after))) + // If no expression after assignment, just return assignment info + if after == "" { + return fmt.Sprintf("%s = %.10g", name, val), true, nil + } + result, err := r.evaluate(strings.Fields(after)) return result, true, err } } diff --git a/internal/rpn/rpn_test.go b/internal/rpn/rpn_test.go index d68d43a..61cdf5d 100644 --- a/internal/rpn/rpn_test.go +++ b/internal/rpn/rpn_test.go @@ -933,3 +933,46 @@ func TestHyperOperatorEdgeCases(t *testing.T) { } } } + +// TestParseAndEvaluateAssignmentNoExpression tests "name value =" without expression +func TestParseAndEvaluateAssignmentNoExpression(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test "x 5 =" without expression + result, err := r.ParseAndEvaluate("x 5 =") + if err != nil { + t.Fatalf("ParseAndEvaluate(%q) returned error: %v", "x 5 =", err) + } + if result != "x = 5" { + t.Errorf("ParseAndEvaluate(%q) = %q, want %q", "x 5 =", result, "x = 5") + } + + // Verify variable was set + val, exists := v.GetVariable("x") + if !exists { + t.Error("Variable x should exist after assignment") + } + if val != 5.0 { + t.Errorf("Variable x = %v, want 5.0", val) + } +} + +// TestHandleAssignmentTrace traces handleAssignment with "x 5 =" +func TestHandleAssignmentTrace(t *testing.T) { + input := "x 5 =" + t.Logf("Input: %q", input) + t.Logf("Contains ' = ': %v", strings.Contains(input, " = ")) + + pos := strings.Index(input, " =") + t.Logf("Index of ' =': %d", pos) + + if pos >= 0 { + before := strings.TrimSpace(input[:pos]) + after := strings.TrimSpace(input[pos+2:]) + t.Logf("Before: %q, After: %q", before, after) + + beforeFields := strings.Fields(before) + t.Logf("BeforeFields: %v (len=%d)", beforeFields, len(beforeFields)) + } +} |
