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::eval::EvalError;
use crate::ir::Program;
use cranelift_jit::JITModule;
use cranelift_object::ObjectModule;
use std::path::Path;
use target_lexicon::Triple;
impl Backend<JITModule> {
@@ -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::<ObjectModule>::eval(program);
assert_eq!(basic_result, compiled_result);
}

View File

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

View File

@@ -59,6 +59,19 @@ macro_rules! run_op {
}
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> {
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<Value>) -> Result<Value, PrimOpError> {
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)),
}
}
}

View File

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

View File

@@ -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,42 +27,36 @@ impl Arbitrary for Program {
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
let optionals = Vec::<Option<(Name, ConstantType)>>::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<String, ConstantType> = 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();
);
} else {
let closures_name = possible_name.0.clone();
let retval = Expression::arbitrary_with((
Some(defined_variables.clone()),
Some(new_type),
Some(possible_type),
))
.prop_map(move |exp| {
Statement::Binding(
Location::manufactured(),
closures_name.clone(),
exp,
)
Statement::Binding(Location::manufactured(), closures_name.clone(), exp)
})
.boxed();
defined_variables.insert(new_name.0, new_type);
defined_variables.insert(possible_name.0, possible_type);
statements.push(retval);
}
}
}
statements
})
@@ -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()
}

View File

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

View File

@@ -133,14 +133,14 @@ AdditiveExpression: Expression = {
// similarly, we group multiplication and division under "multiplicative"
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:AtomicExpression> => Expression::Primitive(Location::new(file_idx, l), "/".to_string(), vec![e1, e2]),
AtomicExpression,
<e1:MultiplicativeExpression> <l:@L> "*" <e2:UnaryExpression> => 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]),
UnaryExpression,
}
UnaryExpression: Expression = {
<l:@L> "-" <e:MultiplicativeExpression> => 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> "-" <e:UnaryExpression> => Expression::Primitive(Location::new(file_idx, l), "-".to_string(), vec![e]),
<l:@L> "<" <v:"<var>"> ">" <e:UnaryExpression> => Expression::Cast(Location::new(file_idx, l), v.to_string(), Box::new(e)),
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 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);

View File

@@ -170,7 +170,10 @@ impl From<ConstantType> 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)));
}