summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/rpn/operations_arithmetic.go65
-rw-r--r--internal/rpn/operations_compare.go10
-rw-r--r--internal/rpn/operations_hyper.go53
-rw-r--r--internal/rpn/operations_metric.go53
-rw-r--r--internal/rpn/operations_metric_cmd.go10
5 files changed, 143 insertions, 48 deletions
diff --git a/internal/rpn/operations_arithmetic.go b/internal/rpn/operations_arithmetic.go
index ca32721..d0b0798 100644
--- a/internal/rpn/operations_arithmetic.go
+++ b/internal/rpn/operations_arithmetic.go
@@ -22,15 +22,21 @@ func (o *Operations) binaryMetricOp(
op string,
compatCheck func(*Metric, *Metric) error,
compute func(float64, float64) float64,
- resultMetricFn func(*MetricRegistry, *Metric, *Metric) *Metric,
+ resultMetricFn func(*MetricRegistry, *Metric, *Metric) (*Metric, error),
) error {
a, b, err := popTwo(stack, op)
if err != nil {
return err
}
- aM := resolveMetric(o.metricRegistry, a)
- bM := resolveMetric(o.metricRegistry, b)
+ aM, err := resolveMetric(o.metricRegistry, a)
+ if err != nil {
+ return buildError(op, err)
+ }
+ bM, err := resolveMetric(o.metricRegistry, b)
+ if err != nil {
+ return buildError(op, err)
+ }
if compatCheck != nil {
if err := compatCheck(aM, bM); err != nil {
return err
@@ -38,7 +44,10 @@ func (o *Operations) binaryMetricOp(
}
pm := o.GetPrefixMode()
- resultMetric := resultMetricFn(o.metricRegistry, aM, bM)
+ resultMetric, err := resultMetricFn(o.metricRegistry, aM, bM)
+ if err != nil {
+ return buildError(op, err)
+ }
aBase, err := convertToBase(o.metricRegistry, a, pm, resultMetric)
if err != nil {
return buildError(op, err)
@@ -47,7 +56,10 @@ func (o *Operations) binaryMetricOp(
if err != nil {
return buildError(op, err)
}
- resultVal := convertFromBase(o.metricRegistry, compute(aBase, bBase), resultMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, compute(aBase, bBase), resultMetric, pm)
+ if err != nil {
+ return buildError(op, err)
+ }
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), resultMetric))
return nil
@@ -64,7 +76,9 @@ func (o *Operations) Add(stack *Stack) error {
return nil
},
func(a, b float64) float64 { return a + b },
- compatibleMetric,
+ func(reg *MetricRegistry, aM, bM *Metric) (*Metric, error) {
+ return compatibleMetric(reg, aM, bM), nil
+ },
)
}
@@ -79,7 +93,9 @@ func (o *Operations) Subtract(stack *Stack) error {
return nil
},
func(a, b float64) float64 { return a - b },
- compatibleMetric,
+ func(reg *MetricRegistry, aM, bM *Metric) (*Metric, error) {
+ return compatibleMetric(reg, aM, bM), nil
+ },
)
}
@@ -108,11 +124,20 @@ func (o *Operations) Divide(stack *Stack) error {
return buildError("/", fmt.Errorf("division by zero"))
}
- aM := resolveMetric(o.metricRegistry, a)
- bM := resolveMetric(o.metricRegistry, b)
+ aM, err := resolveMetric(o.metricRegistry, a)
+ if err != nil {
+ return buildError("/", err)
+ }
+ bM, err := resolveMetric(o.metricRegistry, b)
+ if err != nil {
+ return buildError("/", err)
+ }
pm := o.GetPrefixMode()
- resultMetric := resultMetricForDiv(o.metricRegistry, aM, bM)
+ resultMetric, err := resultMetricForDiv(o.metricRegistry, aM, bM)
+ if err != nil {
+ return buildError("/", err)
+ }
aBase, err := convertToBase(o.metricRegistry, a, pm, resultMetric)
if err != nil {
return buildError("/", err)
@@ -121,7 +146,10 @@ func (o *Operations) Divide(stack *Stack) error {
if err != nil {
return buildError("/", err)
}
- resultVal := convertFromBase(o.metricRegistry, aBase/bBase, resultMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, aBase/bBase, resultMetric, pm)
+ if err != nil {
+ return buildError("/", err)
+ }
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), resultMetric))
return nil
@@ -172,8 +200,14 @@ func (o *Operations) Modulo(stack *Stack) error {
return buildError("%", fmt.Errorf("modulo by zero"))
}
- aM := resolveMetric(o.metricRegistry, a)
- bM := resolveMetric(o.metricRegistry, b)
+ aM, err := resolveMetric(o.metricRegistry, a)
+ if err != nil {
+ return buildError("%", err)
+ }
+ bM, err := resolveMetric(o.metricRegistry, b)
+ if err != nil {
+ return buildError("%", err)
+ }
if !categoriesCompatible(aM, bM) {
return metricError("%", aM, bM)
}
@@ -188,7 +222,10 @@ func (o *Operations) Modulo(stack *Stack) error {
if err != nil {
return buildError("%", err)
}
- resultVal := convertFromBase(o.metricRegistry, math.Mod(aBase, bBase), resultMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, math.Mod(aBase, bBase), resultMetric, pm)
+ if err != nil {
+ return buildError("%", err)
+ }
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), resultMetric))
return nil
diff --git a/internal/rpn/operations_compare.go b/internal/rpn/operations_compare.go
index 81192e9..b21a00c 100644
--- a/internal/rpn/operations_compare.go
+++ b/internal/rpn/operations_compare.go
@@ -13,8 +13,14 @@ func compareValues(o *Operations, stack *Stack, op string, cmp func(float64, flo
return err
}
- aM := resolveMetric(o.metricRegistry, a)
- bM := resolveMetric(o.metricRegistry, b)
+ aM, err := resolveMetric(o.metricRegistry, a)
+ if err != nil {
+ return buildError(op, err)
+ }
+ bM, err := resolveMetric(o.metricRegistry, b)
+ if err != nil {
+ return buildError(op, err)
+ }
if !categoriesCompatible(aM, bM) {
return metricError(op, aM, bM)
}
diff --git a/internal/rpn/operations_hyper.go b/internal/rpn/operations_hyper.go
index 19c9398..34b520b 100644
--- a/internal/rpn/operations_hyper.go
+++ b/internal/rpn/operations_hyper.go
@@ -23,7 +23,11 @@ func (o *Operations) HyperAdd(stack *Stack) error {
// Resolve metrics for all values
metrics := make([]*Metric, len(values))
for i, v := range values {
- metrics[i] = resolveMetric(o.metricRegistry, v)
+ m, err := resolveMetric(o.metricRegistry, v)
+ if err != nil {
+ return buildError("[+]", err)
+ }
+ metrics[i] = m
}
// Validate all are compatible (all same category, or Cool absorbs)
@@ -45,7 +49,10 @@ func (o *Operations) HyperAdd(stack *Stack) error {
sum += base
}
- resultVal := convertFromBase(o.metricRegistry, sum, resultMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, sum, resultMetric, pm)
+ if err != nil {
+ return buildError("[+]", err)
+ }
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), resultMetric))
return nil
@@ -72,7 +79,10 @@ func (o *Operations) HyperMultiply(stack *Stack) error {
}
}
- cool := coolMetric(o.metricRegistry)
+ cool, err := coolMetric(o.metricRegistry)
+ if err != nil {
+ return buildError("[*]", err)
+ }
stack.Push(NewNumberWithMetric(product, o.GetMode(), cool))
return nil
}
@@ -89,7 +99,11 @@ func (o *Operations) HyperSubtract(stack *Stack) error {
// Resolve metrics for all values
metrics := make([]*Metric, len(values))
for i, v := range values {
- metrics[i] = resolveMetric(o.metricRegistry, v)
+ m, err := resolveMetric(o.metricRegistry, v)
+ if err != nil {
+ return buildError("[-]", err)
+ }
+ metrics[i] = m
}
// Validate all are compatible (all same category, or Cool absorbs)
@@ -115,7 +129,10 @@ func (o *Operations) HyperSubtract(stack *Stack) error {
result -= base
}
- resultVal := convertFromBase(o.metricRegistry, result, resultMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, result, resultMetric, pm)
+ if err != nil {
+ return buildError("[-]", err)
+ }
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), resultMetric))
return nil
@@ -145,7 +162,10 @@ func (o *Operations) HyperDivide(stack *Stack) error {
result /= val
}
- cool := coolMetric(o.metricRegistry)
+ cool, err := coolMetric(o.metricRegistry)
+ if err != nil {
+ return buildError("[/]", err)
+ }
stack.Push(NewNumberWithMetric(result, o.GetMode(), cool))
return nil
}
@@ -171,7 +191,10 @@ func (o *Operations) HyperPower(stack *Stack) error {
result = math.Pow(result, val)
}
- cool := coolMetric(o.metricRegistry)
+ cool, err := coolMetric(o.metricRegistry)
+ if err != nil {
+ return buildError("[^]", err)
+ }
stack.Push(NewNumberWithMetric(result, o.GetMode(), cool))
return nil
}
@@ -188,7 +211,11 @@ func (o *Operations) HyperModulo(stack *Stack) error {
// Resolve metrics for all values
metrics := make([]*Metric, len(values))
for i, v := range values {
- metrics[i] = resolveMetric(o.metricRegistry, v)
+ m, err := resolveMetric(o.metricRegistry, v)
+ if err != nil {
+ return buildError("[%]", err)
+ }
+ metrics[i] = m
}
// Validate all are compatible (all same category, or Cool absorbs)
@@ -217,7 +244,10 @@ func (o *Operations) HyperModulo(stack *Stack) error {
result = math.Mod(result, base)
}
- resultVal := convertFromBase(o.metricRegistry, result, resultMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, result, resultMetric, pm)
+ if err != nil {
+ return buildError("[%]", err)
+ }
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), resultMetric))
return nil
@@ -243,7 +273,10 @@ func (o *Operations) hyperLog(stack *Stack, opName string, logFn func(float64) f
result += logFn(val)
}
- cool := coolMetric(o.metricRegistry)
+ cool, err := coolMetric(o.metricRegistry)
+ if err != nil {
+ return buildError(opName, err)
+ }
stack.Push(NewNumberWithMetric(result, o.GetMode(), cool))
return nil
}
diff --git a/internal/rpn/operations_metric.go b/internal/rpn/operations_metric.go
index 4809ce1..decaad8 100644
--- a/internal/rpn/operations_metric.go
+++ b/internal/rpn/operations_metric.go
@@ -6,16 +6,16 @@ package rpn
import "fmt"
// resolveMetric returns the metric for a StackValue, defaulting to Cool if nil.
-func resolveMetric(reg *MetricRegistry, n StackValue) *Metric {
+func resolveMetric(reg *MetricRegistry, n StackValue) (*Metric, error) {
m := n.Metric()
if m == nil {
var ok bool
m, ok = reg.Find("Cool")
if !ok {
- panic("metric registry missing Cool metric")
+ return nil, fmt.Errorf("metric registry missing Cool metric")
}
}
- return m
+ return m, nil
}
// validateCategories checks that all metrics belong to the same category.
@@ -78,7 +78,10 @@ func convertToBase(reg *MetricRegistry, n StackValue, mode PrefixMode, resultMet
if !ok {
return 0, fmt.Errorf("convertToBase: value %q is not numeric", n)
}
- m := resolveMetric(reg, n)
+ m, err := resolveMetric(reg, n)
+ if err != nil {
+ return 0, err
+ }
val, err := nv.Float64()
if err != nil {
return 0, fmt.Errorf("convertToBase: %w", err)
@@ -92,23 +95,27 @@ func convertToBase(reg *MetricRegistry, n StackValue, mode PrefixMode, resultMet
}
// convertFromBase converts a base-unit value back to the given metric.
-func convertFromBase(reg *MetricRegistry, baseVal float64, m *Metric, mode PrefixMode) float64 {
+func convertFromBase(reg *MetricRegistry, baseVal float64, m *Metric, mode PrefixMode) (float64, error) {
if m == nil {
- m = baseMetric(reg, "Cool")
+ var err error
+ m, err = baseMetric(reg, "Cool")
+ if err != nil {
+ return 0, err
+ }
}
- return baseVal / m.Factor(mode)
+ return baseVal / m.Factor(mode), nil
}
// resultMetricForMul computes the resulting metric for multiplication.
-func resultMetricForMul(reg *MetricRegistry, a, b *Metric) *Metric {
+func resultMetricForMul(reg *MetricRegistry, a, b *Metric) (*Metric, error) {
if a == nil || a.Category == Universal {
if b == nil {
return baseMetric(reg, "Cool")
}
- return b
+ return b, nil
}
if b == nil || b.Category == Universal {
- return a
+ return a, nil
}
// Cross-category inference
@@ -127,7 +134,7 @@ func resultMetricForMul(reg *MetricRegistry, a, b *Metric) *Metric {
}
// resultMetricForDiv computes the resulting metric for division.
-func resultMetricForDiv(reg *MetricRegistry, a, b *Metric) *Metric {
+func resultMetricForDiv(reg *MetricRegistry, a, b *Metric) (*Metric, error) {
if a == nil && b == nil {
return baseMetric(reg, "Cool")
}
@@ -135,7 +142,7 @@ func resultMetricForDiv(reg *MetricRegistry, a, b *Metric) *Metric {
if a == nil {
return baseMetric(reg, "Cool")
}
- return a
+ return a, nil
}
// When dividend is Cool (unitless) and divisor has a metric,
// result should be Cool. E.g., 5 / 10km → 0.5 (Cool, not km).
@@ -170,21 +177,21 @@ func metricError(op string, a, b *Metric) error {
}
// coolMetric returns the Cool metric from the registry.
-func coolMetric(reg *MetricRegistry) *Metric {
+func coolMetric(reg *MetricRegistry) (*Metric, error) {
m, ok := reg.Find("Cool")
if !ok {
- panic("metric registry missing Cool metric")
+ return nil, fmt.Errorf("metric registry missing Cool metric")
}
- return m
+ return m, nil
}
// baseMetric looks up a base metric from the registry.
-func baseMetric(reg *MetricRegistry, name string) *Metric {
+func baseMetric(reg *MetricRegistry, name string) (*Metric, error) {
m, ok := reg.Find(name)
if !ok {
- panic(fmt.Sprintf("metric registry missing base unit %q", name))
+ return nil, fmt.Errorf("metric registry missing base unit %q", name)
}
- return m
+ return m, nil
}
// Convert converts a value from its current metric to a target metric.
@@ -204,7 +211,10 @@ func (o *Operations) Convert(stack *Stack) error {
}
// 3. Get metrics
targetMetric := target.Metric()
- valueMetric := resolveMetric(o.metricRegistry, value)
+ valueMetric, err := resolveMetric(o.metricRegistry, value)
+ if err != nil {
+ return buildError("convert", err)
+ }
// 4. Validate same category (or Cool absorbing)
if !categoriesCompatible(valueMetric, targetMetric) {
return metricError("convert", valueMetric, targetMetric)
@@ -215,7 +225,10 @@ func (o *Operations) Convert(stack *Stack) error {
if err != nil {
return buildError("convert", err)
}
- resultVal := convertFromBase(o.metricRegistry, baseVal, targetMetric, pm)
+ resultVal, err := convertFromBase(o.metricRegistry, baseVal, targetMetric, pm)
+ if err != nil {
+ return buildError("convert", err)
+ }
// 6. Push result with target metric
stack.Push(NewNumberWithMetric(resultVal, o.GetMode(), targetMetric))
return nil
diff --git a/internal/rpn/operations_metric_cmd.go b/internal/rpn/operations_metric_cmd.go
index 31a2d2f..8e0e519 100644
--- a/internal/rpn/operations_metric_cmd.go
+++ b/internal/rpn/operations_metric_cmd.go
@@ -66,8 +66,14 @@ func (o *Operations) MetricCompatible(stack *Stack) (string, error) {
vals := stack.Values()
top := vals[len(vals)-1]
second := vals[len(vals)-2]
- mA := resolveMetric(o.metricRegistry, second)
- mB := resolveMetric(o.metricRegistry, top)
+ mA, err := resolveMetric(o.metricRegistry, second)
+ if err != nil {
+ return "", buildError("metric compatible", err)
+ }
+ mB, err := resolveMetric(o.metricRegistry, top)
+ if err != nil {
+ return "", buildError("metric compatible", err)
+ }
compatible := categoriesCompatible(mA, mB)
result := fmt.Sprintf("%s (%s) and %s (%s): %v",
mA.Name, mA.Category, mB.Name, mB.Category, compatible)