Files
ngr/src/ir/ast.rs
Adam Wick f4594bf2cc 🧪 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.
2023-05-13 15:00:08 -05:00

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)
}
}
}
}