Get unary negation working.

This commit is contained in:
2023-06-07 21:38:18 -07:00
parent 469fe35e46
commit b4ad24030f
10 changed files with 138 additions and 74 deletions

View File

@@ -0,0 +1,3 @@
x = 2i64 + 2i64;
y = -x;
print y;

View File

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

View File

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

View File

@@ -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(),
))
} }
} }
} }

View File

@@ -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"));

View File

@@ -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,42 +27,36 @@ 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(),
exp,
)
}) })
.boxed(); .boxed();
defined_variables.insert(new_name.0, new_type); defined_variables.insert(possible_name.0, possible_type);
statements.push(retval); statements.push(retval);
} }
} }
}
statements statements
}) })
@@ -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()
} }

View File

@@ -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

View File

@@ -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,
} }

View File

@@ -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);

View File

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