summaryrefslogtreecommitdiff
path: root/internal/rpn/rpn_parse.go
AgeCommit message (Collapse)Author
2026-05-24fix(rpn): extract setVariableResult helper for assignment paths (task bk)Paul Buetow
2026-05-24fix(rpn): use correct subcommand name in metric error messages (task ak)Paul Buetow
2026-05-24fix(rpn): consolidate extractVarName into rpn_parse.go (task 6k)Paul Buetow
2026-05-24fix(rpn): replace assignmentHandler strategy pattern with if/else chain ↵Paul Buetow
(task 5k)
2026-05-24fix(rpn): restrict isValidIdentifier to letters and underscore only (task 2k)Paul Buetow
2026-05-24fix(rpn): remove dead number-parse check from handleOperator (task 1k)Paul Buetow
2026-05-24refactor(rpn): split large functions in rpn_parse.go (task 6j)Paul Buetow
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
2026-05-24refactor(rpn): simplify isValidIdentifier(), removing dead multi-char loop ↵Paul Buetow
(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.
2026-05-24refactor(rpn): extract checkStackOverflow helper in pushLiteral (#3j)Paul Buetow
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.
2026-05-24rpn: remove unused stack parameter from shouldPushName (task 2j)Paul Buetow
2026-05-24rpn: eliminate duplicate ParseFloat in pushLiteralPaul Buetow
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.
2026-05-24rpn: split evaluate() into focused helpersPaul Buetow
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.
2026-05-24rpn: split handleOperator into focused helpersPaul Buetow
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.
2026-05-24rpn: remove duplicate metricRegistry, use ops.MetricRegistry()Paul Buetow
2026-05-24rpn: use token-based = detection in handleStandardAssignPaul Buetow
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.
2026-05-24rpn: replace fragile error message parsing with direct registry checkPaul Buetow
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.
2026-05-24fix(rpn): use NewRatFromString in RationalMode to preserve precisionPaul Buetow
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.
2026-05-24rpn: implement custom show to display custom metric detailsPaul Buetow
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
2026-05-24rpn: eliminate RPN.mode duplication with Operations.modePaul Buetow
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)
2026-05-24docs(rpn): fix misleading comment in handleStandardAssignPaul Buetow
- 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
2026-05-23refactor: split RPN.evaluate() into focused helper functionsPaul Buetow
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.
2026-05-23refactor: eliminate duplication between handleAssignRight and handleAssignLeftPaul Buetow
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.
2026-05-23refactor: split NewNumber into two constructors (NewNumber, NewNumberWithMetric)Paul Buetow
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.
2026-05-23refactor: reduce GetMetricRegistry() global singleton usage (DIP)Paul Buetow
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.
2026-05-23style: run gofmt on all Go source filesPaul Buetow
Fix formatting in 12 files to match gofmt standards. No logical changes.
2026-05-22refactor(rpn): split StringNum/Symbol out of Number interface (LSP fix)Paul Buetow
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.
2026-05-22feat: implement custom metric commands (define, undefine, list, show)Paul Buetow
- 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
2026-05-22rpn: eliminate duplicated prefixMode state between RPN and OperationsPaul Buetow
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.
2026-05-22doc(rpn): clarify lock ordering in metric set handlersPaul Buetow
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.
2026-05-22wire prefixMode from RPN into Operations for metric-aware arithmeticPaul Buetow
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
2026-05-22fix(rpn): clean up metric subcommands per review feedbackPaul Buetow
- 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
2026-05-22feat(rpn): implement metric subcommandsPaul Buetow
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.
2026-05-22feat(rpn): handle @-prefixed standalone metrics (@GB, @Mbps)Paul Buetow
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.
2026-05-22feat(rpn): parse suffix-notation metrics (e.g., 100Mbps, 5.5GB)Paul Buetow
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.
2026-04-11more on thisPaul Buetow
2026-04-11Add constants lookup in rpn_parse.go handleOperatorPaul Buetow
- Added constants lookup after variable lookup in rpn_parse.go - Now constants like pi, e, phi, sqrt2, inf, nan work in RPN expressions
2026-04-11Fix Float64() return values in test filesPaul Buetow
- 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
2026-04-11Refactor number.go Float64() to return errorsPaul Buetow
- 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()}
2026-03-26fix: address code quality issues from golangci-lintPaul Buetow
- 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 ':'
2026-03-26refactor: Extract RPN assignment handlers to fix SRP violationPaul Buetow
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.
2026-03-26fix: Handle boolean operators == and != correctlyPaul Buetow
2026-03-26feat: Add integration tests for variable assignments and fix RPN parser bugsPaul Buetow
2026-03-25rpn: fix x =: stack-based variable assignmentPaul Buetow
2026-03-25rpn: Fix := and =: operators semanticsPaul Buetow
2026-03-25rpn: Add := and =: assignment operators with = synonymPaul Buetow
2026-03-25code-quality: Various improvements to code quality and thread safetyPaul Buetow
2026-03-25refactor: Refactor RPN to use Number interface uniformly for stack valuesPaul Buetow
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
2026-03-25refactor: Split internal/rpn/rpn.go into separate files for better SRPPaul Buetow
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