#![allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::float_cmp,
clippy::if_not_else
)]
use super::bytecode::{Op, Program};
use crate::location::state::LocationView;
use smallvec::SmallVec;
#[must_use]
pub fn run(prog: &Program, view: &LocationView<'_>) -> f32 {
let mut st: SmallVec<[f32; 16]> = SmallVec::with_capacity(prog.max_stack);
for op in &prog.ops {
match *op {
Op::PushF32(v) => st.push(v),
Op::LoadI64 { offset } => st.push(view.read_i64(offset as usize) as f32),
Op::LoadF32 { offset } => st.push(view.read_f32(offset as usize)),
Op::LoadF64 { offset } => st.push(view.read_f64(offset as usize) as f32),
Op::LoadU32 { offset } => {
#[allow(clippy::cast_precision_loss)]
{
st.push(view.read_u32(offset as usize) as f32);
}
}
Op::Neg => {
let a = pop(&mut st);
st.push(-a);
}
Op::Not => {
let a = pop(&mut st);
st.push(if a == 0.0 { 1.0 } else { 0.0 });
}
Op::Add => binop(&mut st, |a, b| a + b),
Op::Sub => binop(&mut st, |a, b| a - b),
Op::Mul => binop(&mut st, |a, b| a * b),
Op::Div => binop(&mut st, |a, b| if b == 0.0 { 0.0 } else { a / b }),
Op::Lt => binop(&mut st, |a, b| if a < b { 1.0 } else { 0.0 }),
Op::Le => binop(&mut st, |a, b| if a <= b { 1.0 } else { 0.0 }),
Op::Gt => binop(&mut st, |a, b| if a > b { 1.0 } else { 0.0 }),
Op::Ge => binop(&mut st, |a, b| if a >= b { 1.0 } else { 0.0 }),
Op::Eq => binop(&mut st, |a, b| if a == b { 1.0 } else { 0.0 }),
Op::Ne => binop(&mut st, |a, b| if a != b { 1.0 } else { 0.0 }),
Op::And => binop(&mut st, |a, b| if a != 0.0 && b != 0.0 { 1.0 } else { 0.0 }),
Op::Or => binop(&mut st, |a, b| if a != 0.0 || b != 0.0 { 1.0 } else { 0.0 }),
Op::MinA => binop(&mut st, f32::min),
Op::MaxA => binop(&mut st, f32::max),
Op::Abs => {
let a = pop(&mut st);
st.push(a.abs());
}
}
}
st.last().copied().unwrap_or(0.0)
}
#[inline(always)]
#[allow(clippy::inline_always)]
fn pop(st: &mut SmallVec<[f32; 16]>) -> f32 {
st.pop().unwrap_or(0.0)
}
#[inline(always)]
#[allow(clippy::inline_always)]
fn binop<F: Fn(f32, f32) -> f32>(st: &mut SmallVec<[f32; 16]>, f: F) {
let b = pop(st);
let a = pop(st);
st.push(f(a, b));
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::super::{parser::Parser, typecheck::compile};
use super::*;
use crate::event::{AttrSet, KindRef};
use crate::location::state::LocationState;
use crate::schema::attr::Value;
use crate::schema::{AttrType, SchemaBuilder};
use smallvec::smallvec;
#[allow(clippy::unwrap_used)]
fn setup(expr: &str, male: f32, dwell: i64) -> (LocationState<()>, Program) {
let mut b = SchemaBuilder::new();
let _ = b.kind(
"audience",
&[("male_frac", AttrType::F32), ("dwell", AttrType::Int)],
);
let schema = b.build();
let mut st: LocationState<()> = LocationState::new(schema.clone());
let kid = schema.kind("audience").unwrap();
let attrs = AttrSet {
entries: smallvec![
(schema.attr("male_frac").unwrap(), Value::F32(male)),
(schema.attr("dwell").unwrap(), Value::Int(dwell)),
],
};
st.apply_update(KindRef::Id(kid), &attrs);
let ast = Parser::new(expr).parse().unwrap();
let prog = compile(&ast, &schema).unwrap();
(st, prog)
}
#[test]
fn arithmetic() {
let (st, p) = setup("$audience.male_frac + 1.0", 0.25, 0);
assert!((run(&p, &st.view()) - 1.25).abs() < 1e-6);
}
#[test]
fn comparison_returns_boolean_as_f32() {
let (st, p) = setup("$audience.male_frac > 0.5", 0.7, 0);
assert_eq!(run(&p, &st.view()), 1.0);
let (st, p) = setup("$audience.male_frac > 0.5", 0.3, 0);
assert_eq!(run(&p, &st.view()), 0.0);
}
#[test]
fn logical_short_operands() {
let (st, p) = setup("$audience.male_frac > 0.5 && $audience.dwell > 10", 0.7, 12);
assert_eq!(run(&p, &st.view()), 1.0);
}
#[test]
fn division_by_zero_is_zero_not_panic() {
let (st, p) = setup("1.0 / ($audience.male_frac - $audience.male_frac)", 0.0, 0);
assert_eq!(run(&p, &st.view()), 0.0);
}
}