summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-23 23:06:43 +0200
committerPaul Buetow <paul@buetow.org>2026-03-23 23:06:43 +0200
commitc48e1f21d284370e1b76a605fd9b16c46bf37853 (patch)
treec424c7b8f800f48d7c9649963f07643ae91e32fc
parent2b1f044aa59d516d2e3497066751d3baa5b1c749 (diff)
Refactor rpn.handleOperator to use operator registry instead of switch statement
Created OperatorRegistry with HandleStandardOperator and HandleHyperOperator methods. Operators are now registered at RPN initialization instead of being handled in switch statements. This makes the code more maintainable and extensible.
-rw-r--r--internal/rpn/operations.go112
-rw-r--r--internal/rpn/rpn.go285
2 files changed, 193 insertions, 204 deletions
diff --git a/internal/rpn/operations.go b/internal/rpn/operations.go
index 5d90212..88736e6 100644
--- a/internal/rpn/operations.go
+++ b/internal/rpn/operations.go
@@ -60,6 +60,118 @@ func NewOperations(vars VariableStore) *Operations {
}
}
+// OperatorHandler represents a function that handles an operator.
+// Returns (result string, handled bool, error error).
+// result is non-empty only for commands that return immediately (like show, vars).
+// handled indicates if the token was recognized.
+type OperatorHandler func(stack *Stack) (result string, handled bool, err error)
+
+// OperatorRegistry maintains a registry of operators.
+type OperatorRegistry struct {
+ standardOperators map[string]OperatorHandler
+ hyperOperators map[string]OperatorHandler
+}
+
+// NewOperatorRegistry creates a new operator registry and registers all operators.
+func NewOperatorRegistry(op Operator) *OperatorRegistry {
+ registry := &OperatorRegistry{
+ standardOperators: make(map[string]OperatorHandler),
+ hyperOperators: make(map[string]OperatorHandler),
+ }
+
+ // Register standard operators
+ registry.registerStandardOperator("+", func(stack *Stack) error { return op.Add(stack) })
+ registry.registerStandardOperator("-", func(stack *Stack) error { return op.Subtract(stack) })
+ registry.registerStandardOperator("*", func(stack *Stack) error { return op.Multiply(stack) })
+ registry.registerStandardOperator("/", func(stack *Stack) error { return op.Divide(stack) })
+ registry.registerStandardOperator("^", func(stack *Stack) error { return op.Power(stack) })
+ registry.registerStandardOperator("%", func(stack *Stack) error { return op.Modulo(stack) })
+ registry.registerStandardOperator("dup", func(stack *Stack) error { return op.Dup(stack) })
+ registry.registerStandardOperator("swap", func(stack *Stack) error { return op.Swap(stack) })
+ registry.registerStandardOperator("pop", func(stack *Stack) error { return op.Pop(stack) })
+ registry.registerStandardOperator("d", func(stack *Stack) error {
+ return fmt.Errorf("'d' command not supported as standalone token")
+ })
+
+ // Commands that return immediately
+ registry.registerCommandOperator("show", func(stack *Stack) (string, error) { return op.Show(stack) })
+ 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("clear", func(stack *Stack) (string, error) { op.ClearVariables(); return "All variables cleared", nil })
+
+ // Register hyper operators
+ registry.registerHyperOperator("[+]", func(stack *Stack) error { return op.HyperAdd(stack) })
+ registry.registerHyperOperator("[-]", func(stack *Stack) error { return op.HyperSubtract(stack) })
+ registry.registerHyperOperator("[*]", func(stack *Stack) error { return op.HyperMultiply(stack) })
+ registry.registerHyperOperator("[/]", func(stack *Stack) error { return op.HyperDivide(stack) })
+ registry.registerHyperOperator("[^]", func(stack *Stack) error { return op.HyperPower(stack) })
+ registry.registerHyperOperator("[%]", func(stack *Stack) error { return op.HyperModulo(stack) })
+
+ return registry
+}
+
+// registerStandardOperator registers a standard operator that returns empty result.
+func (r *OperatorRegistry) registerStandardOperator(name string, handler func(*Stack) error) {
+ r.standardOperators[name] = func(stack *Stack) (string, bool, error) {
+ if err := handler(stack); err != nil {
+ return "", false, err
+ }
+ return "", true, nil
+ }
+}
+
+// registerCommandOperator registers a command operator that returns a result immediately.
+func (r *OperatorRegistry) registerCommandOperator(name string, handler func(*Stack) (string, error)) {
+ r.standardOperators[name] = func(stack *Stack) (string, bool, error) {
+ result, err := handler(stack)
+ if err != nil {
+ return "", false, err
+ }
+ return result, true, nil
+ }
+}
+
+// registerHyperOperator registers a hyper operator.
+func (r *OperatorRegistry) registerHyperOperator(name string, handler func(*Stack) error) {
+ r.hyperOperators[name] = func(stack *Stack) (string, bool, error) {
+ if err := handler(stack); err != nil {
+ return "", false, err
+ }
+ return "", true, nil
+ }
+}
+
+// HandleStandardOperator handles a standard operator.
+// Returns (result string, handled bool, error error).
+func (r *OperatorRegistry) HandleStandardOperator(stack *Stack, token string) (string, bool, error) {
+ if handler, exists := r.standardOperators[token]; exists {
+ return handler(stack)
+ }
+ return "", false, fmt.Errorf("unknown token '%s'", token)
+}
+
+// HandleHyperOperator handles a hyper operator.
+// Returns (result string, handled bool, error error).
+func (r *OperatorRegistry) HandleHyperOperator(stack *Stack, token string) (string, bool, error) {
+ if handler, exists := r.hyperOperators[token]; exists {
+ return handler(stack)
+ }
+ return "", false, fmt.Errorf("unknown token '%s'", token)
+}
+
+// IsStandardOperator checks if a token is a standard operator.
+func (r *OperatorRegistry) IsStandardOperator(token string) bool {
+ _, exists := r.standardOperators[token]
+ return exists
+}
+
+// IsHyperOperator checks if a token is a hyper operator.
+func (r *OperatorRegistry) IsHyperOperator(token string) bool {
+ _, exists := r.hyperOperators[token]
+ return exists
+}
+
// arithmetic operators
// Add pops two values from stack, adds them, and pushes result.
diff --git a/internal/rpn/rpn.go b/internal/rpn/rpn.go
index 0de21f8..d4744c9 100644
--- a/internal/rpn/rpn.go
+++ b/internal/rpn/rpn.go
@@ -8,17 +8,20 @@ import (
// RPN represents the RPN parser and evaluator.
type RPN struct {
- vars VariableStore
- ops Operator
- maxStack int
- currentStack *Stack
+ vars VariableStore
+ ops Operator
+ opRegistry *OperatorRegistry
+ maxStack int
+ currentStack *Stack
}
// NewRPN creates a new RPN parser and evaluator with the given variable store.
func NewRPN(vars VariableStore) *RPN {
+ ops := NewOperations(vars)
return &RPN{
vars: vars,
- ops: NewOperations(vars),
+ ops: ops,
+ opRegistry: NewOperatorRegistry(ops),
maxStack: 1000, // Reasonable limit for RPN expressions
currentStack: NewStack(),
}
@@ -67,59 +70,33 @@ func (r *RPN) ResultStack(tokens []string) (string, error) {
continue
}
- // Check for operators and special commands
- switch token {
- case "+":
- if err := r.ops.Add(stack); err != nil {
- return "", err
- }
- case "-":
- if err := r.ops.Subtract(stack); err != nil {
- return "", err
- }
- case "*":
- if err := r.ops.Multiply(stack); err != nil {
- return "", err
- }
- case "/":
- if err := r.ops.Divide(stack); err != nil {
- return "", err
- }
- case "^":
- if err := r.ops.Power(stack); err != nil {
- return "", err
- }
- case "%":
- if err := r.ops.Modulo(stack); err != nil {
- return "", err
- }
- case "dup":
- if err := r.ops.Dup(stack); err != nil {
- return "", err
- }
- case "swap":
- if err := r.ops.Swap(stack); err != nil {
- return "", err
- }
- case "pop":
- if err := r.ops.Pop(stack); err != nil {
+ // Check for hyperoperators
+ if handled, result, err := r.handleHyperOperatorWithRegistry(stack, token); err != nil {
+ return "", err
+ } else if handled {
+ return result, nil
+ }
+
+ // Check for standard operators
+ if result, handled, err := r.opRegistry.HandleStandardOperator(stack, token); err != nil {
+ // If the error is not "unknown token", return it
+ // Otherwise, fall through to check for variable
+ if !strings.Contains(err.Error(), "unknown token") {
return "", err
}
- case "show", "showstack", "print":
- return r.ops.Show(stack)
- case "vars":
- return r.ops.ListVariables()
- case "clear":
- r.ops.ClearVariables()
- return "All variables cleared", nil
- default:
- // Check if it's a variable reference (push its value)
- val, exists := r.vars.GetVariable(token)
- if exists {
- stack.Push(val)
- } else {
- return "", fmt.Errorf("unknown token '%s'", token)
+ } else if handled {
+ if result != "" {
+ return result, nil
}
+ continue
+ }
+
+ // Check if it's a variable reference (push its value)
+ val, exists := r.vars.GetVariable(token)
+ if exists {
+ stack.Push(val)
+ } else {
+ return "", fmt.Errorf("unknown token '%s'", token)
}
}
@@ -133,56 +110,35 @@ func (r *RPN) EvalOperator(op string) (string, error) {
r.currentStack = NewStack()
}
- switch op {
- case "+":
- if err := r.ops.Add(r.currentStack); err != nil {
- return "", fmt.Errorf("operator +: %w", err)
- }
- case "-":
- if err := r.ops.Subtract(r.currentStack); err != nil {
- return "", fmt.Errorf("operator -: %w", err)
- }
- case "*":
- if err := r.ops.Multiply(r.currentStack); err != nil {
- return "", fmt.Errorf("operator *: %w", err)
- }
- case "/":
- if err := r.ops.Divide(r.currentStack); err != nil {
- return "", fmt.Errorf("operator /: %w", err)
- }
- case "^":
- if err := r.ops.Power(r.currentStack); err != nil {
- return "", fmt.Errorf("operator ^: %w", err)
- }
- case "%":
- if err := r.ops.Modulo(r.currentStack); err != nil {
- return "", fmt.Errorf("operator %%: %w", err)
+ // Check for hyperoperators
+ if handled, result, err := r.handleHyperOperatorWithRegistry(r.currentStack, op); err != nil {
+ return "", err
+ } else if handled {
+ if result != "" {
+ return result, nil
}
- case "dup":
- if err := r.ops.Dup(r.currentStack); err != nil {
- return "", fmt.Errorf("dup: %w", err)
+ stackShow, err := r.ops.Show(r.currentStack)
+ if err != nil {
+ return "", fmt.Errorf("show stack: %w", err)
}
- case "swap":
- if err := r.ops.Swap(r.currentStack); err != nil {
- return "", fmt.Errorf("swap: %w", err)
+ return stackShow, nil
+ }
+
+ // Check for standard operators
+ if result, handled, err := r.opRegistry.HandleStandardOperator(r.currentStack, op); err != nil {
+ return "", err
+ } else if handled {
+ if result != "" {
+ return result, nil
}
- case "pop":
- if err := r.ops.Pop(r.currentStack); err != nil {
- return "", fmt.Errorf("pop: %w", err)
+ stackShow, err := r.ops.Show(r.currentStack)
+ if err != nil {
+ return "", fmt.Errorf("show stack: %w", err)
}
- case "show", "showstack", "print":
- return r.ops.Show(r.currentStack)
- case "clear":
- r.ops.ClearVariables()
- return "All variables cleared", nil
- case "vars":
- return r.ops.ListVariables()
- default:
- return "", fmt.Errorf("unknown operator '%s'", op)
+ return stackShow, nil
}
- // Return the current stack state
- return r.ops.Show(r.currentStack)
+ return "", fmt.Errorf("unknown operator '%s'", op)
}
// GetCurrentStack returns a copy of the current stack for inspection.
@@ -259,122 +215,43 @@ func (r *RPN) evaluate(tokens []string) (string, error) {
return fmt.Sprintf("%.10g", val), nil
}
-// handleOperator handles operators and special commands
+// handleOperator handles operators and special commands using the operator registry.
func (r *RPN) handleOperator(stack *Stack, token string, tokenIndex int) (string, error) {
- // Handle hyperoperators
- if isHyperOperator(token) {
- if err := r.handleHyperOperator(stack, token); err != nil {
- return "", err
- }
+ // Check if it's a number first
+ if _, err := strconv.ParseFloat(token, 64); err == nil {
return "", nil
}
- // Handle standard operators
- switch token {
- case "+":
- if err := r.ops.Add(stack); err != nil {
- return "", fmt.Errorf("operator +: %w", err)
- }
- case "-":
- if err := r.ops.Subtract(stack); err != nil {
- return "", fmt.Errorf("operator -: %w", err)
- }
- case "*":
- if err := r.ops.Multiply(stack); err != nil {
- return "", fmt.Errorf("operator *: %w", err)
- }
- case "/":
- if err := r.ops.Divide(stack); err != nil {
- return "", fmt.Errorf("operator /: %w", err)
- }
- case "^":
- if err := r.ops.Power(stack); err != nil {
- return "", fmt.Errorf("operator ^: %w", err)
- }
- case "%":
- if err := r.ops.Modulo(stack); err != nil {
- return "", fmt.Errorf("operator %%: %w", err)
- }
- case "dup":
- if err := r.ops.Dup(stack); err != nil {
- return "", fmt.Errorf("dup: %w", err)
- }
- case "swap":
- if err := r.ops.Swap(stack); err != nil {
- return "", fmt.Errorf("swap: %w", err)
- }
- case "pop":
- if err := r.ops.Pop(stack); err != nil {
- return "", fmt.Errorf("pop: %w", err)
- }
- case "show", "showstack", "print":
- result, err := r.ops.Show(stack)
- if err != nil {
- return "", fmt.Errorf("show: %w", err)
- }
- return result, nil
- case "vars":
- result, err := r.ops.ListVariables()
- if err != nil {
- return "", fmt.Errorf("vars: %w", err)
- }
+ // Check if it's a variable reference first (before operators)
+ if val, exists := r.vars.GetVariable(token); exists {
+ stack.Push(val)
+ return "", nil
+ }
+
+ // Check for hyperoperators
+ if handled, result, err := r.handleHyperOperatorWithRegistry(stack, token); err != nil {
+ return "", err
+ } else if handled {
return result, nil
- case "clear":
- r.ops.ClearVariables()
- return "All variables cleared", nil
- case "d":
- return "", fmt.Errorf("'d' command not supported as standalone token")
- default:
- // Check if it's a variable reference (push its value)
- val, exists := r.vars.GetVariable(token)
- if exists {
- stack.Push(val)
- } else {
- return "", fmt.Errorf("rpn: unknown token '%s' at position %d", token, tokenIndex)
- }
}
- return "", nil
-}
-// isHyperOperator checks if the token is a hyperoperator
-func isHyperOperator(token string) bool {
- switch token {
- case "[+]", "[-]", "[*]", "[/]", "[^]", "[%]":
- return true
- default:
- return false
+ // Handle standard operators
+ if result, handled, err := r.opRegistry.HandleStandardOperator(stack, token); err != nil {
+ return "", err
+ } else if handled {
+ return result, nil
}
+
+ return "", fmt.Errorf("unknown token '%s'", token)
}
-// handleHyperOperator handles hyperoperators
-func (r *RPN) handleHyperOperator(stack *Stack, token string) error {
- switch token {
- case "[+]":
- if err := r.ops.HyperAdd(stack); err != nil {
- return fmt.Errorf("hyperoperator [+]: %w", err)
- }
- case "[-]":
- if err := r.ops.HyperSubtract(stack); err != nil {
- return fmt.Errorf("hyperoperator [-]: %w", err)
- }
- case "[*]":
- if err := r.ops.HyperMultiply(stack); err != nil {
- return fmt.Errorf("hyperoperator [*]: %w", err)
- }
- case "[/]":
- if err := r.ops.HyperDivide(stack); err != nil {
- return fmt.Errorf("hyperoperator [/]: %w", err)
- }
- case "[^]":
- if err := r.ops.HyperPower(stack); err != nil {
- return fmt.Errorf("hyperoperator [^]: %w", err)
- }
- case "[%]":
- if err := r.ops.HyperModulo(stack); err != nil {
- return fmt.Errorf("hyperoperator [%%]: %w", err)
- }
+// handleHyperOperatorWithRegistry handles hyperoperators and returns (handled, result, error).
+func (r *RPN) handleHyperOperatorWithRegistry(stack *Stack, token string) (bool, string, error) {
+ if !r.opRegistry.IsHyperOperator(token) {
+ return false, "", nil
}
- return nil
+ result, handled, err := r.opRegistry.HandleHyperOperator(stack, token)
+ return handled, result, err
}
// handleAssignment checks if the input is an assignment format and handles it.