summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-20 21:34:40 +0200
committerPaul Buetow <paul@buetow.org>2026-03-20 21:34:40 +0200
commitaf8d680f3a1271fe6f57c5bba7675e5f06e0d2fd (patch)
tree60e0eea374479ccb55b3cb3b28c806a55b1d29a0 /internal
parentae780ab2d6234e62fc716099405fb9941d86c0c3 (diff)
internal/rpn: add operations.go with operator implementations
- Arithmetic: +, -, *, /, ^, % - Stack manipulation: dup, swap, pop, show - Variable operations: assign, use, delete, list, clear - Thread-safe with sync.RWMutex in Variables - Comprehensive test coverage: 37 test functions - All tests pass, go vet passes
Diffstat (limited to 'internal')
-rw-r--r--internal/rpn/operations.go247
-rw-r--r--internal/rpn/operations_test.go548
-rw-r--r--internal/rpn/variables.go55
3 files changed, 850 insertions, 0 deletions
diff --git a/internal/rpn/operations.go b/internal/rpn/operations.go
new file mode 100644
index 0000000..1248369
--- /dev/null
+++ b/internal/rpn/operations.go
@@ -0,0 +1,247 @@
+package rpn
+
+import (
+ "fmt"
+ "math"
+)
+
+// Operations provides operator implementations and stack manipulation.
+type Operations struct {
+ vars *Variables
+}
+
+// NewOperations creates a new Operations instance with the given variable store.
+func NewOperations(vars *Variables) *Operations {
+ return &Operations{
+ vars: vars,
+ }
+}
+
+// arithmetic operators
+
+// Add pops two values from stack, adds them, and pushes result.
+func (o *Operations) Add(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 + b)
+ return nil
+}
+
+// Subtract pops two values from stack, subtracts (a - b), and pushes result.
+func (o *Operations) 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 - b)
+ return nil
+}
+
+// Multiply pops two values from stack, multiplies them, and pushes result.
+func (o *Operations) 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 * b)
+ return nil
+}
+
+// Divide pops two values from stack, divides (a / b), and pushes result.
+func (o *Operations) Divide(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 == 0 {
+ return fmt.Errorf("division by zero")
+ }
+
+ stack.Push(a / b)
+ return nil
+}
+
+// Power pops two values from stack, raises first to power of second (a ^ b), and pushes result.
+func (o *Operations) 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(math.Pow(a, b))
+ return nil
+}
+
+// Modulo pops two values from stack, computes modulo (a % b), and pushes result.
+func (o *Operations) 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 == 0 {
+ return fmt.Errorf("modulo by zero")
+ }
+
+ stack.Push(math.Mod(a, b))
+ return nil
+}
+
+// stack manipulation operators
+
+// Dup duplicates the top stack value.
+func (o *Operations) Dup(stack *Stack) error {
+ val, err := stack.Peek()
+ if err != nil {
+ return fmt.Errorf("insufficient operands for dup: %w", err)
+ }
+ stack.Push(val)
+ return nil
+}
+
+// Swap swaps the top two stack values.
+func (o *Operations) Swap(stack *Stack) error {
+ if stack.Len() < 2 {
+ return fmt.Errorf("insufficient operands for swap: need at least 2 values")
+ }
+
+ // Get the values without popping
+ vals := stack.Values()
+ top := vals[len(vals)-1]
+ second := vals[len(vals)-2]
+
+ // Pop both
+ stack.Pop()
+ stack.Pop()
+
+ // Push in swapped order
+ stack.Push(top)
+ stack.Push(second)
+
+ return nil
+}
+
+// Pop removes and discards the top stack value.
+func (o *Operations) Pop(stack *Stack) error {
+ _, err := stack.Pop()
+ if err != nil {
+ return fmt.Errorf("insufficient operands for pop: %w", err)
+ }
+ return nil
+}
+
+// Show returns the current stack as a formatted string.
+func (o *Operations) Show(stack *Stack) (string, error) {
+ if stack.Len() == 0 {
+ return "Stack is empty", nil
+ }
+
+ vals := stack.Values()
+ var result string
+ for i, val := range vals {
+ if i > 0 {
+ result += " "
+ }
+ result += fmt.Sprintf("%.10g", val)
+ }
+ return result, nil
+}
+
+// variables operations
+
+// AssignVariable assigns a value from stack to a variable.
+// Usage: `name value =`
+func (o *Operations) AssignVariable(stack *Stack, name string) error {
+ if name == "" {
+ return fmt.Errorf("variable name cannot be empty")
+ }
+
+ if stack.Len() < 1 {
+ return fmt.Errorf("insufficient operands for assignment: need value")
+ }
+
+ val, err := stack.Pop()
+ if err != nil {
+ return err
+ }
+
+ return o.vars.SetVariable(name, val)
+}
+
+// UseVariable pushes a variable's value onto the stack.
+// Usage: `varname` (pushes stored value)
+func (o *Operations) UseVariable(stack *Stack, name string) error {
+ if name == "" {
+ return fmt.Errorf("variable name cannot be empty")
+ }
+
+ val, exists := o.vars.GetVariable(name)
+ if !exists {
+ return fmt.Errorf("undefined variable: %s", name)
+ }
+
+ stack.Push(val)
+ return nil
+}
+
+// DeleteVariable removes a variable.
+// Usage: `name d`
+func (o *Operations) DeleteVariable(name string) error {
+ if name == "" {
+ return fmt.Errorf("variable name cannot be empty")
+ }
+
+ deleted := o.vars.DeleteVariable(name)
+ if !deleted {
+ return fmt.Errorf("undefined variable: %s", name)
+ }
+ return nil
+}
+
+// ListVariables lists all variables.
+// Usage: `vars`
+func (o *Operations) ListVariables() (string, error) {
+ return o.vars.FormatVariables(), nil
+}
+
+// ClearVariables removes all variables.
+// Usage: `clear`
+func (o *Operations) ClearVariables() {
+ o.vars.ClearVariables()
+}
diff --git a/internal/rpn/operations_test.go b/internal/rpn/operations_test.go
new file mode 100644
index 0000000..0083196
--- /dev/null
+++ b/internal/rpn/operations_test.go
@@ -0,0 +1,548 @@
+package rpn
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+)
+
+func TestStackNewStack(t *testing.T) {
+ s := NewStack()
+ if s == nil {
+ t.Fatal("NewStack() returned nil")
+ }
+ if s.Len() != 0 {
+ t.Errorf("NewStack() length = %d, want 0", s.Len())
+ }
+}
+
+func TestStackPushPop(t *testing.T) {
+ s := NewStack()
+ s.Push(1.0)
+ s.Push(2.0)
+ s.Push(3.0)
+
+ if s.Len() != 3 {
+ t.Errorf("Length after 3 pushes = %d, want 3", s.Len())
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() returned error: %v", err)
+ }
+ if val != 3.0 {
+ t.Errorf("Pop() = %v, want 3.0", val)
+ }
+
+ if s.Len() != 2 {
+ t.Errorf("Length after pop = %d, want 2", s.Len())
+ }
+}
+
+func TestStackPeek(t *testing.T) {
+ s := NewStack()
+ s.Push(5.0)
+
+ val, err := s.Peek()
+ if err != nil {
+ t.Fatalf("Peek() returned error: %v", err)
+ }
+ if val != 5.0 {
+ t.Errorf("Peek() = %v, want 5.0", val)
+ }
+
+ // Peek should not remove the value
+ if s.Len() != 1 {
+ t.Errorf("Length after Peek() = %d, want 1", s.Len())
+ }
+}
+
+func TestStackPeekEmpty(t *testing.T) {
+ s := NewStack()
+ _, err := s.Peek()
+ if err == nil {
+ t.Error("Peek() on empty stack should return error")
+ }
+ if !strings.Contains(err.Error(), "stack is empty") {
+ t.Errorf("Peek() error = %v, should contain 'stack is empty'", err)
+ }
+}
+
+func TestStackPopEmpty(t *testing.T) {
+ s := NewStack()
+ _, err := s.Pop()
+ if err == nil {
+ t.Error("Pop() on empty stack should return error")
+ }
+}
+
+func TestStackValues(t *testing.T) {
+ s := NewStack()
+ s.Push(1.0)
+ s.Push(2.0)
+ s.Push(3.0)
+
+ vals := s.Values()
+ if len(vals) != 3 {
+ t.Errorf("Values() length = %d, want 3", len(vals))
+ }
+
+ // 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] != 1.0 || vals[1] != 2.0 || vals[2] != 3.0 {
+ t.Errorf("Values() = %v, want [1 2 3] (bottom-to-top)", vals)
+ }
+}
+
+func TestStackClear(t *testing.T) {
+ s := NewStack()
+ s.Push(1.0)
+ s.Push(2.0)
+ s.Push(3.0)
+
+ s.Clear()
+
+ if s.Len() != 0 {
+ t.Errorf("Length after Clear() = %d, want 0", s.Len())
+ }
+}
+
+func TestOperationsAdd(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(3.0)
+ s.Push(4.0)
+
+ err := o.Add(s)
+ if err != nil {
+ t.Fatalf("Add() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after Add() returned error: %v", err)
+ }
+ if val != 7.0 {
+ t.Errorf("Add result = %v, want 7.0", val)
+ }
+}
+
+func TestOperationsSubtract(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(10.0)
+ s.Push(4.0)
+
+ err := o.Subtract(s)
+ if err != nil {
+ t.Fatalf("Subtract() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after Subtract() returned error: %v", err)
+ }
+ if val != 6.0 {
+ t.Errorf("Subtract result = %v, want 6.0 (10 - 4)", val)
+ }
+}
+
+func TestOperationsMultiply(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(5.0)
+ s.Push(3.0)
+
+ err := o.Multiply(s)
+ if err != nil {
+ t.Fatalf("Multiply() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after Multiply() returned error: %v", err)
+ }
+ if val != 15.0 {
+ t.Errorf("Multiply result = %v, want 15.0", val)
+ }
+}
+
+func TestOperationsDivide(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(20.0)
+ s.Push(4.0)
+
+ err := o.Divide(s)
+ if err != nil {
+ t.Fatalf("Divide() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after Divide() returned error: %v", err)
+ }
+ if val != 5.0 {
+ t.Errorf("Divide result = %v, want 5.0", val)
+ }
+}
+
+func TestOperationsDivideByZero(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(10.0)
+ s.Push(0.0)
+
+ err := o.Divide(s)
+ if err == nil {
+ t.Error("Divide by zero should return error")
+ }
+ if !strings.Contains(err.Error(), "division by zero") {
+ t.Errorf("Divide by zero error = %v, should contain 'division by zero'", err)
+ }
+}
+
+func TestOperationsPower(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(2.0)
+ s.Push(3.0)
+
+ err := o.Power(s)
+ if err != nil {
+ t.Fatalf("Power() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after Power() returned error: %v", err)
+ }
+ if val != 8.0 {
+ t.Errorf("Power result = %v, want 8.0 (2^3)", val)
+ }
+}
+
+func TestOperationsModulo(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(10.0)
+ s.Push(3.0)
+
+ err := o.Modulo(s)
+ if err != nil {
+ t.Fatalf("Modulo() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after Modulo() returned error: %v", err)
+ }
+ if val != 1.0 {
+ t.Errorf("Modulo result = %v, want 1.0 (10 %% 3)", val)
+ }
+}
+
+func TestOperationsModuloByZero(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(10.0)
+ s.Push(0.0)
+
+ err := o.Modulo(s)
+ if err == nil {
+ t.Error("Modulo by zero should return error")
+ }
+}
+
+func TestOperationsInsufficientOperands(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(5.0)
+
+ // Try to add with only one operand
+ err := o.Add(s)
+ if err == nil {
+ t.Error("Add with insufficient operands should return error")
+ }
+}
+
+func TestOperationsDup(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(7.0)
+
+ err := o.Dup(s)
+ if err != nil {
+ t.Fatalf("Dup() returned error: %v", err)
+ }
+
+ if s.Len() != 2 {
+ t.Errorf("Length after Dup() = %d, want 2", s.Len())
+ }
+
+ val1, _ := s.Pop()
+ val2, _ := s.Pop()
+ if val1 != 7.0 || val2 != 7.0 {
+ t.Errorf("Dup values = %v, %v, want both 7.0", val1, val2)
+ }
+}
+
+func TestOperationsSwap(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(1.0)
+ s.Push(2.0)
+
+ err := o.Swap(s)
+ if err != nil {
+ t.Fatalf("Swap() returned error: %v", err)
+ }
+
+ val1, _ := s.Pop()
+ val2, _ := s.Pop()
+ if val1 != 1.0 || val2 != 2.0 {
+ t.Errorf("After Swap, values = %v, %v, want 1.0, 2.0 (swapped)", val1, val2)
+ }
+}
+
+func TestOperationsSwapInsufficient(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(5.0)
+
+ err := o.Swap(s)
+ if err == nil {
+ t.Error("Swap with insufficient operands should return error")
+ }
+}
+
+func TestOperationsPop(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(1.0)
+ s.Push(2.0)
+ s.Push(3.0)
+
+ err := o.Pop(s)
+ if err != nil {
+ t.Fatalf("Pop() returned error: %v", err)
+ }
+
+ if s.Len() != 2 {
+ t.Errorf("Length after Pop() = %d, want 2", s.Len())
+ }
+}
+
+func TestOperationsPopEmpty(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+
+ err := o.Pop(s)
+ if err == nil {
+ t.Error("Pop on empty stack should return error")
+ }
+}
+
+func TestOperationsShow(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(1.0)
+ s.Push(2.0)
+ s.Push(3.0)
+
+ result, err := o.Show(s)
+ if err != nil {
+ t.Fatalf("Show() returned error: %v", err)
+ }
+
+ if result != "1 2 3" {
+ t.Errorf("Show() = %q, want \"1 2 3\"", result)
+ }
+}
+
+func TestOperationsShowEmpty(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+
+ result, err := o.Show(s)
+ if err != nil {
+ t.Fatalf("Show() on empty stack returned error: %v", err)
+ }
+
+ if !strings.Contains(result, "Stack is empty") {
+ t.Errorf("Show() on empty stack = %q, should contain 'Stack is empty'", result)
+ }
+}
+
+func TestOperationsAssignVariable(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+ s.Push(5.0)
+
+ err := o.AssignVariable(s, "x")
+ if err != nil {
+ t.Fatalf("AssignVariable() returned error: %v", err)
+ }
+
+ val, exists := v.GetVariable("x")
+ if !exists {
+ t.Error("Variable x should exist after assignment")
+ }
+ if val != 5.0 {
+ t.Errorf("Variable x value = %v, want 5.0", val)
+ }
+
+ // Verify value was popped from stack
+ if s.Len() != 0 {
+ t.Errorf("Stack length after assignment = %d, want 0", s.Len())
+ }
+}
+
+func TestOperationsAssignVariableEmptyName(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+
+ err := o.AssignVariable(s, "")
+ if err == nil {
+ t.Error("AssignVariable with empty name should return error")
+ }
+}
+
+func TestOperationsUseVariable(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+
+ v.SetVariable("pi", 3.14159)
+
+ err := o.UseVariable(s, "pi")
+ if err != nil {
+ t.Fatalf("UseVariable() returned error: %v", err)
+ }
+
+ val, err := s.Pop()
+ if err != nil {
+ t.Fatalf("Pop() after UseVariable() returned error: %v", err)
+ }
+ if val != 3.14159 {
+ t.Errorf("Variable value pushed to stack = %v, want 3.14159", val)
+ }
+}
+
+func TestOperationsUseVariableUndefined(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+
+ err := o.UseVariable(s, "undefined")
+ if err == nil {
+ t.Error("UseVariable for undefined variable should return error")
+ }
+ if !strings.Contains(err.Error(), "undefined variable") {
+ t.Errorf("UseVariable error = %v, should contain 'undefined variable'", err)
+ }
+}
+
+func TestOperationsDeleteVariable(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+
+ v.SetVariable("temp", 100.0)
+
+ err := o.DeleteVariable("temp")
+ if err != nil {
+ t.Fatalf("DeleteVariable() returned error: %v", err)
+ }
+
+ _, exists := v.GetVariable("temp")
+ if exists {
+ t.Error("Variable should not exist after deletion")
+ }
+}
+
+func TestOperationsDeleteVariableUndefined(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+
+ err := o.DeleteVariable("nonexistent")
+ if err == nil {
+ t.Error("DeleteVariable for undefined variable should return error")
+ }
+}
+
+func TestOperationsListVariables(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+
+ v.SetVariable("x", 1.0)
+ v.SetVariable("y", 2.0)
+
+ result, err := o.ListVariables()
+ if err != nil {
+ t.Fatalf("ListVariables() returned error: %v", err)
+ }
+
+ if strings.Contains(result, "No variables defined") {
+ t.Error("ListVariables should show variables, not 'No variables defined'")
+ }
+ if !strings.Contains(result, "x") || !strings.Contains(result, "y") {
+ t.Errorf("ListVariables output should contain all variable names, got: %s", result)
+ }
+}
+
+func TestOperationsClearVariables(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+
+ v.SetVariable("x", 1.0)
+ v.SetVariable("y", 2.0)
+
+ o.ClearVariables()
+
+ if v.Count() != 0 {
+ t.Errorf("Count after ClearVariables() = %d, want 0", v.Count())
+ }
+}
+
+func TestOperationsConcurrent(t *testing.T) {
+ v := NewVariables().(*Variables)
+ o := NewOperations(v)
+ s := NewStack()
+
+ // Test concurrent variable access
+ done := make(chan bool, 10)
+ for i := 0; i < 5; i++ {
+ go func(id int) {
+ name := fmt.Sprintf("concurrent%d", id)
+ s.Push(float64(id))
+ o.AssignVariable(s, name)
+ done <- true
+ }(i)
+ }
+
+ for i := 0; i < 5; i++ {
+ <-done
+ }
+
+ if v.Count() != 5 {
+ t.Errorf("Final count = %d, want 5", v.Count())
+ }
+}
diff --git a/internal/rpn/variables.go b/internal/rpn/variables.go
index 39d9f50..9356a07 100644
--- a/internal/rpn/variables.go
+++ b/internal/rpn/variables.go
@@ -141,3 +141,58 @@ func (v *Variables) HasVariable(name string) bool {
_, exists := v.variables[name]
return exists
}
+
+// Stack represents a simple float64 stack for RPN calculations.
+type Stack struct {
+ values []float64
+}
+
+// NewStack creates a new empty stack.
+func NewStack() *Stack {
+ return &Stack{
+ values: make([]float64, 0),
+ }
+}
+
+// Push adds a value to the top of the stack.
+func (s *Stack) Push(val float64) {
+ s.values = append(s.values, val)
+}
+
+// Pop removes and returns the top value from the stack.
+// Returns an error if the stack is empty.
+func (s *Stack) Pop() (float64, error) {
+ if len(s.values) == 0 {
+ return 0, fmt.Errorf("stack is empty")
+ }
+
+ val := s.values[len(s.values)-1]
+ s.values = s.values[:len(s.values)-1]
+ return val, nil
+}
+
+// Peek returns the top value without removing it.
+// Returns an error if the stack is empty.
+func (s *Stack) Peek() (float64, error) {
+ if len(s.values) == 0 {
+ return 0, fmt.Errorf("stack is empty")
+ }
+ return s.values[len(s.values)-1], nil
+}
+
+// Len returns the number of values on the stack.
+func (s *Stack) Len() int {
+ return len(s.values)
+}
+
+// Values returns a copy of all stack values (top-to-bottom order).
+func (s *Stack) Values() []float64 {
+ vals := make([]float64, len(s.values))
+ copy(vals, s.values)
+ return vals
+}
+
+// Clear removes all values from the stack.
+func (s *Stack) Clear() {
+ s.values = s.values[:0]
+}