diff --git a/examples/basic/cast1.ngr b/examples/basic/cast1.ngr new file mode 100644 index 0000000..0dc2488 --- /dev/null +++ b/examples/basic/cast1.ngr @@ -0,0 +1,7 @@ +x8 = 5u8; +x16 = x8 + 1u16; +print x16; +x32 = x16 + 1u32; +print x32; +x64 = x32 + 1u64; +print x64; \ No newline at end of file diff --git a/examples/basic/cast2.ngr b/examples/basic/cast2.ngr new file mode 100644 index 0000000..4c61255 --- /dev/null +++ b/examples/basic/cast2.ngr @@ -0,0 +1,7 @@ +x8 = 5i8; +x16 = x8 - 1i16; +print x16; +x32 = x16 - 1i32; +print x32; +x64 = x32 - 1i64; +print x64; \ No newline at end of file diff --git a/src/backend/error.rs b/src/backend/error.rs index caa9e59..7509da3 100644 --- a/src/backend/error.rs +++ b/src/backend/error.rs @@ -1,4 +1,4 @@ -use crate::backend::runtime::RuntimeFunctionError; +use crate::{backend::runtime::RuntimeFunctionError, eval::PrimitiveType, ir::Type}; use codespan_reporting::diagnostic::Diagnostic; use cranelift_codegen::{isa::LookupError, settings::SetError, CodegenError}; use cranelift_module::ModuleError; @@ -39,6 +39,8 @@ pub enum BackendError { LookupError(#[from] LookupError), #[error(transparent)] Write(#[from] cranelift_object::object::write::Error), + #[error("Invalid type cast from {from} to {to}")] + InvalidTypeCast { from: PrimitiveType, to: Type }, } impl From for Diagnostic { @@ -64,6 +66,9 @@ impl From for Diagnostic { BackendError::Write(me) => { Diagnostic::error().with_message(format!("Cranelift object write error: {}", me)) } + BackendError::InvalidTypeCast { from, to } => Diagnostic::error().with_message( + format!("Internal error trying to cast from {} to {}", from, to), + ), } } } @@ -103,6 +108,17 @@ impl PartialEq for BackendError { BackendError::Write(b) => a == b, _ => false, }, + + BackendError::InvalidTypeCast { + from: from1, + to: to1, + } => match other { + BackendError::InvalidTypeCast { + from: from2, + to: to2, + } => from1 == from2 && to1 == to2, + _ => false, + }, } } } diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index 18f040f..d9e8701 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use crate::ir::{Expression, Primitive, Program, Statement, Value, ValueOrRef}; +use crate::eval::PrimitiveType; +use crate::ir::{Expression, Primitive, Program, Statement, Type, Value, ValueOrRef}; use crate::syntax::ConstantType; use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir::{ @@ -307,6 +308,70 @@ impl Expression { Err(BackendError::VariableLookupFailure(name)) } + Expression::Cast(_, target_type, expr) => { + let (val, val_type) = + expr.into_crane(builder, local_variables, global_variables)?; + + match (val_type, &target_type) { + (ConstantType::I8, Type::Primitive(PrimitiveType::I8)) => Ok((val, val_type)), + (ConstantType::I8, Type::Primitive(PrimitiveType::I16)) => { + Ok((builder.ins().sextend(types::I16, val), ConstantType::I16)) + } + (ConstantType::I8, Type::Primitive(PrimitiveType::I32)) => { + Ok((builder.ins().sextend(types::I32, val), ConstantType::I32)) + } + (ConstantType::I8, Type::Primitive(PrimitiveType::I64)) => { + Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)) + } + + (ConstantType::I16, Type::Primitive(PrimitiveType::I16)) => Ok((val, val_type)), + (ConstantType::I16, Type::Primitive(PrimitiveType::I32)) => { + Ok((builder.ins().sextend(types::I32, val), ConstantType::I32)) + } + (ConstantType::I16, Type::Primitive(PrimitiveType::I64)) => { + Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)) + } + + (ConstantType::I32, Type::Primitive(PrimitiveType::I32)) => Ok((val, val_type)), + (ConstantType::I32, Type::Primitive(PrimitiveType::I64)) => { + Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)) + } + + (ConstantType::I64, Type::Primitive(PrimitiveType::I64)) => Ok((val, val_type)), + + (ConstantType::U8, Type::Primitive(PrimitiveType::U8)) => Ok((val, val_type)), + (ConstantType::U8, Type::Primitive(PrimitiveType::U16)) => { + Ok((builder.ins().uextend(types::I16, val), ConstantType::U16)) + } + (ConstantType::U8, Type::Primitive(PrimitiveType::U32)) => { + Ok((builder.ins().uextend(types::I32, val), ConstantType::U32)) + } + (ConstantType::U8, Type::Primitive(PrimitiveType::U64)) => { + Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)) + } + + (ConstantType::U16, Type::Primitive(PrimitiveType::U16)) => Ok((val, val_type)), + (ConstantType::U16, Type::Primitive(PrimitiveType::U32)) => { + Ok((builder.ins().uextend(types::I32, val), ConstantType::U32)) + } + (ConstantType::U16, Type::Primitive(PrimitiveType::U64)) => { + Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)) + } + + (ConstantType::U32, Type::Primitive(PrimitiveType::U32)) => Ok((val, val_type)), + (ConstantType::U32, Type::Primitive(PrimitiveType::U64)) => { + Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)) + } + + (ConstantType::U64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)), + + _ => Err(BackendError::InvalidTypeCast { + from: val_type.into(), + to: target_type, + }), + } + } + Expression::Primitive(_, prim, mut vals) => { let mut values = vec![]; let mut first_type = None; diff --git a/src/bin/ngrc.rs b/src/bin/ngrc.rs index 821b0e2..e486ec0 100644 --- a/src/bin/ngrc.rs +++ b/src/bin/ngrc.rs @@ -17,7 +17,7 @@ fn main() { let args = CommandLineArguments::parse(); let mut compiler = ngr::Compiler::default(); - let output_file = args.output.unwrap_or("output.o".to_string()); + let output_file = args.output.unwrap_or_else(|| "output.o".to_string()); if let Some(bytes) = compiler.compile(&args.file) { std::fs::write(&output_file, bytes) diff --git a/src/eval.rs b/src/eval.rs index cf77f74..a593a63 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -35,11 +35,13 @@ //! mod env; mod primop; +mod primtype; mod value; use cranelift_module::ModuleError; pub use env::{EvalEnvironment, LookupError}; pub use primop::PrimOpError; +pub use primtype::PrimitiveType; pub use value::Value; use crate::backend::BackendError; diff --git a/src/eval/primop.rs b/src/eval/primop.rs index 5fa3a1c..c6bdc05 100644 --- a/src/eval/primop.rs +++ b/src/eval/primop.rs @@ -1,3 +1,4 @@ +use crate::eval::primtype::PrimitiveType; use crate::eval::value::Value; /// Errors that can occur running primitive operations in the evaluators. @@ -22,6 +23,13 @@ pub enum PrimOpError { BadArgCount(String, usize), #[error("Unknown primitive operation {0}")] UnknownPrimOp(String), + #[error("Unsafe cast from {from} to {to}")] + UnsafeCast { + from: PrimitiveType, + to: PrimitiveType, + }, + #[error("Unknown primitive type {0}")] + UnknownPrimType(String), } // Implementing primitives in an interpreter like this is *super* tedious, diff --git a/src/eval/primtype.rs b/src/eval/primtype.rs new file mode 100644 index 0000000..87559a1 --- /dev/null +++ b/src/eval/primtype.rs @@ -0,0 +1,121 @@ +use crate::{ + eval::{PrimOpError, Value}, + syntax::ConstantType, +}; +use std::{fmt::Display, str::FromStr}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PrimitiveType { + U8, + U16, + U32, + U64, + I8, + I16, + I32, + I64, +} + +impl Display for PrimitiveType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PrimitiveType::I8 => write!(f, "i8"), + PrimitiveType::I16 => write!(f, "i16"), + PrimitiveType::I32 => write!(f, "i32"), + PrimitiveType::I64 => write!(f, "i64"), + PrimitiveType::U8 => write!(f, "u8"), + PrimitiveType::U16 => write!(f, "u16"), + PrimitiveType::U32 => write!(f, "u32"), + PrimitiveType::U64 => write!(f, "u64"), + } + } +} + +impl<'a> From<&'a Value> for PrimitiveType { + fn from(value: &Value) -> Self { + match value { + Value::I8(_) => PrimitiveType::I8, + Value::I16(_) => PrimitiveType::I16, + Value::I32(_) => PrimitiveType::I32, + Value::I64(_) => PrimitiveType::I64, + Value::U8(_) => PrimitiveType::U8, + Value::U16(_) => PrimitiveType::U16, + Value::U32(_) => PrimitiveType::U32, + Value::U64(_) => PrimitiveType::U64, + } + } +} + +impl From for PrimitiveType { + fn from(value: ConstantType) -> Self { + match value { + ConstantType::I8 => PrimitiveType::I8, + ConstantType::I16 => PrimitiveType::I16, + ConstantType::I32 => PrimitiveType::I32, + ConstantType::I64 => PrimitiveType::I64, + ConstantType::U8 => PrimitiveType::U8, + ConstantType::U16 => PrimitiveType::U16, + ConstantType::U32 => PrimitiveType::U32, + ConstantType::U64 => PrimitiveType::U64, + } + } +} + +impl FromStr for PrimitiveType { + type Err = PrimOpError; + + fn from_str(s: &str) -> Result { + match s { + "i8" => Ok(PrimitiveType::I8), + "i16" => Ok(PrimitiveType::I16), + "i32" => Ok(PrimitiveType::I32), + "i64" => Ok(PrimitiveType::I64), + "u8" => Ok(PrimitiveType::U8), + "u16" => Ok(PrimitiveType::U16), + "u32" => Ok(PrimitiveType::U32), + "u64" => Ok(PrimitiveType::U64), + _ => Err(PrimOpError::UnknownPrimType(s.to_string())), + } + } +} + +impl PrimitiveType { + /// Try to cast the given value to this type, returning the new value. + /// + /// Returns an error if the cast is not safe *in* *general*. This means that + /// this function will error even if the number will actually fit in the target + /// type, but it would not be generally safe to cast a member of the given + /// type to the target type. (So, for example, "1i64" is a number that could + /// work as a "u64", but since negative numbers wouldn't work, a cast from + /// "1i64" to "u64" will fail.) + pub fn safe_cast(&self, source: &Value) -> Result { + match (self, source) { + (PrimitiveType::U8, Value::U8(x)) => Ok(Value::U8(*x)), + (PrimitiveType::U16, Value::U8(x)) => Ok(Value::U16(*x as u16)), + (PrimitiveType::U16, Value::U16(x)) => Ok(Value::U16(*x)), + (PrimitiveType::U32, Value::U8(x)) => Ok(Value::U32(*x as u32)), + (PrimitiveType::U32, Value::U16(x)) => Ok(Value::U32(*x as u32)), + (PrimitiveType::U32, Value::U32(x)) => Ok(Value::U32(*x)), + (PrimitiveType::U64, Value::U8(x)) => Ok(Value::U64(*x as u64)), + (PrimitiveType::U64, Value::U16(x)) => Ok(Value::U64(*x as u64)), + (PrimitiveType::U64, Value::U32(x)) => Ok(Value::U64(*x as u64)), + (PrimitiveType::U64, Value::U64(x)) => Ok(Value::U64(*x)), + + (PrimitiveType::I8, Value::I8(x)) => Ok(Value::I8(*x)), + (PrimitiveType::I16, Value::I8(x)) => Ok(Value::I16(*x as i16)), + (PrimitiveType::I16, Value::I16(x)) => Ok(Value::I16(*x)), + (PrimitiveType::I32, Value::I8(x)) => Ok(Value::I32(*x as i32)), + (PrimitiveType::I32, Value::I16(x)) => Ok(Value::I32(*x as i32)), + (PrimitiveType::I32, Value::I32(x)) => Ok(Value::I32(*x)), + (PrimitiveType::I64, Value::I8(x)) => Ok(Value::I64(*x as i64)), + (PrimitiveType::I64, Value::I16(x)) => Ok(Value::I64(*x as i64)), + (PrimitiveType::I64, Value::I32(x)) => Ok(Value::I64(*x as i64)), + (PrimitiveType::I64, Value::I64(x)) => Ok(Value::I64(*x)), + + _ => Err(PrimOpError::UnsafeCast { + from: source.into(), + to: *self, + }), + } + } +} diff --git a/src/ir/ast.rs b/src/ir/ast.rs index e659b8d..4ce9ab2 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -1,10 +1,14 @@ -use crate::syntax::{self, ConstantType, Location}; +use crate::{ + eval::PrimitiveType, + syntax::{self, ConstantType, Location}, +}; use internment::ArcIntern; use pretty::{DocAllocator, Pretty}; use proptest::{ prelude::Arbitrary, strategy::{BoxedStrategy, Strategy}, }; +use std::fmt; /// We're going to represent variables as interned strings. /// @@ -115,6 +119,7 @@ where pub enum Expression { Value(Location, Value), Reference(Location, Variable), + Cast(Location, Type, ValueOrRef), Primitive(Location, Primitive, Vec), } @@ -127,6 +132,11 @@ where match self { Expression::Value(_, val) => val.pretty(allocator), Expression::Reference(_, var) => allocator.text(var.as_ref().to_string()), + Expression::Cast(_, t, e) => allocator + .text("<") + .append(t.pretty(allocator)) + .append(allocator.text(">")) + .append(e.pretty(allocator)), Expression::Primitive(_, op, exprs) if exprs.len() == 1 => { op.pretty(allocator).append(exprs[0].pretty(allocator)) } @@ -283,3 +293,28 @@ where } } } + +#[derive(Debug, Eq, PartialEq)] +pub enum Type { + Primitive(PrimitiveType), +} + +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Type +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { + match self { + Type::Primitive(pt) => allocator.text(format!("{}", pt)), + } + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Primitive(pt) => pt.fmt(f), + } + } +} diff --git a/src/ir/eval.rs b/src/ir/eval.rs index 4830ce3..8d46368 100644 --- a/src/ir/eval.rs +++ b/src/ir/eval.rs @@ -1,7 +1,7 @@ use crate::eval::{EvalEnvironment, EvalError, Value}; use crate::ir::{Expression, Program, Statement}; -use super::{Primitive, ValueOrRef}; +use super::{Primitive, Type, ValueOrRef}; impl Program { /// Evaluate the program, returning either an error or a string containing everything @@ -47,6 +47,19 @@ impl Expression { Expression::Reference(_, n) => Ok(env.lookup(n.clone())?), + Expression::Cast(_, t, valref) => { + let value = match valref { + ValueOrRef::Ref(_, n) => env.lookup(n.clone())?, + ValueOrRef::Value(loc, val) => { + Expression::Value(loc.clone(), val.clone()).eval(env)? + } + }; + + match t { + Type::Primitive(pt) => Ok(pt.safe_cast(&value)?), + } + } + Expression::Primitive(_, op, args) => { let mut arg_values = Vec::with_capacity(args.len()); diff --git a/src/ir/from_syntax.rs b/src/ir/from_syntax.rs index 88d0813..3f174c6 100644 --- a/src/ir/from_syntax.rs +++ b/src/ir/from_syntax.rs @@ -1,6 +1,8 @@ use internment::ArcIntern; +use std::str::FromStr; use std::sync::atomic::AtomicUsize; +use crate::eval::PrimitiveType; use crate::ir::ast as ir; use crate::syntax; @@ -104,7 +106,20 @@ impl syntax::Expression { (vec![], ValueOrRef::Ref(loc, ArcIntern::new(name))) } - syntax::Expression::Cast(_, _, _) => unimplemented!(), + syntax::Expression::Cast(loc, t, expr) => { + let (mut prereqs, new_expr) = expr.rebind(base_name); + let new_name = gensym(base_name); + prereqs.push(ir::Statement::Binding( + loc.clone(), + new_name.clone(), + ir::Expression::Cast( + loc.clone(), + ir::Type::Primitive(PrimitiveType::from_str(&t).unwrap()), + new_expr, + ), + )); + (prereqs, ValueOrRef::Ref(loc, new_name)) + } // Primitive expressions are where we do the real work. syntax::Expression::Primitive(loc, prim, mut expressions) => { diff --git a/src/syntax/arbitrary.rs b/src/syntax/arbitrary.rs index 51cec90..1fd9b8d 100644 --- a/src/syntax/arbitrary.rs +++ b/src/syntax/arbitrary.rs @@ -124,7 +124,30 @@ impl Arbitrary for Expression { Union::new([value_strategy, reference_strategy]).boxed() }; - leaf_strategy + let cast_strategy = if let Some(bigger_type) = target_type { + let mut smaller_types = bigger_type.safe_casts_to(); + + if smaller_types.is_empty() { + leaf_strategy + } else { + let duplicated_env = defined_variables.clone(); + let cast_exp = |t, e| Expression::Cast(Location::manufactured(), t, Box::new(e)); + + let smaller_strats: Vec> = smaller_types + .drain(..) + .map(|t| { + Expression::arbitrary_with((Some(duplicated_env.clone()), Some(t))) + .prop_map(move |e| cast_exp(t.name(), e)) + .boxed() + }) + .collect(); + Union::new(smaller_strats).boxed() + } + } else { + leaf_strategy + }; + + cast_strategy .prop_recursive(3, 64, 2, move |inner| { (select(OPERATORS), proptest::collection::vec(inner, 2)).prop_map( move |((operator, arg_count), mut exprs)| { diff --git a/src/syntax/eval.rs b/src/syntax/eval.rs index 9122dd9..f952e45 100644 --- a/src/syntax/eval.rs +++ b/src/syntax/eval.rs @@ -1,7 +1,8 @@ use internment::ArcIntern; -use crate::eval::{EvalEnvironment, EvalError, Value}; +use crate::eval::{EvalEnvironment, EvalError, PrimitiveType, Value}; use crate::syntax::{ConstantType, Expression, Program, Statement}; +use std::str::FromStr; impl Program { /// Evaluate the program, returning either an error or what it prints out when run. @@ -59,7 +60,11 @@ impl Expression { Expression::Reference(_, n) => Ok(env.lookup(ArcIntern::new(n.clone()))?), - Expression::Cast(_, _, _) => unimplemented!(), + Expression::Cast(_, target, expr) => { + let target_type = PrimitiveType::from_str(target)?; + let value = expr.eval(env)?; + Ok(target_type.safe_cast(&value)?) + } Expression::Primitive(_, op, args) => { let mut arg_values = Vec::with_capacity(args.len()); diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 682e266..a545435 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -175,6 +175,34 @@ impl ConstantType { ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 ) } + + /// Return the set of types that can be safely casted into this type. + pub fn safe_casts_to(self) -> Vec { + match self { + ConstantType::I8 => vec![], + ConstantType::I16 => vec![ConstantType::I8], + ConstantType::I32 => vec![ConstantType::I16, ConstantType::I8], + ConstantType::I64 => vec![ConstantType::I32, ConstantType::I16, ConstantType::I8], + ConstantType::U8 => vec![], + ConstantType::U16 => vec![ConstantType::U8], + ConstantType::U32 => vec![ConstantType::U16, ConstantType::U8], + ConstantType::U64 => vec![ConstantType::U32, ConstantType::U16, ConstantType::U8], + } + } + + /// Return the name of the given type, as a string + pub fn name(&self) -> String { + match self { + ConstantType::I8 => "i8".to_string(), + ConstantType::I16 => "i16".to_string(), + ConstantType::I32 => "i32".to_string(), + ConstantType::I64 => "i64".to_string(), + ConstantType::U8 => "u8".to_string(), + ConstantType::U16 => "u16".to_string(), + ConstantType::U32 => "u32".to_string(), + ConstantType::U64 => "u64".to_string(), + } + } } #[derive(Debug, Error, PartialEq)] diff --git a/src/syntax/validate.rs b/src/syntax/validate.rs index feb4ea4..676ff88 100644 --- a/src/syntax/validate.rs +++ b/src/syntax/validate.rs @@ -127,7 +127,7 @@ impl Expression { vec![Error::UnboundVariable(loc.clone(), var.clone())], vec![], ), - Expression::Cast(_, _, _) => unimplemented!(), + Expression::Cast(_, _, expr) => expr.validate(variable_map), Expression::Primitive(_, _, args) => { let mut errors = vec![]; let mut warnings = vec![];