Clean up primitive handling, finally.

This commit is contained in:
2024-04-16 16:20:31 -07:00
parent 763a895285
commit 7d4f182a67
19 changed files with 399 additions and 550 deletions

View File

@@ -374,43 +374,6 @@ impl<M: Module> Backend<M> {
} }
} }
Expression::Primitive(_, ret_type, prim, mut vals) => {
let mut values = vec![];
let mut first_type = None;
for val in vals.drain(..) {
let (compiled, compiled_type) =
self.compile_value_or_ref(val, variables, builder)?;
if let Some(leftmost_type) = first_type {
assert_eq!(leftmost_type, compiled_type);
} else {
first_type = Some(compiled_type);
}
values.push(compiled);
}
let first_type = first_type.expect("primitive op has at least one argument");
// then we just need to tell Cranelift how to do each of our primitives! Much
// like Statements, above, we probably want to eventually shuffle this off into
// a separate function (maybe something off `Primitive`), but for now it's simple
// enough that we just do the `match` here.
match prim {
Primitive::Plus => Ok((builder.ins().iadd(values[0], values[1]), first_type)),
Primitive::Minus if values.len() == 2 => {
Ok((builder.ins().isub(values[0], values[1]), first_type))
}
Primitive::Minus => Ok((builder.ins().ineg(values[0]), first_type)),
Primitive::Times => Ok((builder.ins().imul(values[0], values[1]), first_type)),
Primitive::Divide if ret_type.is_signed() => {
Ok((builder.ins().sdiv(values[0], values[1]), first_type))
}
Primitive::Divide => Ok((builder.ins().udiv(values[0], values[1]), first_type)),
}
}
Expression::Construct(_, ty, _, fields) => { Expression::Construct(_, ty, _, fields) => {
let Type::Structure(type_fields) = ty else { let Type::Structure(type_fields) = ty else {
panic!("Got to backend with non-structure type in structure construction?!"); panic!("Got to backend with non-structure type in structure construction?!");
@@ -493,60 +456,6 @@ impl<M: Module> Backend<M> {
} }
}, },
Expression::Print(_, var) => {
// Get the output buffer (or null) from our general compilation context.
let buffer_ptr = self.output_buffer_ptr();
let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64);
// Get a reference to the string we want to print.
let var_name = match var {
ValueOrRef::Ref(_, _, ref name) => name.as_ref(),
ValueOrRef::Value(_, _, _) => "<unknown>",
};
let string_data_id = self.string_reference(var_name)?;
let local_name_ref = self
.module
.declare_data_in_func(string_data_id, builder.func);
let name_ptr = builder.ins().symbol_value(types::I64, local_name_ref);
let var_type = var.type_of();
let (val, _) = self.compile_value_or_ref(var, variables, builder)?;
let (repr_val, casted_val) = match var_type {
Type::Structure(_) => (ConstantType::I64 as i64, val),
Type::Function(_, _) => (ConstantType::I64 as i64, val),
Type::Primitive(pt) => {
let constant_type = pt.into();
let new_val = match constant_type {
ConstantType::U64 | ConstantType::I64 | ConstantType::Void => val,
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => {
builder.ins().sextend(types::I64, val)
}
ConstantType::U8 | ConstantType::U16 | ConstantType::U32 => {
builder.ins().uextend(types::I64, val)
}
};
(constant_type as i64, new_val)
}
};
let vtype_repr = builder.ins().iconst(types::I64, repr_val);
// Finally, we can generate the call to print.
let print_func_ref = self.runtime_functions.include_runtime_function(
"print",
&mut self.module,
builder.func,
)?;
builder.ins().call(
print_func_ref,
&[buffer_ptr, name_ptr, vtype_repr, casted_val],
);
Ok((builder.ins().iconst(types::I64, 0), VOID_REPR_TYPE))
}
Expression::Bind(_, name, _, expr) => { Expression::Bind(_, name, _, expr) => {
let (value, value_type) = self.compile_expression(*expr, variables, builder)?; let (value, value_type) = self.compile_expression(*expr, variables, builder)?;
let variable = self.generate_local(); let variable = self.generate_local();
@@ -576,8 +485,15 @@ impl<M: Module> Backend<M> {
Ok((value, value_type)) Ok((value, value_type))
} }
Expression::Call(_, _, function, args) => { Expression::Call(_, final_type, function, args) => {
let (arguments, _argument_types): (Vec<_>, Vec<_>) = args // Get a reference to the string we want to print.
let var_name = match args[0] {
ValueOrRef::Ref(_, _, ref name) => name.to_string(),
ValueOrRef::Value(_, _, _) => "<unknown>".to_string(),
ValueOrRef::Primitive(_, _, n) => format!("<primitive {}>", n),
};
let var_type = args[0].type_of();
let (arguments, argument_types): (Vec<_>, Vec<_>) = args
.into_iter() .into_iter()
.map(|x| self.compile_value_or_ref(x, variables, builder)) .map(|x| self.compile_value_or_ref(x, variables, builder))
.collect::<Result<Vec<(_, _)>, BackendError>>()? .collect::<Result<Vec<(_, _)>, BackendError>>()?
@@ -608,6 +524,109 @@ impl<M: Module> Backend<M> {
} }
} }
} }
ValueOrRef::Primitive(_, _, prim) => match prim {
Primitive::Plus => {
assert_eq!(2, arguments.len());
Ok((
builder.ins().iadd(arguments[0], arguments[1]),
argument_types[0],
))
}
Primitive::Minus => {
assert_eq!(2, arguments.len());
Ok((
builder.ins().isub(arguments[0], arguments[1]),
argument_types[0],
))
}
Primitive::Times => {
assert_eq!(2, arguments.len());
Ok((
builder.ins().imul(arguments[0], arguments[1]),
argument_types[0],
))
}
Primitive::Divide if final_type.is_signed() => {
assert_eq!(2, arguments.len());
Ok((
builder.ins().sdiv(arguments[0], arguments[1]),
argument_types[0],
))
}
Primitive::Divide => {
assert_eq!(2, arguments.len());
Ok((
builder.ins().udiv(arguments[0], arguments[1]),
argument_types[0],
))
}
Primitive::Negate => {
assert_eq!(1, arguments.len());
Ok((
builder.ins().ineg(arguments[0]),
argument_types[0],
))
}
Primitive::Print => {
// Get the output buffer (or null) from our general compilation context.
let buffer_ptr = self.output_buffer_ptr();
let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64);
assert_eq!(1, arguments.len());
let string_data_id = self.string_reference(&var_name)?;
let local_name_ref = self
.module
.declare_data_in_func(string_data_id, builder.func);
let name_ptr = builder.ins().symbol_value(types::I64, local_name_ref);
let (repr_val, casted_val) = match var_type {
Type::Structure(_) => (ConstantType::I64 as i64, arguments[0]),
Type::Function(_, _) => (ConstantType::I64 as i64, arguments[0]),
Type::Primitive(pt) => {
let constant_type = pt.into();
let new_val = match constant_type {
ConstantType::U64
| ConstantType::I64
| ConstantType::Void => arguments[0],
ConstantType::I8
| ConstantType::I16
| ConstantType::I32 => {
builder.ins().sextend(types::I64, arguments[0])
}
ConstantType::U8
| ConstantType::U16
| ConstantType::U32 => {
builder.ins().uextend(types::I64, arguments[0])
}
};
(constant_type as i64, new_val)
}
};
let vtype_repr = builder.ins().iconst(types::I64, repr_val);
// Finally, we can generate the call to print.
let print_func_ref = self.runtime_functions.include_runtime_function(
"print",
&mut self.module,
builder.func,
)?;
builder.ins().call(
print_func_ref,
&[buffer_ptr, name_ptr, vtype_repr, casted_val],
);
Ok((builder.ins().iconst(types::I64, 0), VOID_REPR_TYPE))
}
},
} }
} }
} }
@@ -668,6 +687,9 @@ impl<M: Module> Backend<M> {
Ok((value, *ctype)) Ok((value, *ctype))
} }
}, },
ValueOrRef::Primitive(_, _, _) => {
unimplemented!()
}
} }
} }
} }

View File

@@ -87,7 +87,7 @@ macro_rules! run_op {
impl<IR: Clone> Value<IR> { impl<IR: Clone> Value<IR> {
fn unary_op(operation: &str, value: &Value<IR>) -> Result<Value<IR>, PrimOpError<IR>> { fn unary_op(operation: &str, value: &Value<IR>) -> Result<Value<IR>, PrimOpError<IR>> {
match operation { match operation {
"-" => match value { "negate" => match value {
Value::I8(x) => Ok(Value::I8(x.wrapping_neg())), Value::I8(x) => Ok(Value::I8(x.wrapping_neg())),
Value::I16(x) => Ok(Value::I16(x.wrapping_neg())), Value::I16(x) => Ok(Value::I16(x.wrapping_neg())),
Value::I32(x) => Ok(Value::I32(x.wrapping_neg())), Value::I32(x) => Ok(Value::I32(x.wrapping_neg())),
@@ -192,9 +192,10 @@ impl<IR: Clone> Value<IR> {
right.clone(), right.clone(),
)), )),
}, },
Value::Closure(_, _, _, _) | Value::Structure(_, _) | Value::Void => { Value::Closure(_, _, _, _)
Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone())) | Value::Structure(_, _)
} | Value::Primitive(_)
| Value::Void => Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone())),
} }
} }

View File

@@ -42,12 +42,17 @@ impl Display for PrimitiveType {
} }
} }
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Debug, PartialEq, thiserror::Error)] #[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub enum ValuePrimitiveTypeError { pub enum ValuePrimitiveTypeError {
#[error("Could not convert function value to primitive type (possible function name: {0:?}")] #[error("Could not convert function value to primitive type (possible function name: {0:?}")]
CannotConvertFunction(Option<String>), CannotConvertFunction(Option<String>),
#[error("Could not convert structure value to primitive type (possible function name: {0:?}")] #[error("Could not convert structure value to primitive type (possible function name: {0:?}")]
CannotConvertStructure(Option<String>), CannotConvertStructure(Option<String>),
#[error(
"Could not convert primitive operator to primitive type (possible function name: {0:?}"
)]
CannotConvertPrimitive(String),
} }
impl<'a, IR> TryFrom<&'a Value<IR>> for PrimitiveType { impl<'a, IR> TryFrom<&'a Value<IR>> for PrimitiveType {
@@ -72,6 +77,9 @@ impl<'a, IR> TryFrom<&'a Value<IR>> for PrimitiveType {
Value::Structure(name, _) => Err(ValuePrimitiveTypeError::CannotConvertStructure( Value::Structure(name, _) => Err(ValuePrimitiveTypeError::CannotConvertStructure(
name.as_ref().map(|x| (**x).clone()), name.as_ref().map(|x| (**x).clone()),
)), )),
Value::Primitive(prim) => Err(ValuePrimitiveTypeError::CannotConvertPrimitive(
prim.clone(),
)),
} }
} }
} }

View File

@@ -31,6 +31,7 @@ pub enum Value<IR> {
Option<ArcIntern<String>>, Option<ArcIntern<String>>,
HashMap<ArcIntern<String>, Value<IR>>, HashMap<ArcIntern<String>, Value<IR>>,
), ),
Primitive(String),
} }
impl<IR: Clone> Value<IR> { impl<IR: Clone> Value<IR> {
@@ -59,8 +60,13 @@ impl<IR: Clone> Value<IR> {
name.clone(), name.clone(),
fields.iter().map(|(n, v)| (n.clone(), v.strip())).collect(), fields.iter().map(|(n, v)| (n.clone(), v.strip())).collect(),
), ),
Value::Primitive(name) => Value::Primitive(name.clone()),
} }
} }
pub fn primitive<S: ToString>(name: S) -> Self {
Value::Primitive(name.to_string())
}
} }
fn format_value<IR>(value: &Value<IR>, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn format_value<IR>(value: &Value<IR>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -87,6 +93,7 @@ fn format_value<IR>(value: &Value<IR>, f: &mut fmt::Formatter<'_>) -> fmt::Resul
} }
write!(f, " }}") write!(f, " }}")
} }
Value::Primitive(n) => write!(f, "{}", n),
} }
} }
@@ -165,6 +172,10 @@ impl<IR1, IR2> PartialEq<Value<IR2>> for Value<IR1> {
} }
_ => false, _ => false,
}, },
Value::Primitive(n1) => match other {
Value::Primitive(n2) => n1 == n2,
_ => false,
},
} }
} }
} }

View File

@@ -347,12 +347,25 @@ fn generate_random_expression(
Some((operator, arg_count)) => { Some((operator, arg_count)) => {
let primop = Primitive::from_str(operator).expect("chose valid primitive"); let primop = Primitive::from_str(operator).expect("chose valid primitive");
let mut args = vec![base_expr]; let mut args = vec![base_expr];
let mut argtys = vec![];
while args.len() < *arg_count { while args.len() < *arg_count {
args.push(generate_random_valueref(rng, env, Some(primty))); args.push(generate_random_valueref(rng, env, Some(primty)));
argtys.push(Type::Primitive(primty));
} }
Expression::Primitive(Location::manufactured(), out_type, primop, args) let primtype = Type::Function(argtys, Box::new(Type::Primitive(primty)));
Expression::Call(
Location::manufactured(),
out_type,
Box::new(ValueOrRef::Primitive(
Location::manufactured(),
primtype,
primop,
)),
args,
)
} }
}, },
Type::Structure(_) => unimplemented!(), Type::Structure(_) => unimplemented!(),
@@ -377,9 +390,19 @@ fn generate_random_expression(
} else { } else {
let (variable, var_type) = possible_variables.choose(rng).unwrap(); let (variable, var_type) = possible_variables.choose(rng).unwrap();
Expression::Print( Expression::Call(
Location::manufactured(), Location::manufactured(),
ValueOrRef::Ref(Location::manufactured(), var_type.clone(), variable.clone()), Type::void(),
Box::new(ValueOrRef::Primitive(
Location::manufactured(),
Type::void(),
Primitive::Print,
)),
vec![ValueOrRef::Ref(
Location::manufactured(),
var_type.clone(),
variable.clone(),
)],
) )
} }
} }

View File

@@ -106,7 +106,6 @@ impl<T: Clone + TypeWithVoid + TypeWithFunction> TopLevel<T> {
pub enum Expression<Type> { pub enum Expression<Type> {
Atomic(ValueOrRef<Type>), Atomic(ValueOrRef<Type>),
Cast(Location, Type, ValueOrRef<Type>), Cast(Location, Type, ValueOrRef<Type>),
Primitive(Location, Type, Primitive, Vec<ValueOrRef<Type>>),
Construct( Construct(
Location, Location,
Type, Type,
@@ -115,7 +114,6 @@ pub enum Expression<Type> {
), ),
FieldRef(Location, Type, Type, ValueOrRef<Type>, ArcIntern<String>), FieldRef(Location, Type, Type, ValueOrRef<Type>, ArcIntern<String>),
Block(Location, Type, Vec<Expression<Type>>), Block(Location, Type, Vec<Expression<Type>>),
Print(Location, ValueOrRef<Type>),
Call(Location, Type, Box<ValueOrRef<Type>>, Vec<ValueOrRef<Type>>), Call(Location, Type, Box<ValueOrRef<Type>>, Vec<ValueOrRef<Type>>),
Bind(Location, Variable, Type, Box<Expression<Type>>), Bind(Location, Variable, Type, Box<Expression<Type>>),
} }
@@ -127,11 +125,9 @@ impl<Type: Clone + TypeWithVoid> Expression<Type> {
match self { match self {
Expression::Atomic(x) => x.type_of(), Expression::Atomic(x) => x.type_of(),
Expression::Cast(_, t, _) => t.clone(), Expression::Cast(_, t, _) => t.clone(),
Expression::Primitive(_, t, _, _) => t.clone(),
Expression::Construct(_, t, _, _) => t.clone(), Expression::Construct(_, t, _, _) => t.clone(),
Expression::FieldRef(_, t, _, _, _) => t.clone(), Expression::FieldRef(_, t, _, _, _) => t.clone(),
Expression::Block(_, t, _) => t.clone(), Expression::Block(_, t, _) => t.clone(),
Expression::Print(_, _) => Type::void(),
Expression::Call(_, t, _, _) => t.clone(), Expression::Call(_, t, _, _) => t.clone(),
Expression::Bind(_, _, _, _) => Type::void(), Expression::Bind(_, _, _, _) => Type::void(),
} }
@@ -142,12 +138,11 @@ impl<Type: Clone + TypeWithVoid> Expression<Type> {
match self { match self {
Expression::Atomic(ValueOrRef::Ref(l, _, _)) => l, Expression::Atomic(ValueOrRef::Ref(l, _, _)) => l,
Expression::Atomic(ValueOrRef::Value(l, _, _)) => l, Expression::Atomic(ValueOrRef::Value(l, _, _)) => l,
Expression::Atomic(ValueOrRef::Primitive(l, _, _)) => l,
Expression::Cast(l, _, _) => l, Expression::Cast(l, _, _) => l,
Expression::Primitive(l, _, _, _) => l,
Expression::Construct(l, _, _, _) => l, Expression::Construct(l, _, _, _) => l,
Expression::FieldRef(l, _, _, _, _) => l, Expression::FieldRef(l, _, _, _, _) => l,
Expression::Block(l, _, _) => l, Expression::Block(l, _, _) => l,
Expression::Print(l, _) => l,
Expression::Call(l, _, _, _) => l, Expression::Call(l, _, _, _) => l,
Expression::Bind(l, _, _, _) => l, Expression::Bind(l, _, _, _) => l,
} }
@@ -166,6 +161,8 @@ pub enum Primitive {
Minus, Minus,
Times, Times,
Divide, Divide,
Print,
Negate,
} }
impl FromStr for Primitive { impl FromStr for Primitive {
@@ -177,6 +174,8 @@ impl FromStr for Primitive {
"-" => Ok(Primitive::Minus), "-" => Ok(Primitive::Minus),
"*" => Ok(Primitive::Times), "*" => Ok(Primitive::Times),
"/" => Ok(Primitive::Divide), "/" => Ok(Primitive::Divide),
"print" => Ok(Primitive::Print),
"negate" => Ok(Primitive::Negate),
_ => Err(format!("Illegal primitive {}", value)), _ => Err(format!("Illegal primitive {}", value)),
} }
} }
@@ -191,6 +190,7 @@ impl FromStr for Primitive {
pub enum ValueOrRef<Type> { pub enum ValueOrRef<Type> {
Value(Location, Type, Value), Value(Location, Type, Value),
Ref(Location, Type, ArcIntern<String>), Ref(Location, Type, ArcIntern<String>),
Primitive(Location, Type, Primitive),
} }
impl<Type: Clone> ValueOrRef<Type> { impl<Type: Clone> ValueOrRef<Type> {
@@ -198,6 +198,7 @@ impl<Type: Clone> ValueOrRef<Type> {
match self { match self {
ValueOrRef::Ref(_, t, _) => t.clone(), ValueOrRef::Ref(_, t, _) => t.clone(),
ValueOrRef::Value(_, t, _) => t.clone(), ValueOrRef::Value(_, t, _) => t.clone(),
ValueOrRef::Primitive(_, t, _) => t.clone(),
} }
} }
} }

View File

@@ -1,4 +1,4 @@
use super::{Primitive, Type, ValueOrRef}; use super::{Type, ValueOrRef};
use crate::eval::{EvalError, Value}; use crate::eval::{EvalError, Value};
use crate::ir::{Expression, Program, TopLevel, Variable}; use crate::ir::{Expression, Program, TopLevel, Variable};
use crate::util::scoped_map::ScopedMap; use crate::util::scoped_map::ScopedMap;
@@ -65,22 +65,6 @@ where
} }
} }
Expression::Primitive(_, _, op, args) => {
let arg_values = args
.iter()
.map(|x| x.eval(env))
.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`.
match op {
Primitive::Plus => Ok(Value::calculate("+", arg_values)?),
Primitive::Minus => Ok(Value::calculate("-", arg_values)?),
Primitive::Times => Ok(Value::calculate("*", arg_values)?),
Primitive::Divide => Ok(Value::calculate("/", arg_values)?),
}
}
Expression::Construct(_, _, name, fields) => { Expression::Construct(_, _, name, fields) => {
let mut result_fields = HashMap::with_capacity(fields.len()); let mut result_fields = HashMap::with_capacity(fields.len());
@@ -114,16 +98,6 @@ where
Ok(result) Ok(result)
} }
Expression::Print(_, value) => {
let n = match value {
ValueOrRef::Ref(_, _, ref name) => name.as_str(),
ValueOrRef::Value(_, _, _) => "<unknown>",
};
let value = value.eval(env)?;
stdout.push_str(&format!("{} = {}\n", n, value));
Ok(Value::Void)
}
Expression::Bind(_, name, _, value) => { Expression::Bind(_, name, _, value) => {
let value = value.eval(env, stdout)?; let value = value.eval(env, stdout)?;
env.insert(name.clone(), value.clone()); env.insert(name.clone(), value.clone());
@@ -153,6 +127,28 @@ where
closure_env.release_scope(); closure_env.release_scope();
Ok(result) Ok(result)
} }
Value::Primitive(name) if name == "print" => {
if let [ValueOrRef::Ref(loc, ty, name)] = &args[..] {
let value = ValueOrRef::Ref(loc.clone(), ty.clone(), name.clone()).eval(env)?;
let addendum = format!("{} = {}\n", name, value);
stdout.push_str(&addendum);
Ok(Value::Void)
} else {
panic!("Non-reference/non-singleton argument to 'print'");
}
}
Value::Primitive(name) => {
let values = args
.iter()
.map(|x| x.eval(env))
.collect::<Result<_, _>>()?;
println!("primitive {}: args {:?}", name, values);
Value::calculate(name.as_str(), values).map_err(Into::into)
}
_ => Err(EvalError::NotAFunction(loc.clone(), function)), _ => Err(EvalError::NotAFunction(loc.clone(), function)),
} }
} }
@@ -179,6 +175,8 @@ impl<T: Clone> ValueOrRef<T> {
.get(n) .get(n)
.cloned() .cloned()
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.to_string())), .ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.to_string())),
ValueOrRef::Primitive(_, _, prim) => Ok(Value::primitive(prim)),
} }
} }
} }

View File

@@ -53,9 +53,6 @@ impl Expression<Type> {
.append(t.pretty(allocator)) .append(t.pretty(allocator))
.append(allocator.text(">")) .append(allocator.text(">"))
.append(e.pretty(allocator)), .append(e.pretty(allocator)),
Expression::Primitive(_, _, op, exprs) if exprs.len() == 1 => {
op.pretty(allocator).append(exprs[0].pretty(allocator))
}
Expression::Construct(_, _, name, fields) => { Expression::Construct(_, _, name, fields) => {
let inner = allocator let inner = allocator
.intersperse( .intersperse(
@@ -78,19 +75,6 @@ impl Expression<Type> {
.text(".") .text(".")
.append(allocator.text(field.to_string())), .append(allocator.text(field.to_string())),
), ),
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()))
}
Expression::Call(_, _, fun, args) => { Expression::Call(_, _, fun, args) => {
let args = args.iter().map(|x| x.pretty(allocator)); let args = args.iter().map(|x| x.pretty(allocator));
let comma_sepped_args = allocator.intersperse(args, allocator.text(",")); let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
@@ -119,10 +103,6 @@ impl Expression<Type> {
result.append(last).append(allocator.text("}")) result.append(last).append(allocator.text("}"))
} }
}, },
Expression::Print(_, var) => allocator
.text("print")
.append(allocator.space())
.append(var.pretty(allocator)),
Expression::Bind(_, var, ty, expr) => allocator Expression::Bind(_, var, ty, expr) => allocator
.text(var.as_ref().to_string()) .text(var.as_ref().to_string())
.append(allocator.space()) .append(allocator.space())
@@ -144,6 +124,8 @@ impl Primitive {
Primitive::Minus => allocator.text("-"), Primitive::Minus => allocator.text("-"),
Primitive::Times => allocator.text("*"), Primitive::Times => allocator.text("*"),
Primitive::Divide => allocator.text("/"), Primitive::Divide => allocator.text("/"),
Primitive::Print => allocator.text("print"),
Primitive::Negate => allocator.text("negate"),
} }
} }
} }
@@ -153,6 +135,7 @@ impl ValueOrRef<Type> {
match self { match self {
ValueOrRef::Value(_, _, v) => v.pretty(allocator), ValueOrRef::Value(_, _, v) => v.pretty(allocator),
ValueOrRef::Ref(_, _, v) => allocator.text(v.as_ref().to_string()), ValueOrRef::Ref(_, _, v) => allocator.text(v.as_ref().to_string()),
ValueOrRef::Primitive(_, _, p) => p.pretty(allocator),
} }
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::backend::{Backend, BackendError}; use crate::backend::{Backend, BackendError};
use crate::syntax::{ConstantType, Expression, Location, ParserError, Statement, TopLevel}; use crate::syntax::{ConstantType, Expression, Location, ParserError, TopLevel};
use crate::type_infer::TypeInferenceResult; use crate::type_infer::TypeInferenceResult;
use crate::util::scoped_map::ScopedMap; use crate::util::scoped_map::ScopedMap;
use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::diagnostic::Diagnostic;
@@ -130,7 +130,7 @@ impl REPL {
let syntax = TopLevel::parse(entry, source)?; let syntax = TopLevel::parse(entry, source)?;
let program = match syntax { let program = match syntax {
TopLevel::Statement(Statement::Expression(Expression::Binding(loc, name, expr))) => { TopLevel::Expression(Expression::Binding(loc, name, expr)) => {
// if this is a variable binding, and we've never defined this variable before, // if this is a variable binding, and we've never defined this variable before,
// we should tell cranelift about it. this is optimistic; if we fail to compile, // we should tell cranelift about it. this is optimistic; if we fail to compile,
// then we won't use this definition until someone tries again. // then we won't use this definition until someone tries again.
@@ -142,12 +142,15 @@ impl REPL {
crate::syntax::Program { crate::syntax::Program {
items: vec![ items: vec![
TopLevel::Statement(Statement::Expression(Expression::Binding( TopLevel::Expression(Expression::Binding(loc.clone(), name.clone(), expr)),
TopLevel::Expression(Expression::Call(
loc.clone(), loc.clone(),
name.clone(), Box::new(Expression::Primitive(
expr, loc.clone(),
))), crate::syntax::Name::manufactured("print"),
TopLevel::Statement(Statement::Print(loc, name)), )),
vec![Expression::Reference(loc.clone(), name.name)],
)),
], ],
} }
} }

View File

@@ -285,21 +285,26 @@ fn order_of_operations() {
assert_eq!( assert_eq!(
Program::from_str(muladd1).unwrap(), Program::from_str(muladd1).unwrap(),
Program { Program {
items: vec![TopLevel::Statement(Statement::Expression( items: vec![TopLevel::Expression(Expression::Binding(
Expression::Binding(
Location::new(testfile, 0..1), Location::new(testfile, 0..1),
Name::manufactured("x"), Name::manufactured("x"),
Box::new(Expression::Call(
Location::new(testfile, 6..7),
Box::new(Expression::Primitive( Box::new(Expression::Primitive(
Location::new(testfile, 6..7), Location::new(testfile, 6..7),
"+".to_string(), Name::manufactured("+")
)),
vec![ vec![
Expression::Value( Expression::Value(
Location::new(testfile, 4..5), Location::new(testfile, 4..5),
Value::Number(None, None, 1), Value::Number(None, None, 1),
), ),
Expression::Primitive( Expression::Call(
Location::new(testfile, 10..11), Location::new(testfile, 10..11),
"*".to_string(), Box::new(Expression::Primitive(
Location::new(testfile, 10..11),
Name::manufactured("*")
)),
vec![ vec![
Expression::Value( Expression::Value(
Location::new(testfile, 8..9), Location::new(testfile, 8..9),
@@ -313,7 +318,6 @@ fn order_of_operations() {
) )
] ]
)) ))
)
))], ))],
} }
); );

View File

@@ -1,4 +1,4 @@
use crate::syntax::ast::{ConstantType, Expression, Name, Program, Statement, TopLevel, Value}; use crate::syntax::ast::{ConstantType, Expression, Name, Program, TopLevel, Value};
use crate::syntax::location::Location; use crate::syntax::location::Location;
use proptest::sample::select; use proptest::sample::select;
use proptest::{ use proptest::{
@@ -72,19 +72,26 @@ impl Arbitrary for Program {
genenv.bindings.insert(psi.name.clone(), psi.binding_type); genenv.bindings.insert(psi.name.clone(), psi.binding_type);
items.push( items.push(
expr.prop_map(move |expr| { expr.prop_map(move |expr| {
TopLevel::Statement(Statement::Expression(Expression::Binding( TopLevel::Expression(Expression::Binding(
Location::manufactured(), Location::manufactured(),
psi.name.clone(), psi.name.clone(),
Box::new(expr), Box::new(expr),
))) ))
}) })
.boxed(), .boxed(),
); );
} else { } else {
let printers = genenv.bindings.keys().map(|n| { let printers = genenv.bindings.keys().map(|n| {
Just(TopLevel::Statement(Statement::Print( Just(TopLevel::Expression(Expression::Call(
Location::manufactured(), Location::manufactured(),
Name::manufactured(n), Box::new(Expression::Primitive(
Location::manufactured(),
Name::manufactured("print"),
)),
vec![Expression::Reference(
Location::manufactured(),
n.to_string(),
)],
))) )))
}); });
items.push(Union::new(printers).boxed()); items.push(Union::new(printers).boxed());
@@ -186,7 +193,14 @@ impl Arbitrary for Expression {
while args.len() > count { while args.len() > count {
args.pop(); args.pop();
} }
Expression::Primitive(Location::manufactured(), oper.to_string(), args) Expression::Call(
Location::manufactured(),
Box::new(Expression::Primitive(
Location::manufactured(),
Name::manufactured(oper),
)),
args,
)
}) })
}) })
.boxed() .boxed()

View File

@@ -26,7 +26,7 @@ pub struct Program {
/// and functions /// and functions
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum TopLevel { pub enum TopLevel {
Statement(Statement), Expression(Expression),
Function( Function(
Option<Name>, Option<Name>,
Vec<(Name, Option<Type>)>, Vec<(Name, Option<Type>)>,
@@ -86,38 +86,6 @@ impl fmt::Display for Name {
} }
} }
/// A parsed statement.
///
/// Statements are guaranteed to be syntactically valid, but may be
/// complete nonsense at the semantic level. Which is to say, all the
/// print statements were correctly formatted, and all the variables
/// referenced are definitely valid symbols, but they may not have
/// been defined or anything.
///
/// Note that equivalence testing on statements is independent of
/// source location; it is testing if the two statements say the same
/// thing, not if they are the exact same statement.
#[derive(Clone, Debug)]
pub enum Statement {
Print(Location, Name),
Expression(Expression),
}
impl PartialEq for Statement {
fn eq(&self, other: &Self) -> bool {
match self {
Statement::Print(_, name1) => match other {
Statement::Print(_, name2) => name1 == name2,
_ => false,
},
Statement::Expression(e1) => match other {
Statement::Expression(e2) => e1 == e2,
_ => false,
},
}
}
}
/// An expression in the underlying syntax. /// An expression in the underlying syntax.
/// ///
/// Like statements, these expressions are guaranteed to have been /// Like statements, these expressions are guaranteed to have been
@@ -131,12 +99,25 @@ pub enum Expression {
Reference(Location, String), Reference(Location, String),
FieldRef(Location, Box<Expression>, Name), FieldRef(Location, Box<Expression>, Name),
Cast(Location, String, Box<Expression>), Cast(Location, String, Box<Expression>),
Primitive(Location, String, Vec<Expression>), Primitive(Location, Name),
Call(Location, Box<Expression>, Vec<Expression>), Call(Location, Box<Expression>, Vec<Expression>),
Block(Location, Vec<Statement>), Block(Location, Vec<Expression>),
Binding(Location, Name, Box<Expression>), Binding(Location, Name, Box<Expression>),
} }
impl Expression {
pub fn primitive(loc: Location, name: &str, args: Vec<Expression>) -> Expression {
Expression::Call(
loc.clone(),
Box::new(Expression::Primitive(
loc.clone(),
Name::new(name, loc.clone()),
)),
args,
)
}
}
impl PartialEq for Expression { impl PartialEq for Expression {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match self { match self {
@@ -160,8 +141,8 @@ impl PartialEq for Expression {
Expression::Cast(_, t2, e2) => t1 == t2 && e1 == e2, Expression::Cast(_, t2, e2) => t1 == t2 && e1 == e2,
_ => false, _ => false,
}, },
Expression::Primitive(_, prim1, args1) => match other { Expression::Primitive(_, prim1) => match other {
Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2, Expression::Primitive(_, prim2) => prim1 == prim2,
_ => false, _ => false,
}, },
Expression::Call(_, f1, a1) => match other { Expression::Call(_, f1, a1) => match other {
@@ -189,7 +170,7 @@ impl Expression {
Expression::Reference(loc, _) => loc, Expression::Reference(loc, _) => loc,
Expression::FieldRef(loc, _, _) => loc, Expression::FieldRef(loc, _, _) => loc,
Expression::Cast(loc, _, _) => loc, Expression::Cast(loc, _, _) => loc,
Expression::Primitive(loc, _, _) => loc, Expression::Primitive(loc, _) => loc,
Expression::Call(loc, _, _) => loc, Expression::Call(loc, _, _) => loc,
Expression::Block(loc, _) => loc, Expression::Block(loc, _) => loc,
Expression::Binding(loc, _, _) => loc, Expression::Binding(loc, _, _) => loc,

View File

@@ -1,5 +1,5 @@
use crate::eval::{EvalError, PrimitiveType, Value}; use crate::eval::{EvalError, PrimitiveType, Value};
use crate::syntax::{ConstantType, Expression, Name, Program, Statement, TopLevel}; use crate::syntax::{ConstantType, Expression, Name, Program, TopLevel};
use crate::util::scoped_map::ScopedMap; use crate::util::scoped_map::ScopedMap;
use internment::ArcIntern; use internment::ArcIntern;
use std::collections::HashMap; use std::collections::HashMap;
@@ -40,7 +40,7 @@ impl Program {
} }
} }
TopLevel::Statement(stmt) => last_result = stmt.eval(&mut stdout, &mut env)?, TopLevel::Expression(expr) => last_result = expr.eval(&mut stdout, &mut env)?,
TopLevel::Structure(_, _, _) => { TopLevel::Structure(_, _, _) => {
last_result = Value::Void; last_result = Value::Void;
@@ -52,32 +52,6 @@ impl Program {
} }
} }
impl Statement {
fn eval(
&self,
stdout: &mut String,
env: &mut ScopedMap<ArcIntern<String>, Value<Expression>>,
) -> Result<Value<Expression>, EvalError<Expression>> {
match self {
Statement::Print(loc, name) => {
let value = env
.get(&name.clone().intern())
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), name.name.clone()))?;
let value = if let Value::Number(x) = value {
Value::U64(*x)
} else {
value.clone()
};
let line = format!("{} = {}\n", name, value);
stdout.push_str(&line);
Ok(Value::Void)
}
Statement::Expression(e) => e.eval(stdout, env),
}
}
}
impl Expression { impl Expression {
fn eval( fn eval(
&self, &self,
@@ -144,16 +118,7 @@ impl Expression {
Ok(target_type.safe_cast(&value)?) Ok(target_type.safe_cast(&value)?)
} }
Expression::Primitive(_, op, args) => { Expression::Primitive(_, op) => Ok(Value::primitive(op.name.clone())),
let mut arg_values = Vec::with_capacity(args.len());
for arg in args.iter() {
// yay, recursion! makes this pretty straightforward
arg_values.push(arg.eval(stdout, env)?);
}
Ok(Value::calculate(op, arg_values)?)
}
Expression::Call(loc, fun, args) => { Expression::Call(loc, fun, args) => {
let function = fun.eval(stdout, env)?; let function = fun.eval(stdout, env)?;
@@ -178,6 +143,31 @@ impl Expression {
closure_env.release_scope(); closure_env.release_scope();
Ok(result) Ok(result)
} }
Value::Primitive(name) if name == "print" => {
if let [Expression::Reference(_, name)] = &args[..] {
let value = Expression::Reference(loc.clone(), name.clone()).eval(stdout, env)?;
let value = match value {
Value::Number(x) => Value::U64(x),
x => x,
};
let addendum = format!("{} = {}\n", name, value);
stdout.push_str(&addendum);
Ok(Value::Void)
} else {
panic!("Non-reference/non-singleton argument to 'print': {:?}", args);
}
}
Value::Primitive(name) => {
let values = args
.iter()
.map(|x| x.eval(stdout, env))
.collect::<Result<_, _>>()?;
Value::calculate(name.as_str(), values).map_err(Into::into)
}
_ => Err(EvalError::NotAFunction(loc.clone(), function)), _ => Err(EvalError::NotAFunction(loc.clone(), function)),
} }
} }

View File

@@ -9,7 +9,7 @@
//! eventually want to leave lalrpop behind.) //! eventually want to leave lalrpop behind.)
//! //!
use crate::syntax::{Location, ParserError}; use crate::syntax::{Location, ParserError};
use crate::syntax::ast::{Program,TopLevel,Statement,Expression,Value,Name,Type}; use crate::syntax::ast::{Program,TopLevel,Expression,Value,Name,Type};
use crate::syntax::tokens::{ConstantType, Token}; use crate::syntax::tokens::{ConstantType, Token};
use internment::ArcIntern; use internment::ArcIntern;
@@ -81,7 +81,7 @@ ProgramTopLevel: Vec<TopLevel> = {
pub TopLevel: TopLevel = { pub TopLevel: TopLevel = {
<f:Function> => f, <f:Function> => f,
<s:Structure> => s, <s:Structure> => s,
<s:Statement> ";" => TopLevel::Statement(s), <s:Expression> ";" => TopLevel::Expression(s),
} }
Function: TopLevel = { Function: TopLevel = {
@@ -132,34 +132,6 @@ TypeName: Name = {
Name::new(v, Location::new(file_idx, name_start..name_end)), Name::new(v, Location::new(file_idx, name_start..name_end)),
} }
Statements: Vec<Statement> = {
// a statement is either a set of statements followed by another
// statement (note, here, that you can name the result of a sub-parse
// using <name: subrule>) ...
<mut stmts:Statements> ";" <stmt:Statement> => {
stmts.push(stmt);
stmts
},
<stmt:Statement> => {
vec![stmt]
}
}
#[inline]
Statement: Statement = {
// A statement can just be a print statement.
<ls: @L> "print" <name_start: @L> <v:"<var>"> <name_end: @L> <le: @L> =>
Statement::Print(
Location::new(file_idx, ls..le),
Name::new(v, Location::new(file_idx, name_start..name_end)),
),
// A statement can just be an expression.
<e: Expression> =>
Statement::Expression(e),
}
// Expressions! Expressions are a little fiddly, because we're going to // Expressions! Expressions are a little fiddly, because we're going to
// use a little bit of a trick to make sure that we get operator precedence // use a little bit of a trick to make sure that we get operator precedence
// right. The trick works by creating a top-level `Expression` grammar entry // right. The trick works by creating a top-level `Expression` grammar entry
@@ -198,6 +170,21 @@ BindingExpression: Expression = {
Box::new(e), Box::new(e),
), ),
PrintExpression,
}
PrintExpression: Expression = {
<ls: @L> "print" <pe: @L> <e: ConstructorExpression> <le: @L> =>
Expression::Call(
Location::new(file_idx, ls..le),
Box::new(
Expression::Primitive(
Location::new(file_idx, ls..pe),
Name::new("print", Location::new(file_idx, ls..pe)),
),
),
vec![e],
),
ConstructorExpression, ConstructorExpression,
} }
@@ -214,24 +201,24 @@ FieldSetter: (Name, Expression) = {
// we group addition and subtraction under the heading "additive" // we group addition and subtraction under the heading "additive"
AdditiveExpression: Expression = { AdditiveExpression: Expression = {
<ls: @L> <e1:AdditiveExpression> <l: @L> "+" <e2:MultiplicativeExpression> <le: @L> => <ls: @L> <e1:AdditiveExpression> <l: @L> "+" <e2:MultiplicativeExpression> <le: @L> =>
Expression::Primitive(Location::new(file_idx, ls..le), "+".to_string(), vec![e1, e2]), Expression::primitive(Location::new(file_idx, ls..le), "+", vec![e1, e2]),
<ls: @L> <e1:AdditiveExpression> <l: @L> "-" <e2:MultiplicativeExpression> <le: @L> => <ls: @L> <e1:AdditiveExpression> <l: @L> "-" <e2:MultiplicativeExpression> <le: @L> =>
Expression::Primitive(Location::new(file_idx, ls..le), "-".to_string(), vec![e1, e2]), Expression::primitive(Location::new(file_idx, ls..le), "-", vec![e1, e2]),
MultiplicativeExpression, MultiplicativeExpression,
} }
// similarly, we group multiplication and division under "multiplicative" // similarly, we group multiplication and division under "multiplicative"
MultiplicativeExpression: Expression = { MultiplicativeExpression: Expression = {
<ls: @L> <e1:MultiplicativeExpression> <l: @L> "*" <e2:UnaryExpression> <le: @L> => <ls: @L> <e1:MultiplicativeExpression> <l: @L> "*" <e2:UnaryExpression> <le: @L> =>
Expression::Primitive(Location::new(file_idx, ls..le), "*".to_string(), vec![e1, e2]), Expression::primitive(Location::new(file_idx, ls..le), "*", vec![e1, e2]),
<ls: @L> <e1:MultiplicativeExpression> <l: @L> "/" <e2:UnaryExpression> <le: @L> => <ls: @L> <e1:MultiplicativeExpression> <l: @L> "/" <e2:UnaryExpression> <le: @L> =>
Expression::Primitive(Location::new(file_idx, ls..le), "/".to_string(), vec![e1, e2]), Expression::primitive(Location::new(file_idx, ls..le), "/", vec![e1, e2]),
UnaryExpression, UnaryExpression,
} }
UnaryExpression: Expression = { UnaryExpression: Expression = {
<l: @L> "-" <e:UnaryExpression> <le: @L> => <l: @L> "-" <e:UnaryExpression> <le: @L> =>
Expression::Primitive(Location::new(file_idx, l..le), "-".to_string(), vec![e]), Expression::primitive(Location::new(file_idx, l..le), "negate", vec![e]),
<l: @L> "<" <v:"<var>"> ">" <e:UnaryExpression> <le: @L> => <l: @L> "<" <v:"<var>"> ">" <e:UnaryExpression> <le: @L> =>
Expression::Cast(Location::new(file_idx, l..le), v.to_string(), Box::new(e)), Expression::Cast(Location::new(file_idx, l..le), v.to_string(), Box::new(e)),
CallExpression, CallExpression,
@@ -257,13 +244,21 @@ AtomicExpression: Expression = {
// just a number // just a number
<l: @L> <n:"<num>"> <end: @L> => Expression::Value(Location::new(file_idx, l..end), Value::Number(n.0, n.1, n.2)), <l: @L> <n:"<num>"> <end: @L> => Expression::Value(Location::new(file_idx, l..end), Value::Number(n.0, n.1, n.2)),
// this expression could actually be a block! // this expression could actually be a block!
<s:@L> "{" <stmts:Statements> "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), stmts), <s:@L> "{" <exprs:Expressions> ";"? "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), exprs),
<s:@L> "{" "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), vec![]), <s:@L> "{" "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), vec![]),
// finally, let people parenthesize expressions and get back to a // finally, let people parenthesize expressions and get back to a
// lower precedence // lower precedence
"(" <e:Expression> ")" => e, "(" <e:Expression> ")" => e,
} }
Expressions: Vec<Expression> = {
<e:Expression> => vec![e],
<mut exps:Expressions> ";" <e:Expression> => {
exps.push(e);
exps
}
}
// Lifted from the LALRPop book, a comma-separated list of T that may or // Lifted from the LALRPop book, a comma-separated list of T that may or
// may not conclude with a comma. // may not conclude with a comma.
Comma<T>: Vec<T> = { Comma<T>: Vec<T> = {

View File

@@ -1,4 +1,4 @@
use crate::syntax::ast::{ConstantType, Expression, Program, Statement, TopLevel, Type, Value}; use crate::syntax::ast::{ConstantType, Expression, Program, TopLevel, Type, Value};
use crate::util::pretty::{derived_display, Allocator}; use crate::util::pretty::{derived_display, Allocator};
use pretty::{DocAllocator, DocBuilder}; use pretty::{DocAllocator, DocBuilder};
@@ -20,7 +20,7 @@ impl Program {
impl TopLevel { impl TopLevel {
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> { pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
match self { match self {
TopLevel::Statement(stmt) => stmt.pretty(allocator), TopLevel::Expression(expr) => expr.pretty(allocator),
TopLevel::Function(name, args, rettype, body) => allocator TopLevel::Function(name, args, rettype, body) => allocator
.text("function") .text("function")
.append(allocator.space()) .append(allocator.space())
@@ -87,18 +87,6 @@ impl TopLevel {
} }
} }
impl Statement {
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
match self {
Statement::Print(_, var) => allocator
.text("print")
.append(allocator.space())
.append(allocator.text(var.to_string())),
Statement::Expression(e) => e.pretty(allocator),
}
}
}
impl Expression { impl Expression {
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> { pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
match self { match self {
@@ -129,25 +117,7 @@ impl Expression {
.text(t.clone()) .text(t.clone())
.angles() .angles()
.append(e.pretty(allocator)), .append(e.pretty(allocator)),
Expression::Primitive(_, op, exprs) if exprs.len() == 1 => allocator Expression::Primitive(_, op) => allocator.text(op.name.clone()),
.text(op.to_string())
.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(allocator.text(op.to_string()))
.append(allocator.space())
.append(right)
.parens()
}
Expression::Primitive(_, op, exprs) => {
let call = allocator.text(op.to_string());
let args = exprs.iter().map(|x| x.pretty(allocator));
let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
call.append(comma_sepped_args.parens())
}
Expression::Call(_, fun, args) => { Expression::Call(_, fun, args) => {
let args = args.iter().map(|x| x.pretty(allocator)); let args = args.iter().map(|x| x.pretty(allocator));
let comma_sepped_args = allocator.intersperse(args, allocator.text(",")); let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
@@ -245,6 +215,5 @@ impl Type {
derived_display!(Program); derived_display!(Program);
derived_display!(TopLevel); derived_display!(TopLevel);
derived_display!(Statement);
derived_display!(Expression); derived_display!(Expression);
derived_display!(Value); derived_display!(Value);

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
eval::PrimitiveType, eval::PrimitiveType,
syntax::{Expression, Location, Program, Statement, TopLevel}, syntax::{Expression, Location, Program, TopLevel},
util::scoped_map::ScopedMap, util::scoped_map::ScopedMap,
}; };
use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::diagnostic::Diagnostic;
@@ -132,47 +132,12 @@ impl TopLevel {
bound_variables.release_scope(); bound_variables.release_scope();
result result
} }
TopLevel::Statement(stmt) => stmt.validate(bound_variables), TopLevel::Expression(expr) => expr.validate(bound_variables),
TopLevel::Structure(_, _, _) => (vec![], vec![]), TopLevel::Structure(_, _, _) => (vec![], vec![]),
} }
} }
} }
impl Statement {
/// Validate that the statement makes semantic sense, not just syntactic sense.
///
/// This checks for things like references to variables that don't exist, for
/// example, and generates warnings for things that are inadvisable but not
/// actually a problem. Since statements appear in a broader context, you'll
/// need to provide the set of variables that are bound where this statement
/// occurs. We use a `HashMap` to map these bound locations to the locations
/// where their bound, because these locations are handy when generating errors
/// and warnings.
fn validate(
&self,
bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
let mut errors = vec![];
let mut warnings = vec![];
match self {
Statement::Print(_, var) if bound_variables.contains_key(&var.name) => {}
Statement::Print(loc, var) => {
errors.push(Error::UnboundVariable(loc.clone(), var.to_string()))
}
Statement::Expression(e) => {
let (mut exp_errors, mut exp_warnings) = e.validate(bound_variables);
errors.append(&mut exp_errors);
warnings.append(&mut exp_warnings);
}
}
(errors, warnings)
}
}
impl Expression { impl Expression {
fn validate( fn validate(
&self, &self,
@@ -207,18 +172,7 @@ impl Expression {
(errs, warns) (errs, warns)
} }
Expression::Primitive(_, _, args) => { Expression::Primitive(_, _) => (vec![], vec![]),
let mut errors = vec![];
let mut warnings = vec![];
for expr in args.iter() {
let (mut err, mut warn) = expr.validate(variable_map);
errors.append(&mut err);
warnings.append(&mut warn);
}
(errors, warnings)
}
Expression::Call(_, func, args) => { Expression::Call(_, func, args) => {
let (mut errors, mut warnings) = func.validate(variable_map); let (mut errors, mut warnings) = func.validate(variable_map);

View File

@@ -9,7 +9,7 @@ use std::str::FromStr;
enum TopLevelItem { enum TopLevelItem {
Type(ArcIntern<String>, ir::TypeOrVar), Type(ArcIntern<String>, ir::TypeOrVar),
Expression(ir::TopLevel<ir::TypeOrVar>), Value(ir::TopLevel<ir::TypeOrVar>),
} }
/// This function takes a syntactic program and converts it into the IR version of the /// This function takes a syntactic program and converts it into the IR version of the
@@ -32,7 +32,7 @@ pub fn convert_program(
let tli = convert_top_level(item, &mut constraint_db, &mut renames, &mut bindings); let tli = convert_top_level(item, &mut constraint_db, &mut renames, &mut bindings);
match tli { match tli {
TopLevelItem::Expression(item) => items.push(item), TopLevelItem::Value(item) => items.push(item),
TopLevelItem::Type(name, decl) => { TopLevelItem::Type(name, decl) => {
let _ = type_definitions.insert(name, decl); let _ = type_definitions.insert(name, decl);
} }
@@ -137,7 +137,7 @@ fn convert_top_level(
// Remember to exit this scoping level! // Remember to exit this scoping level!
renames.release_scope(); renames.release_scope();
TopLevelItem::Expression(ir::TopLevel::Function( TopLevelItem::Value(ir::TopLevel::Function(
function_name, function_name,
arginfo, arginfo,
rettype, rettype,
@@ -145,8 +145,8 @@ fn convert_top_level(
)) ))
} }
syntax::TopLevel::Statement(stmt) => TopLevelItem::Expression(ir::TopLevel::Statement( syntax::TopLevel::Expression(expr) => TopLevelItem::Value(ir::TopLevel::Statement(
convert_statement(stmt, constraint_db, renames, bindings), convert_expression(expr, constraint_db, renames, bindings).0,
)), )),
syntax::TopLevel::Structure(_loc, name, fields) => { syntax::TopLevel::Structure(_loc, name, fields) => {
@@ -161,46 +161,6 @@ fn convert_top_level(
} }
} }
/// This function takes a syntactic statements and converts it into a series of
/// IR statements, adding type variables and constraints as necessary.
///
/// We generate a series of statements because we're going to flatten all
/// incoming expressions so that they are no longer recursive. This will
/// generate a bunch of new bindings for all the subexpressions, which we
/// return as a bundle.
///
/// See the safety warning on [`convert_program`]! This function assumes that
/// you have run [`Statement::validate`], and will trigger panics in error
/// conditions if you have run that and had it come back clean.
fn convert_statement(
statement: syntax::Statement,
constraint_db: &mut Vec<Constraint>,
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
bindings: &mut HashMap<ArcIntern<String>, ir::TypeOrVar>,
) -> ir::Expression<ir::TypeOrVar> {
match statement {
syntax::Statement::Print(loc, name) => {
let iname = ArcIntern::new(name.to_string());
let final_name = renames
.get(&iname)
.cloned()
.unwrap_or_else(|| iname.clone());
let varty = bindings
.get(&final_name)
.expect("print variable defined before use")
.clone();
constraint_db.push(Constraint::Printable(loc.clone(), varty.clone()));
ir::Expression::Print(loc.clone(), ir::ValueOrRef::Ref(loc, varty, final_name))
}
syntax::Statement::Expression(e) => {
convert_expression(e, constraint_db, renames, bindings).0
}
}
}
/// This function takes a syntactic expression and converts it into a series /// This function takes a syntactic expression and converts it into a series
/// of IR statements, adding type variables and constraints as necessary. /// of IR statements, adding type variables and constraints as necessary.
/// ///
@@ -359,39 +319,54 @@ fn convert_expression(
(finalize_expression(prereqs, res), target_type) (finalize_expression(prereqs, res), target_type)
} }
syntax::Expression::Primitive(loc, fun, mut args) => { syntax::Expression::Primitive(loc, name) => {
let primop = ir::Primitive::from_str(&fun).expect("valid primitive"); let primop = ir::Primitive::from_str(&name.name).expect("valid primitive");
let mut prereqs = vec![];
let mut nargs = vec![];
let mut atypes = vec![];
let ret_type = ir::TypeOrVar::new();
for arg in args.drain(..) { match primop {
let (aexp, atype) = convert_expression(arg, constraint_db, renames, bindings); ir::Primitive::Plus | ir::Primitive::Times | ir::Primitive::Divide => {
let (aprereqs, asimple) = simplify_expr(aexp); let numeric_type = ir::TypeOrVar::new_located(loc.clone());
constraint_db.push(Constraint::NumericType(loc.clone(), numeric_type.clone()));
merge_prereq(&mut prereqs, aprereqs); let funtype = ir::TypeOrVar::Function(
nargs.push(asimple); vec![numeric_type.clone(), numeric_type.clone()],
atypes.push(atype); Box::new(numeric_type.clone()),
);
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
(ir::Expression::Atomic(result_value), funtype)
} }
constraint_db.push(Constraint::ProperPrimitiveArgs( ir::Primitive::Minus => {
loc.clone(), let numeric_type = ir::TypeOrVar::new_located(loc.clone());
primop, constraint_db.push(Constraint::NumericType(loc.clone(), numeric_type.clone()));
atypes.clone(), let funtype = ir::TypeOrVar::Function(
ret_type.clone(), vec![numeric_type.clone(), numeric_type.clone()],
)); Box::new(numeric_type.clone()),
);
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
(ir::Expression::Atomic(result_value), funtype)
}
let last_call = ir::Expression::Primitive(loc.clone(), ret_type.clone(), primop, nargs); ir::Primitive::Print => {
let arg_type = ir::TypeOrVar::new_located(loc.clone());
constraint_db.push(Constraint::Printable(loc.clone(), arg_type.clone()));
let funtype = ir::TypeOrVar::Function(
vec![arg_type],
Box::new(ir::TypeOrVar::Primitive(PrimitiveType::Void)),
);
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
(ir::Expression::Atomic(result_value), funtype)
}
if prereqs.is_empty() { ir::Primitive::Negate => {
(last_call, ret_type) let arg_type = ir::TypeOrVar::new_located(loc.clone());
} else { constraint_db.push(Constraint::NumericType(loc.clone(), arg_type.clone()));
prereqs.push(last_call); constraint_db.push(Constraint::IsSigned(loc.clone(), arg_type.clone()));
( let funtype = ir::TypeOrVar::Function(
ir::Expression::Block(loc, ret_type.clone(), prereqs), vec![arg_type.clone()],
ret_type, Box::new(arg_type),
) );
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
(ir::Expression::Atomic(result_value), funtype)
}
} }
} }
@@ -444,10 +419,10 @@ fn convert_expression(
let mut ret_type = ir::TypeOrVar::Primitive(PrimitiveType::Void); let mut ret_type = ir::TypeOrVar::Primitive(PrimitiveType::Void);
let mut exprs = vec![]; let mut exprs = vec![];
for statement in stmts { for xpr in stmts {
let expr = convert_statement(statement, constraint_db, renames, bindings); let (expr, expr_type) = convert_expression(xpr, constraint_db, renames, bindings);
ret_type = expr.type_of(); ret_type = expr_type;
exprs.push(expr); exprs.push(expr);
} }

View File

@@ -54,15 +54,6 @@ fn finalize_expression(
finalize_val_or_ref(val_or_ref, resolutions), finalize_val_or_ref(val_or_ref, resolutions),
), ),
Expression::Primitive(loc, ty, prim, mut args) => Expression::Primitive(
loc,
finalize_type(ty, resolutions),
prim,
args.drain(..)
.map(|x| finalize_val_or_ref(x, resolutions))
.collect(),
),
Expression::Construct(loc, ty, name, fields) => Expression::Construct( Expression::Construct(loc, ty, name, fields) => Expression::Construct(
loc, loc,
finalize_type(ty, resolutions), finalize_type(ty, resolutions),
@@ -97,10 +88,6 @@ fn finalize_expression(
Expression::Block(loc, finalize_type(ty, resolutions), final_exprs) Expression::Block(loc, finalize_type(ty, resolutions), final_exprs)
} }
Expression::Print(loc, var) => {
Expression::Print(loc, finalize_val_or_ref(var, resolutions))
}
Expression::Call(loc, ty, fun, args) => Expression::Call( Expression::Call(loc, ty, fun, args) => Expression::Call(
loc, loc,
finalize_type(ty, resolutions), finalize_type(ty, resolutions),
@@ -147,6 +134,9 @@ fn finalize_val_or_ref(
) -> ValueOrRef<Type> { ) -> ValueOrRef<Type> {
match valref { match valref {
ValueOrRef::Ref(loc, ty, var) => ValueOrRef::Ref(loc, finalize_type(ty, resolutions), var), ValueOrRef::Ref(loc, ty, var) => ValueOrRef::Ref(loc, finalize_type(ty, resolutions), var),
ValueOrRef::Primitive(loc, ty, prim) => {
ValueOrRef::Primitive(loc, finalize_type(ty, resolutions), prim)
}
ValueOrRef::Value(loc, ty, val) => { ValueOrRef::Value(loc, ty, val) => {
let new_type = finalize_type(ty, resolutions); let new_type = finalize_type(ty, resolutions);

View File

@@ -12,8 +12,6 @@ pub enum Constraint {
Printable(Location, TypeOrVar), Printable(Location, TypeOrVar),
/// The provided numeric value fits in the given constant type /// The provided numeric value fits in the given constant type
FitsInNumType(Location, TypeOrVar, u64), FitsInNumType(Location, TypeOrVar, u64),
/// The given primitive has the proper arguments types associated with it
ProperPrimitiveArgs(Location, Primitive, Vec<TypeOrVar>, TypeOrVar),
/// The given type can be casted to the target type safely /// The given type can be casted to the target type safely
CanCastTo(Location, TypeOrVar, TypeOrVar), CanCastTo(Location, TypeOrVar, TypeOrVar),
/// The given type has the given field in it, and the type of that field /// The given type has the given field in it, and the type of that field
@@ -41,13 +39,6 @@ impl fmt::Display for Constraint {
match self { match self {
Constraint::Printable(_, ty) => write!(f, "PRINTABLE {}", ty), Constraint::Printable(_, ty) => write!(f, "PRINTABLE {}", ty),
Constraint::FitsInNumType(_, ty, num) => write!(f, "FITS_IN {} {}", num, ty), Constraint::FitsInNumType(_, ty, num) => write!(f, "FITS_IN {} {}", num, ty),
Constraint::ProperPrimitiveArgs(_, op, args, ret) if args.len() == 1 => {
write!(f, "PRIM {} {} -> {}", op, args[0], ret)
}
Constraint::ProperPrimitiveArgs(_, op, args, ret) if args.len() == 2 => {
write!(f, "PRIM {} ({}, {}) -> {}", op, args[0], args[1], ret)
}
Constraint::ProperPrimitiveArgs(_, op, _, ret) => write!(f, "PRIM {} -> {}", op, ret),
Constraint::CanCastTo(_, ty, ty2) => write!(f, "CAST {} -> {}", ty, ty2), Constraint::CanCastTo(_, ty, ty2) => write!(f, "CAST {} -> {}", ty, ty2),
Constraint::TypeHasField(_, ty1, field, ty2) => { Constraint::TypeHasField(_, ty1, field, ty2) => {
write!(f, "FIELD {}.{} -> {}", ty1, field, ty2) write!(f, "FIELD {}.{} -> {}", ty1, field, ty2)
@@ -84,10 +75,6 @@ impl Constraint {
Constraint::IsSigned(_, ty) => ty.replace(name, replace_with), Constraint::IsSigned(_, ty) => ty.replace(name, replace_with),
Constraint::IsSomething(_, ty) => ty.replace(name, replace_with), Constraint::IsSomething(_, ty) => ty.replace(name, replace_with),
Constraint::NumericType(_, ty) => ty.replace(name, replace_with), Constraint::NumericType(_, ty) => ty.replace(name, replace_with),
Constraint::ProperPrimitiveArgs(_, _, args, ret) => {
ret.replace(name, replace_with)
| args.iter_mut().any(|x| x.replace(name, replace_with))
}
Constraint::NamedTypeIs(_, name, ty) => ty.replace(name, replace_with), Constraint::NamedTypeIs(_, name, ty) => ty.replace(name, replace_with),
} }
} }
@@ -269,12 +256,6 @@ impl From<TypeInferenceError> for Diagnostic<usize> {
TypeInferenceError::CouldNotSolve(Constraint::Printable(loc, ty)) => loc TypeInferenceError::CouldNotSolve(Constraint::Printable(loc, ty)) => loc
.labelled_error("internal error") .labelled_error("internal error")
.with_message(format!("Could not determine if type {} was printable", ty)), .with_message(format!("Could not determine if type {} was printable", ty)),
TypeInferenceError::CouldNotSolve(Constraint::ProperPrimitiveArgs(loc, prim, _, _)) => {
loc.labelled_error("internal error").with_message(format!(
"Could not tell if primitive {} received the proper argument types",
prim
))
}
TypeInferenceError::CouldNotSolve(Constraint::IsSomething(loc, _)) => { TypeInferenceError::CouldNotSolve(Constraint::IsSomething(loc, _)) => {
loc.labelled_error("could not infer type") loc.labelled_error("could not infer type")
.with_message("Could not find *any* type information; is this an unused function argument?") .with_message("Could not find *any* type information; is this an unused function argument?")
@@ -621,60 +602,6 @@ pub fn solve_constraints(
changed_something = true; changed_something = true;
} }
Constraint::ProperPrimitiveArgs(loc, prim, mut args, ret) => match prim {
Primitive::Plus | Primitive::Minus | Primitive::Times | Primitive::Divide
if args.len() == 2 =>
{
let right = args.pop().expect("2>0");
let left = args.pop().expect("2>1");
new_constraints.push(Constraint::NumericType(loc.clone(), left.clone()));
new_constraints.push(Constraint::Equivalent(
loc.clone(),
left.clone(),
right,
));
new_constraints.push(Constraint::Equivalent(loc.clone(), left, ret));
changed_something = true;
all_constraints_solved = false;
tracing::trace!(primitive = %prim, "we expanded out a binary primitive operation");
}
Primitive::Minus if args.len() == 1 => {
let value = args.pop().expect("1>0");
new_constraints.push(Constraint::NumericType(loc.clone(), value.clone()));
new_constraints.push(Constraint::IsSigned(loc.clone(), value.clone()));
new_constraints.push(Constraint::Equivalent(loc, value, ret));
changed_something = true;
all_constraints_solved = false;
tracing::trace!(primitive = %prim, "we expanded out a unary primitive operation");
}
Primitive::Plus | Primitive::Times | Primitive::Divide => {
errors.push(TypeInferenceError::WrongPrimitiveArity(
loc,
prim,
2,
2,
args.len(),
));
changed_something = true;
tracing::trace!(primitive = %prim, provided_arity = args.len(), "binary primitive operation arity is wrong");
}
Primitive::Minus => {
errors.push(TypeInferenceError::WrongPrimitiveArity(
loc,
prim,
1,
2,
args.len(),
));
changed_something = true;
tracing::trace!(primitive = %prim, provided_arity = args.len(), "unary primitive operation arity is wrong");
}
},
// Some equivalences we can/should solve directly // Some equivalences we can/should solve directly
Constraint::Equivalent( Constraint::Equivalent(
loc, loc,