From f3beef8ce2f538783889c55d685fa30a471983b7 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 24 May 2026 14:01:31 +0300 Subject: refactor(repl): introduce RPNCalculator interface to fix DIP violation (task rj) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/repl/handlers.go | 40 ++++++++++++++++++++++++++++------------ 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 -- cgit v1.2.3