Files
ngr/src/ir/eval.rs
2023-12-03 17:32:37 -08:00

141 lines
4.9 KiB
Rust

use super::{Primitive, Type, ValueOrRef};
use crate::eval::{EvalError, Value};
use crate::ir::{Expression, Program, TopLevel, Variable};
use crate::util::scoped_map::ScopedMap;
type IRValue<T> = Value<Expression<T>>;
type IREvalError<T> = EvalError<Expression<T>>;
impl<T: Clone + Into<Type>> Program<T> {
/// Evaluate the program, returning either an error or the result of the final
/// statement and the complete contents of the console output.
///
/// The print outs will be newline separated, with one print out per line.
pub fn eval(&self) -> Result<(IRValue<T>, String), IREvalError<T>> {
let mut env: ScopedMap<Variable, IRValue<T>> = ScopedMap::new();
let mut stdout = String::new();
let mut last_value = Value::Void;
for stmt in self.items.iter() {
match stmt {
TopLevel::Function(name, args, _, body) => {
let closure = Value::Closure(
Some(name.clone()),
env.clone(),
args.iter().map(|(x, _)| x.clone()).collect(),
body.clone(),
);
env.insert(name.clone(), closure.clone());
last_value = closure;
}
TopLevel::Statement(expr) => {
last_value = expr.eval(&env, &mut stdout)?;
}
}
}
Ok((last_value, stdout))
}
}
impl<T> Expression<T>
where
T: Clone + Into<Type>,
{
fn eval(
&self,
env: &ScopedMap<Variable, IRValue<T>>,
stdout: &mut String,
) -> Result<IRValue<T>, IREvalError<T>> {
match self {
Expression::Atomic(x) => x.eval(env),
Expression::Cast(_, t, valref) => {
let value = valref.eval(env)?;
let ty = t.clone().into();
match ty {
Type::Primitive(pt) => Ok(pt.safe_cast(&value)?),
Type::Function(_, _) => Err(EvalError::CastToFunction(ty.to_string())),
}
}
Expression::Primitive(_, _, op, args) => {
let arg_values = args
.iter()
.map(|x| x.eval(env))
.collect::<Result<Vec<IRValue<T>>, IREvalError<T>>>()?;
// and then finally we call `calculate` to run them. trust me, it's nice
// to not have to deal with all the nonsense hidden under `calculate`.
match op {
Primitive::Plus => Ok(Value::calculate("+", arg_values)?),
Primitive::Minus => Ok(Value::calculate("-", arg_values)?),
Primitive::Times => Ok(Value::calculate("*", arg_values)?),
Primitive::Divide => Ok(Value::calculate("/", arg_values)?),
}
}
Expression::Block(_, _, _) => {
unimplemented!()
}
Expression::Print(loc, n) => {
let value = env
.get(n)
.cloned()
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.to_string()))?;
stdout.push_str(&format!("{} = {}\n", n, value));
Ok(Value::Void)
}
Expression::Bind(_, _, _, _) => unimplemented!(),
}
}
}
impl<T: Clone> ValueOrRef<T> {
fn eval(
&self,
env: &ScopedMap<Variable, IRValue<T>>,
) -> Result<IRValue<T>, IREvalError<T>> {
match self {
ValueOrRef::Value(_, _, v) => match v {
super::Value::I8(_, v) => Ok(Value::I8(*v)),
super::Value::I16(_, v) => Ok(Value::I16(*v)),
super::Value::I32(_, v) => Ok(Value::I32(*v)),
super::Value::I64(_, v) => Ok(Value::I64(*v)),
super::Value::U8(_, v) => Ok(Value::U8(*v)),
super::Value::U16(_, v) => Ok(Value::U16(*v)),
super::Value::U32(_, v) => Ok(Value::U32(*v)),
super::Value::U64(_, v) => Ok(Value::U64(*v)),
},
ValueOrRef::Ref(loc, _, n) => env
.get(n)
.cloned()
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.to_string())),
}
}
}
#[test]
fn two_plus_three() {
let input = crate::syntax::Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
let ir = input.type_infer().expect("test should be type-valid");
let (_, output) = ir.eval().expect("runs successfully");
assert_eq!("x = 5u64\n", &output);
}
#[test]
fn lotsa_math() {
let input =
crate::syntax::Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works");
let ir = input.type_infer().expect("test should be type-valid");
let (_, output) = ir.eval().expect("runs successfully");
assert_eq!("x = 7u64\n", &output);
}