todo: arbitrary ir
This commit is contained in:
21
src/ir/arbitrary.rs
Normal file
21
src/ir/arbitrary.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use crate::ir::{Program, TopLevel, Expression, ValueOrRef, Value, Type};
|
||||
use proptest::{
|
||||
prelude::Arbitrary,
|
||||
strategy::{BoxedStrategy, Strategy},
|
||||
};
|
||||
|
||||
impl<Type: core::fmt::Debug> Arbitrary for Program<Type> {
|
||||
type Parameters = crate::syntax::arbitrary::GenerationEnvironment;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
unimplemented!()
|
||||
//crate::syntax::Program::arbitrary_with(args)
|
||||
// .prop_map(|x| {
|
||||
// x.type_infer()
|
||||
// .expect("arbitrary_with should generate type-correct programs")
|
||||
// })
|
||||
// .boxed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@ use crate::{
|
||||
};
|
||||
use internment::ArcIntern;
|
||||
use pretty::{BoxAllocator, DocAllocator, Pretty};
|
||||
use proptest::{
|
||||
prelude::Arbitrary,
|
||||
strategy::{BoxedStrategy, Strategy},
|
||||
};
|
||||
use std::{fmt, str::FromStr, sync::atomic::AtomicUsize};
|
||||
|
||||
/// We're going to represent variables as interned strings.
|
||||
@@ -78,21 +74,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Type: core::fmt::Debug> Arbitrary for Program<Type> {
|
||||
type Parameters = crate::syntax::arbitrary::GenerationEnvironment;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
|
||||
unimplemented!()
|
||||
//crate::syntax::Program::arbitrary_with(args)
|
||||
// .prop_map(|x| {
|
||||
// x.type_infer()
|
||||
// .expect("arbitrary_with should generate type-correct programs")
|
||||
// })
|
||||
// .boxed()
|
||||
}
|
||||
}
|
||||
|
||||
/// A thing that can sit at the top level of a file.
|
||||
///
|
||||
/// For the moment, these are statements and functions. Other things
|
||||
@@ -144,7 +125,7 @@ where
|
||||
/// a primitive), any subexpressions have been bound to variables so
|
||||
/// that the referenced data will always either be a constant or a
|
||||
/// variable reference.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Expression<Type> {
|
||||
Atomic(ValueOrRef<Type>),
|
||||
Cast(Location, Type, ValueOrRef<Type>),
|
||||
@@ -497,7 +478,7 @@ impl fmt::Display for TypeOrVar {
|
||||
Some((last_one, rest)) => {
|
||||
write!(f, "(")?;
|
||||
for arg in rest.iter() {
|
||||
write!(f, "{}, ", arg);
|
||||
write!(f, "{}, ", arg)?;
|
||||
}
|
||||
write!(f, "{})", last_one)?;
|
||||
}
|
||||
@@ -510,6 +491,12 @@ impl fmt::Display for TypeOrVar {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TypeOrVar {
|
||||
fn default() -> Self {
|
||||
TypeOrVar::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOrVar {
|
||||
/// Generate a fresh type variable that is different from all previous type variables.
|
||||
///
|
||||
@@ -532,7 +519,7 @@ impl TypeOrVar {
|
||||
}
|
||||
}
|
||||
|
||||
trait TypeWithVoid {
|
||||
pub trait TypeWithVoid {
|
||||
fn void() -> Self;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,55 @@
|
||||
use super::{Primitive, Type, ValueOrRef};
|
||||
use crate::eval::{EvalEnvironment, EvalError, Value};
|
||||
use crate::ir::{Expression, Program, TopLevel};
|
||||
use crate::eval::{EvalError, Value};
|
||||
use crate::ir::{Expression, Program, TopLevel, Variable};
|
||||
use crate::util::scoped_map::ScopedMap;
|
||||
|
||||
impl<Type> Program<Type> {
|
||||
/// Evaluate the program, returning either an error or a string containing everything
|
||||
/// the program printed out.
|
||||
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<String, EvalError> {
|
||||
let mut env = EvalEnvironment::empty();
|
||||
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(_, _, _, _) => unimplemented!(),
|
||||
TopLevel::Function(name, args, _, body) => {
|
||||
let closure = Value::Closure(
|
||||
Some(name.clone()),
|
||||
env.clone(),
|
||||
args.iter().map(|(x, _)| x.clone()).collect(),
|
||||
body.clone(),
|
||||
);
|
||||
|
||||
TopLevel::Statement(_) => unimplemented!(),
|
||||
env.insert(name.clone(), closure.clone());
|
||||
|
||||
last_value = closure;
|
||||
}
|
||||
|
||||
TopLevel::Statement(expr) => {
|
||||
last_value = expr.eval(&env, &mut stdout)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(stdout)
|
||||
Ok((last_value, stdout))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Expression<T>
|
||||
where
|
||||
T: Clone + Into<Type>,
|
||||
T: Clone + Into<Type>,
|
||||
{
|
||||
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
|
||||
fn eval(
|
||||
&self,
|
||||
env: &ScopedMap<Variable, IRValue<T>>,
|
||||
stdout: &mut String,
|
||||
) -> Result<IRValue<T>, IREvalError<T>> {
|
||||
match self {
|
||||
Expression::Atomic(x) => x.eval(env),
|
||||
|
||||
@@ -45,7 +67,7 @@ where
|
||||
let arg_values = args
|
||||
.iter()
|
||||
.map(|x| x.eval(env))
|
||||
.collect::<Result<Vec<Value>, EvalError>>()?;
|
||||
.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`.
|
||||
@@ -61,15 +83,25 @@ where
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
Expression::Print(_, _) => 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> ValueOrRef<T> {
|
||||
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
|
||||
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)),
|
||||
@@ -82,7 +114,10 @@ impl<T> ValueOrRef<T> {
|
||||
super::Value::U64(_, v) => Ok(Value::U64(*v)),
|
||||
},
|
||||
|
||||
ValueOrRef::Ref(_, _, n) => Ok(env.lookup(n.clone())?),
|
||||
ValueOrRef::Ref(loc, _, n) => env
|
||||
.get(n)
|
||||
.cloned()
|
||||
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +126,7 @@ impl<T> ValueOrRef<T> {
|
||||
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");
|
||||
let (_, output) = ir.eval().expect("runs successfully");
|
||||
assert_eq!("x = 5u64\n", &output);
|
||||
}
|
||||
|
||||
@@ -100,6 +135,6 @@ 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");
|
||||
let (_, output) = ir.eval().expect("runs successfully");
|
||||
assert_eq!("x = 7u64\n", &output);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user