| Age | Commit message (Collapse) | Author |
|
|
|
assertion (task yj)
|
|
All operator registry keys are lowercase, so strings.ToLower(token)
was a no-op. Removing the unnecessary lowercasing and the unused
op variable, passing the original token directly to
IsStandardOperator/IsHyperOperator/EvalOperator.
|
|
Extract the duplicated type switch on StackValue in AssignLeft()
and AssignRight() into a single extractVarName() helper, closing
the OCP violation — adding a new StackValue subtype as a valid
variable name now only requires editing one function.
|
|
Add variadic preChecks parameter to binaryMetricOp, allowing callers to
run a validation on the right operand before metric resolution. This
eliminates the full pipeline duplication in Divide() — it now delegates
to binaryMetricOp with a preCheck that guards against division by zero.
|
|
Add var _ Interface assertions to verify implementations at compile
time:
- internal/rpn/variables.go: assert *Variables against
VariableReader, VariableWriter, VariablePersistence, and
VariableStore
- internal/repl/handlers.go: assert *BuiltInCommandHandler,
*RPNHandler, *PercentageHandler, and *ErrorHandler against
CommandHandler (BaseHandler intentionally excluded as it lacks a
Handle method and is only meant for embedding)
- internal/repl/completer.go: assert *AutoCompleteAdapter against
readline.AutoCompleter (adds readline import)
Note: internal/rpn/constants.go already had assertions in place.
|
|
Extract runCommand() in cmd/gt/main.go into separate helpers
following SRP:
- parseFlags(): extract --log flag parsing
- dispatchInput(): route by arg count/content (version, stdin, args)
- handleNoInput(): TTY→REPL or stdin→parse path
- startREPL(): REPL launch with optional log file
- tryParse(): RPN-first with percentage fallback
runCommand() is now a 3-line orchestrator delegating to parseFlags()
and dispatchInput(). No behavioral change; all tests pass.
|
|
Replace the hardcoded strings.HasPrefix check for "rpn " and "calc "
with a loop over a package-level rpnPrefixes slice. Adding a new prefix
now only requires appending to the slice, satisfying the Open/Closed
Principle.
|
|
responsibility (sj)
Move built-in constants into a package-level builtInConstants map so
loadBuiltInConstants() only copies from it (no return value). Reload
and Clear now simply clear and copy from the source map, removing the
maps.DeleteFunc dance and the unused maps import.
|
|
OCP violation: adding a new Category required editing the String()
method. Use a parallel categoryNames slice indexed by Category value
instead, so adding a new category only requires appending to the
slice. _sentinel already bounds the range.
|
|
Move the inline logic for the 'd' (delete variable) operator into a
proper Operations.Delete method, following the same registration pattern
as all other operators. Adds Delete to the StackOperator interface.
|
|
interface (nj)
Accept the OperatorProvider interface instead of concrete *Operations in
NewOperatorRegistry() and all register* methods. OperatorProvider embeds
ArithmeticOperator, PowerIntOperator, LogarithmicOperator, BooleanOperator,
StackOperator, VariableOperator, ConstantOperator, MetricOperator, and
HyperOperator, allowing mocking or alternative implementations.
|
|
sub-interfaces (#wj)
Follows the same ISP-compliant pattern as VariableStore (VariableReader,
VariableWriter, VariablePersistence). The fat 7-method ConstantsProvider
is now split into:
- ConstantsReader: GetConstant, ListConstants, Count, HasConstant
- ConstantsWriter: SetConstant
- ConstantsAdmin: ClearConstants, ReloadBuiltInConstants
- ConstantsProvider: embeds all three for convenience
RPN.consts narrowed to ConstantsReader since RPN only reads constants;
GetConstants() asserts to ConstantsProvider for callers that need full access.
Added compile-time satisfaction checks for all sub-interfaces.
|
|
If stack.Pop() errors midway through the loop, already-popped values
are pushed back in reverse order before returning the error, preventing
stack corruption from concurrent modification or unexpected failures.
|
|
Replace hardcoded switch statements in resultMetricForMul and
resultMetricForDiv with package-level maps (multiplicationInference,
divisionInference). Adding new category pairs now only requires
appending to the maps instead of editing control flow, satisfying
OCP.
|
|
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.
|
|
The dispatchToken guard for standalone "=" always fires before the
registry lookup, making the "=" registration in operator_registry.go
dead code. Assignment with "=" is handled at the expression level by
handleStandardAssign, not via the stack-level operator registry.
Also update related tests:
- TestAssignmentOperatorRegistry: remove "=" from registered operators
- TestResultStackErrors: "=" is no longer an operator, expects
"unknown token" instead of "insufficient operands"
|
|
NewOperatorRegistry() was 60+ lines of registration calls. Split into six
category-specific helpers: registerArithmeticOperators,
registerComparisonOperators, registerStackOperators,
registerVariableOperators, registerCommandOperators, and
registerHyperOperators.
NewOperatorRegistry() is now a thin ~13-line orchestrator.
|
|
Replace stack.Values() with direct Pop() calls in Swap().
Previously created a full stack copy just to read the top two values.
Behavior is unchanged — ensureStackLength guard guarantees pops succeed.
|
|
Deduplicate identical VariableInfo sorting logic across
ListVariables(), formatVariablesUnsafe(), and Save(). All three
callers now use a single unexported sortVariableInfos helper.
|
|
Factor out the repeated save-restore-evaluate pattern from
RPNHandler.Handle() into a single helper method so the stack
is always restored on parse errors, across all call sites.
|
|
Define OperationsProvider interface composed of focused sub-interfaces
(ModeController, MetricCommander, CustomMetricManager) plus the existing
StackOperator. Change RPN.ops from *Operations to OperationsProvider so the
high-level RPN module depends on abstractions, not the concrete type (DIP).
Add compile-time assertions for the new interfaces in operations.go.
|
|
The ArithmeticOperator interface bundled basic arithmetic (+, -, *, /, ^, %),
logarithmic operations (Log2, Log10, Ln), and metric conversion (Convert) into
a single interface, violating ISP and LSP.
Split into:
- ArithmeticOperator — Add, Subtract, Multiply, Divide, Power, Modulo
- LogarithmicOperator — Log2, Log10, Ln
- MetricOperator — Convert
Updated compile-time interface checks in operations.go and the trailing
comment in operations_interfaces.go. No behavioral change.
|
|
Extract inline helper methods to bring dispatchToken,
handleMetricCommand, and handleCustomCommand under 50 lines:
- handleInlineAssignment: extracts := / =: stack assignment logic
- handleMetricPrefix: handles metric binary/decimal prefix mode switching
- handleCustomDefine: handles 'custom define' subcommand
- handleCustomUndefine: handles 'custom undefine' subcommand
dispatchToken: 55 -> 47 lines
handleMetricCommand: 40 -> 38 lines
handleCustomCommand: 43 -> 31 lines
|
|
(task 5j)
The loop checking remaining characters was dead code — the final
check meant any token longer than 1 char was rejected
regardless of what the loop found. Replace the entire function with a
simple single-char check.
|
|
NewOperations() no longer creates a ConstantsProvider internally since
NewRPN() always calls SetConstants() immediately after. Eliminates a
wasted allocation with no behavioral change.
|
|
Deduplicate the repeated stack overflow check in pushLiteral() by
extracting it into a checkStackOverflow() helper method. Replaces
three identical inline checks with calls to the new helper.
|
|
|
|
Document all invocation modes (single-expression, REPL, pipe, stdin),
boolean coercion rules, version/help behavior, exit codes, and practical
use cases for scripts and CI pipelines.
|
|
|
|
|
|
Test and document rational number mode:
- rat on/off/toggle commands (REPL only)
- How big.Rat integration works internally
- Precision comparison examples with actual output
- Known limitation: +, -, % fail for non-dyadic decimals (0.1, 0.2)
due to Rat.Float64() rejecting lossy conversions
- Performance trade-offs vs float64 mode
- Edge cases including metrics, constants, and variables
- When to use rational mode and when to stick with float mode
|
|
Document custom metric commands (define/undefine/list/show) with
syntax, REPL workflow examples, arithmetic usage, practical use cases,
and edge cases including duplicate names, invalid categories, and
factor zero behavior.
|
|
Document all metric subcommands with real CLI output examples:
- metric show: inspect metric info for top of stack
- metric list: list all metric categories
- metric <category>: list units in a category
- metric compatible: check compatibility of two values
- metric decimal set / metric binary set: SI vs IEC modes
Covers REPL vs single-command differences, SI vs IEC prefix modes,
and practical use cases for exploring and verifying metrics.
|
|
Document the @prefix convert syntax with real examples for all
metric categories (DataRate, DataSize, Time, Weight, Speed,
Distance, Universal). Includes practical use cases for bandwidth
planning, travel, cooking, and data storage, plus edge case
coverage for incompatible categories, unknown metrics, Cool
absorbing, and SI/IEC interoperability.
|
|
Merge three identical single-token blocks (operator, number, symbol)
into one block with conditional checks. Eliminates redundant
strings.Fields calls and len(fields)==1 checks.
The 'rat' command early-return is not redundant — it correctly
short-circuits before ExecuteCommand is reached.
|
|
toFloat64 already includes the op name in its error message,
so wrapping with buildError produced double prefixes like
'**: **: value X is not numeric'.
- Exponent check: use buildError with plain errors.New
- Base check: return toFloat64 error directly (already has context)
|
|
- Extend nAryMetricOp with optional per-operand validate callback
(used by HyperModulo for modulo-by-zero check)
- HyperMultiply: use nAryScalarOp instead of manual multiplication
- HyperModulo: use nAryMetricOp with validate callback instead of
inline metric resolution and conversion
HyperModulo reduced from ~45 to ~5 lines.
All tests pass.
|
|
type Number = NumericValue was a legacy alias that created confusion
— callers could use Number or NumericValue interchangeably. Removed
the alias and updated:
- NewNumber/NewNumberWithMetric return types: Number -> NumericValue
- Comments referencing Number now say NumericValue
The alias added no value since Number was never used as a distinct
type anywhere in the codebase.
|
|
The Operator interface (~40 methods) embedded 7 sub-interfaces plus
mode/prefix/metric/custom methods. Since RPN is the sole client and
*Operations is the sole implementor, the combined interface added
indirection without practical benefit.
Changes:
- Remove type Operator entirely
- RPN.ops: Operator -> *Operations (concrete type)
- NewOperatorRegistry: Operator -> *Operations
- Compile-time checks: one per sub-interface (7 checks)
- Sub-interfaces preserved for documentation/organization
|
|
pushLiteral called strconv.ParseFloat twice for non-RationalMode
input: once to check if token is a number, once to get the value.
Capture the parsed value from the first call to avoid redundant
parsing.
|
|
buildError(opName, ...) prepends opName to the error, so the inner
error should not repeat it. Changed from:
buildError(opName, fmt.Errorf("%s undefined...", opName))
to:
buildError(opName, errors.New("undefined..."))
Produces "lg: undefined for non-positive numbers" instead of
"lg: lg undefined for non-positive numbers".
|
|
Extract evaluate() (~130 lines) into:
- evaluate(): setup and orchestration (20 lines)
- evaluateTokens(): token loop with handled tracking (12 lines)
- dispatchToken(): single token dispatch (58 lines)
- checkVariableName(): variable name detection for assignment (21 lines)
- processResult(): final stack state and result formatting (25 lines)
All helpers under ~50 lines except dispatchToken (58, close).
Behavior preserved - all tests pass.
|
|
Extract handleOperator (~65 lines) into:
- checkAndPushSymbol(): :x syntax check and symbol push
- resolveVariableOrConstant(): variable and constant lookups
- dispatchOperator(): operator dispatch with symbol fallback
All helpers under 30 lines. Behavior preserved - all tests pass.
|
|
Extract shared patterns from hyper operators:
- nAryMetricOp: metric resolution, category validation, base unit
conversion, binary fn application, result conversion (used by
HyperAdd, HyperSubtract)
- nAryScalarOp: applies binary fn to pre-converted float64 values
and pushes with Cool metric (used by HyperDivide, HyperPower)
HyperMultiply stays inline (unique init-to-1 pattern).
HyperModulo stays inline (metric-aware with modulo-by-zero check).
Reduces operations_hyper.go from ~300 to ~268 lines.
|
|
RPN.GetMode/SetMode/GetPrefixMode/SetPrefixMode acquired r.mu
before delegating to Operations methods that already acquire
o.mu. Since Operations guards mode and prefixMode with its own
RWMutex, the RPN-level locks were redundant double-locking
on separate mutexes for the same data.
Removed the r.mu locking from these four methods. Thread safety
is preserved by Operations' internal mutex.
|
|
Replace += string concatenation in Show() with strings.Builder
for O(n) performance instead of O(n^2). ListConstants() already
used strings.Builder.
|
|
= was registered as AssignLeft (pops name then value) which has
reversed stack semantics for the convention name value =.
Changed to AssignRight (pops value then name) to match.
This is latent dead code (parser intercepts = before reaching
executeOperator) but fixing it prevents future confusion.
Also corrected misleading comments in operations_variables.go
to reflect correct operator-function mappings.
|
|
Define const invalidCategory Category = -1 so parseCategory
returns a clearly-invalid sentinel instead of Category(0)/Universal
on unknown names. This prevents silent misclassification if a
callers ignores the bool return value.
Adds tests for parseCategory with known categories, unknown names,
empty string, and sentinel validity.
|
|
Complete documentation of all 7 metric categories with units,
suffix notation, unit conversion, metric-aware arithmetic rules,
cross-category inference, SI/IEC prefix modes, custom metrics,
and practical use cases (bandwidth, travel, weight).
|