diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-22 13:01:03 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-22 13:01:03 +0300 |
| commit | efbc1bc8a73434a01b1385d3ccd1449b44ffaa14 (patch) | |
| tree | f85cbb78f7b59fc7668be75e52d75f392f4ed16a | |
| parent | 508a78b08b640e33a714ceb73daad14cd9d31147 (diff) | |
| parent | d0cb0dfad408553f4e90e12ea4b235b912885dd1 (diff) | |
Merge branch 'develop': deduplicate metric helper functions
Task ie: Generalized validateCategories and resultMetricForAdd in
operations_metric.go to handle both binary and N-ary cases. Binary
helpers delegate to N-ary versions. Removed duplicated helpers from
operations_hyper.go. Net: -25 lines, zero behavioral change.
| -rw-r--r-- | internal/rpn/operations_hyper.go | 48 | ||||
| -rw-r--r-- | internal/rpn/operations_metric.go | 65 |
2 files changed, 44 insertions, 69 deletions
diff --git a/internal/rpn/operations_hyper.go b/internal/rpn/operations_hyper.go index cd4e546..c6c4beb 100644 --- a/internal/rpn/operations_hyper.go +++ b/internal/rpn/operations_hyper.go @@ -26,12 +26,12 @@ func (o *Operations) HyperAdd(stack *Stack) error { } // Validate all are compatible (all same category, or Cool absorbs) - if err := validateSameCategory(metrics, "[+]"); err != nil { + if err := validateCategories(metrics, "[+]"); err != nil { return err } // Result metric: first non-Cool metric (Cool absorbs), or Cool - resultMetric := resultMetricForHyperAdd(metrics) + resultMetric := resultMetricForAdd(metrics) // Convert all to base units, sum, convert back pm := o.GetPrefixMode() @@ -92,12 +92,12 @@ func (o *Operations) HyperSubtract(stack *Stack) error { } // Validate all are compatible (all same category, or Cool absorbs) - if err := validateSameCategory(metrics, "[-]"); err != nil { + if err := validateCategories(metrics, "[-]"); err != nil { return err } // Result metric: first non-Cool metric (Cool absorbs), or Cool - resultMetric := resultMetricForHyperAdd(metrics) + resultMetric := resultMetricForAdd(metrics) // Convert all to base units, subtract, convert back pm := o.GetPrefixMode() @@ -191,12 +191,12 @@ func (o *Operations) HyperModulo(stack *Stack) error { } // Validate all are compatible (all same category, or Cool absorbs) - if err := validateSameCategory(metrics, "[%]"); err != nil { + if err := validateCategories(metrics, "[%]"); err != nil { return err } // Result metric: first non-Cool metric (Cool absorbs), or Cool - resultMetric := resultMetricForHyperAdd(metrics) + resultMetric := resultMetricForAdd(metrics) // Convert all to base units, compute modulo, convert back pm := o.GetPrefixMode() @@ -297,40 +297,4 @@ func (o *Operations) HyperLn(stack *Stack) error { return nil } -// resultMetricForHyperAdd finds the appropriate result metric for add/subtract/modulo hyper operations. -// When Cool absorbs a non-Cool category, use the first non-Cool metric. -// When all are Cool, use Cool. -func resultMetricForHyperAdd(metrics []*Metric) *Metric { - for _, m := range metrics { - if m != nil && m.Category != Universal { - return m - } - } - // All Universal (Cool) or empty slice — default to Cool. - // In practice, metrics is never empty (popAll enforces >= 2 operands), - // but we handle it defensively. - if len(metrics) > 0 && metrics[0] != nil { - return metrics[0] - } - m, _ := GetMetricRegistry().Find("Cool") - return m -} -// validateSameCategory checks that all metrics belong to the same category. -// Cool (Universal) absorbs — it is compatible with any single non-Universal category. -// Returns an error if metrics span multiple non-Universal categories. -func validateSameCategory(metrics []*Metric, opName string) error { - var dominantCat Category = Universal - for _, m := range metrics { - if m == nil || m.Category == Universal { - continue - } - if dominantCat == Universal { - dominantCat = m.Category - } else if m.Category != dominantCat { - return fmt.Errorf("%s: incompatible metrics: mixed %s and %s categories", - opName, dominantCat, m.Category) - } - } - return nil -} diff --git a/internal/rpn/operations_metric.go b/internal/rpn/operations_metric.go index c5f60d2..c3e8326 100644 --- a/internal/rpn/operations_metric.go +++ b/internal/rpn/operations_metric.go @@ -14,39 +14,50 @@ func resolveMetric(reg *MetricRegistry, n Number) *Metric { return m } -// categoriesCompatible checks if two metrics are compatible for arithmetic. -// Cool (Universal) is compatible with anything. Same category is compatible. -func categoriesCompatible(a, b *Metric) bool { - if a == nil || b == nil { - return true +// validateCategories checks that all metrics belong to the same category. +// Cool (Universal) absorbs — it is compatible with any single non-Universal category. +// Returns nil if compatible, error if metrics span multiple non-Universal categories. +// Works for both binary (2 metrics) and N-ary (slice) cases. +func validateCategories(metrics []*Metric, opName string) error { + var dominantCat Category = Universal + for _, m := range metrics { + if m == nil || m.Category == Universal { + continue + } + if dominantCat == Universal { + dominantCat = m.Category + } else if m.Category != dominantCat { + return fmt.Errorf("%s: incompatible metrics: mixed %s and %s categories", + opName, dominantCat, m.Category) + } } - if a.Category == Universal || b.Category == Universal { - return true + return nil +} + +// resultMetricForAdd finds the appropriate result metric for add/subtract/modulo operations. +// When Cool absorbs a non-Cool category, use the first non-Cool metric. +// When all are Cool, use Cool. +func resultMetricForAdd(metrics []*Metric) *Metric { + for _, m := range metrics { + if m != nil && m.Category != Universal { + return m + } + } + if len(metrics) > 0 && metrics[0] != nil { + return metrics[0] } - return a.Category == b.Category + m, _ := GetMetricRegistry().Find("Cool") + return m +} + +// categoriesCompatible checks if two metrics are compatible for arithmetic. +func categoriesCompatible(a, b *Metric) bool { + return validateCategories([]*Metric{a, b}, "") == nil } // compatibleMetric returns the resulting metric for + and - operations. -// Cool absorbs: if either is Cool, result is the other's metric (or Cool if both). -// Same category: result uses left operand's metric. func compatibleMetric(reg *MetricRegistry, a, b *Metric) *Metric { - if a == nil { - a = coolMetric(reg) - } - if b == nil { - b = coolMetric(reg) - } - if a.Category == Universal && b.Category == Universal { - return a // Cool - } - if a.Category == Universal { - return b - } - if b.Category == Universal { - return a - } - // Same category: use left operand's metric - return a + return resultMetricForAdd([]*Metric{a, b}) } // convertToBase converts a Number's value to its metric's base unit. |
