fuchsia_triage/metrics/
arithmetic.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::{MathFunction, MetricValue, Problem};
6
7enum PromotedOperands {
8    Float(Vec<f64>),
9    Int(Vec<i64>),
10}
11
12// TODO(https://fxbug.dev/42134879): More informative error messages as part of structured errors.
13
14pub fn calculate(function: &MathFunction, operands: &[MetricValue]) -> MetricValue {
15    // Arity check. + and * are well-defined for 1..N arguments, but the parser will only
16    // give us 2 arguments. This check avoids panics from internal bugs.
17    match function {
18        MathFunction::Min | MathFunction::Max if !operands.is_empty() => {}
19        MathFunction::Min | MathFunction::Max => {
20            return super::syntax_error("No operands in math expression");
21        }
22        MathFunction::Abs if operands.len() == 1 => {}
23        MathFunction::Abs => return super::syntax_error("Abs requires exactly one operand."),
24        _ if operands.len() == 2 => {}
25        _ => return super::internal_bug("Internal bug. Function needs 2 arguments."),
26    }
27    let operands = match promote_type(operands) {
28        Ok(operands) => operands,
29        Err(value) => return value,
30    };
31    match operands {
32        PromotedOperands::Float(operands) => MetricValue::Float(match function {
33            MathFunction::Add => operands[0] + operands[1],
34            MathFunction::Sub => operands[0] - operands[1],
35            MathFunction::Mul => operands[0] * operands[1],
36            MathFunction::FloatDiv | MathFunction::IntDiv if operands[1] == 0.0 => {
37                return super::value_error("Division by zero")
38            }
39            MathFunction::FloatDivChecked | MathFunction::IntDivChecked if operands[1] == 0.0 => {
40                return MetricValue::Problem(Problem::Ignore(vec![Problem::ValueError(
41                    "Division by zero".to_string(),
42                )]))
43            }
44            MathFunction::FloatDiv | MathFunction::FloatDivChecked => operands[0] / operands[1],
45            MathFunction::IntDiv | MathFunction::IntDivChecked => {
46                return match super::safe_float_to_int(operands[0] / operands[1]) {
47                    Some(int) => MetricValue::Int(int),
48                    None => super::value_error("Non-numeric division result"),
49                }
50            }
51            MathFunction::Greater => return MetricValue::Bool(operands[0] > operands[1]),
52            MathFunction::Less => return MetricValue::Bool(operands[0] < operands[1]),
53            MathFunction::GreaterEq => return MetricValue::Bool(operands[0] >= operands[1]),
54            MathFunction::LessEq => return MetricValue::Bool(operands[0] <= operands[1]),
55            MathFunction::Min => fold(operands, &f64::min),
56            MathFunction::Max => fold(operands, &f64::max),
57            MathFunction::Abs => operands[0].abs(),
58        }),
59        PromotedOperands::Int(operands) => MetricValue::Int(match function {
60            MathFunction::Add => operands[0] + operands[1],
61            MathFunction::Sub => operands[0] - operands[1],
62            MathFunction::Mul => operands[0] * operands[1],
63            MathFunction::FloatDiv | MathFunction::IntDiv if operands[1] == 0 => {
64                return super::value_error("Division by zero")
65            }
66            MathFunction::FloatDivChecked | MathFunction::IntDivChecked if operands[1] == 0 => {
67                return MetricValue::Problem(Problem::Ignore(vec![Problem::ValueError(
68                    "Division by zero".to_string(),
69                )]))
70            }
71            MathFunction::FloatDiv | MathFunction::FloatDivChecked => {
72                return MetricValue::Float(operands[0] as f64 / operands[1] as f64)
73            }
74            MathFunction::IntDiv | MathFunction::IntDivChecked => operands[0] / operands[1],
75            MathFunction::Greater => return MetricValue::Bool(operands[0] > operands[1]),
76            MathFunction::Less => return MetricValue::Bool(operands[0] < operands[1]),
77            MathFunction::GreaterEq => return MetricValue::Bool(operands[0] >= operands[1]),
78            MathFunction::LessEq => return MetricValue::Bool(operands[0] <= operands[1]),
79            MathFunction::Min => fold(operands, &i64::min),
80            MathFunction::Max => fold(operands, &i64::max),
81            MathFunction::Abs => operands[0].abs(),
82        }),
83    }
84}
85
86fn fold<T: num_traits::Num + Copy>(operands: Vec<T>, function: &dyn (Fn(T, T) -> T)) -> T {
87    let mut iter = operands.iter();
88    let mut result = *iter.next().unwrap(); // Checked non-empty in calculate()
89    loop {
90        match iter.next() {
91            Some(next) => result = function(result, *next),
92            None => return result,
93        }
94    }
95}
96
97fn promote_type(operands: &[MetricValue]) -> Result<PromotedOperands, MetricValue> {
98    let mut int_vec = Vec::with_capacity(operands.len());
99    let mut float_vec = Vec::with_capacity(operands.len());
100    let mut error_vec = Vec::with_capacity(operands.len());
101    let mut non_numeric_error = None;
102    for o in operands.iter() {
103        match super::unwrap_for_math(o) {
104            MetricValue::Int(value) => {
105                int_vec.push(*value);
106                float_vec.push(*value as f64);
107            }
108            MetricValue::Float(value) => {
109                float_vec.push(*value);
110            }
111            MetricValue::Problem(problem) => {
112                error_vec.push(problem);
113            }
114            bad_type => {
115                non_numeric_error = Some(Problem::Missing(format!("{} not numeric", bad_type)));
116            }
117        }
118    }
119    if int_vec.len() == operands.len() {
120        return Ok(PromotedOperands::Int(int_vec));
121    }
122    if float_vec.len() == operands.len() {
123        return Ok(PromotedOperands::Float(float_vec));
124    }
125    if let Some(ref err) = non_numeric_error {
126        error_vec.push(err);
127    }
128    Err(MetricValue::Problem(super::MetricState::important_problem(error_vec)))
129}
130
131// Correct operation of this file is tested in parse.rs.