diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-11 21:50:15 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-11 21:50:15 +0300 |
| commit | b315ebbcd92e58249c6ed8f04217ef7adcdde5d5 (patch) | |
| tree | d264b595dd2215523ecd1f50540342a3697b612b /internal/rpn | |
| parent | 8dbe047feaae419d9a5bdc34dfe9153e6704fd7f (diff) | |
more on this
Diffstat (limited to 'internal/rpn')
| -rw-r--r-- | internal/rpn/number.go | 41 | ||||
| -rw-r--r-- | internal/rpn/operations_test.go | 170 | ||||
| -rw-r--r-- | internal/rpn/rpn_parse.go | 6 |
3 files changed, 159 insertions, 58 deletions
diff --git a/internal/rpn/number.go b/internal/rpn/number.go index 2915a2a..1dd71e0 100644 --- a/internal/rpn/number.go +++ b/internal/rpn/number.go @@ -123,8 +123,12 @@ func (f *Float) Add(other Number) (Number, error) { if err != nil { return nil, fmt.Errorf("cannot add: %w", err) } + fF, err := f.Float64() + if err != nil { + return nil, fmt.Errorf("cannot add: %w", err) + } // Use Float64() to handle both regular numbers and boolean values - return NewFloat(f.n + otherF), nil + return NewFloat(fF + otherF), nil } // Sub returns the difference of two float numbers. @@ -133,8 +137,12 @@ func (f *Float) Sub(other Number) (Number, error) { if err != nil { return nil, fmt.Errorf("cannot subtract: %w", err) } + fF, err := f.Float64() + if err != nil { + return nil, fmt.Errorf("cannot subtract: %w", err) + } // Use Float64() to handle both regular numbers and boolean values - return NewFloat(f.n - otherF), nil + return NewFloat(fF - otherF), nil } // Mul returns the product of two float numbers. @@ -143,8 +151,12 @@ func (f *Float) Mul(other Number) (Number, error) { if err != nil { return nil, fmt.Errorf("cannot multiply: %w", err) } + fF, err := f.Float64() + if err != nil { + return nil, fmt.Errorf("cannot multiply: %w", err) + } // Use Float64() to handle both regular numbers and boolean values - return NewFloat(f.n * otherF), nil + return NewFloat(fF * otherF), nil } // Div returns the quotient of two float numbers. @@ -153,11 +165,15 @@ func (f *Float) Div(other Number) (Number, error) { if err != nil { return nil, fmt.Errorf("cannot divide: %w", err) } + fF, err := f.Float64() + if err != nil { + return nil, fmt.Errorf("cannot divide: %w", err) + } if other.IsZero() { return nil, fmt.Errorf("division by zero") } // Use Float64() to handle both regular numbers and boolean values - return NewFloat(f.n / otherF), nil + return NewFloat(fF / otherF), nil } // Pow returns this float raised to the power of another. @@ -166,8 +182,12 @@ func (f *Float) Pow(other Number) (Number, error) { if err != nil { return nil, fmt.Errorf("cannot power: %w", err) } + fF, err := f.Float64() + if err != nil { + return nil, fmt.Errorf("cannot power: %w", err) + } // Use Float64() to handle both regular numbers and boolean values - return NewFloat(math.Pow(f.n, otherF)), nil + return NewFloat(math.Pow(fF, otherF)), nil } // Mod returns the remainder of this float divided by another. @@ -176,11 +196,15 @@ func (f *Float) Mod(other Number) (Number, error) { if err != nil { return nil, fmt.Errorf("cannot modulo: %w", err) } + fF, err := f.Float64() + if err != nil { + return nil, fmt.Errorf("cannot modulo: %w", err) + } if other.IsZero() { return nil, fmt.Errorf("modulo by zero") } // Use Float64() to handle both regular numbers and boolean values - return NewFloat(math.Mod(f.n, otherF)), nil + return NewFloat(math.Mod(fF, otherF)), nil } // IsZero returns true if the float is zero. @@ -277,7 +301,10 @@ func (r *Rat) Float64() (float64, error) { } return 0, nil } - f, _ := r.n.Float64() + f, ok := r.n.Float64() + if !ok { + return 0, fmt.Errorf("cannot convert rational number to float64") + } return f, nil } diff --git a/internal/rpn/operations_test.go b/internal/rpn/operations_test.go index b9dcc98..013e212 100644 --- a/internal/rpn/operations_test.go +++ b/internal/rpn/operations_test.go @@ -35,7 +35,9 @@ func TestStackPushPop(t *testing.T) { if err != nil { t.Fatalf("Pop() returned error: %v", err) } - if val.Float64() != 3.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 3.0 { t.Errorf("Pop() = %v, want 3.0", val) } @@ -52,7 +54,9 @@ func TestStackPeek(t *testing.T) { if err != nil { t.Fatalf("Peek() returned error: %v", err) } - if val.Float64() != 5.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 5.0 { t.Errorf("Peek() = %v, want 5.0", val) } @@ -94,8 +98,14 @@ func TestStackValues(t *testing.T) { // Values() returns values in storage order (bottom-to-top) // Push order: 1, 2, 3 so storage is [1, 2, 3] with 3 on top - if vals[0].Float64() != 1.0 || vals[1].Float64() != 2.0 || vals[2].Float64() != 3.0 { - t.Errorf("Values() = %v, want [1 2 3] (bottom-to-top)", vals) + for i, v := range vals { + val, err := v.Float64() + if err != nil { + t.Fatalf("Float64() returned error: %v", err) + } + if val != float64(i+1) { + t.Errorf("Values()[%d] = %v, want %d", i, v, i+1) + } } } @@ -128,7 +138,9 @@ func TestOperationsAdd(t *testing.T) { if err != nil { t.Fatalf("Pop() after Add() returned error: %v", err) } - if val.Float64() != 7.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 7.0 { t.Errorf("Add result = %v, want 7.0", val) } } @@ -149,7 +161,9 @@ func TestOperationsSubtract(t *testing.T) { if err != nil { t.Fatalf("Pop() after Subtract() returned error: %v", err) } - if val.Float64() != 6.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 6.0 { t.Errorf("Subtract result = %v, want 6.0 (10 - 4)", val) } } @@ -170,7 +184,9 @@ func TestOperationsMultiply(t *testing.T) { if err != nil { t.Fatalf("Pop() after Multiply() returned error: %v", err) } - if val.Float64() != 15.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 15.0 { t.Errorf("Multiply result = %v, want 15.0", val) } } @@ -191,7 +207,9 @@ func TestOperationsDivide(t *testing.T) { if err != nil { t.Fatalf("Pop() after Divide() returned error: %v", err) } - if val.Float64() != 5.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 5.0 { t.Errorf("Divide result = %v, want 5.0", val) } } @@ -228,7 +246,9 @@ func TestOperationsPower(t *testing.T) { if err != nil { t.Fatalf("Pop() after Power() returned error: %v", err) } - if val.Float64() != 8.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 8.0 { t.Errorf("Power result = %v, want 8.0 (2^3)", val) } } @@ -249,7 +269,9 @@ func TestOperationsModulo(t *testing.T) { if err != nil { t.Fatalf("Pop() after Modulo() returned error: %v", err) } - if val.Float64() != 1.0 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 1.0 { t.Errorf("Modulo result = %v, want 1.0 (10 %% 3)", val) } } @@ -297,8 +319,15 @@ func TestOperationsDup(t *testing.T) { val1, _ := s.Pop() val2, _ := s.Pop() - if val1.Float64() != 7.0 || val2.Float64() != 7.0 { - t.Errorf("Dup values = %v, %v, want both 7.0", val1, val2) + if v1, err := val1.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v1 != 7.0 { + t.Errorf("val1.Float64() = %v, want 7.0", v1) + } + if v2, err := val2.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v2 != 7.0 { + t.Errorf("val2.Float64() = %v, want 7.0", v2) } } @@ -316,8 +345,15 @@ func TestOperationsSwap(t *testing.T) { val1, _ := s.Pop() val2, _ := s.Pop() - if val1.Float64() != 1.0 || val2.Float64() != 2.0 { - t.Errorf("After Swap, values = %v, %v, want 1.0, 2.0 (swapped)", val1, val2) + if v1, err := val1.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v1 != 1.0 { + t.Errorf("val1.Float64() = %v, want 1.0", v1) + } + if v2, err := val2.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v2 != 2.0 { + t.Errorf("val2.Float64() = %v, want 2.0", v2) } } @@ -449,7 +485,9 @@ func TestOperationsUseVariable(t *testing.T) { if err != nil { t.Fatalf("Pop() after UseVariable() returned error: %v", err) } - if val.Float64() != 3.14159 { + if v, err := val.Float64(); err != nil { + t.Fatalf("Float64() returned error: %v", err) + } else if v != 3.14159 { t.Errorf("Variable value pushed to stack = %v, want 3.14159", val) } } @@ -576,8 +614,10 @@ func TestLog2(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 3.0 { - t.Errorf("Log2(8) = %f, want 3.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 3.0 { + t.Errorf("Log2(8) = %f, want 3.0)", v) } // Test log₂(1) = 0 @@ -590,8 +630,10 @@ func TestLog2(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 0.0 { - t.Errorf("Log2(1) = %f, want 0.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 0.0 { + t.Errorf("Log2(1) = %f, want 0.0)", v) } // Test log₂(0) should error @@ -616,8 +658,10 @@ func TestLog10(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 2.0 { - t.Errorf("Log10(100) = %f, want 2.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 2.0 { + t.Errorf("Log10(100) = %f, want 2.0)", v) } // Test log₁₀(1) = 0 @@ -630,8 +674,10 @@ func TestLog10(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 0.0 { - t.Errorf("Log10(1) = %f, want 0.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 0.0 { + t.Errorf("Log10(1) = %f, want 0.0)", v) } } @@ -649,8 +695,10 @@ func TestLn(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if math.Abs(val.Float64()-1.0) > 0.0001 { - t.Errorf("ln(e) = %f, want ~1.0", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if math.Abs(v-1.0) > 0.0001 { + t.Errorf("ln(e) = %f, want ~1.0", v) } // Test ln(1) = 0 @@ -663,8 +711,10 @@ func TestLn(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 0.0 { - t.Errorf("Ln(1) = %f, want 0.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 0.0 { + t.Errorf("Ln(1) = %f, want 0.0)", v) } } @@ -682,8 +732,10 @@ func TestLog2WithBoolean(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 0.0 { - t.Errorf("Log2(true) = %f, want 0.0 (log₂(1) = 0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 0.0 { + t.Errorf("Log2(true) = %f, want 0.0 (log₂(1) = 0)", v) } // Test with boolean false (should be converted to 0, log₂(0) should error) @@ -708,8 +760,10 @@ func TestLog10WithBoolean(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 0.0 { - t.Errorf("Log10(true) = %f, want 0.0 (log₁₀(1) = 0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 0.0 { + t.Errorf("Log10(true) = %f, want 0.0 (log₁₀(1) = 0)", v) } // Test with boolean false (should be converted to 0, log₁₀(0) should error) @@ -734,8 +788,10 @@ func TestLnWithBoolean(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 0.0 { - t.Errorf("Ln(true) = %f, want 0.0 (ln(1) = 0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 0.0 { + t.Errorf("Ln(true) = %f, want 0.0 (ln(1) = 0)", v) } // Test with boolean false (should be converted to 0, ln(0) should error) @@ -774,8 +830,10 @@ func TestLnEdgeCases(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() > -6.0 || val.Float64() < -7.0 { - t.Errorf("Ln(0.001) = %f, want ~-6.9 (ln(0.001))", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v > -6.0 || v < -7.0 { + t.Errorf("Ln(0.001) = %f, want ~-6.9 (ln(0.001))", v) } } @@ -795,8 +853,10 @@ func TestHyperLog2WithBoolean(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 2.0 { - t.Errorf("HyperLog2(4, true) = %f, want 2.0 (log₂(4) + log₂(1) = 2 + 0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 2.0 { + t.Errorf("HyperLog2(4, true) = %f, want 2.0 (log₂(4) + log₂(1) = 2 + 0)", v) } // Test hyperlog₂(4, false) = log₂(4) + log₂(0) should error @@ -825,8 +885,10 @@ func TestHyperLog10WithBoolean(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 1.0 { - t.Errorf("HyperLog10(10, true) = %f, want 1.0 (log₁₀(10) + log₁₀(1) = 1 + 0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 1.0 { + t.Errorf("HyperLog10(10, true) = %f, want 1.0 (log₁₀(10) + log₁₀(1) = 1 + 0)", v) } // Test hyperlog₁₀(10, false) = log₁₀(10) + log₁₀(0) should error @@ -854,8 +916,10 @@ func TestHyperLnWithBoolean(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if math.Abs(val.Float64()-1.0) > 0.0001 { - t.Errorf("HyperLn(e, true) = %f, want ~1.0 (ln(e) + ln(1) = 1 + 0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if math.Abs(v-1.0) > 0.0001 { + t.Errorf("HyperLn(e, true) = %f, want ~1.0 (ln(e) + ln(1) = 1 + 0)", v) } // Test hyperln(e, false) = ln(e) + ln(0) should error @@ -882,8 +946,10 @@ func TestHyperLog2(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 6.0 { - t.Errorf("HyperLog2(4, 16) = %f, want 6.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 6.0 { + t.Errorf("HyperLog2(4, 16) = %f, want 6.0)", v) } // Test with single value (should error, like other hyper operators) @@ -909,8 +975,10 @@ func TestHyperLog10(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != 3.0 { - t.Errorf("HyperLog10(10, 100) = %f, want 3.0)", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != 3.0 { + t.Errorf("HyperLog10(10, 100) = %f, want 3.0)", v) } } @@ -929,8 +997,10 @@ func TestHyperLn(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if math.Abs(val.Float64()-3.0) > 0.0001 { - t.Errorf("HyperLn(e, e²) = %f, want ~3.0", val.Float64()) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if math.Abs(v-3.0) > 0.0001 { + t.Errorf("HyperLn(e, e²) = %f, want ~3.0", v) } } @@ -1005,8 +1075,10 @@ func TestOperatorRegistryHandleStandardOperator(t *testing.T) { if err != nil { t.Errorf("Pop() returned error: %v", err) } - if val.Float64() != tc.expected { - t.Errorf("Result = %f, want %f", val.Float64(), tc.expected) + if v, err := val.Float64(); err != nil { + t.Errorf("Float64() returned error: %v", err) + } else if v != tc.expected { + t.Errorf("Result = %f, want %f", v, tc.expected) } }) } diff --git a/internal/rpn/rpn_parse.go b/internal/rpn/rpn_parse.go index 45531bb..9d6d8e2 100644 --- a/internal/rpn/rpn_parse.go +++ b/internal/rpn/rpn_parse.go @@ -506,8 +506,10 @@ func (r *RPN) handleOperator(stack *Stack, token string, tokenIndex int) (string // isValidIdentifier checks if a token looks like a valid variable identifier. // Valid identifiers contain only alphanumeric characters and underscores, // and start with a letter or underscore (not a digit or special character). -// For RPN symbol support, we also limit to single-character identifiers -// (like x, y, z) to avoid converting percentage expression words into symbols. +// +// IMPORTANT: To prevent natural language words (like "what", "is", "of") from +// being incorrectly treated as RPN symbols in mixed-mode expressions, +// this function currently restricts valid identifiers to a length of 1. func isValidIdentifier(token string) bool { if len(token) == 0 { return false |
