| Age | Commit message (Collapse) | Author |
|
|
|
|
|
|
|
(task 5k)
|
|
|
|
|
|
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.
|
|
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.
|
|
|
|
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.
|
|
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.
|
|
|
|
Replace fragile strings.Contains checks with proper tokenization.
The old code matched '=' via substring search with hacky guards for
'==' and '!=', which could misparse inputs like 'a == b = 5'.
Now Tokenize(input) splits on whitespace first, so '=' is only
detected as a standalone token — '==' and '!=' are naturally distinct
tokens and can never trigger a false positive.
Also adds regression tests confirming == and != are not treated as
assignments.
|
|
In handleOperator, replace strings.Contains(err.Error(), "unknown token")
with a direct check against opRegistry.IsStandardOperator and
opRegistry.IsHyperOperator. This is robust against future changes to
error message text.
|
|
In pushLiteral, use NewRatFromString(token) instead of
NewRat(ParseFloat(token)) when mode is RationalMode. This preserves
the full precision of the original numeric string by parsing it
directly into big.Rat, avoiding the float64 intermediate that loses
precision.
In ResultStack, replace NewNumber(val, mode) with explicit mode
dispatch for variable references. Note: variables stored as float64
still lose precision at storage time, but the push path is now
consistent.
|
|
Previously, 'custom show' delegated to MetricShow(), which displayed
metric info for the top stack value — identical to 'metric show' and
making 'custom show' a pointless alias.
Now 'custom show' lists all custom metrics with name, category, base
unit, and factor. Optionally accepts a metric name argument to show
details for a specific custom metric only.
- Add CustomShow(stack, name) method to Operator interface
- Implement CustomShow on Operations with per-metric detail output
- Update handleCustomCommand to call CustomShow instead of MetricShow
|
|
Remove the mode field from the RPN struct, which was duplicated from
Operations.mode. All reads of r.mode in rpn_parse.go and rpn_ops.go
now go through r.ops.GetMode().
- Add GetMode() to the Operator interface so RPN can access mode
through its Operator dependency
- Remove mode field from RPN struct in rpn_state.go
- Remove mode initialization from NewRPN
- Update SetMode to only set mode on Operations
- Update GetMode to delegate to Operations
- Replace r.mode with r.ops.GetMode() in rpn_parse.go (5 sites)
- Replace r.mode with r.ops.GetMode() in rpn_ops.go (1 site)
|
|
- Rename comment subject from 'standardAssignHandler' to match actual
function name 'handleStandardAssign'
- Correct stack position description: in 'name value =' format, name
is pushed first (bottom of stack) and value second (top), not the
reverse as the comment previously stated
|
|
Evaluate() was ~200 lines with mixed concerns. Extracted 4 helpers:
- pushLiteral(): number/boolean/metric/@metric literal parsing (~45 lines)
- handleMetricCommand(): 'metric <subcmd>' dispatch (~50 lines)
- handleCustomCommand(): 'custom <subcmd>' dispatch (~55 lines)
- shouldPushName(): variable-assignment detection logic (~25 lines)
Evaluate() is now ~130 lines with clear delegation to helpers.
Stack assignment (2-token inline) remains in evaluate() for clarity.
|
|
Both functions had identical structure (~37 lines each, ~74 total).
Extracted shared logic into handleAssignmentOp() and tryAssignment()
helpers, reducing to ~25 lines of shared code plus two 3-line wrappers.
The helpers parameterize the operator string (':=', '=:') and handle
the two field orderings (value name vs name value) automatically.
|
|
Eliminate confusing variadic *Metric parameter from NewNumber().
Callers either pass nothing (defaults to Cool) or one metric —
variadic implied zero-or-many, causing confusion.
New API:
NewNumber(value, mode) — metric defaults to Cool
NewNumberWithMetric(value, mode, metric) — explicit metric
All callers updated to use the appropriate constructor.
No behavioral changes; same defaults, clearer API.
|
|
Dependency Inversion: scope MetricRegistry as a dependency rather than
relying on a global singleton. This enables testing with custom registries
and decouples code from global mutable state.
Changes:
- NewRPN(vars, reg...): accepts optional *MetricRegistry parameter.
Defaults to GetMetricRegistry() when not provided (backward compatible).
- NewOperations(vars, reg...): accepts optional *MetricRegistry parameter.
Defaults to GetMetricRegistry() when not provided (backward compatible).
- parseNumberWithMetric(token, reg): requires *MetricRegistry parameter
instead of calling GetMetricRegistry() internally.
- RPN struct stores metricRegistry field, used by rpn_parse.go for
metric lookups (@ prefix and parseNumberWithMetric).
- Added Operations.MetricRegistry() getter for external access.
All existing callers remain backward compatible via variadic defaults.
Tests updated to pass GetMetricRegistry() to parseNumberWithMetric.
|
|
Fix formatting in 12 files to match gofmt standards.
No logical changes.
|
|
StringNum and Symbol implemented Number but their Float64(),
Compare(), Bool(), IsZero(), IsNegative(), and SetMetric() methods
always returned errors. Any code accepting Number had to defensively
check IsString()/IsSymbol() before arithmetic, defeating the interface.
Split Number into two interfaces:
- StackValue: base interface for anything on the stack
(String, IsBool, IsString, IsSymbol, Metric)
- NumericValue: embeds StackValue + arithmetic contract
(Float64, IsZero, IsNegative, Compare, Bool, SetMetric)
Float and Rat implement both; StringNum and Symbol implement
StackValue only. Keep Number as a type alias for backward compat.
Updated Stack, popStack/popTwo/popAll, toFloat64, resolveMetric,
convertToBase, GetCurrentStack/SetCurrentStack, and all callers
to use the correct interface level.
|
|
- Add Unregister() to MetricRegistry for removing custom metrics
- Handle 'custom show', 'custom list', 'custom define', 'custom undefine' in rpn_parse.go evaluate()
- Add CustomList, CustomDefine, CustomUndefine methods to Operations
- Update Operator interface with new custom metric methods
- Add comprehensive tests for all custom metric operations
|
|
Remove the prefixMode field from the RPN struct; it was duplicated from
Operations where it is actually used. Add GetPrefixMode to the Operator
interface so RPN can delegate both GetPrefixMode and SetPrefixMode to the
Operations instance behind the interface.
In rpn_parse.go evaluate(), drop the direct r.prefixMode = IEC/SI writes
and the now-unnecessary lock ordering comment; only r.ops.SetPrefixMode()
remains.
|
|
No deadlock risk: evaluate() and RPN.SetPrefixMode() both acquire
locks in the same order (r.mu → o.mu). The nested lock in evaluate()
is safe — o.mu (Operations) is a separate mutex from r.mu (RPN).
Resolves task ce: lock ordering analysis shows no circular wait.
|
|
Add prefixMode field to Operations struct with thread-safe Get/Set
accessors, propagating the user-set prefix mode (SI/IEC) from the RPN
parser through the evaluate path.
- Replace all hardcoded SI arguments in convertToBase/convertFromBase
calls with o.GetPrefixMode() across arithmetic, comparison, and
convert operations
- Cache GetPrefixMode() once per operation to avoid repeated mutex
overhead
- Sync r.ops.SetPrefixMode() alongside r.prefixMode in metric binary/
decimal set handlers (no deadlock: Operations.mu is separate from
RPN.mu)
- Update MetricShow to display factor using current prefix mode
- Add SetPrefixMode to the Operator interface
- Add end-to-end tests verifying prefix mode affects conversion,
arithmetic, and comparison results
|
|
- Remove dead 'i += 2' in metric binary/decimal set handlers
(the increment is followed by immediate return, never used)
- Clarify MetricShow always uses SI factor in display
(prefixMode not yet wired into computations)
- Add test for metric Custom category
|
|
Add multi-word 'metric' command with subcommands for inspecting and
configuring metric units:
- metric show: show metric info (name, category, base unit, factor) for
top of stack
- metric list: list all registered metric categories
- metric <Category>: list all metrics in a specific category (e.g., DataRate)
- metric binary set: set prefix mode to IEC (1024-based)
- metric decimal set: set prefix mode to SI (1000-based)
- metric compatible: check if top two stack values have compatible metrics
Adds prefixMode field to RPN struct with thread-safe getter/setter.
Handles the 'metric' token in evaluate() before falling through to
operators, consuming subsequent tokens as subcommands.
|
|
Token @GB pushes Number(1, mode, GB) onto the stack.
Uses FindWithAliases for metric lookup (exact, alias, case-insensitive).
Returns error for unknown metrics.
No conflict with : symbols (@ is distinct from : prefix).
Includes unit and integration tests.
|
|
Add parseNumberWithMetric() that splits tokens into number + metric suffix.
Handles integers, decimals, scientific notation, and +/-signs.
Uses FindWithAliases for metric lookup (exact -> alias -> case-insensitive).
Bps (capital B) correctly rejected — data rate units are case-sensitive.
Wired into rpn_parse.go evaluate loop, pushing NewNumber with metric.
Includes tests for basic parsing, aliases, and exact-match guard.
|
|
|
|
- Added constants lookup after variable lookup in rpn_parse.go
- Now constants like pi, e, phi, sqrt2, inf, nan work in RPN expressions
|
|
- operations_test.go: Update Float64() calls to handle (float64, error) return
- rpn_parse.go: Update val.Float64() calls in assignment handler
- number.go: Add IsString/IsSymbol to Float, Rat, StringNum, Symbol
- Fix IsZero() and ToFloat() to use val, _ := Float64() pattern
|
|
- Float64() now returns (float64, error) instead of just float64
- All arithmetic methods (Add, Sub, Mul, Div, Pow, Mod, Compare) return errors
- IsString() and IsSymbol() added to Number interface
- Float and Rat now implement IsString() and IsSymbol()
- StringNum and Symbol updated to implement complete Number interface
- IsZero() updated to use val, _ := Float64() pattern
- ToFloat() updated to ignore error from Float64()}
|
|
- Fix error handling in test files by explicitly ignoring error returns
- Remove trailing punctuation from error message in rpn_parse.go
Test file changes:
- cli_test.go: Use _ = os.Remove() in Cleanup function
- concurrent_test.go: Use _, _ = runRPN() in concurrent goroutines
Code change:
- rpn_parse.go: Changed error message to end with 'colon' instead of ':'
|
|
Created specialized handlers for each assignment operator type:
- handleAssignRight (for := operator)
- handleAssignLeft (for =: operator)
- handleStandardAssign (for = operator)
The assignmentHandler uses the Strategy pattern to delegate to specialized
handlers based on the assignment operator found in the input. Each handler
has a single, well-defined responsibility, addressing the SRP violation
in the previous monolithic handleAssignment method.
All tests pass and code quality is maintained.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This commit refactors the internal/rpn package to use the Number interface
instead of the old Value struct for stack values. Key changes:
1. Updated Number interface to include IsBool() and Bool() methods for
boolean value support
2. Modified Float and Rat types to support boolean mode with:
- isBool and boolVal fields
- Float64() returns 1 for true, 0 for false
- String() returns 'true' or 'false' for boolean values
3. Updated Stack to use []Number instead of []Value
4. Updated all operations to use the Number interface methods directly
- Add, Sub, Mul, Div, Pow, Mod now use Float64() for values
5. Updated tests to use NewNumber() with mode parameter instead of
NewNumberValue(), and use Float64() instead of Number()
Benefits:
- Simplified code - no need for toNumber() and NewNumberValue() wrappers
- Better type safety - stack values are Number interface instances
- Boolean-to-number coercion works correctly in all operations
|
|
Created new files for better separation of concerns:
- internal/rpn/mode.go: CalculationMode enum
- internal/rpn/rpn_state.go: RPN struct and state management
- internal/rpn/rpn_ops.go: Operator execution and evaluation
- internal/rpn/rpn_parse.go: Parsing and assignment handling
Original rpn.go now contains only the RPN struct and constructor.
Benefits:
- Better Single Responsibility Principle (SRP)
- Improved code organization and readability
- Easier to maintain and test individual components
|