From 4465f7abdc72887a422b2ddd9afc43ee811b2e9b Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 11 Apr 2026 20:53:58 +0300 Subject: Refactor number.go to return errors instead of panicking - Changed Number interface methods to return errors instead of panicking - Float64() now returns (float64, error) - Add, Sub, Mul, Pow, Compare, Bool() now return (Number, error) or (int, error) - StringNum and Symbol now return errors for unsupported operations - Added IsString() and IsSymbol() to Number interface - Removed unused arithmetic.go file - Updated operations.go, boolean_ops.go, hyper.go to handle errors - Added constants registry (internal/rpn/constants.go) with built-in math constants - Added constants_test.go with comprehensive unit tests - Updated README.md with constants documentation --- internal/rpn/arithmetic.go | 186 --------------------- internal/rpn/boolean_ops.go | 66 +++++++- internal/rpn/constants.go | 170 ++++++++++++++++++++ internal/rpn/constants_test.go | 356 +++++++++++++++++++++++++++++++++++++++++ internal/rpn/hyper.go | 73 +++++++-- internal/rpn/number.go | 251 ++++++++++++++++++----------- internal/rpn/operations.go | 247 ++++++++++++++++++++++++---- internal/rpn/rpn_state.go | 9 ++ internal/rpn/variable.go | 6 +- 9 files changed, 1035 insertions(+), 329 deletions(-) delete mode 100644 internal/rpn/arithmetic.go create mode 100644 internal/rpn/constants.go create mode 100644 internal/rpn/constants_test.go (limited to 'internal') diff --git a/internal/rpn/arithmetic.go b/internal/rpn/arithmetic.go deleted file mode 100644 index 049cf25..0000000 --- a/internal/rpn/arithmetic.go +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2026 Paul Buetow - -package rpn - -import ( - "fmt" - "math" -) - -// ArithmeticOperations provides arithmetic operator implementations. -type ArithmeticOperations struct { - mode CalculationMode -} - -// NewArithmeticOperations creates a new ArithmeticOperations instance. -func NewArithmeticOperations(mode CalculationMode) *ArithmeticOperations { - return &ArithmeticOperations{mode: mode} -} - -// Add pops two values from stack, adds them, and pushes result. -func (o *ArithmeticOperations) Add(stack *Stack) error { - bVal, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for +: %w", err) - } - - aVal, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for +: %w", err) - } - - // Use the Number interface for arithmetic - stack.Push(aVal.Add(bVal)) - return nil -} - -// Subtract pops two values from stack, subtracts (a - b), and pushes result. -func (o *ArithmeticOperations) Subtract(stack *Stack) error { - b, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for -: %w", err) - } - - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for -: %w", err) - } - - stack.Push(a.Sub(b)) - return nil -} - -// Multiply pops two values from stack, multiplies them, and pushes result. -func (o *ArithmeticOperations) Multiply(stack *Stack) error { - b, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for *: %w", err) - } - - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for *: %w", err) - } - - stack.Push(a.Mul(b)) - return nil -} - -// Divide pops two values from stack, divides (a / b), and pushes result. -func (o *ArithmeticOperations) Divide(stack *Stack) error { - b, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for /: %w", err) - } - - if b.IsZero() { - return fmt.Errorf("division by zero") - } - - a, err2 := stack.Pop() - if err2 != nil { - return fmt.Errorf("insufficient operands for /: %w", err2) - } - - result, err2 := a.Div(b) - if err2 != nil { - return fmt.Errorf("division error: %w", err2) - } - stack.Push(result) - return nil -} - -// Power pops two values from stack, raises first to power of second (a ^ b), and pushes result. -func (o *ArithmeticOperations) Power(stack *Stack) error { - b, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for ^: %w", err) - } - - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for ^: %w", err) - } - - stack.Push(a.Pow(b)) - return nil -} - -// Modulo pops two values from stack, computes modulo (a % b), and pushes result. -func (o *ArithmeticOperations) Modulo(stack *Stack) error { - b, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for %%: %w", err) - } - - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for %%: %w", err) - } - - if b.IsZero() { - return fmt.Errorf("modulo by zero") - } - - result, err := a.Mod(b) - if err != nil { - return fmt.Errorf("modulo error: %w", err) - } - stack.Push(result) - return nil -} - -// Log2 pops one value from stack, computes log base 2 (log₂(a)), and pushes result. -func (o *ArithmeticOperations) Log2(stack *Stack) error { - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for lg: %w", err) - } - - // Check if value is zero or negative - val := a.Float64() - if val <= 0 { - return fmt.Errorf("log2 undefined for non-positive numbers") - } - - // Compute log2 using the number interface - stack.Push(NewNumber(math.Log2(val), o.mode)) - return nil -} - -// Log10 pops one value from stack, computes log base 10 (log₁₀(a)), and pushes result. -func (o *ArithmeticOperations) Log10(stack *Stack) error { - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for log: %w", err) - } - - // Check if value is zero or negative - val := a.Float64() - if val <= 0 { - return fmt.Errorf("log10 undefined for non-positive numbers") - } - - // Compute log10 using the number interface - stack.Push(NewNumber(math.Log10(val), o.mode)) - return nil -} - -// Ln pops one value from stack, computes natural log (ln(a)), and pushes result. -func (o *ArithmeticOperations) Ln(stack *Stack) error { - a, err := stack.Pop() - if err != nil { - return fmt.Errorf("insufficient operands for ln: %w", err) - } - - // Check if value is zero or negative - val := a.Float64() - if val <= 0 { - return fmt.Errorf("ln undefined for non-positive numbers") - } - - // Compute ln using the number interface - stack.Push(NewNumber(math.Log(val), o.mode)) - return nil -} diff --git a/internal/rpn/boolean_ops.go b/internal/rpn/boolean_ops.go index d07aa26..21501a9 100644 --- a/internal/rpn/boolean_ops.go +++ b/internal/rpn/boolean_ops.go @@ -28,7 +28,16 @@ func (o *BooleanOperations) GT(stack *Stack) error { return fmt.Errorf("insufficient operands for gt: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() > b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal > bVal)) return nil } @@ -44,7 +53,16 @@ func (o *BooleanOperations) LT(stack *Stack) error { return fmt.Errorf("insufficient operands for lt: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() < b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal < bVal)) return nil } @@ -60,7 +78,16 @@ func (o *BooleanOperations) GTE(stack *Stack) error { return fmt.Errorf("insufficient operands for gte: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() >= b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal >= bVal)) return nil } @@ -76,7 +103,16 @@ func (o *BooleanOperations) LTE(stack *Stack) error { return fmt.Errorf("insufficient operands for lte: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() <= b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal <= bVal)) return nil } @@ -92,7 +128,16 @@ func (o *BooleanOperations) EQ(stack *Stack) error { return fmt.Errorf("insufficient operands for eq: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() == b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal == bVal)) return nil } @@ -108,6 +153,15 @@ func (o *BooleanOperations) NEQ(stack *Stack) error { return fmt.Errorf("insufficient operands for neq: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() != b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal != bVal)) return nil } diff --git a/internal/rpn/constants.go b/internal/rpn/constants.go new file mode 100644 index 0000000..53d9df6 --- /dev/null +++ b/internal/rpn/constants.go @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Paul Buetow + +package rpn + +import ( + "math" + "sort" + "sync" +) + +// ConstantsProvider defines the interface for reading constant values. +type ConstantsProvider interface { + GetConstant(name string) (float64, bool) + ListConstants() []ConstantInfo + Count() int + HasConstant(name string) bool + SetConstant(name string, value float64) error + ClearConstants() + ReloadBuiltInConstants() +} + +// ConstantInfo represents a single constant with its name and value. +type ConstantInfo struct { + Name string + Value float64 +} + +// Constants stores constant name-value pairs for RPN calculations. +// It provides thread-safe access to constant storage. +type Constants struct { + mu sync.RWMutex + constants map[string]float64 +} + +// NewConstants creates and initializes a new Constants instance with built-in constants. +func NewConstants() *Constants { + c := &Constants{ + constants: make(map[string]float64), + } + c.loadBuiltInConstants() + return c +} + +// loadBuiltInConstants loads the standard mathematical constants. +func (c *Constants) loadBuiltInConstants() { + // Pi (π) - ratio of a circle's circumference to its diameter + c.constants["pi"] = math.Pi + c.constants["π"] = math.Pi + + // Euler's number (e) - base of natural logarithm + c.constants["e"] = math.E + c.constants["euler"] = math.E + + // Golden ratio (φ) + c.constants["phi"] = 1.618033988749895 + c.constants["φ"] = 1.618033988749895 + + // Square root of 2 + c.constants["sqrt2"] = 1.414213562373095 + c.constants["√2"] = 1.414213562373095 + + // Infinity + c.constants["inf"] = math.Inf(1) + c.constants["infinity"] = math.Inf(1) + + // NaN (Not a Number) + c.constants["nan"] = math.NaN() +} + +// SetConstant assigns a value to a constant name. +func (c *Constants) SetConstant(name string, value float64) error { + c.mu.Lock() + defer c.mu.Unlock() + + c.constants[name] = value + return nil +} + +// GetConstant retrieves the value of a constant. +// Returns the value and true if found, or 0 and false if not found. +func (c *Constants) GetConstant(name string) (float64, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + value, exists := c.constants[name] + return value, exists +} + +// ListConstants returns a sorted list of all constant names and their values. +func (c *Constants) ListConstants() []ConstantInfo { + c.mu.RLock() + defer c.mu.RUnlock() + + var infos []ConstantInfo + for name, value := range c.constants { + infos = append(infos, ConstantInfo{Name: name, Value: value}) + } + + // Sort by name for consistent output + sort.Slice(infos, func(i, j int) bool { + return infos[i].Name < infos[j].Name + }) + + return infos +} + +// ClearConstants removes all constants from storage. +// Note: This clears only user-defined constants; built-in constants are preserved. +func (c *Constants) ClearConstants() { + c.mu.Lock() + defer c.mu.Unlock() + + // Remove only user-defined constants (not built-in ones) + builtIns := map[string]bool{ + "pi": true, "π": true, + "e": true, "euler": true, + "phi": true, "φ": true, + "sqrt2": true, "√2": true, + "inf": true, "infinity": true, + "nan": true, + } + for k := range c.constants { + if !builtIns[k] { + delete(c.constants, k) + } + } +} + +// ReloadBuiltInConstants restores all built-in constants. +// This is called internally when ClearConstants is used to ensure +// built-in constants are preserved. +func (c *Constants) ReloadBuiltInConstants() { + c.mu.Lock() + defer c.mu.Unlock() + + // First remove only user-defined constants + builtIns := map[string]bool{ + "pi": true, "π": true, + "e": true, "euler": true, + "phi": true, "φ": true, + "sqrt2": true, "√2": true, + "inf": true, "infinity": true, + "nan": true, + } + for k := range c.constants { + if !builtIns[k] { + delete(c.constants, k) + } + } + // Then reload built-in constants + c.loadBuiltInConstants() +} + +// Count returns the number of defined constants. +func (c *Constants) Count() int { + c.mu.RLock() + defer c.mu.RUnlock() + + return len(c.constants) +} + +// HasConstant checks if a constant exists. +func (c *Constants) HasConstant(name string) bool { + c.mu.RLock() + defer c.mu.RUnlock() + + _, exists := c.constants[name] + return exists +} diff --git a/internal/rpn/constants_test.go b/internal/rpn/constants_test.go new file mode 100644 index 0000000..3a17b76 --- /dev/null +++ b/internal/rpn/constants_test.go @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Paul Buetow + +package rpn + +import ( + "math" + "strings" + "testing" +) + +func TestNewConstants(t *testing.T) { + c := NewConstants() + if c == nil { + t.Fatal("NewConstants() returned nil") + } + if c.Count() == 0 { + t.Error("NewConstants() should have built-in constants") + } +} + +func TestConstants_GetConstant(t *testing.T) { + c := NewConstants() + + tests := []struct { + name string + key string + expected float64 + }{ + {"pi", "pi", math.Pi}, + {"Pi with Greek letter", "π", math.Pi}, + {"Euler's number", "e", math.E}, + {"Euler with name", "euler", math.E}, + {"Golden ratio", "phi", 1.618033988749895}, + {"Golden ratio with Greek letter", "φ", 1.618033988749895}, + {"Square root of 2", "sqrt2", 1.414213562373095}, + {"Square root of 2 with symbol", "√2", 1.414213562373095}, + {"Infinity", "inf", math.Inf(1)}, + {"Infinity with name", "infinity", math.Inf(1)}, + {"NaN", "nan", math.NaN()}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + val, exists := c.GetConstant(tt.key) + if !exists { + t.Errorf("Constant %q should exist", tt.key) + return + } + if tt.key == "nan" { + if !math.IsNaN(val) { + t.Errorf("Constant %q = %v, want NaN", tt.key, val) + } + } else if tt.key == "inf" || tt.key == "infinity" { + if val != math.Inf(1) { + t.Errorf("Constant %q = %v, want %v", tt.key, val, math.Inf(1)) + } + } else { + if val != tt.expected { + t.Errorf("Constant %q = %v, want %v", tt.key, val, tt.expected) + } + } + }) + } +} + +func TestConstants_GetConstant_NonExistent(t *testing.T) { + c := NewConstants() + + val, exists := c.GetConstant("nonexistent") + if exists { + t.Errorf("Non-existent constant should return exists=false") + } + if val != 0 { + t.Errorf("Non-existent constant value should be 0, got %v", val) + } +} + +func TestConstants_Count(t *testing.T) { + c := NewConstants() + count := c.Count() + + // Should have at least: pi, e, phi, sqrt2, inf, nan = 6 + if count < 6 { + t.Errorf("Count = %d, want at least 6 built-in constants", count) + } +} + +func TestConstants_HasConstant(t *testing.T) { + c := NewConstants() + + if !c.HasConstant("pi") { + t.Error("HasConstant(\"pi\") should return true") + } + if !c.HasConstant("e") { + t.Error("HasConstant(\"e\") should return true") + } + if c.HasConstant("nonexistent") { + t.Error("HasConstant(\"nonexistent\") should return false") + } +} + +func TestConstants_ListConstants(t *testing.T) { + c := NewConstants() + infos := c.ListConstants() + + if len(infos) == 0 { + t.Error("ListConstants() should return at least one constant") + } + + // Check that pi is in the list + foundPi := false + foundE := false + for _, info := range infos { + if info.Name == "pi" { + foundPi = true + if info.Value != math.Pi { + t.Errorf("pi constant value = %v, want %v", info.Value, math.Pi) + } + } + if info.Name == "e" { + foundE = true + if info.Value != math.E { + t.Errorf("e constant value = %v, want %v", info.Value, math.E) + } + } + } + + if !foundPi { + t.Error("pi constant not found in ListConstants() output") + } + if !foundE { + t.Error("e constant not found in ListConstants() output") + } +} + +func TestConstants_ListConstantsSorted(t *testing.T) { + c := NewConstants() + infos := c.ListConstants() + + // Verify the list is sorted alphabetically by name + for i := 0; i < len(infos)-1; i++ { + if infos[i].Name > infos[i+1].Name { + t.Errorf("ListConstants() not sorted: %q > %q", infos[i].Name, infos[i+1].Name) + } + } +} + +func TestConstants_SetConstant(t *testing.T) { + c := NewConstants() + + // Test setting a custom constant + err := c.SetConstant("custom", 42.0) + if err != nil { + t.Errorf("SetConstant() returned error: %v", err) + } + + val, exists := c.GetConstant("custom") + if !exists { + t.Error("Custom constant should exist after SetConstant()") + } + if val != 42.0 { + t.Errorf("Custom constant value = %v, want 42.0", val) + } +} + +func TestConstants_ClearConstants(t *testing.T) { + c := NewConstants() + + // First add a user-defined constant + c.SetConstant("custom", 42.0) + + // Clear the constants + c.ClearConstants() + + // Built-in constants should still exist (pi, e, phi, sqrt2, inf, nan = 6 + 1 duplicate each = 11) + // But custom should be removed + count := c.Count() + if count < 11 { + t.Errorf("Count after ClearConstants() = %d, want at least 11 (built-in constants)", count) + } + + // Verify custom constant is gone + _, exists := c.GetConstant("custom") + if exists { + t.Error("Custom constant should be removed after ClearConstants()") + } +} + +func TestConstants_ThreadSafety(t *testing.T) { + c := NewConstants() + + done := make(chan bool, 10) + for i := 0; i < 10; i++ { + go func(id int) { + name := "thread" + string(rune(id+'0')) + c.SetConstant(name, float64(id*10)) + val, exists := c.GetConstant(name) + if !exists { + t.Errorf("Thread %d: constant %q should exist", id, name) + } + if val != float64(id*10) { + t.Errorf("Thread %d: constant %q = %v, want %v", id, name, val, float64(id*10)) + } + done <- true + }(i) + } + + for i := 0; i < 10; i++ { + <-done + } +} + +func TestConstants_RetrieveInRPN(t *testing.T) { + v := NewVariables() + r := NewRPN(v) + + // Test pi constant + result, err := r.ParseAndEvaluate("pi") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"pi\") returned error: %v", err) + } + if result != "3.141592654" { + t.Errorf("ParseAndEvaluate(\"pi\") = %q, want \"3.141592654\"", result) + } + + // Create a new RPN instance for each test to avoid stack state conflicts + v2 := NewVariables() + r2 := NewRPN(v2) + + // Test e constant + result, err = r2.ParseAndEvaluate("e") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"e\") returned error: %v", err) + } + if result != "2.718281828" { + t.Errorf("ParseAndEvaluate(\"e\") = %q, want \"2.718281828\"", result) + } + + // Create a new RPN instance for the next test + v3 := NewVariables() + r3 := NewRPN(v3) + + // Test pi in expression + result, err = r3.ParseAndEvaluate("pi 2 *") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"pi 2 *\") returned error: %v", err) + } + if result != "6.283185307" { + t.Errorf("ParseAndEvaluate(\"pi 2 *\") = %q, want \"6.283185307\"", result) + } + + // Create a new RPN instance for phi test + v4 := NewVariables() + r4 := NewRPN(v4) + + // Test phi constant + result, err = r4.ParseAndEvaluate("phi") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"phi\") returned error: %v", err) + } + if result != "1.618033989" { + t.Errorf("ParseAndEvaluate(\"phi\") = %q, want \"1.618033989\"", result) + } +} + +func TestConstants_RetrieveWithGreekLetters(t *testing.T) { + v := NewVariables() + r := NewRPN(v) + + // Test pi with Greek letter + result, err := r.ParseAndEvaluate("π") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"π\") returned error: %v", err) + } + if result != "3.141592654" { + t.Errorf("ParseAndEvaluate(\"π\") = %q, want \"3.141592654\"", result) + } + + // Create a new RPN instance for phi test + v2 := NewVariables() + r2 := NewRPN(v2) + + // Test phi with Greek letter + result, err = r2.ParseAndEvaluate("φ") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"φ\") returned error: %v", err) + } + if result != "1.618033989" { + t.Errorf("ParseAndEvaluate(\"φ\") = %q, want \"1.618033989\"", result) + } +} + +func TestConstants_ConflictWithVariables(t *testing.T) { + v := NewVariables() + r := NewRPN(v) + + // First set a variable named pi + result, err := r.ParseAndEvaluate("pi = 3.0") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"pi = 3.0\") returned error: %v", err) + } + if result != "pi = 3" { + t.Errorf("ParseAndEvaluate(\"pi = 3.0\") = %q, want \"pi = 3\"", result) + } + + // Now using pi should get the variable value, not the constant + result, err = r.ParseAndEvaluate("pi") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"pi\") after variable set returned error: %v", err) + } + if result != "3" { + t.Errorf("ParseAndEvaluate(\"pi\") after variable set = %q, want \"3\"", result) + } +} + +func TestConstantsCommand(t *testing.T) { + v := NewVariables() + r := NewRPN(v) + result, err := r.ParseAndEvaluate("constants") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"constants\") returned error: %v", err) + } + if !strings.Contains(result, "pi") || !strings.Contains(result, "e") { + t.Errorf("constants output should contain pi and e, got: %s", result) + } +} + +func TestClearConstantsCommand(t *testing.T) { + v := NewVariables() + r := NewRPN(v) + + // First add a custom constant + result, err := r.ParseAndEvaluate("custom 42 =") + if err != nil { + t.Fatalf("Failed to set custom constant: %v", err) + } + + // Clear constants + result, err = r.ParseAndEvaluate("clearconstants") + if err != nil { + t.Fatalf("ParseAndEvaluate(\"clearconstants\") returned error: %v", err) + } + if result != "All constants cleared" { + t.Errorf("clearconstants result = %q, want \"All constants cleared\"", result) + } + + // Built-in constants should still exist + result, err = r.ParseAndEvaluate("pi") + if err != nil { + t.Fatalf("pi constant should still work after clearconstants: %v", err) + } + if result != "3.141592654" { + t.Errorf("pi after clearconstants = %q, want \"3.141592654\"", result) + } +} diff --git a/internal/rpn/hyper.go b/internal/rpn/hyper.go index 58a0ca2..af589ca 100644 --- a/internal/rpn/hyper.go +++ b/internal/rpn/hyper.go @@ -42,7 +42,11 @@ func (o *HyperOperations) HyperAdd(stack *Stack) error { // Process left-associative with Number interface sum := 0.0 for i := 0; i < len(values); i++ { - sum += values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperadd: failed to get float64 value: %w", err) + } + sum += val } stack.Push(NewNumber(sum, o.mode)) return nil @@ -60,7 +64,11 @@ func (o *HyperOperations) HyperMultiply(stack *Stack) error { if err != nil { return fmt.Errorf("hypermultiply: %w", err) } - product *= val.Float64() + floatVal, err := val.Float64() + if err != nil { + return fmt.Errorf("hypermultiply: failed to get float64 value: %w", err) + } + product *= floatVal } stack.Push(NewNumber(product, o.mode)) return nil @@ -88,9 +96,17 @@ func (o *HyperOperations) HyperSubtract(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hypersubtract: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - result -= values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hypersubtract: failed to get float64 value: %w", err) + } + result -= val } stack.Push(NewNumber(result, o.mode)) return nil @@ -118,9 +134,16 @@ func (o *HyperOperations) HyperDivide(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hyperdivide: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperdivide: failed to get float64 value: %w", err) + } if val == 0 { return fmt.Errorf("division by zero") } @@ -152,9 +175,17 @@ func (o *HyperOperations) HyperPower(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hyperpower: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - result = math.Pow(result, values[i].Float64()) + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperpower: failed to get float64 value: %w", err) + } + result = math.Pow(result, val) } stack.Push(NewNumber(result, o.mode)) return nil @@ -182,9 +213,16 @@ func (o *HyperOperations) HyperModulo(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hypermodulo: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hypermodulo: failed to get float64 value: %w", err) + } if val == 0 { return fmt.Errorf("modulo by zero") } @@ -219,7 +257,10 @@ func (o *HyperOperations) HyperLog2(stack *Stack) error { // Sum the log2 of all values with Number interface var result float64 = 0 for i := 0; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperlog2: failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("hyperlog2 undefined for non-positive numbers") } @@ -256,7 +297,10 @@ func (o *HyperOperations) HyperLog10(stack *Stack) error { // Sum the log10 of all values var result float64 = 0 for i := 0; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperlog10: failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("hyperlog10 undefined for non-positive numbers") } @@ -293,7 +337,10 @@ func (o *HyperOperations) HyperLn(stack *Stack) error { // Sum the natural log of all values with Number interface var result float64 = 0 for i := 0; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperln: failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("hyperln undefined for non-positive numbers") } diff --git a/internal/rpn/number.go b/internal/rpn/number.go index dc2b2cb..bb6f765 100644 --- a/internal/rpn/number.go +++ b/internal/rpn/number.go @@ -15,32 +15,43 @@ import ( type Number interface { // String returns the string representation of the number. String() string - // Float64 returns the float64 representation, or panics if not representable. - Float64() float64 + // Float64 returns the float64 representation. + // Returns error if the number is not representable (e.g., StringNum, Symbol). + Float64() (float64, error) // Add returns the sum of this number and another. - Add(other Number) Number + // Returns error if the operation is not supported (e.g., StringNum, Symbol). + Add(other Number) (Number, error) // Sub returns the difference of this number and another. - Sub(other Number) Number + // Returns error if the operation is not supported (e.g., StringNum, Symbol). + Sub(other Number) (Number, error) // Mul returns the product of this number and another. - Mul(other Number) Number + // Returns error if the operation is not supported (e.g., StringNum, Symbol). + Mul(other Number) (Number, error) // Div returns the quotient of this number and another. - // Returns (nil, error) if division by zero. + // Returns (nil, error) if division by zero or operation not supported. Div(other Number) (Number, error) // Pow returns this number raised to the power of another. - Pow(other Number) Number + // Returns error if the operation is not supported (e.g., StringNum, Symbol). + Pow(other Number) (Number, error) // Mod returns the remainder of this number divided by another. - // Returns (nil, error) if modulo by zero. + // Returns (nil, error) if modulo by zero or operation not supported. Mod(other Number) (Number, error) // IsZero returns true if the number is zero. IsZero() bool // IsNegative returns true if the number is negative. IsNegative() bool // Compare returns -1, 0, or 1 if this number is less than, equal to, or greater than another. - Compare(other Number) int + // Returns error if the operation is not supported (e.g., StringNum, Symbol). + Compare(other Number) (int, error) // IsBool returns true if this number represents a boolean value. IsBool() bool - // Bool returns the boolean value, or false if not a boolean. - Bool() bool + // Bool returns the boolean value. + // Returns error if the number is not a boolean. + Bool() (bool, error) + // IsString returns true if this number represents a string value. + IsString() bool + // IsSymbol returns true if this number represents a symbol. + IsSymbol() bool } // NewNumber creates a Number from a float64 value. @@ -82,14 +93,14 @@ func (f *Float) String() string { } // Float64 returns the float64 value. -func (f *Float) Float64() float64 { +func (f *Float) Float64() (float64, error) { if f.isBool { if f.boolVal { - return 1 + return 1, nil } - return 0 + return 0, nil } - return f.n + return f.n, nil } // IsBool returns true if this number represents a boolean value. @@ -97,51 +108,79 @@ func (f *Float) IsBool() bool { return f.isBool } -// Bool returns the boolean value, or false if not a boolean. -func (f *Float) Bool() bool { - return f.boolVal +// Bool returns the boolean value. +// Returns error if the number is not a boolean. +func (f *Float) Bool() (bool, error) { + if !f.isBool { + return false, fmt.Errorf("not a boolean") + } + return f.boolVal, nil } // Add returns the sum of two float numbers. -func (f *Float) Add(other Number) Number { +func (f *Float) Add(other Number) (Number, error) { + otherF, err := other.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.Float64() + other.Float64()) + return NewFloat(f.n + otherF), nil } // Sub returns the difference of two float numbers. -func (f *Float) Sub(other Number) Number { +func (f *Float) Sub(other Number) (Number, error) { + otherF, err := other.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.Float64() - other.Float64()) + return NewFloat(f.n - otherF), nil } // Mul returns the product of two float numbers. -func (f *Float) Mul(other Number) Number { +func (f *Float) Mul(other Number) (Number, error) { + otherF, err := other.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.Float64() * other.Float64()) + return NewFloat(f.n * otherF), nil } // Div returns the quotient of two float numbers. func (f *Float) Div(other Number) (Number, error) { + otherF, err := other.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.Float64() / other.Float64()), nil + return NewFloat(f.n / otherF), nil } // Pow returns this float raised to the power of another. -func (f *Float) Pow(other Number) Number { +func (f *Float) Pow(other Number) (Number, error) { + otherF, err := other.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.Float64(), other.Float64())) + return NewFloat(math.Pow(f.n, otherF)), nil } // Mod returns the remainder of this float divided by another. func (f *Float) Mod(other Number) (Number, error) { + otherF, err := other.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.Float64(), other.Float64())), nil + return NewFloat(math.Mod(f.n, otherF)), nil } // IsZero returns true if the float is zero. @@ -156,15 +195,18 @@ func (f *Float) IsNegative() bool { } // Compare returns -1, 0, or 1 if this float is less than, equal to, or greater than another. -func (f *Float) Compare(other Number) int { - otherF := other.Float64() +func (f *Float) Compare(other Number) (int, error) { + otherF, err := other.Float64() + if err != nil { + return 0, fmt.Errorf("cannot compare: %w", err) + } if f.n < otherF { - return -1 + return -1, nil } if f.n > otherF { - return 1 + return 1, nil } - return 0 + return 0, nil } // Rat is a Number implementation using *big.Rat. @@ -217,15 +259,15 @@ func (r *Rat) String() string { } // Float64 returns the float64 representation. -func (r *Rat) Float64() float64 { +func (r *Rat) Float64() (float64, error) { if r.isBool { if r.boolVal { - return 1 + return 1, nil } - return 0 + return 0, nil } f, _ := r.n.Float64() - return f + return f, nil } // IsBool returns true if this number represents a boolean value. @@ -233,30 +275,46 @@ func (r *Rat) IsBool() bool { return r.isBool } -// Bool returns the boolean value, or false if not a boolean. -func (r *Rat) Bool() bool { - return r.boolVal +// Bool returns the boolean value. +// Returns error if the number is not a boolean. +func (r *Rat) Bool() (bool, error) { + if !r.isBool { + return false, fmt.Errorf("not a boolean") + } + return r.boolVal, nil } // Add returns the sum of two rational numbers. -func (r *Rat) Add(other Number) Number { +func (r *Rat) Add(other Number) (Number, error) { + otherRat, ok := other.(*Rat) + if !ok { + return nil, fmt.Errorf("cannot add: operand is not a rational number") + } result := &big.Rat{} - result.Add(r.n, other.(*Rat).n) - return &Rat{n: result} + result.Add(r.n, otherRat.n) + return &Rat{n: result}, nil } // Sub returns the difference of two rational numbers. -func (r *Rat) Sub(other Number) Number { +func (r *Rat) Sub(other Number) (Number, error) { + otherRat, ok := other.(*Rat) + if !ok { + return nil, fmt.Errorf("cannot subtract: operand is not a rational number") + } result := &big.Rat{} - result.Sub(r.n, other.(*Rat).n) - return &Rat{n: result} + result.Sub(r.n, otherRat.n) + return &Rat{n: result}, nil } // Mul returns the product of two rational numbers. -func (r *Rat) Mul(other Number) Number { +func (r *Rat) Mul(other Number) (Number, error) { + otherRat, ok := other.(*Rat) + if !ok { + return nil, fmt.Errorf("cannot multiply: operand is not a rational number") + } result := &big.Rat{} - result.Mul(r.n, other.(*Rat).n) - return &Rat{n: result} + result.Mul(r.n, otherRat.n) + return &Rat{n: result}, nil } // Div returns the quotient of two rational numbers. @@ -264,20 +322,28 @@ func (r *Rat) Div(other Number) (Number, error) { if other.IsZero() { return nil, fmt.Errorf("division by zero") } + otherRat, ok := other.(*Rat) + if !ok { + return nil, fmt.Errorf("cannot divide: operand is not a rational number") + } result := &big.Rat{} - result.Quo(r.n, other.(*Rat).n) + result.Quo(r.n, otherRat.n) return &Rat{n: result}, nil } // Pow returns this rational raised to the power of another. -func (r *Rat) Pow(other Number) Number { +func (r *Rat) Pow(other Number) (Number, error) { + otherF, err := other.Float64() + if err != nil { + return nil, fmt.Errorf("cannot power: %w", err) + } // For rational powers, convert to float and back // This may lose precision but is necessary for non-integer exponents - power := other.Float64() + power := otherF result := &big.Rat{} f, _ := r.n.Float64() result.SetFloat64(math.Pow(f, power)) - return &Rat{n: result} + return &Rat{n: result}, nil } // Mod returns the remainder of this rational divided by another. @@ -289,7 +355,10 @@ func (r *Rat) Mod(other Number) (Number, error) { // This may lose precision but is necessary for non-integer moduli result := &big.Rat{} f1, _ := r.n.Float64() - f2 := other.Float64() + f2, err := other.Float64() + if err != nil { + return nil, fmt.Errorf("cannot modulo: %w", err) + } result.SetFloat64(math.Mod(f1, f2)) return &Rat{n: result}, nil } @@ -305,8 +374,12 @@ func (r *Rat) IsNegative() bool { } // Compare returns -1, 0, or 1 if this rational is less than, equal to, or greater than another. -func (r *Rat) Compare(other Number) int { - return r.n.Cmp(other.(*Rat).n) +func (r *Rat) Compare(other Number) (int, error) { + otherRat, ok := other.(*Rat) + if !ok { + return 0, fmt.Errorf("cannot compare: operand is not a rational number") + } + return r.n.Cmp(otherRat.n), nil } // ToRat converts a Number to *big.Rat. @@ -338,9 +411,9 @@ func (s *StringNum) String() string { return s.value } -// Float64 returns 0 for string numbers (not numeric). -func (s *StringNum) Float64() float64 { - panic("string not supported for Float64()") +// Float64 returns error for string numbers (not numeric). +func (s *StringNum) Float64() (float64, error) { + return 0, fmt.Errorf("string not supported for Float64()") } // IsString returns true for StringNum. @@ -348,18 +421,18 @@ func (s *StringNum) IsString() bool { return true } -// Other methods panic as they're not supported for strings -func (s *StringNum) Add(other Number) Number { panic("string not supported for addition") } -func (s *StringNum) Sub(other Number) Number { panic("string not supported for subtraction") } -func (s *StringNum) Mul(other Number) Number { panic("string not supported for multiplication") } -func (s *StringNum) Div(other Number) (Number, error) { panic("string not supported for division") } -func (s *StringNum) Pow(other Number) Number { panic("string not supported for power") } -func (s *StringNum) Mod(other Number) (Number, error) { panic("string not supported for modulo") } -func (s *StringNum) IsZero() bool { return false } -func (s *StringNum) IsNegative() bool { return false } -func (s *StringNum) Compare(other Number) int { panic("string not supported for comparison") } -func (s *StringNum) Bool() bool { panic("string not supported for Bool()") } -func (s *StringNum) IsBool() bool { panic("string not supported for IsBool()") } +// Other methods return errors for strings +func (s *StringNum) Add(other Number) (Number, error) { return nil, fmt.Errorf("string not supported for addition") } +func (s *StringNum) Sub(other Number) (Number, error) { return nil, fmt.Errorf("string not supported for subtraction") } +func (s *StringNum) Mul(other Number) (Number, error) { return nil, fmt.Errorf("string not supported for multiplication") } +func (s *StringNum) Div(other Number) (Number, error) { return nil, fmt.Errorf("string not supported for division") } +func (s *StringNum) Pow(other Number) (Number, error) { return nil, fmt.Errorf("string not supported for power") } +func (s *StringNum) Mod(other Number) (Number, error) { return nil, fmt.Errorf("string not supported for modulo") } +func (s *StringNum) IsZero() bool { return false } +func (s *StringNum) IsNegative() bool { return false } +func (s *StringNum) Compare(other Number) (int, error) { return 0, fmt.Errorf("string not supported for comparison") } +func (s *StringNum) Bool() (bool, error) { return false, fmt.Errorf("string not supported for Bool()") } +func (s *StringNum) IsBool() bool { return false } // Symbol represents a variable symbol on the stack. // Symbols are created when: @@ -380,9 +453,9 @@ func (s *Symbol) String() string { return ":" + s.name } -// Float64 returns 0 for symbols (not numeric). -func (s *Symbol) Float64() float64 { - panic("symbol not supported for Float64()") +// Float64 returns error for symbols (not numeric). +func (s *Symbol) Float64() (float64, error) { + return 0, fmt.Errorf("symbol not supported for Float64()") } // Name returns the symbol name. @@ -396,23 +469,23 @@ func (s *Symbol) IsSymbol() bool { } // Other methods return errors for symbols -func (s *Symbol) Add(other Number) Number { - panic("symbol not supported for addition") +func (s *Symbol) Add(other Number) (Number, error) { + return nil, fmt.Errorf("symbol not supported for addition") } -func (s *Symbol) Sub(other Number) Number { - panic("symbol not supported for subtraction") +func (s *Symbol) Sub(other Number) (Number, error) { + return nil, fmt.Errorf("symbol not supported for subtraction") } -func (s *Symbol) Mul(other Number) Number { - panic("symbol not supported for multiplication") +func (s *Symbol) Mul(other Number) (Number, error) { + return nil, fmt.Errorf("symbol not supported for multiplication") } func (s *Symbol) Div(other Number) (Number, error) { - panic("symbol not supported for division") + return nil, fmt.Errorf("symbol not supported for division") } -func (s *Symbol) Pow(other Number) Number { - panic("symbol not supported for power") +func (s *Symbol) Pow(other Number) (Number, error) { + return nil, fmt.Errorf("symbol not supported for power") } func (s *Symbol) Mod(other Number) (Number, error) { - panic("symbol not supported for modulo") + return nil, fmt.Errorf("symbol not supported for modulo") } func (s *Symbol) IsZero() bool { return false @@ -420,12 +493,12 @@ func (s *Symbol) IsZero() bool { func (s *Symbol) IsNegative() bool { return false } -func (s *Symbol) Compare(other Number) int { - panic("symbol not supported for comparison") +func (s *Symbol) Compare(other Number) (int, error) { + return 0, fmt.Errorf("symbol not supported for comparison") } -func (s *Symbol) Bool() bool { - panic("symbol not supported for Bool()") +func (s *Symbol) Bool() (bool, error) { + return false, fmt.Errorf("symbol not supported for Bool()") } func (s *Symbol) IsBool() bool { - panic("symbol not supported for IsBool()") + return false } diff --git a/internal/rpn/operations.go b/internal/rpn/operations.go index 2d8bacf..36975d4 100644 --- a/internal/rpn/operations.go +++ b/internal/rpn/operations.go @@ -6,6 +6,7 @@ package rpn import ( "fmt" "math" + "strings" "sync" ) @@ -61,6 +62,12 @@ type VariableOperator interface { AssignRight(stack *Stack) error } +// ConstantOperator defines the interface for constant operations. +type ConstantOperator interface { + ListConstants() (string, error) + ClearConstants() +} + // Operator is the combined interface for all operator implementations. // This allows RPN to depend on an abstraction instead of the concrete Operations type. type Operator interface { @@ -69,6 +76,7 @@ type Operator interface { HyperOperator StackOperator VariableOperator + ConstantOperator // SetMode sets the calculation mode for number formatting SetMode(CalculationMode) // AssignLeft assigns a value to a variable (for := operator) @@ -79,9 +87,10 @@ type Operator interface { // Operations provides operator implementations and stack manipulation. type Operations struct { - vars VariableStore - mode CalculationMode - mu sync.RWMutex + vars VariableStore + consts ConstantsProvider + mode CalculationMode + mu sync.RWMutex } // Ensure Operations implements Operator at compile time. @@ -91,9 +100,11 @@ var _ Operator = (*Operations)(nil) // NewOperations creates a new Operations instance with the given variable store. func NewOperations(vars VariableStore) *Operations { + consts := NewConstants() return &Operations{ - vars: vars, - mode: FloatMode, // default + vars: vars, + consts: consts, + mode: FloatMode, // default } } @@ -169,7 +180,9 @@ func NewOperatorRegistry(op Operator) *OperatorRegistry { registry.registerCommandOperator("showstack", func(stack *Stack) (string, error) { return op.Show(stack) }) registry.registerCommandOperator("print", func(stack *Stack) (string, error) { return op.Show(stack) }) registry.registerCommandOperator("vars", func(stack *Stack) (string, error) { return op.ListVariables() }) + registry.registerCommandOperator("constants", func(stack *Stack) (string, error) { return op.ListConstants() }) registry.registerCommandOperator("clear", func(stack *Stack) (string, error) { op.ClearVariables(); return "All variables cleared", nil }) + registry.registerCommandOperator("clearconstants", func(stack *Stack) (string, error) { op.ClearConstants(); return "All constants cleared", nil }) // Register hyper operators registry.registerHyperOperator("[+]", func(stack *Stack) error { return op.HyperAdd(stack) }) @@ -261,7 +274,11 @@ func (o *Operations) Add(stack *Stack) error { } // Use the Number interface for arithmetic - stack.Push(aVal.Add(bVal)) + result, err := aVal.Add(bVal) + if err != nil { + return fmt.Errorf("addition error: %w", err) + } + stack.Push(result) return nil } @@ -277,7 +294,11 @@ func (o *Operations) Subtract(stack *Stack) error { return fmt.Errorf("insufficient operands for -: %w", err) } - stack.Push(a.Sub(b)) + result, err := a.Sub(b) + if err != nil { + return fmt.Errorf("subtraction error: %w", err) + } + stack.Push(result) return nil } @@ -293,7 +314,11 @@ func (o *Operations) Multiply(stack *Stack) error { return fmt.Errorf("insufficient operands for *: %w", err) } - stack.Push(a.Mul(b)) + result, err := a.Mul(b) + if err != nil { + return fmt.Errorf("multiplication error: %w", err) + } + stack.Push(result) return nil } @@ -333,7 +358,11 @@ func (o *Operations) Power(stack *Stack) error { return fmt.Errorf("insufficient operands for ^: %w", err) } - stack.Push(a.Pow(b)) + result, err := a.Pow(b) + if err != nil { + return fmt.Errorf("power error: %w", err) + } + stack.Push(result) return nil } @@ -378,7 +407,10 @@ func (o *Operations) Log2(stack *Stack) error { // Use Float64() to convert value to float64, handling boolean values: // - true → 1, false → 0 - val := a.Float64() + val, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("log2 undefined for non-positive numbers") } @@ -398,7 +430,10 @@ func (o *Operations) Log10(stack *Stack) error { // Use Float64() to convert value to float64, handling boolean values: // - true → 1, false → 0 - val := a.Float64() + val, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("log10 undefined for non-positive numbers") } @@ -418,7 +453,10 @@ func (o *Operations) Ln(stack *Stack) error { // Use Float64() to convert value to float64, handling boolean values: // - true → 1, false → 0 - val := a.Float64() + val, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("ln undefined for non-positive numbers") } @@ -455,7 +493,11 @@ func (o *Operations) HyperAdd(stack *Stack) error { // Process left-associative with Number interface sum := 0.0 for i := 0; i < len(values); i++ { - sum += values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperadd: failed to get float64 value: %w", err) + } + sum += val } mode := o.GetMode() stack.Push(NewNumber(sum, mode)) @@ -474,7 +516,11 @@ func (o *Operations) HyperMultiply(stack *Stack) error { if err != nil { return fmt.Errorf("hypermultiply: %w", err) } - product *= val.Float64() + floatVal, err := val.Float64() + if err != nil { + return fmt.Errorf("hypermultiply: failed to get float64 value: %w", err) + } + product *= floatVal } mode := o.GetMode() stack.Push(NewNumber(product, mode)) @@ -503,9 +549,17 @@ func (o *Operations) HyperSubtract(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hypersubtract: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - result -= values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hypersubtract: failed to get float64 value: %w", err) + } + result -= val } mode := o.GetMode() stack.Push(NewNumber(result, mode)) @@ -534,9 +588,16 @@ func (o *Operations) HyperDivide(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hyperdivide: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperdivide: failed to get float64 value: %w", err) + } if val == 0 { return fmt.Errorf("division by zero") } @@ -569,9 +630,17 @@ func (o *Operations) HyperPower(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hyperpower: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - result = math.Pow(result, values[i].Float64()) + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperpower: failed to get float64 value: %w", err) + } + result = math.Pow(result, val) } mode := o.GetMode() stack.Push(NewNumber(result, mode)) @@ -600,9 +669,16 @@ func (o *Operations) HyperModulo(stack *Stack) error { } // Process left-associative with Number interface - result := values[0].Float64() + firstVal, err := values[0].Float64() + if err != nil { + return fmt.Errorf("hypermodulo: failed to get float64 value: %w", err) + } + result := firstVal for i := 1; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hypermodulo: failed to get float64 value: %w", err) + } if val == 0 { return fmt.Errorf("modulo by zero") } @@ -639,7 +715,10 @@ func (o *Operations) HyperLog2(stack *Stack) error { // - true → 1, false → 0 var result float64 = 0 for i := 0; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperlog2: failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("hyperlog2 undefined for non-positive numbers") } @@ -678,7 +757,10 @@ func (o *Operations) HyperLog10(stack *Stack) error { // - true → 1, false → 0 var result float64 = 0 for i := 0; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperlog10: failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("hyperlog10 undefined for non-positive numbers") } @@ -717,7 +799,10 @@ func (o *Operations) HyperLn(stack *Stack) error { // - true → 1, false → 0 var result float64 = 0 for i := 0; i < len(values); i++ { - val := values[i].Float64() + val, err := values[i].Float64() + if err != nil { + return fmt.Errorf("hyperln: failed to get float64 value: %w", err) + } if val <= 0 { return fmt.Errorf("hyperln undefined for non-positive numbers") } @@ -742,7 +827,16 @@ func (o *Operations) GT(stack *Stack) error { return fmt.Errorf("insufficient operands for gt: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() > b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal > bVal)) return nil } @@ -758,7 +852,16 @@ func (o *Operations) LT(stack *Stack) error { return fmt.Errorf("insufficient operands for lt: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() < b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal < bVal)) return nil } @@ -774,7 +877,16 @@ func (o *Operations) GTE(stack *Stack) error { return fmt.Errorf("insufficient operands for gte: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() >= b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal >= bVal)) return nil } @@ -790,7 +902,16 @@ func (o *Operations) LTE(stack *Stack) error { return fmt.Errorf("insufficient operands for lte: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() <= b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal <= bVal)) return nil } @@ -806,7 +927,16 @@ func (o *Operations) EQ(stack *Stack) error { return fmt.Errorf("insufficient operands for eq: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() == b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal == bVal)) return nil } @@ -822,7 +952,16 @@ func (o *Operations) NEQ(stack *Stack) error { return fmt.Errorf("insufficient operands for neq: %w", err) } - stack.Push(NewFloatFromBool(a.Float64() != b.Float64())) + aVal, err := a.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for a: %w", err) + } + bVal, err := b.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for b: %w", err) + } + + stack.Push(NewFloatFromBool(aVal != bVal)) return nil } @@ -911,7 +1050,11 @@ func (o *Operations) AssignVariable(stack *Stack, name string) error { } // Convert Number to float64 for variable storage - return o.vars.SetVariable(name, val.Float64()) + valF, err := val.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for variable: %w", err) + } + return o.vars.SetVariable(name, valF) } // UseVariable pushes a variable's value onto the stack. @@ -957,6 +1100,34 @@ func (o *Operations) ClearVariables() { o.vars.ClearVariables() } +// ListConstants lists all constants. +// Usage: `constants` +func (o *Operations) ListConstants() (string, error) { + infos := o.consts.ListConstants() + if len(infos) == 0 { + return "No constants defined", nil + } + var sb strings.Builder + for i, info := range infos { + if i > 0 { + sb.WriteString("\n") + } + sb.WriteString(info.Name) + sb.WriteString(" = ") + // Use Number interface for consistent formatting + num := NewNumber(info.Value, FloatMode) + sb.WriteString(num.String()) + } + return sb.String(), nil +} + +// ClearConstants removes all constants from storage. +// Note: This clears only user-defined constants; built-in constants are preserved. +// Usage: `clearconstants` +func (o *Operations) ClearConstants() { + o.consts.ReloadBuiltInConstants() +} + // 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. @@ -983,7 +1154,11 @@ func (o *Operations) AssignLeft(stack *Stack) error { varName = name.String() } - return o.vars.SetVariable(varName, val.Float64()) + valF, err := val.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for variable: %w", err) + } + return o.vars.SetVariable(varName, valF) } // AssignRight assigns a value to a variable (for := operator). @@ -1012,5 +1187,9 @@ func (o *Operations) AssignRight(stack *Stack) error { varName = name.String() } - return o.vars.SetVariable(varName, val.Float64()) + valF, err := val.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for variable: %w", err) + } + return o.vars.SetVariable(varName, valF) } diff --git a/internal/rpn/rpn_state.go b/internal/rpn/rpn_state.go index fc1faea..1985048 100644 --- a/internal/rpn/rpn_state.go +++ b/internal/rpn/rpn_state.go @@ -13,6 +13,7 @@ import ( type RPN struct { mu sync.RWMutex vars VariableStore + consts ConstantsProvider ops Operator opRegistry *OperatorRegistry assignHandler *assignmentHandler @@ -23,10 +24,12 @@ type RPN struct { // NewRPN creates a new RPN parser and evaluator with the given variable store. func NewRPN(vars VariableStore) *RPN { + consts := NewConstants() ops := NewOperations(vars) ops.SetMode(FloatMode) // Set default mode return &RPN{ vars: vars, + consts: consts, ops: ops, opRegistry: NewOperatorRegistry(ops), assignHandler: newAssignmentHandler(), @@ -36,6 +39,12 @@ func NewRPN(vars VariableStore) *RPN { } } +// GetConstants returns the constants provider. +// This method is thread-safe for concurrent reads. +func (r *RPN) GetConstants() ConstantsProvider { + return r.consts +} + // GetMode returns the current calculation mode. // This method is thread-safe for concurrent reads. func (r *RPN) GetMode() CalculationMode { diff --git a/internal/rpn/variable.go b/internal/rpn/variable.go index f3302df..6bde73d 100644 --- a/internal/rpn/variable.go +++ b/internal/rpn/variable.go @@ -26,7 +26,11 @@ func (o *VariableOperations) AssignVariable(stack *Stack, name string) error { } // Convert Number to float64 for variable storage - return o.vars.SetVariable(name, val.Float64()) + valF, err := val.Float64() + if err != nil { + return fmt.Errorf("failed to get float64 value for variable: %w", err) + } + return o.vars.SetVariable(name, valF) } // UseVariable pushes a variable's value onto the stack. -- cgit v1.2.3