summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-20 22:31:16 +0200
committerPaul Buetow <paul@buetow.org>2026-03-20 22:31:16 +0200
commit7df37824bb6408fb137eb2eb22bc93563ed37535 (patch)
treeb9257c5e25bb2a5ea9ddf5428c67d6b61737d71d /internal
parent015d238aa65d5b1ac087e13d3a07d262bc9790d1 (diff)
internal/rpn: split ParseAndEvaluate into smaller helper functions
- Extracted handleAssignment method to handle assignment formats - ParseAndEvaluate now delegates to handleAssignment and evaluate - This reduces function size and improves readability
Diffstat (limited to 'internal')
-rw-r--r--internal/rpn/rpn.go119
1 files changed, 63 insertions, 56 deletions
diff --git a/internal/rpn/rpn.go b/internal/rpn/rpn.go
index 348da96..5fd1d39 100644
--- a/internal/rpn/rpn.go
+++ b/internal/rpn/rpn.go
@@ -27,81 +27,88 @@ func NewRPN(vars VariableStore) *RPN {
// ParseAndEvaluate parses and evaluates an RPN expression.
// Returns the result as a formatted string or an error.
func (r *RPN) ParseAndEvaluate(input string) (string, error) {
+ // Validate input and initialize
input = strings.TrimSpace(input)
if input == "" {
return "", fmt.Errorf("empty expression")
}
-
- // Initialize stack if nil (first use)
if r.currentStack == nil {
r.currentStack = NewStack()
}
- // Handle single assignment: "name value ="
- // This is when the entire input is just an assignment
- if strings.Contains(input, " = ") {
- parts := strings.SplitN(input, " = ", 2)
- if len(parts) == 2 {
- name := strings.TrimSpace(parts[0])
- valueStr := strings.TrimSpace(parts[1])
- // Validate name is a single word (variable name)
- nameFields := strings.Fields(name)
- if len(nameFields) == 1 {
- // Validate value is a single number
- valueFields := strings.Fields(valueStr)
- if len(valueFields) == 1 {
- val, err := strconv.ParseFloat(valueFields[0], 64)
- if err != nil {
- return "", fmt.Errorf("invalid value '%s' for assignment: %w", valueFields[0], err)
- }
- if err := r.vars.SetVariable(nameFields[0], val); err != nil {
- return "", err
- }
- return fmt.Sprintf("%s = %.10g", nameFields[0], val), nil
+ // Handle assignment formats
+ if assignmentResult, isAssignment, err := r.handleAssignment(input); err != nil {
+ return "", err
+ } else if isAssignment {
+ return assignmentResult, nil
+ }
+
+ // Evaluate standard RPN expression
+ tokens := Tokenize(input)
+ if len(tokens) == 0 {
+ return "", fmt.Errorf("no valid tokens found")
+ }
+
+ return r.evaluate(tokens)
+}
+
+// handleAssignment checks if the input is an assignment format and handles it.
+// Returns (result string, isAssignment bool, error error).
+func (r *RPN) handleAssignment(input string) (string, bool, error) {
+ if !strings.Contains(input, " = ") {
+ return "", false, nil
+ }
+
+ // Handle single assignment: "name = value"
+ if parts := strings.SplitN(input, " = ", 2); len(parts) == 2 {
+ name := strings.TrimSpace(parts[0])
+ valueStr := strings.TrimSpace(parts[1])
+
+ // Validate name is a single word (variable name)
+ nameFields := strings.Fields(name)
+ if len(nameFields) == 1 {
+ // Validate value is a single number
+ valueFields := strings.Fields(valueStr)
+ if len(valueFields) == 1 {
+ val, err := strconv.ParseFloat(valueFields[0], 64)
+ if err != nil {
+ return "", false, fmt.Errorf("invalid value '%s' for assignment: %w", valueFields[0], err)
+ }
+ if err := r.vars.SetVariable(nameFields[0], val); err != nil {
+ return "", false, err
}
+ return fmt.Sprintf("%s = %.10g", nameFields[0], val), true, nil
}
}
}
// Handle assignment with expression: "name value = expression..."
- // Format: variable_name value = expression (where = comes after value)
- if strings.Contains(input, " = ") {
- // Check if the input matches pattern: "name value = expr..."
- // where name and value are single tokens, and = comes after value
- // For example: "x 5 = x x +" or "pi 3.14 = pi 2 *"
-
- // Find " = " position and split
- pos := strings.Index(input, " = ")
- if pos >= 0 {
- before := input[:pos] // "name value"
- after := input[pos+3:] // "expr..."
-
- beforeFields := strings.Fields(before)
- if len(beforeFields) == 2 {
- name := beforeFields[0]
- valueStr := beforeFields[1]
-
- // Try to parse value as a number
- val, err := strconv.ParseFloat(valueStr, 64)
- if err == nil {
- // Valid assignment pattern: "name value = expr..."
- if err := r.vars.SetVariable(name, val); err != nil {
- return "", err
- }
-
- // Evaluate the remaining expression
- return r.evaluate(strings.Fields(strings.TrimSpace(after)))
+ pos := strings.Index(input, " = ")
+ if pos >= 0 {
+ before := input[:pos] // "name value"
+ after := input[pos+3:] // "expr..."
+
+ beforeFields := strings.Fields(before)
+ if len(beforeFields) == 2 {
+ name := beforeFields[0]
+ valueStr := beforeFields[1]
+
+ // Try to parse value as a number
+ val, err := strconv.ParseFloat(valueStr, 64)
+ if err == nil {
+ // Valid assignment pattern: "name value = expr..."
+ if err := r.vars.SetVariable(name, val); err != nil {
+ return "", false, err
}
+
+ // Evaluate the remaining expression
+ result, err := r.evaluate(strings.Fields(strings.TrimSpace(after)))
+ return result, true, err
}
}
}
- tokens := Tokenize(input)
- if len(tokens) == 0 {
- return "", fmt.Errorf("no valid tokens found")
- }
-
- return r.evaluate(tokens)
+ return "", false, nil
}
// ResultStack returns the final stack state after evaluation.