diff --git a/Cargo.toml b/Cargo.toml index 85ecff1..8ad2b17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,8 @@ [workspace] resolver = "2" -members = ["crates/*"] \ No newline at end of file +members = ["crates/*"] + +[workspace.dependencies] +anyhow = "1.0.86" +im = "15.1.0" +expect-test = "1.5.0" diff --git a/crates/dyro-poc/Cargo.toml b/crates/dyro-poc/Cargo.toml new file mode 100644 index 0000000..a9702b7 --- /dev/null +++ b/crates/dyro-poc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dyro-poc" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +im.workspace = true + +[dev-dependencies] +expect-test.workspace = true + +[features] +debug-ast-to-anf = [] diff --git a/crates/dyro-poc/src/anf.rs b/crates/dyro-poc/src/anf.rs new file mode 100644 index 0000000..6be89a6 --- /dev/null +++ b/crates/dyro-poc/src/anf.rs @@ -0,0 +1,99 @@ +#![allow(dead_code)] + +use crate::ast; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ANFVar(pub u32); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ANFType(pub ast::ASTType); + +impl ANFType { + pub fn size(&self) -> usize { + use ast::ASTType::*; + + match self.0 { + // Don't want to consider ZST now + Unit => 1, + Int => 4, + Bool => 1, + Tuple(ref types) => types.iter().map(|t| ANFType(t.clone()).size()).sum(), + + // [heap/stack enum, size, value] + MutPtr(_) => 9, + + // We probably can't store these types in memory so 0 + String => 0, + Function(_, _) => 0, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ANFUnaryOp(pub ast::ASTUnaryOp); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ANFBinaryOp(pub ast::ASTBinaryOp); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ANFNode { + Let { + binding: ANFVar, + value: ANFSimpleExpression, + body: Box, + }, + LetFn { + name: ANFVar, + args: Vec<(ANFVar, ANFType)>, + return_type: ANFType, + value: Box, + body: Box, + }, + Simple(ANFSimpleExpression), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ANFSimpleExpression { + Unit, + Int(i32), + Bool(bool), + Var(ANFVar), + String(String), + SpecialFunction(crate::special::SpecialFunction), + If { + condition: ANFVar, + // We need to use ANFSimpleExpression here to support function call (it could be a continuation here?) + then: Box, + r#else: Box, + }, + Call { + function: ANFVar, + type_arguments: Vec, + arguments: Vec, + }, + TupleAccess { + tuple: ANFVar, + index: usize, + }, + Tuple { + elements: Vec, + }, + ArrayRead { + array: ANFVar, + index: ANFVar, + }, + ArraySet { + array: ANFVar, + index: ANFVar, + value: ANFVar, + }, + UnaryOp { + op: ANFUnaryOp, + operand: ANFVar, + }, + BinaryOp { + op: ANFBinaryOp, + left: ANFVar, + right: ANFVar, + }, +} diff --git a/crates/dyro-poc/src/ast.rs b/crates/dyro-poc/src/ast.rs new file mode 100644 index 0000000..3149c76 --- /dev/null +++ b/crates/dyro-poc/src/ast.rs @@ -0,0 +1,108 @@ +#![allow(dead_code)] + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ASTVar(pub String); + +impl From<&str> for ASTVar { + fn from(s: &str) -> Self { + Self(s.to_owned()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ASTType { + Unit, + Int, + Bool, + String, + MutPtr(Box), + Tuple(Vec), + Function(Vec, Box), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ASTUnaryOp { + Neg, + Not, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ASTBinaryOp { + Add, + Sub, + Mul, + Div, + Eq, + Lt, + Gt, + Le, + Ge, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ASTNode { + Let { + binding: ASTVar, + value: Box, + body: Box, + }, + LetFn { + name: ASTVar, + args: Vec<(ASTVar, ASTType)>, + return_type: ASTType, + value: Box, + body: Box, + }, + Unit, + Int(i32), + Bool(bool), + Var(ASTVar), + /// For panic call only + String(String), + SpecialFunction(crate::special::SpecialFunction), + If { + condition: Box, + then: Box, + r#else: Box, + r#type: ASTType, + }, + /// Partial evaluation is not supported + Call { + function: Box, + type_arguments: Vec, + arguments: Vec, + }, + TupleAccess { + tuple: Box, + index: usize, + }, + Tuple { + elements: Vec, + }, + /// array[index] + ArrayRead { + array: Box, + index: Box, + }, + /// array[index] = value + ArraySet { + array: Box, + index: Box, + value: Box, + }, + /// (first; second) equivalent to let _ = first in second + /// removed in ANF + Sequence { + first: Box, + second: Box, + }, + UnaryOp { + op: ASTUnaryOp, + operand: Box, + }, + BinaryOp { + op: ASTBinaryOp, + left: Box, + right: Box, + }, +} diff --git a/crates/dyro-poc/src/ast_to_anf.rs b/crates/dyro-poc/src/ast_to_anf.rs new file mode 100644 index 0000000..c0bf0ac --- /dev/null +++ b/crates/dyro-poc/src/ast_to_anf.rs @@ -0,0 +1,433 @@ +use crate::{anf, ast, special}; + +struct ASTToANFConverter { + var_counter: u32, +} + +impl ASTToANFConverter { + fn new() -> Self { + Self { var_counter: 0 } + } + + fn new_var(&mut self) -> anf::ANFVar { + let var = self.var_counter; + self.var_counter += 1; + anf::ANFVar(var) + } + + fn convert( + &mut self, + context: im::HashMap, + value: &ast::ASTNode, + ) -> anyhow::Result { + let dummy_var = self.new_var(); + self.convert_bind_into( + context, + dummy_var, + value, + anf::ANFNode::Simple(anf::ANFSimpleExpression::Var(dummy_var)), + ) + } + + fn convert_bind_into( + &mut self, + context: im::HashMap, + binding: anf::ANFVar, + value: &ast::ASTNode, + body: anf::ANFNode, + ) -> anyhow::Result { + #[cfg(feature = "debug-ast-to-anf")] + let context_backup = context.clone(); + #[cfg(feature = "debug-ast-to-anf")] + let body_backup = body.clone(); + + let result = match value { + ast::ASTNode::Unit => { + let value = anf::ANFSimpleExpression::Unit; + Ok(anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }) + } + + ast::ASTNode::Let { + binding: let_binding, + value: let_value, + body: let_body, + } => { + let let_value_var = self.new_var(); + let let_body_var = binding; + + let mut let_body_context = context.clone(); + let_body_context.insert(let_binding.clone(), let_value_var); + let result = + self.convert_bind_into(let_body_context, let_body_var, let_body, body)?; + + self.convert_bind_into(context, let_value_var, let_value, result) + } + + ast::ASTNode::LetFn { + name: fn_name, + args: fn_args, + return_type: fn_return_type, + value: fn_value, + body: fn_body, + } => { + // Before: + // let = (let x = in ) in + // + // After: + // let x = in let = in + // let = in + let fn_var = self.new_var(); + let fn_body_var = binding; + + // allow rec by default + let mut fn_body_context = context.clone(); + fn_body_context.insert(fn_name.clone(), fn_var); + + let mut fn_value_context = fn_body_context.clone(); + let fn_anf_args = fn_args + .iter() + .map(|(arg_name, arg_type)| { + let arg_var = self.new_var(); + fn_value_context.insert(arg_name.clone(), arg_var.clone()); + (arg_var, anf::ANFType(arg_type.clone())) + }) + .collect::>(); + + let result = anf::ANFNode::Let { + binding: binding, + value: anf::ANFSimpleExpression::Var(fn_body_var), + body: Box::new(body), + }; + let result = + self.convert_bind_into(fn_body_context, fn_body_var, fn_body, result)?; + + Ok(anf::ANFNode::LetFn { + name: fn_var, + args: fn_anf_args, + return_type: anf::ANFType(fn_return_type.clone()), + value: Box::new(self.convert(fn_value_context, fn_value)?), + body: Box::new(result), + }) + } + + ast::ASTNode::Int(i) => { + let value = anf::ANFSimpleExpression::Int(*i); + Ok(anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }) + } + + ast::ASTNode::Bool(b) => { + let value = anf::ANFSimpleExpression::Bool(*b); + Ok(anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }) + } + + ast::ASTNode::Var(v) => { + let value = anf::ANFSimpleExpression::Var( + context + .get(&v) + .ok_or_else(|| anyhow::anyhow!("Variable {} not found in context", v.0))? + .clone(), + ); + Ok(anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }) + } + + ast::ASTNode::String(s) => { + let value = anf::ANFSimpleExpression::String(s.clone()); + Ok(anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }) + } + + ast::ASTNode::If { + condition, + then, + r#else, + r#type, + } => { + // Before: + // let = if then else in + // After: + // let () = in + // let () = in + // let = in + // let = if then () else () in + + let condition_var = self.new_var(); + let then_var = self.new_var(); + let else_var = self.new_var(); + + let value = anf::ANFSimpleExpression::If { + condition: condition_var, + then: Box::new(anf::ANFSimpleExpression::Call { + function: then_var, + type_arguments: vec![], + arguments: vec![], + }), + r#else: Box::new(anf::ANFSimpleExpression::Call { + function: else_var, + type_arguments: vec![], + arguments: vec![], + }), + }; + + let result = anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }; + + let result = + self.convert_bind_into(context.clone(), condition_var, condition, result)?; + + let result = anf::ANFNode::LetFn { + name: else_var, + args: vec![], + return_type: anf::ANFType(r#type.clone()), + value: Box::new(self.convert(context.clone(), r#else)?), + body: Box::new(result), + }; + + let result = anf::ANFNode::LetFn { + name: then_var, + args: vec![], + return_type: anf::ANFType(r#type.clone()), + value: Box::new(self.convert(context.clone(), then)?), + body: Box::new(result), + }; + + Ok(result) + } + + ast::ASTNode::Call { + function, + type_arguments, + arguments, + } => { + let type_arguments = type_arguments + .iter() + .map(|t| anf::ANFType(t.clone())) + .collect(); + + let function_var = self.new_var(); + let arguments_vars = arguments + .iter() + .map(|arg| self.new_var()) + .collect::>(); + + let mut result = anf::ANFNode::Let { + binding, + value: anf::ANFSimpleExpression::Call { + function: function_var, + type_arguments, + arguments: arguments_vars.clone(), + }, + body: Box::new(body), + }; + + for (index, arg) in arguments.iter().enumerate().rev() { + result = self.convert_bind_into( + context.clone(), + arguments_vars[index], + arg, + result, + )?; + } + + self.convert_bind_into(context, function_var, function, result) + } + + ast::ASTNode::TupleAccess { tuple, index } => { + // tuple[index] + // -> let tuple_var = tuple in let index_var = index in tuple_var[index_var] + let tuple_var = self.new_var(); + + let value = anf::ANFSimpleExpression::TupleAccess { + tuple: tuple_var, + index: *index, + }; + + let result = anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }; + + self.convert_bind_into(context, tuple_var, tuple, result) + } + + ast::ASTNode::Tuple { elements } => { + // (e1, e2, e3) + // -> let e1_var = e1 in let e2_var = e2 in let e3_var = e3 in (e1_var, e2_var, e3_var) + let mut vars = Vec::new(); + for _ in elements.iter() { + vars.push(self.new_var()); + } + + let mut result = anf::ANFNode::Let { + binding, + value: anf::ANFSimpleExpression::Tuple { + elements: vars.clone(), + }, + body: Box::new(body), + }; + + for (index, element) in elements.iter().enumerate().rev() { + result = + self.convert_bind_into(context.clone(), vars[index], element, result)?; + } + + Ok(result) + } + + ast::ASTNode::ArrayRead { array, index } => { + // Before: + // let = [] in + // After: + // let = in + // let = in + // let = [] in + let array_var = self.new_var(); + let index_var = self.new_var(); + + let result = anf::ANFNode::Let { + binding, + value: anf::ANFSimpleExpression::ArrayRead { + array: array_var, + index: index_var, + }, + body: Box::new(body), + }; + + let result = self.convert_bind_into(context.clone(), index_var, index, result)?; + self.convert_bind_into(context, array_var, array, result) + } + + ast::ASTNode::ArraySet { + array, + index, + value, + } => { + // Before: + // let = ([] := ) in + // After: + // let = in + // let = in + // let = in + // let = [] := in + + let array_var = self.new_var(); + let index_var = self.new_var(); + let value_var = self.new_var(); + + let result = anf::ANFNode::Let { + binding, + value: anf::ANFSimpleExpression::ArraySet { + array: array_var, + index: index_var, + value: value_var, + }, + body: Box::new(body), + }; + + let result = self.convert_bind_into(context.clone(), value_var, value, result)?; + let result = self.convert_bind_into(context.clone(), index_var, index, result)?; + self.convert_bind_into(context, array_var, array, result) + } + + ast::ASTNode::Sequence { first, second } => { + let second_var = self.new_var(); + let second = self.convert_bind_into( + context.clone(), + second_var, + &second, + anf::ANFNode::Simple(anf::ANFSimpleExpression::Var(second_var)), + )?; + + let first_dummy_var = self.new_var(); + self.convert_bind_into(context, first_dummy_var, &first, second) + } + + ast::ASTNode::UnaryOp { op, operand } => { + let operand_var = self.new_var(); + + let remaining = anf::ANFNode::Simple(anf::ANFSimpleExpression::UnaryOp { + op: anf::ANFUnaryOp(op.clone()), + operand: operand_var, + }); + + self.convert_bind_into(context, operand_var, &operand, remaining) + } + + ast::ASTNode::BinaryOp { op, left, right } => { + let left_var = self.new_var(); + let right_var = self.new_var(); + + let value = anf::ANFSimpleExpression::BinaryOp { + op: anf::ANFBinaryOp(op.clone()), + left: left_var, + right: right_var, + }; + + let result = anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }; + + let result = self.convert_bind_into(context.clone(), right_var, &right, result)?; + self.convert_bind_into(context, left_var, &left, result) + } + + ast::ASTNode::SpecialFunction(special) => { + let value = anf::ANFSimpleExpression::SpecialFunction(special.clone()); + Ok(anf::ANFNode::Let { + binding, + value, + body: Box::new(body), + }) + } + }; + + #[cfg(feature = "debug-ast-to-anf")] + eprintln!( + "convert_bind_into({:?}, {:?}, {:#?}, {:?}) = {:#?}", + context_backup, binding, value, body_backup, result + ); + result + } + + fn convert_ast(&mut self, ast: ast::ASTNode) -> anyhow::Result { + let result_var = self.new_var(); + self.convert_bind_into( + im::HashMap::new(), + result_var, + &ast, + anf::ANFNode::Simple(anf::ANFSimpleExpression::Var(result_var)), + ) + } +} + +impl TryFrom for anf::ANFNode { + type Error = anyhow::Error; + + fn try_from(value: ast::ASTNode) -> Result { + ASTToANFConverter::new().convert_ast(value) + } +} diff --git a/crates/dyro-poc/src/interpreter.rs b/crates/dyro-poc/src/interpreter.rs new file mode 100644 index 0000000..de60fe6 --- /dev/null +++ b/crates/dyro-poc/src/interpreter.rs @@ -0,0 +1,609 @@ +use crate::{ + anf::{self, ANFType}, + ast::{self}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PtrLocation { + Stack(u32), + Heap(u32), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Value { + Unit, + Int(i32), + String(String), + Bool(bool), + Tuple(Vec), + SpecialFunction(crate::special::SpecialFunction), + MutPtr { + location: PtrLocation, + r#type: anf::ANFType, + // Size in bytes (not length) + size: u32, + }, + Function { + args: Vec<(anf::ANFVar, anf::ANFType)>, + return_type: anf::ANFType, + value: anf::ANFNode, + }, +} + +impl Value { + pub fn to_bytes(&self) -> Vec { + match self { + Value::Unit => vec![0], + Value::Int(i) => i.to_le_bytes().to_vec(), + Value::Bool(b) => vec![*b as u8], + Value::Tuple(elements) => elements.iter().flat_map(Value::to_bytes).collect(), + Value::MutPtr { + location, + size, + r#type: _, + } => match location { + PtrLocation::Stack(offset) => [ + vec![1], + size.to_le_bytes().to_vec(), + offset.to_le_bytes().to_vec(), + ] + .concat(), + PtrLocation::Heap(offset) => [ + vec![2], + size.to_le_bytes().to_vec(), + offset.to_le_bytes().to_vec(), + ] + .concat(), + }, + Value::SpecialFunction(_) => panic!("SpecialFunction cannot be converted to bytes"), + Value::String(_) => panic!("String cannot be converted to bytes"), + Value::Function { .. } => panic!("Function cannot be converted to bytes"), + } + } + + pub fn from_bytes(bytes: Vec, r#type: ANFType) -> anyhow::Result { + use ast::ASTType::*; + let type_size = r#type.size(); + match (r#type.0, bytes.len()) { + (Unit, 1) => Ok(Value::Unit), + (Int, 4) => Ok(Value::Int(i32::from_le_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + ]))), + (Bool, 1) => Ok(Value::Bool(bytes[0] != 0)), + (Tuple(types), l) if type_size == l => { + let mut elements = Vec::new(); + let mut offset = 0; + for t in types { + let size = ANFType(t.clone()).size(); + elements.push(Value::from_bytes( + bytes[offset..offset + size].to_vec(), + anf::ANFType(t), + )?); + offset += size; + } + Ok(Value::Tuple(elements)) + } + (MutPtr(t), 8) => { + let location = match bytes[0] { + 1 => PtrLocation::Stack(u32::from_le_bytes([ + bytes[5], bytes[6], bytes[7], bytes[8], + ])), + 2 => PtrLocation::Heap(u32::from_le_bytes([ + bytes[5], bytes[6], bytes[7], bytes[8], + ])), + _ => return Err(anyhow::anyhow!("Invalid location")), + }; + let size = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]); + Ok(Value::MutPtr { + location, + r#type: anf::ANFType(*t), + size, + }) + } + (String | Function(_, _), _) => Err(anyhow::anyhow!("Invalid type for bytes")), + _ => Err(anyhow::anyhow!("Invalid bytes")), + } + } + + pub fn to_readable_string(&self) -> String { + match self { + Value::Unit => "()".to_string(), + Value::Int(i) => i.to_string(), + Value::String(s) => format!("{:?}", s), + Value::Bool(b) => b.to_string(), + Value::Tuple(elements) => format!( + "({})", + elements + .iter() + .map(Value::to_readable_string) + .collect::>() + .join(", ") + ), + Value::MutPtr { + location, + r#type, + size, + } => format!("MutPtr({:?}, {:?}, {})", location, r#type, size), + Value::Function { + args, + return_type, + value: _, + } => format!( + "Function({:?}, {:?})", + args.iter() + .map(|(var, r#type)| format!("{}: {:?}", var.0, r#type)) + .collect::>() + .join(", "), + return_type + ), + Value::SpecialFunction(special_function) => format!("{:?}", special_function), + } + } +} + +impl Value { + pub fn anf_type(&self) -> anf::ANFType { + use ast::ASTType::*; + + anf::ANFType(match self { + Value::Unit => Unit, + Value::Int(_) => Int, + Value::String(_) => String, + Value::Bool(_) => Bool, + Value::Tuple(elements) => { + Tuple(elements.iter().map(Value::anf_type).map(|t| t.0).collect()) + } + Value::MutPtr { r#type, .. } => MutPtr(Box::new(r#type.clone().0)), + Value::Function { + args, + return_type, + value: _, + } => Function( + args.iter().map(|(_, t)| t.0.clone()).collect(), + Box::new(return_type.clone().0), + ), + Value::SpecialFunction(_) => unreachable!(), + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum HeapValue { + Byte(u8), + Poison, + Undefined, +} + +impl HeapValue { + pub fn as_byte(&self) -> anyhow::Result { + match self { + HeapValue::Byte(b) => Ok(*b), + HeapValue::Poison => Err(anyhow::anyhow!("Poison value")), + HeapValue::Undefined => Err(anyhow::anyhow!("Undefined value")), + } + } +} + +pub enum RootOrParent { + Root { heap: Vec }, + Parent(Box), +} + +pub struct Interpreter { + pub parent: RootOrParent, + pub stack: Vec>, +} + +impl Interpreter { + pub fn new() -> Self { + Self { + parent: RootOrParent::Root { heap: Vec::new() }, + stack: Vec::new(), + } + } + + pub fn ensure_stack(&mut self, var: anf::ANFVar) { + while self.stack.len() <= var.0 as usize { + self.stack.push(None); + } + } + + pub fn alloc(&mut self, size: usize) -> PtrLocation { + match &mut self.parent { + RootOrParent::Root { heap } => { + let offset = heap.len(); + heap.resize(offset + size, HeapValue::Poison); + PtrLocation::Heap(offset as u32) + } + RootOrParent::Parent(parent) => parent.alloc(size), + } + } + + pub fn read_heap(&self, offset: usize) -> anyhow::Result { + match &self.parent { + RootOrParent::Root { ref heap } => heap + .get(offset) + .map(HeapValue::as_byte) + .transpose()? + .ok_or_else(|| anyhow::anyhow!("Invalid heap access")), + RootOrParent::Parent(parent) => parent.read_heap(offset), + } + } + + pub fn write_heap(&mut self, offset: usize, value: HeapValue) -> anyhow::Result<()> { + match &mut self.parent { + RootOrParent::Root { heap } => { + while heap.len() <= offset { + heap.push(HeapValue::Undefined); + } + heap[offset] = value; + Ok(()) + } + RootOrParent::Parent(parent) => parent.write_heap(offset, value), + } + } + + pub fn calc_unary_op(&self, op: anf::ANFUnaryOp, value: Value) -> anyhow::Result { + use crate::ast::ASTUnaryOp::*; + match op.0 { + Neg => match value { + Value::Int(i) => Ok(Value::Int(-i)), + _ => Err(anyhow::anyhow!("Invalid type for Neg")), + }, + Not => match value { + Value::Bool(b) => Ok(Value::Bool(!b)), + _ => Err(anyhow::anyhow!("Invalid type for Not")), + }, + } + } + + pub fn calc_binary_op( + &self, + op: anf::ANFBinaryOp, + left: Value, + right: Value, + ) -> anyhow::Result { + use crate::ast::ASTBinaryOp::*; + match op.0 { + Add => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l + r)), + _ => Err(anyhow::anyhow!("Invalid types for Add")), + }, + Sub => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l - r)), + _ => Err(anyhow::anyhow!("Invalid types for Sub")), + }, + Mul => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l * r)), + _ => Err(anyhow::anyhow!("Invalid types for Mul")), + }, + Div => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l / r)), + _ => Err(anyhow::anyhow!("Invalid types for Div")), + }, + Eq => Ok(Value::Bool(left == right)), + Lt => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Bool(l < r)), + _ => Err(anyhow::anyhow!("Invalid types for Lt")), + }, + Gt => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Bool(l > r)), + _ => Err(anyhow::anyhow!("Invalid types for Gt")), + }, + Le => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Bool(l <= r)), + _ => Err(anyhow::anyhow!("Invalid types for Le")), + }, + Ge => match (left, right) { + (Value::Int(l), Value::Int(r)) => Ok(Value::Bool(l >= r)), + _ => Err(anyhow::anyhow!("Invalid types for Ge")), + }, + } + } + + pub fn eval_var(&self, var: anf::ANFVar) -> anyhow::Result { + match self.stack.get(var.0 as usize).cloned().flatten() { + Some(value) => Ok(value), + None => match &self.parent { + RootOrParent::Parent(parent) => parent.eval_var(var), + RootOrParent::Root { .. } => Err(anyhow::anyhow!("Variable {} not found", var.0)), + }, + } + } + + pub fn call_special_function( + &mut self, + special_function: crate::special::SpecialFunction, + type_arguments: Vec, + arguments: Vec, + ) -> anyhow::Result { + use crate::special::SpecialFunction::*; + + match (special_function, &type_arguments[..], &arguments[..]) { + (Alloc, [r#type], [length]) => { + let type_size = r#type.size(); + let alloc_length: usize = if let Value::Int(i) = self.eval_var(*length)? { + i.try_into() + .map_err(|_| anyhow::anyhow!("Invalid length"))? + } else { + return Err(anyhow::anyhow!("Invalid argument for Alloc")); + }; + let size = type_size * alloc_length; + let location = self.alloc(size); + + Ok(Value::MutPtr { + location, + r#type: r#type.clone(), + size: size.try_into().unwrap(), + }) + } + (Dealloc, [r#type], [ptr, length]) => { + let type_size = r#type.size(); + let length: usize = if let Value::Int(i) = self.eval_var(*length)? { + i.try_into() + .map_err(|_| anyhow::anyhow!("Invalid length"))? + } else { + return Err(anyhow::anyhow!("Invalid argument for Dealloc")); + }; + let ptr = self.eval_var(*ptr)?; + match ptr { + Value::MutPtr { + location, + r#type: _, + size, + } => { + let offset = match location { + PtrLocation::Stack(_offset) => unimplemented!(), + PtrLocation::Heap(offset) => offset as usize, + }; + // TODO: confirm whether to check r#type == ptr_type + anyhow::ensure!( + length * type_size == size.try_into().unwrap(), + "Invalid size for Dealloc" + ); + for i in 0..length { + for j in 0..type_size { + self.write_heap(offset + i * type_size + j, HeapValue::Undefined)?; + } + } + Ok(Value::Unit) + } + _ => Err(anyhow::anyhow!("Invalid argument for Dealloc")), + } + } + (Print, [r#type], [var]) => { + let value = self.eval_var(*var)?; + anyhow::ensure!( + value.anf_type() == *r#type, + "Invalid type for Print: type-parameter is {:?} but value is {:?}", + r#type, + value.anf_type(), + ); + + println!("{}", value.to_readable_string()); + Ok(Value::Unit) + } + (Panic, [], [var]) => { + let message = self.eval_var(*var)?; + if let Value::String(s) = message { + Err(anyhow::anyhow!("Panic: {}", s)) + } else { + Err(anyhow::anyhow!("Invalid argument {:?} for Panic", message)) + } + } + (function, _, _) => anyhow::bail!("Invalid arguments for {:?}", function), + } + } + + pub fn eval_simple_expression<'a>( + &'a mut self, + expr: &anf::ANFSimpleExpression, + ) -> anyhow::Result { + match expr { + anf::ANFSimpleExpression::Unit => Ok(Value::Unit), + anf::ANFSimpleExpression::Int(i) => Ok(Value::Int(*i)), + anf::ANFSimpleExpression::Bool(b) => Ok(Value::Bool(*b)), + anf::ANFSimpleExpression::Var(v) => self.eval_var(*v), + anf::ANFSimpleExpression::String(s) => Ok(Value::String(s.clone())), + anf::ANFSimpleExpression::SpecialFunction(special_function) => { + Ok(Value::SpecialFunction(*special_function)) + } + anf::ANFSimpleExpression::BinaryOp { op, left, right } => { + self.calc_binary_op(*op, self.eval_var(*left)?, self.eval_var(*right)?) + } + anf::ANFSimpleExpression::UnaryOp { op, operand } => { + self.calc_unary_op(*op, self.eval_var(*operand)?) + } + anf::ANFSimpleExpression::If { + condition, + then, + r#else, + } => { + let condition = self.eval_var(*condition)?; + match condition { + Value::Bool(true) => self.eval_simple_expression(then), + Value::Bool(false) => self.eval_simple_expression(r#else), + _ => Err(anyhow::anyhow!("Invalid type for If")), + } + } + anf::ANFSimpleExpression::Call { + function, + type_arguments, + arguments, + } => { + let function = self.eval_var(*function)?; + match function { + Value::SpecialFunction(special_function) => self.call_special_function( + special_function, + type_arguments.clone(), + arguments.clone(), + ), + Value::Function { + args, + value, + return_type, + } => { + anyhow::ensure!( + args.len() == arguments.len(), + "Invalid number of arguments" + ); + + let mut new_interpreter = Interpreter { + parent: RootOrParent::Parent(Box::new(std::mem::replace( + self, + Interpreter::new(), + ))), + stack: Vec::new(), + }; + + for ((var, r#type), value) in args.iter().zip(arguments.iter()) { + new_interpreter.ensure_stack(*var); + new_interpreter.stack[var.0 as usize] = + Some(new_interpreter.eval_var(*value)?); + anyhow::ensure!( + &new_interpreter.stack[var.0 as usize] + .as_ref() + .unwrap() + .anf_type() + == r#type, + "Invalid argument" + ); + } + + let result = new_interpreter.eval_node(&value)?; + anyhow::ensure!(result.anf_type() == return_type, "Invalid return type"); + + match new_interpreter.parent { + RootOrParent::Parent(parent) => { + *self = *parent; + } + _ => panic!("Invalid parent"), + } + + Ok(result) + } + _ => Err(anyhow::anyhow!("Invalid type for Call")), + } + } + anf::ANFSimpleExpression::TupleAccess { tuple, index } => { + match self.eval_var(*tuple)? { + Value::Tuple(elements) => elements + .get(*index) + .cloned() + .ok_or_else(|| anyhow::anyhow!("Index out of bounds")), + _ => Err(anyhow::anyhow!("Invalid type for TupleAccess")), + } + } + anf::ANFSimpleExpression::Tuple { elements } => elements + .iter() + .map(|var| self.eval_var(*var)) + .collect::>>() + .map(Value::Tuple), + anf::ANFSimpleExpression::ArrayRead { array, index } => { + let array = self.eval_var(*array)?; + let index = self.eval_var(*index)?; + + match (array, index) { + ( + Value::MutPtr { + location, + r#type, + size, + }, + Value::Int(index), + ) => { + let offset = match location { + PtrLocation::Stack(_offset) => unimplemented!(), + PtrLocation::Heap(offset) => offset as usize, + }; + let type_size = r#type.size(); + let bytes = (0..type_size) + .map(|i| { + anyhow::ensure!( + index as usize * type_size + i < size.try_into().unwrap(), + "Index out of bounds" + ); + self.read_heap(offset + index as usize * type_size + i) + }) + .collect::>>()?; + Value::from_bytes(bytes, r#type) + } + _ => Err(anyhow::anyhow!("Invalid types for ArrayRead")), + } + } + anf::ANFSimpleExpression::ArraySet { + array, + index, + value, + } => { + let array = self.eval_var(*array)?; + let index = self.eval_var(*index)?; + let value = self.eval_var(*value)?; + + match (array, index, value) { + ( + Value::MutPtr { + location, + r#type, + size, + }, + Value::Int(index), + value, + ) => { + let offset = match location { + PtrLocation::Stack(_offset) => unimplemented!(), + PtrLocation::Heap(offset) => offset as usize, + }; + let type_size = r#type.size(); + let bytes = value.to_bytes(); + anyhow::ensure!(bytes.len() == type_size, "Invalid size for ArraySet"); + for (i, byte) in bytes.into_iter().enumerate() { + anyhow::ensure!( + index as usize * type_size + i < size.try_into().unwrap(), + "Index out of bounds" + ); + self.write_heap( + offset + index as usize * type_size + i, + HeapValue::Byte(byte), + )?; + } + Ok(Value::Tuple(Vec::new())) + } + _ => Err(anyhow::anyhow!("Invalid types for ArraySet")), + } + } + } + } + + pub fn eval_node(&mut self, node: &anf::ANFNode) -> anyhow::Result { + match node { + anf::ANFNode::Let { + binding, + value, + body, + } => { + self.ensure_stack(*binding); + self.stack[binding.0 as usize] = Some(self.eval_simple_expression(value)?); + self.eval_node(body) + } + + anf::ANFNode::LetFn { + name, + args, + return_type, + value, + body, + } => { + self.ensure_stack(*name); + self.stack[name.0 as usize] = Some(Value::Function { + args: args.clone(), + return_type: return_type.clone(), + value: (**value).clone(), + }); + self.eval_node(body) + } + + anf::ANFNode::Simple(expr) => self.eval_simple_expression(expr), + } + } +} diff --git a/crates/dyro-poc/src/lib.rs b/crates/dyro-poc/src/lib.rs new file mode 100644 index 0000000..47463c2 --- /dev/null +++ b/crates/dyro-poc/src/lib.rs @@ -0,0 +1,27 @@ +pub mod anf; +pub mod ast; +pub mod ast_to_anf; +pub mod interpreter; +pub mod special; + +#[macro_export] +macro_rules! seq { + ($a:expr) => { + $a + }; + ($a:expr; $($rest:expr);+) => { + Sequence { + first: Box::new($a), + second: Box::new(seq!($($rest);+)) + } + }; +} + +#[macro_export] +macro_rules! ast { + ($x:expr) => {{ + use dyro_poc::ast::{ASTBinaryOp::*, ASTNode::*, ASTUnaryOp::*}; + type T = dyro_poc::ast::ASTType; + $x + }}; +} diff --git a/crates/dyro-poc/src/special.rs b/crates/dyro-poc/src/special.rs new file mode 100644 index 0000000..b8135de --- /dev/null +++ b/crates/dyro-poc/src/special.rs @@ -0,0 +1,23 @@ +use std::str::FromStr; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SpecialFunction { + Alloc, + Dealloc, + Print, + Panic, +} + +impl FromStr for SpecialFunction { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "alloc" => Ok(SpecialFunction::Alloc), + "dealloc" => Ok(SpecialFunction::Dealloc), + "print" => Ok(SpecialFunction::Print), + "panic" => Ok(SpecialFunction::Panic), + _ => Err(()), + } + } +} diff --git a/crates/dyro-poc/tests/integration.rs b/crates/dyro-poc/tests/integration.rs new file mode 100644 index 0000000..8937b79 --- /dev/null +++ b/crates/dyro-poc/tests/integration.rs @@ -0,0 +1,513 @@ +use dyro_poc::*; + +fn integration_test_success(ast_node: ast::ASTNode, expected: interpreter::Value) { + let anf: anf::ANFNode = ast_node.try_into().unwrap(); + let mut interpreter = interpreter::Interpreter::new(); + assert_eq!(interpreter.eval_node(&anf).unwrap(), expected); +} + +fn integration_test_failure(ast_node: ast::ASTNode, expected_error: &str) { + let anf: anf::ANFNode = ast_node.try_into().unwrap(); + let mut interpreter = interpreter::Interpreter::new(); + let result = interpreter.eval_node(&anf); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains(expected_error)); +} + +#[test] +fn let_test() { + integration_test_success( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Int(1)), + body: Box::new(Var("x".into())) + }), + interpreter::Value::Int(1), + ); +} + +#[test] +fn let_fn_test() { + // let f x = x + 1 in f 2 + integration_test_success( + dyro_poc::ast!(LetFn { + name: "f".into(), + args: vec![("x".into(), T::Int)], + return_type: T::Int, + value: Box::new(BinaryOp { + op: Add, + left: Box::new(Var("x".into())), + right: Box::new(Int(1)) + }), + body: Box::new(Call { + function: Box::new(Var("f".into())), + type_arguments: vec![], + arguments: vec![Int(2)] + }) + }), + interpreter::Value::Int(3), + ); +} + +#[test] +fn let_fn_wrong_type_test() { + // let f x = x + 1 in f true + integration_test_failure( + dyro_poc::ast!(LetFn { + name: "f".into(), + args: vec![("x".into(), T::Int)], + return_type: T::Int, + value: Box::new(BinaryOp { + op: Add, + left: Box::new(Var("x".into())), + right: Box::new(Int(1)) + }), + body: Box::new(Call { + function: Box::new(Var("f".into())), + type_arguments: vec![], + arguments: vec![Bool(true)] + }) + }), + "Invalid argument", + ); +} + +#[test] +fn int_test() { + integration_test_success(dyro_poc::ast!(Int(1)), interpreter::Value::Int(1)); +} + +#[test] +fn bool_test() { + integration_test_success(dyro_poc::ast!(Bool(true)), interpreter::Value::Bool(true)); +} + +#[test] +fn string_test() { + integration_test_success( + dyro_poc::ast!(String("hello".into())), + interpreter::Value::String("hello".into()), + ); +} + +#[test] +fn if_test() { + integration_test_success( + dyro_poc::ast!(If { + condition: Box::new(Bool(true)), + then: Box::new(Int(1)), + r#else: Box::new(Int(2)), + r#type: T::Int, + }), + interpreter::Value::Int(1), + ); +} + +#[test] +fn if_compare_test() { + integration_test_success( + dyro_poc::ast!(If { + condition: Box::new(BinaryOp { + op: Eq, + left: Box::new(Int(1)), + right: Box::new(Int(1)) + }), + then: Box::new(Int(1)), + r#else: Box::new(Int(2)), + r#type: T::Int, + }), + interpreter::Value::Int(1), + ); +} + +#[test] +fn addition_test() { + integration_test_success( + dyro_poc::ast!(BinaryOp { + op: Add, + left: Box::new(Int(1)), + right: Box::new(Int(2)) + }), + interpreter::Value::Int(3), + ); +} + +#[test] +fn let_addition_test() { + integration_test_success( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Int(1)), + body: Box::new(BinaryOp { + op: Add, + left: Box::new(Var("x".into())), + right: Box::new(Int(2)) + }) + }), + interpreter::Value::Int(3), + ); +} + +#[test] +fn tuple_test() { + integration_test_success( + dyro_poc::ast!(Tuple { + elements: vec![Int(1), Int(2), Int(3)] + }), + interpreter::Value::Tuple(vec![ + interpreter::Value::Int(1), + interpreter::Value::Int(2), + interpreter::Value::Int(3), + ]), + ); +} + +#[test] +fn tuple_access_test() { + integration_test_success( + dyro_poc::ast!(TupleAccess { + tuple: Box::new(Tuple { + elements: vec![Int(1), Int(2), Int(3)] + }), + index: 1 + }), + interpreter::Value::Int(2), + ); +} + +#[test] +fn panic_test() { + // Call panic with message + integration_test_failure( + dyro_poc::ast!(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Panic)), + type_arguments: vec![], + arguments: vec![String("error".into())] + }), + "error", + ); +} + +#[test] +fn alloc_poison_test() { + // let x = alloc(10) in x[0] + integration_test_failure( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)] + }), + body: Box::new(ArrayRead { + array: Box::new(Var("x".into())), + index: Box::new(Int(0)) + }) + }), + "Poison", + ); +} + +#[test] +fn alloc_read_write_test() { + // let x = alloc(10) in x[0] = 1; x[0] + integration_test_success( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)] + }), + body: Box::new(Sequence { + first: Box::new(ArraySet { + array: Box::new(Var("x".into())), + index: Box::new(Int(0)), + value: Box::new(Int(1)) + }), + second: Box::new(ArrayRead { + array: Box::new(Var("x".into())), + index: Box::new(Int(0)) + }) + }) + }), + interpreter::Value::Int(1), + ); +} + +#[test] +fn if_side_effect_test() { + // if false then panic("error") else 1 + integration_test_success( + dyro_poc::ast!(If { + condition: Box::new(Bool(false)), + then: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Panic)), + type_arguments: vec![], + arguments: vec![String("error".into())] + }), + r#else: Box::new(Int(1)), + r#type: T::Int, + }), + interpreter::Value::Int(1), + ); +} + +#[test] +fn recursion_test() { + // let pow2 x = if x == 0 then 1 else 2 * pow2 (x - 1) in pow2 3 + integration_test_success( + dyro_poc::ast!(LetFn { + name: "pow2".into(), + args: vec![("x".into(), T::Int)], + return_type: T::Int, + value: Box::new(If { + condition: Box::new(BinaryOp { + op: Eq, + left: Box::new(Var("x".into())), + right: Box::new(Int(0)) + }), + then: Box::new(Int(1)), + r#else: Box::new(BinaryOp { + op: Mul, + left: Box::new(Int(2)), + right: Box::new(Call { + function: Box::new(Var("pow2".into())), + type_arguments: vec![T::Int], + arguments: vec![BinaryOp { + op: Sub, + left: Box::new(Var("x".into())), + right: Box::new(Int(1)) + }] + }) + }), + r#type: T::Int, + }), + body: Box::new(Call { + function: Box::new(Var("pow2".into())), + type_arguments: vec![], + arguments: vec![Int(3)] + }) + }), + interpreter::Value::Int(8), + ); +} + +#[test] +fn copy_test() { + /* + let copy (newptr: *mut i32) (ptr: *mut i32) (len: i32) : Unit = + let for (i: i32) : Unit = + if i < len then + newptr[i] = ptr[i]; + for (i + 1) + else + () + in for 0 + in + let x = alloc(10) in + let y = alloc(10) in + x[0] = 5; + x[1] = 10; + x[2] = 15; + x[3] = 20; + copy(y, x, 4); + y[2] + */ + integration_test_success( + dyro_poc::ast!(LetFn { + name: "copy".into(), + args: vec![ + ("newptr".into(), T::MutPtr(Box::new(T::Int))), + ("ptr".into(), T::MutPtr(Box::new(T::Int))), + ("len".into(), T::Int), + ], + return_type: T::Unit, + value: Box::new(LetFn { + name: "for".into(), + args: vec![("i".into(), T::Int)], + return_type: T::Unit, + value: Box::new(If { + condition: Box::new(BinaryOp { + op: Lt, + left: Box::new(Var("i".into())), + right: Box::new(Var("len".into())), + }), + then: Box::new(Sequence { + first: Box::new(ArraySet { + array: Box::new(Var("newptr".into())), + index: Box::new(Var("i".into())), + value: Box::new(ArrayRead { + array: Box::new(Var("ptr".into())), + index: Box::new(Var("i".into())), + }), + }), + second: Box::new(Call { + function: Box::new(Var("for".into())), + type_arguments: vec![], + arguments: vec![BinaryOp { + op: Add, + left: Box::new(Var("i".into())), + right: Box::new(Int(1)), + }], + }), + }), + r#else: Box::new(Unit), + r#type: T::Unit, + }), + body: Box::new(Call { + function: Box::new(Var("for".into())), + type_arguments: vec![], + arguments: vec![Int(0)], + }) + }), + body: Box::new(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)], + }), + body: Box::new(Let { + binding: "y".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)], + }), + body: Box::new(seq! { + ArraySet { array: Box::new(Var("x".into())), index: Box::new(Int(0)), value: Box::new(Int(5)) }; + ArraySet { array: Box::new(Var("x".into())), index: Box::new(Int(1)), value: Box::new(Int(10)) }; + ArraySet { array: Box::new(Var("x".into())), index: Box::new(Int(2)), value: Box::new(Int(15)) }; + ArraySet { array: Box::new(Var("x".into())), index: Box::new(Int(3)), value: Box::new(Int(20)) }; + Call { + function: Box::new(Var("copy".into())), + type_arguments: vec![], + arguments: vec![ + Var("y".into()), + Var("x".into()), + Int(4), + ], + }; + ArrayRead { array: Box::new(Var("y".into())), index: Box::new(Int(2)) } + }) + }) + }) + }), + interpreter::Value::Int(15), + ); +} + +#[test] +fn dealloc_test() { + // let x = alloc(10) in dealloc(x, 10) + integration_test_success( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)], + }), + body: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Dealloc)), + type_arguments: vec![T::Int], + arguments: vec![Var("x".into()), Int(10)], + }), + }), + interpreter::Value::Unit, + ); +} + +#[test] +fn dealloc_wrong_size_test() { + // let x = alloc(10) in dealloc(x, 5) + integration_test_failure( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)], + }), + body: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Dealloc)), + type_arguments: vec![T::Int], + arguments: vec![Var("x".into()), Int(5)], + }), + }), + "Invalid size", + ); +} + +#[test] +fn dealloc_different_type_test() { + // let x = alloc(10) in dealloc(x, 40) + integration_test_success( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)], + }), + body: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Dealloc)), + type_arguments: vec![T::Bool], + arguments: vec![Var("x".into()), Int(40)], + }), + }), + interpreter::Value::Unit, + ); +} + +#[test] +fn read_after_dealloc_test() { + // let x = alloc(10) in (x[0] = 5; dealloc(x, 10); x[0]) + integration_test_failure( + dyro_poc::ast!(Let { + binding: "x".into(), + value: Box::new(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Alloc)), + type_arguments: vec![T::Int], + arguments: vec![Int(10)], + }), + body: Box::new(seq! { + ArraySet { array: Box::new(Var("x".into())), index: Box::new(Int(0)), value: Box::new(Int(5)) }; + Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Dealloc)), + type_arguments: vec![T::Int], + arguments: vec![Var("x".into()), Int(10)], + }; + ArrayRead { array: Box::new(Var("x".into())), index: Box::new(Int(0)) } + }), + }), + "Undefined", + ); +} + +#[test] +fn print_test() { + // print("hello") + integration_test_success( + dyro_poc::ast!(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Print)), + type_arguments: vec![T::String], + arguments: vec![String("hello".into())], + }), + interpreter::Value::Unit, + ); +} + +#[test] +fn print_bad_type_test() { + // print(1) + integration_test_failure( + dyro_poc::ast!(Call { + function: Box::new(SpecialFunction(crate::special::SpecialFunction::Print)), + type_arguments: vec![T::String], + arguments: vec![Int(1)], + }), + "Invalid type for Print", + ); +}