diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/rpn/operations.go | 176 | ||||
| -rw-r--r-- | internal/rpn/rpn.go | 24 | ||||
| -rw-r--r-- | internal/rpn/rpn_test.go | 152 |
3 files changed, 352 insertions, 0 deletions
diff --git a/internal/rpn/operations.go b/internal/rpn/operations.go index cbcc8fe..2f4b84c 100644 --- a/internal/rpn/operations.go +++ b/internal/rpn/operations.go @@ -123,6 +123,182 @@ func (o *Operations) Modulo(stack *Stack) error { return nil } +// Hyper operators - operate on all values on the stack + +// HyperAdd pops all values from stack, adds them left-associative, and pushes result. +func (o *Operations) HyperAdd(stack *Stack) error { + if stack.Len() < 2 { + return fmt.Errorf("insufficient operands for hyperadd: need at least 2 values") + } + + // Pop all values into a slice (in reverse order - top first) + var values []float64 + for stack.Len() > 0 { + val, err := stack.Pop() + if err != nil { + return fmt.Errorf("hyperadd: %w", err) + } + values = append(values, val) + } + + // Reverse to get left-to-right order (first pushed = first in) + for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 { + values[i], values[j] = values[j], values[i] + } + + // Process left-associative + sum := 0.0 + for i := 0; i < len(values); i++ { + sum += values[i] + } + stack.Push(sum) + return nil +} + +// HyperMultiply pops all values from stack, multiplies them left-associative, and pushes result. +func (o *Operations) HyperMultiply(stack *Stack) error { + if stack.Len() < 2 { + return fmt.Errorf("insufficient operands for hypermultiply: need at least 2 values") + } + + product := 1.0 + for stack.Len() > 0 { + val, err := stack.Pop() + if err != nil { + return fmt.Errorf("hypermultiply: %w", err) + } + product *= val + } + stack.Push(product) + return nil +} + +// HyperSubtract pops all values from stack, subtracts them left-associative, and pushes result. +func (o *Operations) HyperSubtract(stack *Stack) error { + if stack.Len() < 2 { + return fmt.Errorf("insufficient operands for hypersubtract: need at least 2 values") + } + + // Pop all values into a slice (in reverse order - top first) + var values []float64 + for stack.Len() > 0 { + val, err := stack.Pop() + if err != nil { + return fmt.Errorf("hypersubtract: %w", err) + } + values = append(values, val) + } + + // Reverse to get left-to-right order (first pushed = first in) + for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 { + values[i], values[j] = values[j], values[i] + } + + // Process left-associative + result := values[0] + for i := 1; i < len(values); i++ { + result -= values[i] + } + stack.Push(result) + return nil +} + +// HyperDivide pops all values from stack, divides them left-associative, and pushes result. +func (o *Operations) HyperDivide(stack *Stack) error { + if stack.Len() < 2 { + return fmt.Errorf("insufficient operands for hyperdivide: need at least 2 values") + } + + // Pop all values into a slice (in reverse order - top first) + var values []float64 + for stack.Len() > 0 { + val, err := stack.Pop() + if err != nil { + return fmt.Errorf("hyperdivide: %w", err) + } + values = append(values, val) + } + + // Reverse to get left-to-right order (first pushed = first in) + for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 { + values[i], values[j] = values[j], values[i] + } + + // Process left-associative + result := values[0] + for i := 1; i < len(values); i++ { + if values[i] == 0 { + return fmt.Errorf("division by zero") + } + result /= values[i] + } + stack.Push(result) + return nil +} + +// HyperPower pops all values from stack, raises to power left-associative, and pushes result. +func (o *Operations) HyperPower(stack *Stack) error { + if stack.Len() < 2 { + return fmt.Errorf("insufficient operands for hyperpower: need at least 2 values") + } + + // Pop all values into a slice (in reverse order - top first) + var values []float64 + for stack.Len() > 0 { + val, err := stack.Pop() + if err != nil { + return fmt.Errorf("hyperpower: %w", err) + } + values = append(values, val) + } + + // Reverse to get left-to-right order (first pushed = first in) + for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 { + values[i], values[j] = values[j], values[i] + } + + // Process left-associative + result := values[0] + for i := 1; i < len(values); i++ { + result = math.Pow(result, values[i]) + } + stack.Push(result) + return nil +} + +// HyperModulo pops all values from stack, computes modulo left-associative, and pushes result. +func (o *Operations) HyperModulo(stack *Stack) error { + if stack.Len() < 2 { + return fmt.Errorf("insufficient operands for hypermodulo: need at least 2 values") + } + + // Pop all values into a slice (in reverse order - top first) + var values []float64 + for stack.Len() > 0 { + val, err := stack.Pop() + if err != nil { + return fmt.Errorf("hypermodulo: %w", err) + } + values = append(values, val) + } + + // Reverse to get left-to-right order (first pushed = first in) + for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 { + values[i], values[j] = values[j], values[i] + } + + // Process left-associative + result := values[0] + for i := 1; i < len(values); i++ { + if values[i] == 0 { + return fmt.Errorf("modulo by zero") + } + result = math.Mod(result, values[i]) + } + stack.Push(result) + return nil +} + // stack manipulation operators // Dup duplicates the top stack value. diff --git a/internal/rpn/rpn.go b/internal/rpn/rpn.go index 07a5fb4..d21dd67 100644 --- a/internal/rpn/rpn.go +++ b/internal/rpn/rpn.go @@ -160,6 +160,30 @@ func (r *RPN) evaluate(tokens []string) (string, error) { if err := r.ops.Modulo(stack); err != nil { return "", fmt.Errorf("operator %%: %w", err) } + case "[+]": + if err := r.ops.HyperAdd(stack); err != nil { + return "", fmt.Errorf("hyperoperator [+]: %w", err) + } + case "[-]": + if err := r.ops.HyperSubtract(stack); err != nil { + return "", fmt.Errorf("hyperoperator [-]: %w", err) + } + case "[*]": + if err := r.ops.HyperMultiply(stack); err != nil { + return "", fmt.Errorf("hyperoperator [*]: %w", err) + } + case "[/]": + if err := r.ops.HyperDivide(stack); err != nil { + return "", fmt.Errorf("hyperoperator [/]: %w", err) + } + case "[^]": + if err := r.ops.HyperPower(stack); err != nil { + return "", fmt.Errorf("hyperoperator [^]: %w", err) + } + case "[%]": + if err := r.ops.HyperModulo(stack); err != nil { + return "", fmt.Errorf("hyperoperator [%%]: %w", err) + } case "dup": if err := r.ops.Dup(stack); err != nil { return "", fmt.Errorf("dup: %w", err) diff --git a/internal/rpn/rpn_test.go b/internal/rpn/rpn_test.go index aa285ac..412d3db 100644 --- a/internal/rpn/rpn_test.go +++ b/internal/rpn/rpn_test.go @@ -781,3 +781,155 @@ func TestRPNClearStack(t *testing.T) { t.Errorf("After clear = %q, want 'All variables cleared'", result) } } + +// Hyper operator tests + +func TestHyperAdd(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test: 1 2 3 4 5 [+] + result, err := r.ParseAndEvaluate("1 2 3 4 5 [+]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "15" { + t.Errorf("1 2 3 4 5 [+] = %q, want '15'", result) + } +} + +func TestHyperAddEdgeCases(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test with two values: 10 20 [+] + result, err := r.ParseAndEvaluate("10 20 [+]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "30" { + t.Errorf("10 20 [+] = %q, want '30'", result) + } + + // Test with single value should error - use fresh instance to avoid stack state + v2 := NewVariables().(*Variables) + r2 := NewRPN(v2) + _, err = r2.ParseAndEvaluate("5 [+]") + if err == nil { + t.Error("5 [+] should return error") + } +} + +func TestHyperSubtract(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test: 10 3 2 [-] => 10 - 3 - 2 = 5 + result, err := r.ParseAndEvaluate("10 3 2 [-]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "5" { + t.Errorf("10 3 2 [-] = %q, want '5'", result) + } +} + +func TestHyperMultiply(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test: 2 3 4 [*] => 2 * 3 * 4 = 24 + result, err := r.ParseAndEvaluate("2 3 4 [*]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "24" { + t.Errorf("2 3 4 [*] = %q, want '24'", result) + } +} + +func TestHyperDivide(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test: 100 5 2 [/] => 100 / 5 / 2 = 10 + result, err := r.ParseAndEvaluate("100 5 2 [/]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "10" { + t.Errorf("100 5 2 [/] = %q, want '10'", result) + } +} + +func TestHyperDivideByZero(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + _, err := r.ParseAndEvaluate("100 0 [/]") + if err == nil { + t.Error("100 0 [/] should return error") + } +} + +func TestHyperPower(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test: 2 3 2 [^] => 2 ^ 3 ^ 2 = (2 ^ 3) ^ 2 = 8 ^ 2 = 64 + result, err := r.ParseAndEvaluate("2 3 2 [^]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "64" { + t.Errorf("2 3 2 [^] = %q, want '64'", result) + } +} + +func TestHyperModulo(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test: 100 7 3 [%%] => 100 %% 7 %% 3 = 2 %% 3 = 2 + result, err := r.ParseAndEvaluate("100 7 3 [%]") + if err != nil { + t.Fatalf("ParseAndEvaluate failed: %v", err) + } + if result != "2" { + t.Errorf("100 7 3 [%%] = %q, want '2'", result) + } +} + +func TestHyperModuloByZero(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + _, err := r.ParseAndEvaluate("100 0 [%]") + if err == nil { + t.Error("100 0 [%] should return error") + } +} + +func TestHyperOperatorEdgeCases(t *testing.T) { + // Test with single value should error for all hyper operators + testCases := []struct { + input string + operands int + }{ + {"100 [%]", 1}, + {"5 [+]", 1}, + {"10 [-]", 1}, + {"2 [*]", 1}, + {"100 [/]", 1}, + {"2 [^]", 1}, + } + + for _, tc := range testCases { + v := NewVariables().(*Variables) + r := NewRPN(v) + _, err := r.ParseAndEvaluate(tc.input) + if err == nil { + t.Errorf("%s should return error for insufficient operands", tc.input) + } + } +} |
