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.
193 lines
5.2 KiB
Rust
193 lines
5.2 KiB
Rust
use internment::ArcIntern;
|
|
use pretty::{DocAllocator, Pretty};
|
|
use proptest::{
|
|
prelude::Arbitrary,
|
|
strategy::{BoxedStrategy, Strategy},
|
|
};
|
|
|
|
use crate::syntax::Location;
|
|
|
|
type Variable = ArcIntern<String>;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Program {
|
|
pub statements: Vec<Statement>,
|
|
}
|
|
|
|
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
|
|
where
|
|
A: 'a,
|
|
D: ?Sized + DocAllocator<'a, A>,
|
|
{
|
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
|
let mut result = allocator.nil();
|
|
|
|
for stmt in self.statements.iter() {
|
|
result = result
|
|
.append(stmt.pretty(allocator))
|
|
.append(allocator.text(";"))
|
|
.append(allocator.hardline());
|
|
}
|
|
|
|
result
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Program {
|
|
type Parameters = ();
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
|
|
crate::syntax::Program::arbitrary_with(args)
|
|
.prop_map(|x| Program::from(x.simplify()))
|
|
.boxed()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Statement {
|
|
Binding(Location, Variable, Expression),
|
|
Print(Location, Variable),
|
|
}
|
|
|
|
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Statement
|
|
where
|
|
A: 'a,
|
|
D: ?Sized + DocAllocator<'a, A>,
|
|
{
|
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
|
match self {
|
|
Statement::Binding(_, var, expr) => allocator
|
|
.text(var.as_ref().to_string())
|
|
.append(allocator.space())
|
|
.append(allocator.text("="))
|
|
.append(allocator.space())
|
|
.append(expr.pretty(allocator)),
|
|
Statement::Print(_, var) => allocator
|
|
.text("print")
|
|
.append(allocator.space())
|
|
.append(allocator.text(var.as_ref().to_string())),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Expression {
|
|
Value(Location, Value),
|
|
Reference(Location, Variable),
|
|
Primitive(Location, Primitive, Vec<ValueOrRef>),
|
|
}
|
|
|
|
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Expression
|
|
where
|
|
A: 'a,
|
|
D: ?Sized + DocAllocator<'a, A>,
|
|
{
|
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
|
match self {
|
|
Expression::Value(_, val) => val.pretty(allocator),
|
|
Expression::Reference(_, var) => allocator.text(var.as_ref().to_string()),
|
|
Expression::Primitive(_, op, exprs) if exprs.len() == 1 => {
|
|
op.pretty(allocator).append(exprs[0].pretty(allocator))
|
|
}
|
|
Expression::Primitive(_, op, exprs) if exprs.len() == 2 => {
|
|
let left = exprs[0].pretty(allocator);
|
|
let right = exprs[1].pretty(allocator);
|
|
|
|
left.append(allocator.space())
|
|
.append(op.pretty(allocator))
|
|
.append(allocator.space())
|
|
.append(right)
|
|
.parens()
|
|
}
|
|
Expression::Primitive(_, op, exprs) => {
|
|
allocator.text(format!("!!{:?} with {} arguments!!", op, exprs.len()))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum Primitive {
|
|
Plus,
|
|
Minus,
|
|
Times,
|
|
Divide,
|
|
}
|
|
|
|
impl<'a> TryFrom<&'a str> for Primitive {
|
|
type Error = String;
|
|
|
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
match value {
|
|
"+" => Ok(Primitive::Plus),
|
|
"-" => Ok(Primitive::Minus),
|
|
"*" => Ok(Primitive::Times),
|
|
"/" => Ok(Primitive::Divide),
|
|
_ => Err(format!("Illegal primitive {}", value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Primitive
|
|
where
|
|
A: 'a,
|
|
D: ?Sized + DocAllocator<'a, A>,
|
|
{
|
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
|
match self {
|
|
Primitive::Plus => allocator.text("+"),
|
|
Primitive::Minus => allocator.text("-"),
|
|
Primitive::Times => allocator.text("*"),
|
|
Primitive::Divide => allocator.text("/"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ValueOrRef {
|
|
Value(Location, Value),
|
|
Ref(Location, ArcIntern<String>),
|
|
}
|
|
|
|
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b ValueOrRef
|
|
where
|
|
A: 'a,
|
|
D: ?Sized + DocAllocator<'a, A>,
|
|
{
|
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
|
match self {
|
|
ValueOrRef::Value(_, v) => v.pretty(allocator),
|
|
ValueOrRef::Ref(_, v) => allocator.text(v.as_ref().to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Value {
|
|
Number(Option<u8>, i64),
|
|
}
|
|
|
|
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Value
|
|
where
|
|
A: 'a,
|
|
D: ?Sized + DocAllocator<'a, A>,
|
|
{
|
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
|
match self {
|
|
Value::Number(opt_base, value) => {
|
|
let value_str = match opt_base {
|
|
None => format!("{}", value),
|
|
Some(2) => format!("0b{:b}", value),
|
|
Some(8) => format!("0o{:o}", value),
|
|
Some(10) => format!("0d{}", value),
|
|
Some(16) => format!("0x{:x}", value),
|
|
Some(_) => format!("!!{:x}!!", value),
|
|
};
|
|
|
|
allocator.text(value_str)
|
|
}
|
|
}
|
|
}
|
|
}
|