summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/rpn/operations.go176
-rw-r--r--internal/rpn/rpn.go24
-rw-r--r--internal/rpn/rpn_test.go152
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)
+ }
+ }
+}