diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-24 14:01:31 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-24 14:01:31 +0300 |
| commit | f3beef8ce2f538783889c55d685fa30a471983b7 (patch) | |
| tree | 4f1df576d4d085933cea0cb8e9956f30b8492e6d | |
| parent | 4c64d3869230b2aa22f7f2635e0851e355b24ca4 (diff) | |
refactor(repl): introduce RPNCalculator interface to fix DIP violation (task rj)
Handlers accessed repl.rpnState.rpnCalc — three levels of concrete types
violating DIP and Law of Demeter. Define an RPNCalculator interface
capturing only the methods handlers actually need, and expose it via
REPL.RpnCalculator() so handlers depend on the interface, not the
concrete chain of *rpn.RPN.
| -rw-r--r-- | internal/repl/handlers.go | 40 | ||||
| -rw-r--r-- | internal/repl/repl.go | 10 |
2 files changed, 38 insertions, 12 deletions
diff --git a/internal/repl/handlers.go b/internal/repl/handlers.go index c2f8af5..069e945 100644 --- a/internal/repl/handlers.go +++ b/internal/repl/handlers.go @@ -12,6 +12,20 @@ import ( "codeberg.org/snonux/gt/internal/rpn" ) +// RPNCalculator defines the methods needed by REPL handlers to interact with +// the RPN engine. By depending on this interface instead of the concrete *rpn.RPN, +// handlers obey DIP and the Law of Demeter. +type RPNCalculator interface { + ParseAndEvaluate(string) (string, error) + EvalOperator(string) (string, error) + GetCurrentStack() []rpn.StackValue + SetCurrentStack([]rpn.StackValue) + SetMode(rpn.CalculationMode) + GetMode() rpn.CalculationMode + IsStandardOperator(string) bool + IsHyperOperator(string) bool +} + // CommandHandler represents a handler in the chain of responsibility pattern. // Each handler can process a command or pass it to the next handler in the chain. // @@ -104,21 +118,21 @@ func handleRatCommand(repl *REPL, input string) (string, bool, error) { } modeArg := strings.ToLower(args[1]) - rpnCalc := repl.rpnState.rpnCalc + calc := repl.RpnCalculator() switch modeArg { case "on": - rpnCalc.SetMode(rpn.RationalMode) + calc.SetMode(rpn.RationalMode) return "Rational mode enabled", true, nil case "off": - rpnCalc.SetMode(rpn.FloatMode) + calc.SetMode(rpn.FloatMode) return "Rational mode disabled (using float64)", true, nil case "toggle": - if rpnCalc.GetMode() == rpn.FloatMode { - rpnCalc.SetMode(rpn.RationalMode) + if calc.GetMode() == rpn.FloatMode { + calc.SetMode(rpn.RationalMode) return "Rational mode enabled", true, nil } else { - rpnCalc.SetMode(rpn.FloatMode) + calc.SetMode(rpn.FloatMode) return "Rational mode disabled (using float64)", true, nil } default: @@ -136,10 +150,11 @@ type RPNHandler struct { // evalWithStackRestore evaluates an RPN expression, restoring the stack on error // so that a failed parse never corrupts the user's stack. func (h *RPNHandler) evalWithStackRestore(repl *REPL, input string) (string, error) { - savedStack := repl.rpnState.rpnCalc.GetCurrentStack() - result, err := repl.rpnState.rpnCalc.ParseAndEvaluate(input) + calc := repl.RpnCalculator() + savedStack := calc.GetCurrentStack() + result, err := calc.ParseAndEvaluate(input) if err != nil { - repl.rpnState.rpnCalc.SetCurrentStack(savedStack) + calc.SetCurrentStack(savedStack) return "", err } return result, nil @@ -173,7 +188,8 @@ func (h *RPNHandler) Handle(repl *REPL, input string) (output string, handled bo } // Try RPN parsing first (for bare RPN expressions like "3 4 +") - if repl.rpnState != nil { + calc := repl.RpnCalculator() + if calc != nil { // Check if input looks like RPN (contains spaces or is a single known operator) if strings.Contains(input, " ") { result, err := h.evalWithStackRestore(repl, input) @@ -188,8 +204,8 @@ func (h *RPNHandler) Handle(repl *REPL, input string) (output string, handled bo if len(fields) == 1 { token := fields[0] op := strings.ToLower(token) - if repl.rpnState.rpnCalc.IsStandardOperator(op) || repl.rpnState.rpnCalc.IsHyperOperator(op) { - result, err := repl.rpnState.rpnCalc.EvalOperator(op) + if calc.IsStandardOperator(op) || calc.IsHyperOperator(op) { + result, err := calc.EvalOperator(op) if err != nil { return "", true, err } diff --git a/internal/repl/repl.go b/internal/repl/repl.go index 98ff23e..2ecd9ea 100644 --- a/internal/repl/repl.go +++ b/internal/repl/repl.go @@ -87,6 +87,16 @@ type REPL struct { logWriter io.WriteCloser } +// RpnCalculator returns the RPN calculator behind this REPL as an interface. +// This lets handlers depend on the RPNCalculator interface (DIP) rather than +// reaching through repl.rpnState.rpnCalc into concrete types. +func (r *REPL) RpnCalculator() RPNCalculator { + if r.rpnState == nil { + return nil + } + return r.rpnState.rpnCalc +} + // ReadlinePrompt provides an interactive prompt using chzyer/readline. // It supports: // - Ctrl+R for reverse history search |
