🤔 Add a type inference engine, along with typed literals. #4
3
examples/basic/negation.ngr
Normal file
3
examples/basic/negation.ngr
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
x = 2i64 + 2i64;
|
||||||
|
y = -x;
|
||||||
|
print y;
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
use crate::eval::EvalError;
|
use crate::eval::EvalError;
|
||||||
use crate::ir::Program;
|
use crate::ir::Program;
|
||||||
use cranelift_jit::JITModule;
|
use cranelift_jit::JITModule;
|
||||||
use cranelift_object::ObjectModule;
|
use cranelift_object::ObjectModule;
|
||||||
|
use std::path::Path;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
impl Backend<JITModule> {
|
impl Backend<JITModule> {
|
||||||
@@ -127,6 +126,16 @@ proptest::proptest! {
|
|||||||
let basic_result = basic_result.map(|x| x.replace('\n', "\r\n"));
|
let basic_result = basic_result.map(|x| x.replace('\n', "\r\n"));
|
||||||
|
|
||||||
if !matches!(basic_result, Err(EvalError::PrimOp(PrimOpError::MathFailure(_)))) {
|
if !matches!(basic_result, Err(EvalError::PrimOp(PrimOpError::MathFailure(_)))) {
|
||||||
|
// use pretty::{DocAllocator, Pretty};
|
||||||
|
// let allocator = pretty::BoxAllocator;
|
||||||
|
// allocator
|
||||||
|
// .text("---------------")
|
||||||
|
// .append(allocator.hardline())
|
||||||
|
// .append(program.pretty(&allocator))
|
||||||
|
// .1
|
||||||
|
// .render_colored(70, pretty::termcolor::StandardStream::stdout(pretty::termcolor::ColorChoice::Auto))
|
||||||
|
// .expect("rendering works");
|
||||||
|
|
||||||
let compiled_result = Backend::<ObjectModule>::eval(program);
|
let compiled_result = Backend::<ObjectModule>::eval(program);
|
||||||
assert_eq!(basic_result, compiled_result);
|
assert_eq!(basic_result, compiled_result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,28 +308,39 @@ impl Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expression::Primitive(_, prim, mut vals) => {
|
Expression::Primitive(_, prim, mut vals) => {
|
||||||
// we're going to use `pop`, so we're going to pull and compile the right value ...
|
let mut values = vec![];
|
||||||
let (right, rtype) =
|
let mut first_type = None;
|
||||||
vals.pop()
|
|
||||||
.unwrap()
|
for val in vals.drain(..) {
|
||||||
.into_crane(builder, local_variables, global_variables)?;
|
let (compiled, compiled_type) =
|
||||||
// ... and then the left.
|
val.into_crane(builder, local_variables, global_variables)?;
|
||||||
let (left, ltype) =
|
|
||||||
vals.pop()
|
if let Some(leftmost_type) = first_type {
|
||||||
.unwrap()
|
assert_eq!(leftmost_type, compiled_type);
|
||||||
.into_crane(builder, local_variables, global_variables)?;
|
} else {
|
||||||
|
first_type = Some(compiled_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(compiled);
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_type = first_type.expect("primitive op has at least one argument");
|
||||||
|
|
||||||
assert_eq!(rtype, ltype, "primitive argument types match");
|
|
||||||
// then we just need to tell Cranelift how to do each of our primitives! Much
|
// 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
|
// 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
|
// a separate function (maybe something off `Primitive`), but for now it's simple
|
||||||
// enough that we just do the `match` here.
|
// enough that we just do the `match` here.
|
||||||
match prim {
|
match prim {
|
||||||
Primitive::Plus => Ok((builder.ins().iadd(left, right), ltype)),
|
Primitive::Plus => Ok((builder.ins().iadd(values[0], values[1]), first_type)),
|
||||||
Primitive::Minus => Ok((builder.ins().isub(left, right), ltype)),
|
Primitive::Minus if values.len() == 2 => {
|
||||||
Primitive::Times => Ok((builder.ins().imul(left, right), ltype)),
|
Ok((builder.ins().isub(values[0], values[1]), first_type))
|
||||||
Primitive::Divide if rtype.is_signed() => Ok((builder.ins().sdiv(left, right), ltype)),
|
}
|
||||||
Primitive::Divide => Ok((builder.ins().udiv(left, right), ltype)),
|
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 first_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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,19 @@ macro_rules! run_op {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
fn unary_op(operation: &str, value: &Value) -> Result<Value, PrimOpError> {
|
||||||
|
match operation {
|
||||||
|
"-" => match value {
|
||||||
|
Value::I8(x) => Ok(Value::I8(-x)),
|
||||||
|
Value::I16(x) => Ok(Value::I16(-x)),
|
||||||
|
Value::I32(x) => Ok(Value::I32(-x)),
|
||||||
|
Value::I64(x) => Ok(Value::I64(-x)),
|
||||||
|
_ => Err(PrimOpError::BadTypeFor("-", value.clone())),
|
||||||
|
},
|
||||||
|
_ => Err(PrimOpError::BadArgCount(operation.to_owned(), 1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn binary_op(operation: &str, left: &Value, right: &Value) -> Result<Value, PrimOpError> {
|
fn binary_op(operation: &str, left: &Value, right: &Value) -> Result<Value, PrimOpError> {
|
||||||
match left {
|
match left {
|
||||||
Value::I8(x) => match right {
|
Value::I8(x) => match right {
|
||||||
@@ -137,13 +150,10 @@ impl Value {
|
|||||||
/// its worth being careful to make sure that your inputs won't cause either
|
/// its worth being careful to make sure that your inputs won't cause either
|
||||||
/// condition.
|
/// condition.
|
||||||
pub fn calculate(operation: &str, values: Vec<Value>) -> Result<Value, PrimOpError> {
|
pub fn calculate(operation: &str, values: Vec<Value>) -> Result<Value, PrimOpError> {
|
||||||
if values.len() == 2 {
|
match values.len() {
|
||||||
Value::binary_op(operation, &values[0], &values[1])
|
1 => Value::unary_op(operation, &values[0]),
|
||||||
} else {
|
2 => Value::binary_op(operation, &values[0], &values[1]),
|
||||||
Err(PrimOpError::BadArgCount(
|
x => Err(PrimOpError::BadArgCount(operation.to_string(), x)),
|
||||||
operation.to_string(),
|
|
||||||
values.len(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use codespan_reporting::files::SimpleFiles;
|
|
||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
use crate::ir::Program as IR;
|
use crate::ir::Program as IR;
|
||||||
use crate::syntax::Program as Syntax;
|
use crate::syntax::Program as Syntax;
|
||||||
|
use codespan_reporting::files::SimpleFiles;
|
||||||
use cranelift_jit::JITModule;
|
use cranelift_jit::JITModule;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/examples.rs"));
|
include!(concat!(env!("OUT_DIR"), "/examples.rs"));
|
||||||
@@ -8,6 +8,7 @@ use proptest::{
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
|
const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
|
||||||
|
const OPERATORS: &[(&str, usize)] = &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Name(String);
|
struct Name(String);
|
||||||
@@ -26,40 +27,34 @@ impl Arbitrary for Program {
|
|||||||
type Strategy = BoxedStrategy<Self>;
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
|
||||||
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
|
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
|
||||||
let optionals = Vec::<Option<(Name, ConstantType)>>::arbitrary();
|
let optionals = Vec::<(Name, ConstantType, u8)>::arbitrary();
|
||||||
|
|
||||||
optionals
|
optionals
|
||||||
.prop_flat_map(|mut possible_names| {
|
.prop_flat_map(|mut possible_names| {
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
let mut defined_variables: HashMap<String, ConstantType> = HashMap::new();
|
let mut defined_variables: HashMap<String, ConstantType> = HashMap::new();
|
||||||
|
|
||||||
for possible_name in possible_names.drain(..) {
|
for (possible_name, possible_type, dice_roll) in possible_names.drain(..) {
|
||||||
match possible_name {
|
if !defined_variables.is_empty() && dice_roll < 100 {
|
||||||
None if defined_variables.is_empty() => continue,
|
statements.push(
|
||||||
None => statements.push(
|
|
||||||
Union::new(defined_variables.keys().map(|name| {
|
Union::new(defined_variables.keys().map(|name| {
|
||||||
Just(Statement::Print(Location::manufactured(), name.to_string()))
|
Just(Statement::Print(Location::manufactured(), name.to_string()))
|
||||||
}))
|
}))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
),
|
);
|
||||||
Some((new_name, new_type)) => {
|
} else {
|
||||||
let closures_name = new_name.0.clone();
|
let closures_name = possible_name.0.clone();
|
||||||
let retval = Expression::arbitrary_with((
|
let retval = Expression::arbitrary_with((
|
||||||
Some(defined_variables.clone()),
|
Some(defined_variables.clone()),
|
||||||
Some(new_type),
|
Some(possible_type),
|
||||||
))
|
))
|
||||||
.prop_map(move |exp| {
|
.prop_map(move |exp| {
|
||||||
Statement::Binding(
|
Statement::Binding(Location::manufactured(), closures_name.clone(), exp)
|
||||||
Location::manufactured(),
|
})
|
||||||
closures_name.clone(),
|
.boxed();
|
||||||
exp,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
defined_variables.insert(new_name.0, new_type);
|
defined_variables.insert(possible_name.0, possible_type);
|
||||||
statements.push(retval);
|
statements.push(retval);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,13 +126,28 @@ impl Arbitrary for Expression {
|
|||||||
|
|
||||||
leaf_strategy
|
leaf_strategy
|
||||||
.prop_recursive(3, 64, 2, move |inner| {
|
.prop_recursive(3, 64, 2, move |inner| {
|
||||||
(
|
(select(OPERATORS), proptest::collection::vec(inner, 2)).prop_map(
|
||||||
select(super::BINARY_OPERATORS),
|
move |((operator, arg_count), mut exprs)| {
|
||||||
proptest::collection::vec(inner, 2),
|
if arg_count == 1 && operator == "-" {
|
||||||
|
if target_type.map(|x| x.is_signed()).unwrap_or(false) {
|
||||||
|
Expression::Primitive(
|
||||||
|
Location::manufactured(),
|
||||||
|
operator.to_string(),
|
||||||
|
exprs,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
exprs.pop().unwrap()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exprs.truncate(arg_count);
|
||||||
|
Expression::Primitive(
|
||||||
|
Location::manufactured(),
|
||||||
|
operator.to_string(),
|
||||||
|
exprs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.prop_map(move |(operator, exprs)| {
|
|
||||||
Expression::Primitive(Location::manufactured(), operator.to_string(), exprs)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
pub use crate::syntax::tokens::ConstantType;
|
pub use crate::syntax::tokens::ConstantType;
|
||||||
use crate::syntax::Location;
|
use crate::syntax::Location;
|
||||||
|
|
||||||
/// The set of valid binary operators.
|
|
||||||
pub static BINARY_OPERATORS: &[&str] = &["+", "-", "*", "/"];
|
|
||||||
|
|
||||||
/// A structure represented a parsed program.
|
/// A structure represented a parsed program.
|
||||||
///
|
///
|
||||||
/// One `Program` is associated with exactly one input file, and the
|
/// One `Program` is associated with exactly one input file, and the
|
||||||
|
|||||||
@@ -133,14 +133,14 @@ AdditiveExpression: Expression = {
|
|||||||
|
|
||||||
// similarly, we group multiplication and division under "multiplicative"
|
// similarly, we group multiplication and division under "multiplicative"
|
||||||
MultiplicativeExpression: Expression = {
|
MultiplicativeExpression: Expression = {
|
||||||
<e1:MultiplicativeExpression> <l:@L> "*" <e2:AtomicExpression> => Expression::Primitive(Location::new(file_idx, l), "*".to_string(), vec![e1, e2]),
|
<e1:MultiplicativeExpression> <l:@L> "*" <e2:UnaryExpression> => Expression::Primitive(Location::new(file_idx, l), "*".to_string(), vec![e1, e2]),
|
||||||
<e1:MultiplicativeExpression> <l:@L> "/" <e2:AtomicExpression> => Expression::Primitive(Location::new(file_idx, l), "/".to_string(), vec![e1, e2]),
|
<e1:MultiplicativeExpression> <l:@L> "/" <e2:UnaryExpression> => Expression::Primitive(Location::new(file_idx, l), "/".to_string(), vec![e1, e2]),
|
||||||
AtomicExpression,
|
UnaryExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
UnaryExpression: Expression = {
|
UnaryExpression: Expression = {
|
||||||
<l:@L> "-" <e:MultiplicativeExpression> => Expression::Primitive(Location::new(file_idx, l), "-".to_string(), vec![e]),
|
<l:@L> "-" <e:UnaryExpression> => Expression::Primitive(Location::new(file_idx, l), "-".to_string(), vec![e]),
|
||||||
<l:@L> "<" <v:"<var>"> ">" <e:MultiplicativeExpression> => Expression::Cast(Location::new(file_idx, l), v.to_string(), Box::new(e)),
|
<l:@L> "<" <v:"<var>"> ">" <e:UnaryExpression> => Expression::Cast(Location::new(file_idx, l), v.to_string(), Box::new(e)),
|
||||||
AtomicExpression,
|
AtomicExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::syntax::ast::{Expression, Program, Statement, Value, BINARY_OPERATORS};
|
use crate::syntax::ast::{Expression, Program, Statement, Value};
|
||||||
use pretty::{DocAllocator, DocBuilder, Pretty};
|
use pretty::{DocAllocator, DocBuilder, Pretty};
|
||||||
|
|
||||||
use super::ConstantType;
|
use super::ConstantType;
|
||||||
@@ -56,14 +56,10 @@ where
|
|||||||
.text(t.clone())
|
.text(t.clone())
|
||||||
.angles()
|
.angles()
|
||||||
.append(e.pretty(allocator)),
|
.append(e.pretty(allocator)),
|
||||||
Expression::Primitive(_, op, exprs) if BINARY_OPERATORS.contains(&op.as_ref()) => {
|
Expression::Primitive(_, op, exprs) if exprs.len() == 1 => allocator
|
||||||
assert_eq!(
|
.text(op.to_string())
|
||||||
exprs.len(),
|
.append(exprs[0].pretty(allocator)),
|
||||||
2,
|
Expression::Primitive(_, op, exprs) if exprs.len() == 2 => {
|
||||||
"Found binary operator with {} components?",
|
|
||||||
exprs.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
let left = exprs[0].pretty(allocator);
|
let left = exprs[0].pretty(allocator);
|
||||||
let right = exprs[1].pretty(allocator);
|
let right = exprs[1].pretty(allocator);
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,10 @@ impl From<ConstantType> for cranelift_codegen::ir::Type {
|
|||||||
impl ConstantType {
|
impl ConstantType {
|
||||||
/// Returns true if the given type is (a) numeric and (b) signed;
|
/// Returns true if the given type is (a) numeric and (b) signed;
|
||||||
pub fn is_signed(&self) -> bool {
|
pub fn is_signed(&self) -> bool {
|
||||||
matches!(self, ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64)
|
matches!(
|
||||||
|
self,
|
||||||
|
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,3 +292,28 @@ fn lexer_spans() {
|
|||||||
assert_eq!(lex0.next(), Some((Token::Number((None, None, 1)), 8..9)));
|
assert_eq!(lex0.next(), Some((Token::Number((None, None, 1)), 8..9)));
|
||||||
assert_eq!(lex0.next(), None);
|
assert_eq!(lex0.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn further_spans() {
|
||||||
|
let mut lex0 = Token::lexer("x = 2i64 + 2i64;\ny = -x;\nprint y;").spanned();
|
||||||
|
assert_eq!(lex0.next(), Some((Token::var("x"), 0..1)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Equals, 2..3)));
|
||||||
|
assert_eq!(
|
||||||
|
lex0.next(),
|
||||||
|
Some((Token::Number((None, Some(ConstantType::I64), 2)), 4..8))
|
||||||
|
);
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Operator('+'), 9..10)));
|
||||||
|
assert_eq!(
|
||||||
|
lex0.next(),
|
||||||
|
Some((Token::Number((None, Some(ConstantType::I64), 2)), 11..15))
|
||||||
|
);
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Semi, 15..16)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::var("y"), 17..18)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Equals, 19..20)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Operator('-'), 21..22)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::var("x"), 22..23)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Semi, 23..24)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Print, 25..30)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::var("y"), 31..32)));
|
||||||
|
assert_eq!(lex0.next(), Some((Token::Semi, 32..33)));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user