From b4ad24030ff977564e3d866a943666ac145e2fa1 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Wed, 7 Jun 2023 21:38:18 -0700 Subject: [PATCH] Get unary negation working. --- examples/basic/negation.ngr | 3 ++ src/backend/eval.rs | 13 +++++-- src/backend/into_crane.rs | 43 ++++++++++++++--------- src/eval/primop.rs | 24 +++++++++---- src/examples.rs | 4 +-- src/syntax/arbitrary.rs | 68 +++++++++++++++++++++---------------- src/syntax/ast.rs | 3 -- src/syntax/parser.lalrpop | 10 +++--- src/syntax/pretty.rs | 14 +++----- src/syntax/tokens.rs | 30 +++++++++++++++- 10 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 examples/basic/negation.ngr diff --git a/examples/basic/negation.ngr b/examples/basic/negation.ngr new file mode 100644 index 0000000..18fd866 --- /dev/null +++ b/examples/basic/negation.ngr @@ -0,0 +1,3 @@ +x = 2i64 + 2i64; +y = -x; +print y; \ No newline at end of file diff --git a/src/backend/eval.rs b/src/backend/eval.rs index e9c88f1..2b7a1a2 100644 --- a/src/backend/eval.rs +++ b/src/backend/eval.rs @@ -1,10 +1,9 @@ -use std::path::Path; - use crate::backend::Backend; use crate::eval::EvalError; use crate::ir::Program; use cranelift_jit::JITModule; use cranelift_object::ObjectModule; +use std::path::Path; use target_lexicon::Triple; impl Backend { @@ -127,6 +126,16 @@ proptest::proptest! { let basic_result = basic_result.map(|x| x.replace('\n', "\r\n")); 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::::eval(program); assert_eq!(basic_result, compiled_result); } diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index f0f4e25..18f040f 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -308,28 +308,39 @@ impl Expression { } Expression::Primitive(_, prim, mut vals) => { - // we're going to use `pop`, so we're going to pull and compile the right value ... - let (right, rtype) = - vals.pop() - .unwrap() - .into_crane(builder, local_variables, global_variables)?; - // ... and then the left. - let (left, ltype) = - vals.pop() - .unwrap() - .into_crane(builder, local_variables, global_variables)?; + let mut values = vec![]; + let mut first_type = None; + + for val in vals.drain(..) { + let (compiled, compiled_type) = + val.into_crane(builder, local_variables, global_variables)?; + + 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"); - assert_eq!(rtype, ltype, "primitive argument types match"); // 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(left, right), ltype)), - Primitive::Minus => Ok((builder.ins().isub(left, right), ltype)), - Primitive::Times => Ok((builder.ins().imul(left, right), ltype)), - Primitive::Divide if rtype.is_signed() => Ok((builder.ins().sdiv(left, right), ltype)), - Primitive::Divide => Ok((builder.ins().udiv(left, right), ltype)), + 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 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)), } } } diff --git a/src/eval/primop.rs b/src/eval/primop.rs index ffc74f2..5fa3a1c 100644 --- a/src/eval/primop.rs +++ b/src/eval/primop.rs @@ -59,6 +59,19 @@ macro_rules! run_op { } impl Value { + fn unary_op(operation: &str, value: &Value) -> Result { + 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 { match left { 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 /// condition. pub fn calculate(operation: &str, values: Vec) -> Result { - if values.len() == 2 { - Value::binary_op(operation, &values[0], &values[1]) - } else { - Err(PrimOpError::BadArgCount( - operation.to_string(), - values.len(), - )) + match values.len() { + 1 => Value::unary_op(operation, &values[0]), + 2 => Value::binary_op(operation, &values[0], &values[1]), + x => Err(PrimOpError::BadArgCount(operation.to_string(), x)), } } } diff --git a/src/examples.rs b/src/examples.rs index 47b0bdc..46dd175 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -1,7 +1,7 @@ -use codespan_reporting::files::SimpleFiles; use crate::backend::Backend; use crate::ir::Program as IR; use crate::syntax::Program as Syntax; +use codespan_reporting::files::SimpleFiles; use cranelift_jit::JITModule; -include!(concat!(env!("OUT_DIR"), "/examples.rs")); \ No newline at end of file +include!(concat!(env!("OUT_DIR"), "/examples.rs")); diff --git a/src/syntax/arbitrary.rs b/src/syntax/arbitrary.rs index 0d3a9a9..51cec90 100644 --- a/src/syntax/arbitrary.rs +++ b/src/syntax/arbitrary.rs @@ -8,6 +8,7 @@ use proptest::{ use std::collections::HashMap; const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*"; +const OPERATORS: &[(&str, usize)] = &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)]; #[derive(Debug)] struct Name(String); @@ -26,40 +27,34 @@ impl Arbitrary for Program { type Strategy = BoxedStrategy; fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - let optionals = Vec::>::arbitrary(); + let optionals = Vec::<(Name, ConstantType, u8)>::arbitrary(); optionals .prop_flat_map(|mut possible_names| { let mut statements = Vec::new(); let mut defined_variables: HashMap = HashMap::new(); - for possible_name in possible_names.drain(..) { - match possible_name { - None if defined_variables.is_empty() => continue, - None => statements.push( + for (possible_name, possible_type, dice_roll) in possible_names.drain(..) { + if !defined_variables.is_empty() && dice_roll < 100 { + statements.push( Union::new(defined_variables.keys().map(|name| { Just(Statement::Print(Location::manufactured(), name.to_string())) })) .boxed(), - ), - Some((new_name, new_type)) => { - let closures_name = new_name.0.clone(); - let retval = Expression::arbitrary_with(( - Some(defined_variables.clone()), - Some(new_type), - )) - .prop_map(move |exp| { - Statement::Binding( - Location::manufactured(), - closures_name.clone(), - exp, - ) - }) - .boxed(); + ); + } else { + let closures_name = possible_name.0.clone(); + let retval = Expression::arbitrary_with(( + Some(defined_variables.clone()), + Some(possible_type), + )) + .prop_map(move |exp| { + Statement::Binding(Location::manufactured(), closures_name.clone(), exp) + }) + .boxed(); - defined_variables.insert(new_name.0, new_type); - statements.push(retval); - } + defined_variables.insert(possible_name.0, possible_type); + statements.push(retval); } } @@ -131,13 +126,28 @@ impl Arbitrary for Expression { leaf_strategy .prop_recursive(3, 64, 2, move |inner| { - ( - select(super::BINARY_OPERATORS), - proptest::collection::vec(inner, 2), + (select(OPERATORS), proptest::collection::vec(inner, 2)).prop_map( + move |((operator, arg_count), mut exprs)| { + 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() } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 5e5792a..5e356e5 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1,9 +1,6 @@ pub use crate::syntax::tokens::ConstantType; use crate::syntax::Location; -/// The set of valid binary operators. -pub static BINARY_OPERATORS: &[&str] = &["+", "-", "*", "/"]; - /// A structure represented a parsed program. /// /// One `Program` is associated with exactly one input file, and the diff --git a/src/syntax/parser.lalrpop b/src/syntax/parser.lalrpop index 43019c3..3cedfc2 100644 --- a/src/syntax/parser.lalrpop +++ b/src/syntax/parser.lalrpop @@ -133,14 +133,14 @@ AdditiveExpression: Expression = { // similarly, we group multiplication and division under "multiplicative" MultiplicativeExpression: Expression = { - "*" => Expression::Primitive(Location::new(file_idx, l), "*".to_string(), vec![e1, e2]), - "/" => Expression::Primitive(Location::new(file_idx, l), "/".to_string(), vec![e1, e2]), - AtomicExpression, + "*" => Expression::Primitive(Location::new(file_idx, l), "*".to_string(), vec![e1, e2]), + "/" => Expression::Primitive(Location::new(file_idx, l), "/".to_string(), vec![e1, e2]), + UnaryExpression, } UnaryExpression: Expression = { - "-" => Expression::Primitive(Location::new(file_idx, l), "-".to_string(), vec![e]), - "<" "> ">" => Expression::Cast(Location::new(file_idx, l), v.to_string(), Box::new(e)), + "-" => Expression::Primitive(Location::new(file_idx, l), "-".to_string(), vec![e]), + "<" "> ">" => Expression::Cast(Location::new(file_idx, l), v.to_string(), Box::new(e)), AtomicExpression, } diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index 7d92589..6a9338f 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -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 super::ConstantType; @@ -56,14 +56,10 @@ where .text(t.clone()) .angles() .append(e.pretty(allocator)), - Expression::Primitive(_, op, exprs) if BINARY_OPERATORS.contains(&op.as_ref()) => { - assert_eq!( - exprs.len(), - 2, - "Found binary operator with {} components?", - exprs.len() - ); - + Expression::Primitive(_, op, exprs) if exprs.len() == 1 => allocator + .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); diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 2ce31e3..682e266 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -170,7 +170,10 @@ impl From for cranelift_codegen::ir::Type { impl ConstantType { /// Returns true if the given type is (a) numeric and (b) signed; 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(), 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))); +}