summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-23 22:15:30 +0200
committerPaul Buetow <paul@buetow.org>2026-03-23 22:15:30 +0200
commit0ed3c78acd270c0bd8dec73dedb82f3edc02024f (patch)
tree84bc0dbd3d82e9e999d0c8879be103eccb18dcad /internal
parentb433b4cbd250008020c8d4162e45dd0571d7a6ca (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.go23
-rw-r--r--internal/rpn/rpn_test.go43
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))
+ }
+}