1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Paul Buetow
package repl
import (
"fmt"
"strconv"
"strings"
"codeberg.org/snonux/perc/internal/perc"
"codeberg.org/snonux/perc/internal/rpn"
)
// 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.
//
// Handlers implement the Handle method to process REPL commands and expressions.
// If a handler cannot process the input, it calls Next() to forward to the next handler.
type CommandHandler interface {
Handle(repl *REPL, input string) (output string, handled bool, err error)
SetNext(next CommandHandler)
}
// BaseHandler provides common functionality for all handlers in the chain.
// It stores a reference to the next handler and provides the Next() method
// for forwarding requests.
type BaseHandler struct {
next CommandHandler
}
// SetNext sets the next handler in the chain.
// This enables building a chain of responsibility by linking handlers together.
//
// next: the next CommandHandler in the chain
func (h *BaseHandler) SetNext(next CommandHandler) {
h.next = next
}
// Next forwards the request to the next handler in the chain.
// If there is no next handler, it returns (false, nil) indicating the request
// was not handled.
//
// repl: the REPL instance
// input: the command string to process
// Returns: (output string, handled bool, err error)
func (h *BaseHandler) Next(repl *REPL, input string) (output string, handled bool, err error) {
if h.next == nil {
return "", false, nil
}
return h.next.Handle(repl, input)
}
// BuiltInCommandHandler handles built-in commands like help, clear, quit, exit.
// It also handles special commands that require RPN state access (e.g., "rat").
// If the input doesn't match a built-in command, it forwards to the next handler.
type BuiltInCommandHandler struct {
BaseHandler
}
// Handle processes built-in commands from the input string.
// It first checks if the input starts with a built-in command using isBuiltinCommand.
// Special handling is provided for the "rat" command which requires RPN state access.
// If the command is handled, it returns the output and sets handled=true.
// Otherwise, it forwards to the next handler in the chain.
//
// repl: the REPL instance
// input: the command string to process
// Returns: (output string, handled bool, err error)
func (h *BuiltInCommandHandler) Handle(repl *REPL, input string) (output string, handled bool, err error) {
if cmd, ok := isBuiltinCommand(input); ok {
args := strings.Fields(cmd)
if len(args) > 0 {
subCmd := strings.ToLower(args[0])
// Handle rat command specially - needs RPN state access
if subCmd == "rat" {
return handleRatCommand(repl, input)
}
}
output, err := ExecuteCommand(cmd)
if err != nil {
return "", true, err
}
return output, true, nil
}
return h.Next(repl, input)
}
// handleRatCommand handles the rat mode command with access to RPN state.
// It allows switching between float64 and rational number modes for RPN calculations.
//
// Valid modes:
// - "on": Enable rational number mode
// - "off": Disable rational mode (use float64)
// - "toggle": Switch between the two modes
//
// repl: the REPL instance (provides access to RPN state)
// input: the full command string (e.g., "rat on")
// Returns: (output string, handled bool, err error)
func handleRatCommand(repl *REPL, input string) (string, bool, error) {
args := strings.Fields(input)
if len(args) < 2 {
return "rat command requires an argument: on, off, or toggle", true, nil
}
modeArg := strings.ToLower(args[1])
rpnState := repl.rpnState
switch modeArg {
case "on":
rpnState.rpnCalc.SetMode(rpn.RationalMode)
return "Rational mode enabled", true, nil
case "off":
rpnState.rpnCalc.SetMode(rpn.FloatMode)
return "Rational mode disabled (using float64)", true, nil
case "toggle":
if rpnState.rpnCalc.GetMode() == rpn.FloatMode {
rpnState.rpnCalc.SetMode(rpn.RationalMode)
return "Rational mode enabled", true, nil
} else {
rpnState.rpnCalc.SetMode(rpn.FloatMode)
return "Rational mode disabled (using float64)", true, nil
}
default:
return "Unknown rat mode: " + modeArg + ". Valid modes: on, off, toggle", true, nil
}
}
// RPNHandler handles RPN (Reverse Polish Notation) expressions and RPN-related commands.
// It processes commands with "rpn" or "calc" prefixes, bare RPN expressions,
// and single RPN operators (e.g., "+", "dup", "swap", "show").
type RPNHandler struct {
BaseHandler
}
// Handle processes RPN commands and expressions.
// It handles:
// - Commands with "rpn" or "calc" prefix
// - Bare RPN expressions (e.g., "3 4 +")
// - Single RPN operators on the current stack
// - Single numbers (push onto stack)
//
// If the input doesn't match any RPN pattern, it forwards to the next handler.
//
// repl: the REPL instance (provides access to RPN state)
// input: the command string to process
// Returns: (output string, handled bool, err error)
func (h *RPNHandler) Handle(repl *REPL, input string) (output string, handled bool, err error) {
// Check for rpn/calc prefix
lowerInput := strings.ToLower(input)
if strings.HasPrefix(lowerInput, "rpn ") || strings.HasPrefix(lowerInput, "calc ") {
// Extract the expression after rpn/calc
rest := strings.TrimSpace(strings.TrimPrefix(input, strings.SplitN(input, " ", 2)[0]))
result, err := repl.rpnState.rpnCalc.ParseAndEvaluate(rest)
if err != nil {
return "", true, err
}
return result, true, nil
}
// Try RPN parsing first (for bare RPN expressions like "3 4 +")
if state := repl.rpnState; state != nil {
// Check if input looks like RPN (contains spaces or is a single known operator)
if strings.Contains(input, " ") {
result, err := state.rpnCalc.ParseAndEvaluate(input)
if err == nil {
return result, true, nil
}
}
// Try evaluating as a single operator on the current RPN stack
fields := strings.Fields(input)
if len(fields) == 1 {
op := strings.ToLower(fields[0])
// Check if it's a known operator (standard or hyper)
isStandardOp := op == "+" || op == "-" || op == "*" || op == "/" || op == "^" || op == "%" ||
op == "dup" || op == "swap" || op == "pop" || op == "show" || op == "clear" || op == "vars" ||
op == "lg" || op == "log" || op == "ln"
isHyperOp := op == "[+]" || op == "[-]" || op == "[*]" || op == "[/]" || op == "[^]" || op == "[%]" ||
op == "[lg]" || op == "[log]" || op == "[ln]"
if isStandardOp || isHyperOp {
result, err := state.rpnCalc.EvalOperator(op)
if err != nil {
return "", true, err
}
return result, true, nil
}
}
// Check if input is a single number (valid RPN - pushes number onto stack)
if len(fields) == 1 {
if _, err := strconv.ParseFloat(fields[0], 64); err == nil {
// Push the number onto the RPN stack using ParseAndEvaluate
// This maintains the RPN state across multiple inputs in REPL mode
result, err := state.rpnCalc.ParseAndEvaluate(fields[0])
if err != nil {
return "", true, err
}
return result, true, nil
}
}
// Check if input is a symbol syntax (:x) - valid RPN that pushes a symbol
if len(fields) == 1 {
token := fields[0]
if len(token) > 0 && token[0] == ':' {
// This is a symbol syntax like :x
result, err := state.rpnCalc.ParseAndEvaluate(token)
if err != nil {
return "", true, err
}
return result, true, nil
}
}
}
return h.Next(repl, input)
}
// PercentageHandler handles percentage calculation expressions.
// It uses the perc.Parse function to evaluate expressions like:
// - "20% of 150"
// - "what is 20% of 150"
// - "30 is what % of 150"
// - "30 is 20% of what"
type PercentageHandler struct {
BaseHandler
}
// Handle processes percentage calculation expressions.
// If the input matches a percentage expression pattern, it evaluates and returns
// the result. Otherwise, it forwards to the next handler.
//
// repl: the REPL instance
// input: the command string to process
// Returns: (output string, handled bool, err error)
func (h *PercentageHandler) Handle(repl *REPL, input string) (output string, handled bool, err error) {
// Run the percentage calculation
result, err := perc.Parse(input)
if err != nil {
// Not a percentage expression, pass to next handler
return h.Next(repl, input)
}
return result, true, nil
}
// ErrorHandler handles unknown commands and invalid expressions.
// It returns an error indicating that the input was not recognized.
type ErrorHandler struct {
BaseHandler
}
// Handle processes unknown commands by returning an error.
// This is typically the last handler in the chain.
//
// repl: the REPL instance
// input: the command string that was not handled by previous handlers
// Returns: ("", false, error) with an error describing the unknown command
func (h *ErrorHandler) Handle(repl *REPL, input string) (output string, handled bool, err error) {
// Unknown command - return error
return "", false, fmt.Errorf("unknown command or invalid expression: %s", input)
}
// NewCommandChain creates and returns the complete command handling chain.
// The chain is built in the following order:
// 1. BuiltInCommandHandler: handles built-in commands (help, clear, quit, exit, rat)
// 2. RPNHandler: handles RPN expressions and operators
// 3. PercentageHandler: handles percentage calculations
// 4. ErrorHandler: handles unknown commands (returns error)
//
// Returns a CommandHandler representing the first handler in the chain
func NewCommandChain() CommandHandler {
// Create handlers
builtInHandler := &BuiltInCommandHandler{}
rpnHandler := &RPNHandler{}
percentageHandler := &PercentageHandler{}
errorHandler := &ErrorHandler{}
// Build the chain: BuiltIn -> RPN -> Percentage -> Error
builtInHandler.SetNext(rpnHandler)
rpnHandler.SetNext(percentageHandler)
percentageHandler.SetNext(errorHandler)
return builtInHandler
}
|