summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/gt/main.go9
-rw-r--r--internal/repl/repl_test.go11
-rw-r--r--internal/rpn/operations.go50
-rw-r--r--internal/rpn/operations_test.go20
-rw-r--r--internal/rpn/rpn_parse.go59
-rw-r--r--internal/rpn/rpn_test.go22
6 files changed, 102 insertions, 69 deletions
diff --git a/cmd/gt/main.go b/cmd/gt/main.go
index 15372d8..2e3df47 100644
--- a/cmd/gt/main.go
+++ b/cmd/gt/main.go
@@ -125,6 +125,15 @@ func runREPL() error {
func runRPN(input string) (string, error) {
vars := rpn.NewVariables()
rpnCalc := rpn.NewRPN(vars)
+
+ // Strip "rpn " or "calc " prefix if present
+ input = strings.TrimSpace(input)
+ if strings.HasPrefix(input, "rpn ") {
+ input = strings.TrimPrefix(input, "rpn ")
+ } else if strings.HasPrefix(input, "calc ") {
+ input = strings.TrimPrefix(input, "calc ")
+ }
+
return rpnCalc.ParseAndEvaluate(input)
}
diff --git a/internal/repl/repl_test.go b/internal/repl/repl_test.go
index 2b7ed16..9276135 100644
--- a/internal/repl/repl_test.go
+++ b/internal/repl/repl_test.go
@@ -651,15 +651,18 @@ func TestExecutorWithAssignmentRight(t *testing.T) {
}
}
+
func TestExecutorWithAssignmentAfterCalculation(t *testing.T) {
// Test that assignment works after a calculation
- executor("1 2 + x =:")
+ // Note: This test uses a fresh variable name to avoid conflicts with previous tests
+ // that may have set x=5 from TestExecutorWithAssignmentRight
+ executor("1 2 + z =:")
state := getRPNState()
- val, exists := state.vars.GetVariable("x")
+ val, exists := state.vars.GetVariable("z")
if !exists {
- t.Errorf("Variable x should exist")
+ t.Errorf("Variable z should exist")
}
if val != 3 {
- t.Errorf("Variable x = %v, want 3", val)
+ t.Errorf("Variable z = %v, want 3", val)
}
}
diff --git a/internal/rpn/operations.go b/internal/rpn/operations.go
index b21e3a9..bc14678 100644
--- a/internal/rpn/operations.go
+++ b/internal/rpn/operations.go
@@ -154,8 +154,8 @@ func NewOperatorRegistry(op Operator) *OperatorRegistry {
registry.registerStandardOperator("neq", func(stack *Stack) error { return op.NEQ(stack) })
registry.registerStandardOperator("!=", func(stack *Stack) error { return op.NEQ(stack) })
registry.registerStandardOperator("=", func(stack *Stack) error { return op.AssignLeft(stack) })
- registry.registerStandardOperator(":=", func(stack *Stack) error { return op.AssignLeft(stack) })
- registry.registerStandardOperator("=:", func(stack *Stack) error { return op.AssignRight(stack) })
+ registry.registerStandardOperator(":=", func(stack *Stack) error { return op.AssignRight(stack) })
+ registry.registerStandardOperator("=:", func(stack *Stack) error { return op.AssignLeft(stack) })
registry.registerStandardOperator("dup", func(stack *Stack) error { return op.Dup(stack) })
registry.registerStandardOperator("swap", func(stack *Stack) error { return op.Swap(stack) })
registry.registerStandardOperator("pop", func(stack *Stack) error { return op.Pop(stack) })
@@ -948,20 +948,19 @@ func (o *Operations) ClearVariables() {
o.vars.ClearVariables()
}
-// AssignLeft assigns a value to a variable (for := and = operators).
-// Pops variable name from stack, pops value from stack, assigns value to name.
-// For := operator, the stack order is: value name := (value on bottom, name on top).
+// AssignLeft assigns a value to a variable (for =: operator).
+// Stack order: value name =: (value on bottom, name on top).
// This function pops name first (top of stack), then value.
-// Usage: `value name :=`
+// Usage: `value name =:` (e.g., `5 x =:`)
func (o *Operations) AssignLeft(stack *Stack) error {
- val, err := stack.Pop()
+ name, err := stack.Pop()
if err != nil {
- return fmt.Errorf("insufficient operands for assignment: need value")
+ return fmt.Errorf("insufficient operands for =: : need variable name")
}
- name, err := stack.Pop()
+ val, err := stack.Pop()
if err != nil {
- return fmt.Errorf("insufficient operands for assignment: need variable name")
+ return fmt.Errorf("insufficient operands for =: : need value")
}
// Get the variable name - if it's StringNum, get the string; otherwise convert to string
@@ -976,29 +975,19 @@ func (o *Operations) AssignLeft(stack *Stack) error {
return o.vars.SetVariable(varName, val.Float64())
}
-
-
-// AssignRight assigns a value to a variable (for =: operator).
-// For =: operator, the stack order is: name value =: (name on bottom, value on top).
-// This function pops value first (top of stack), then name (below it).
-// Usage: `name value =:` (e.g., `x 5 =:`)
-
-// AssignRight assigns a value to a variable (for =: operator).
-// For =: operator, the stack order is: name value =: (name on bottom, value on top).
-// This function pops name first (top of stack), then value.
-// Usage: `name value =:` (e.g., `x 5 =:`)
-// Note: When called via the RPN parser, the name is pushed as StringNum.
-// We pop name first (StringNum), then value (Number).
+// AssignRight assigns a value to a variable (for := operator).
+// Stack order: name value := (name on bottom, value on top).
+// This function pops value first (top of stack), then name.
+// Usage: `name value :=` (e.g., `x 5 :=`)
func (o *Operations) AssignRight(stack *Stack) error {
- // Pop name first (top of stack), then value
- name, err := stack.Pop()
+ val, err := stack.Pop()
if err != nil {
- return fmt.Errorf("insufficient operands for =: : need variable name")
+ return fmt.Errorf("insufficient operands for := : need value")
}
- val, err := stack.Pop()
+ name, err := stack.Pop()
if err != nil {
- return fmt.Errorf("insufficient operands for =: : need value")
+ return fmt.Errorf("insufficient operands for := : need variable name")
}
// Get the variable name - if it's StringNum, get the string; otherwise convert to string
@@ -1010,8 +999,5 @@ func (o *Operations) AssignRight(stack *Stack) error {
varName = name.String()
}
- // Get the value as float64
- varValue := val.Float64()
-
- return o.vars.SetVariable(varName, varValue)
+ return o.vars.SetVariable(varName, val.Float64())
}
diff --git a/internal/rpn/operations_test.go b/internal/rpn/operations_test.go
index 4725f7b..b9dcc98 100644
--- a/internal/rpn/operations_test.go
+++ b/internal/rpn/operations_test.go
@@ -1018,11 +1018,11 @@ func TestAssignLeft(t *testing.T) {
o := NewOperations(v)
s := NewStack()
- // For "5 x :=":
- // AssignLeft pops val first, then name
- // Push name first (will be popped second), then value (will be popped first)
- s.Push(NewStringNum("x")) // name (will be popped second)
- s.Push(NewNumber(5, FloatMode)) // value (will be popped first)
+ // For "5 x =:":
+ // AssignLeft pops name first, then value
+ // Push value first (will be popped second), then name (will be popped first)
+ s.Push(NewNumber(5, FloatMode)) // value (will be popped second)
+ s.Push(NewStringNum("x")) // name (will be popped first)
err := o.AssignLeft(s)
if err != nil {
@@ -1049,11 +1049,11 @@ func TestAssignRight(t *testing.T) {
o := NewOperations(v)
s := NewStack()
- // For "x 5 =:":
- // AssignRight pops name first, then value
- // Push value first (will be popped second), then name (will be popped first)
- s.Push(NewNumber(5, FloatMode)) // value (will be popped second)
- s.Push(NewStringNum("x")) // name (will be popped first)
+ // For "x 5 :=":
+ // AssignRight pops value first, then name
+ // Push name first (will be popped second), then value (will be popped first)
+ s.Push(NewStringNum("x")) // name (will be popped second)
+ s.Push(NewNumber(5, FloatMode)) // value (will be popped first)
err := o.AssignRight(s)
if err != nil {
diff --git a/internal/rpn/rpn_parse.go b/internal/rpn/rpn_parse.go
index 1a033f2..f8f5738 100644
--- a/internal/rpn/rpn_parse.go
+++ b/internal/rpn/rpn_parse.go
@@ -81,19 +81,22 @@ func (r *RPN) evaluate(input string, tokens []string) (string, error) {
}
// Check if this is a variable name for assignment (:= or =:)
- // For := (left assignment): value name := - peek ahead to see if next token is := or =:
- // For =: (right assignment): name value =: - first token is always a variable name
+ // For := (right assignment): name value := - first token is always a variable name
+ // For =: (left assignment): value name =: - token before =: is a variable name
shouldPushName := false
if i+1 < len(tokens) {
nextToken := tokens[i+1]
if nextToken == ":=" || nextToken == "=:" {
- // This token is a variable name (for := case)
- shouldPushName = true
+ // This token is a variable name
+ // Only push as StringNum if it's not a number
+ if _, err := strconv.ParseFloat(token, 64); err != nil {
+ shouldPushName = true
+ }
}
}
- // Special case: first token in =: expression (e.g., "x 5 =:")
- if i == 0 && len(tokens) >= 3 && tokens[len(tokens)-1] == "=:" {
+ // Special case: first token in := expression (e.g., "x 5 :=")
+ if i == 0 && len(tokens) >= 3 && tokens[len(tokens)-1] == ":=" {
shouldPushName = true
}
@@ -171,6 +174,8 @@ func (r *RPN) handleOperator(stack *Stack, token string, tokenIndex int) (string
// Returns (result string, isAssignment bool, error error).
func (r *RPN) handleAssignment(input string) (string, bool, error) {
// Handle := operator
+ // Format 1: value name := (value on bottom, name on top)
+ // Format 2: name value := (name on bottom, value on top) - for compatibility
if strings.Contains(input, ":=") {
pos := strings.Index(input, ":=")
if pos >= 0 {
@@ -179,7 +184,7 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) {
beforeFields := strings.Fields(before)
if len(beforeFields) == 2 {
- // value name := (for := operator)
+ // Try value name := format first
name := beforeFields[1]
valueStr := beforeFields[0]
@@ -194,11 +199,29 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) {
result, err := r.evaluate(input, strings.Fields(after))
return result, true, err
}
+
+ // Try name value := format (for backward compatibility)
+ name = beforeFields[0]
+ valueStr = beforeFields[1]
+
+ val, err = strconv.ParseFloat(valueStr, 64)
+ if err == nil {
+ if err := r.vars.SetVariable(name, val); err != nil {
+ return "", false, err
+ }
+ if after == "" {
+ return fmt.Sprintf("%s = %.10g", name, val), true, nil
+ }
+ result, err := r.evaluate(input, strings.Fields(after))
+ return result, true, err
+ }
}
}
}
// Handle =: operator
+ // Format 1: value name =: (value on bottom, name on top)
+ // Format 2: name value =: (name on bottom, value on top) - for compatibility
if strings.Contains(input, "=:") {
pos := strings.Index(input, "=:")
if pos >= 0 {
@@ -207,9 +230,9 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) {
beforeFields := strings.Fields(before)
if len(beforeFields) == 2 {
- // name value =: (for =: operator)
- name := beforeFields[0]
- valueStr := beforeFields[1]
+ // Try value name =: format first
+ name := beforeFields[1]
+ valueStr := beforeFields[0]
val, err := strconv.ParseFloat(valueStr, 64)
if err == nil {
@@ -222,6 +245,22 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) {
result, err := r.evaluate(input, strings.Fields(after))
return result, true, err
}
+
+ // Try name value =: format (for backward compatibility)
+ name = beforeFields[0]
+ valueStr = beforeFields[1]
+
+ val, err = strconv.ParseFloat(valueStr, 64)
+ if err == nil {
+ if err := r.vars.SetVariable(name, val); err != nil {
+ return "", false, err
+ }
+ if after == "" {
+ return fmt.Sprintf("%s = %.10g", name, val), true, nil
+ }
+ result, err := r.evaluate(input, strings.Fields(after))
+ return result, true, err
+ }
}
}
}
diff --git a/internal/rpn/rpn_test.go b/internal/rpn/rpn_test.go
index 8357d2f..3106581 100644
--- a/internal/rpn/rpn_test.go
+++ b/internal/rpn/rpn_test.go
@@ -594,7 +594,7 @@ func TestResultStackErrors(t *testing.T) {
{
name: "insufficient operands for =",
input: []string{"="},
- expectedError: "insufficient operands for assignment",
+ expectedError: "insufficient operands for =:",
},
}
@@ -1334,26 +1334,26 @@ func TestParseAndEvaluateAssignmentLeftRight(t *testing.T) {
expectedValue float64
}{
{
- name: "5 x := (left assignment)",
- input: "5 x :=",
+ name: "5 x =: (left assignment)",
+ input: "5 x =:",
expectedVar: "x",
expectedValue: 5,
},
{
- name: "x 5 =: (right assignment)",
- input: "x 5 =:",
+ name: "x 5 := (right assignment)",
+ input: "x 5 :=",
expectedVar: "x",
expectedValue: 5,
},
{
- name: "3 y := (left assignment)",
- input: "3 y :=",
+ name: "3 y =: (left assignment)",
+ input: "3 y =:",
expectedVar: "y",
expectedValue: 3,
},
{
- name: "y 3 =: (right assignment)",
- input: "y 3 =:",
+ name: "y 3 := (right assignment)",
+ input: "y 3 :=",
expectedVar: "y",
expectedValue: 3,
},
@@ -1375,9 +1375,6 @@ func TestParseAndEvaluateAssignmentLeftRight(t *testing.T) {
t.Fatalf("ParseAndEvaluate(%q) returned error: %v", tt.input, err)
}
- // For assignment, result may be empty (side effect is variable setting)
- // The important thing is the variable was set correctly
-
// Verify variable was set
val, exists := v.GetVariable(tt.expectedVar)
if !exists {
@@ -1390,7 +1387,6 @@ func TestParseAndEvaluateAssignmentLeftRight(t *testing.T) {
}
}
-// TestRPNIncrementalAssignment tests assignment with incremental operations
func TestRPNIncrementalAssignment(t *testing.T) {
v := NewVariables()
r := NewRPN(v)