🧪 Add evaluation tests to ensure that passes retain NGR semantics. (#2)

This change adds `Arbitrary` instances to the key IR data types (both as
syntax and as native IR), as well as evaluator functions for both. This
way, we can ensure that the evaluation of one version of the IR is
observationally equivalent to another version of the IR, or even a later
IR. It also adds a similar ability through both static file compilation
and the JIT, to ensure that the translation through Cranelift and our
runtime works as expected.

This actually found a couple issues in its creation, and I hope is
helpful extensions into more interesting programs.
This commit is contained in:
2023-04-16 16:07:45 -07:00
parent 99fb12910f
commit f4594bf2cc
20 changed files with 610 additions and 22 deletions

64
src/syntax/eval.rs Normal file
View File

@@ -0,0 +1,64 @@
use internment::ArcIntern;
use crate::eval::{EvalEnvironment, EvalError, Value};
use crate::syntax::{Expression, Program, Statement};
impl Program {
pub fn eval(&self) -> Result<String, EvalError> {
let mut env = EvalEnvironment::empty();
let mut stdout = String::new();
for stmt in self.statements.iter() {
match stmt {
Statement::Binding(_, name, value) => {
let actual_value = value.eval(&env)?;
env = env.extend(ArcIntern::new(name.clone()), actual_value);
}
Statement::Print(_, name) => {
let value = env.lookup(ArcIntern::new(name.clone()))?;
let line = format!("{} = {}\n", name, value);
stdout.push_str(&line);
}
}
}
Ok(stdout)
}
}
impl Expression {
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
match self {
Expression::Value(_, v) => match v {
super::Value::Number(_, v) => Ok(Value::I64(*v)),
},
Expression::Reference(_, n) => Ok(env.lookup(ArcIntern::new(n.clone()))?),
Expression::Primitive(_, op, args) => {
let mut arg_values = Vec::with_capacity(args.len());
for arg in args.iter() {
arg_values.push(arg.eval(env)?);
}
Ok(Value::calculate(op, arg_values)?)
}
}
}
}
#[test]
fn two_plus_three() {
let input = Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
let output = input.eval().expect("runs successfully");
assert_eq!("x = 5i64\n", &output);
}
#[test]
fn lotsa_math() {
let input = Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works");
let output = input.eval().expect("runs successfully");
assert_eq!("x = 7i64\n", &output);
}