Files
modeling-app/src/wasm-lib/grackle/src/tests.rs
Adam Chalmers e04b09fcd8 Grackle: unary operations (#1308)
Support compiling logical not and sign-flipping negation.
2024-01-23 13:57:09 +11:00

702 lines
19 KiB
Rust

use ep::UnaryArithmetic;
use pretty_assertions::assert_eq;
use super::*;
fn must_plan(program: &str) -> (Vec<Instruction>, BindingScope) {
let tokens = kcl_lib::token::lexer(program);
let parser = kcl_lib::parser::Parser::new(tokens);
let ast = parser.ast().unwrap();
let mut p = Planner::new();
let (instrs, _) = p.build_plan(ast).unwrap();
(instrs, p.binding_scope)
}
fn should_not_compile(program: &str) -> CompileError {
let tokens = kcl_lib::token::lexer(program);
let parser = kcl_lib::parser::Parser::new(tokens);
let ast = parser.ast().unwrap();
let mut p = Planner::new();
p.build_plan(ast).unwrap_err()
}
#[test]
fn assignments() {
let program = "
let x = 1
let y = 2";
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 2i64.into(),
}
]
);
}
#[test]
fn bind_array() {
let program = r#"let x = [44, 55, "sixty-six"]"#;
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 44i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 55i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(2),
value: "sixty-six".to_owned().into(),
}
]
);
}
#[test]
fn bind_nested_array() {
let program = r#"let x = [44, [55, "sixty-six"]]"#;
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 44i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 55i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(2),
value: "sixty-six".to_owned().into(),
}
]
);
}
#[test]
fn bind_arrays_with_objects_elements() {
let program = r#"let x = [44, {a: 55, b: "sixty-six"}]"#;
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 44i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 55i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(2),
value: "sixty-six".to_owned().into(),
}
]
);
}
#[test]
fn statement_after_return() {
let program = "fn f = () => {
return 1
let x = 2
}
f()";
let err = should_not_compile(program);
assert_eq!(err, CompileError::MultipleReturns);
}
#[test]
fn name_not_found() {
// Users can't assign `y` to anything because `y` is undefined.
let err = should_not_compile("let x = y");
assert_eq!(err, CompileError::Undefined { name: "y".to_owned() });
}
#[test]
fn aliases() {
let program = "
let x = 1
let y = x";
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
}]
);
}
#[test]
fn use_native_function_add() {
let program = "let x = add(1,2)";
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into()
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 2i64.into()
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO.offset(1))
},
destination: Address::ZERO.offset(2),
}
]
);
}
#[test]
fn use_native_function_id() {
let program = "let x = id(2)";
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 2i64.into()
}]
);
}
#[test]
#[ignore = "haven't done computed properties yet"]
fn computed_array_index() {
let program = r#"
let array = ["a", "b", "c"]
let index = 1+1
let prop = array[index]
"#;
let (_plan, scope) = must_plan(program);
match scope.get("prop").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO + 1);
}
other => {
panic!("expected 'prop' bound to 0x0 but it was bound to {other:?}");
}
}
}
#[test]
#[ignore = "haven't done computed properties yet"]
fn computed_member_expressions() {
let program = r#"
let obj = {x: 1, y: 2}
let index = "x"
let prop = obj[index]
"#;
let (_plan, scope) = must_plan(program);
match scope.get("prop").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO + 1);
}
other => {
panic!("expected 'prop' bound to 0x0 but it was bound to {other:?}");
}
}
}
#[test]
fn member_expressions_object() {
let program = r#"
let obj = {x: 1, y: 2}
let prop = obj["y"]
"#;
let (_plan, scope) = must_plan(program);
match scope.get("prop").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO + 1);
}
other => {
panic!("expected 'prop' bound to 0x0 but it was bound to {other:?}");
}
}
}
#[test]
fn member_expressions_array() {
let program = "
let array = [[1,2],[3,4]]
let first = array[0][0]
let last = array[1][1]
";
let (_plan, scope) = must_plan(program);
match scope.get("first").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO);
}
other => {
panic!("expected 'number' bound to 0x0 but it was bound to {other:?}");
}
}
match scope.get("last").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO + 3);
}
other => {
panic!("expected 'number' bound to 0x3 but it was bound to {other:?}");
}
}
}
#[test]
fn compile_flipped_sign() {
let program = "let x = 3
let y = -x";
let (plan, _scope) = must_plan(program);
let expected = vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 3i64.into(),
},
Instruction::UnaryArithmetic {
arithmetic: UnaryArithmetic {
operation: ep::UnaryOperation::Neg,
operand: ep::Operand::Reference(Address::ZERO),
},
destination: Address::ZERO + 1,
},
];
assert_eq!(plan, expected);
}
#[test]
fn add_literals() {
let program = "let x = 1 + 2";
let (plan, _scope) = must_plan(program);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into()
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 2i64.into()
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO.offset(1)),
},
destination: Address::ZERO.offset(2),
}
]
);
}
#[test]
fn add_vars() {
let program = "
let one = 1
let two = 2
let x = one + two";
let (plan, _bindings) = must_plan(program);
let addr0 = Address::ZERO;
let addr1 = Address::ZERO.offset(1);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: addr0,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: addr1,
value: 2i64.into(),
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
operand0: ep::Operand::Reference(addr0),
operand1: ep::Operand::Reference(addr1),
},
destination: Address::ZERO.offset(2),
}
]
);
}
#[test]
fn composite_binary_exprs() {
let program = "
let x = 1
let y = 2
let z = 3
let six = x + y + z
";
let (plan, _bindings) = must_plan(program);
let addr0 = Address::ZERO;
let addr1 = Address::ZERO.offset(1);
let addr2 = Address::ZERO.offset(2);
let addr3 = Address::ZERO.offset(3);
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: addr0,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: addr1,
value: 2i64.into(),
},
Instruction::SetPrimitive {
address: addr2,
value: 3i64.into(),
},
// Adds 1 + 2
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
operand0: ep::Operand::Reference(addr0),
operand1: ep::Operand::Reference(addr1),
},
destination: addr3,
},
// Adds `x` + 3, where `x` is (1 + 2)
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
operand0: ep::Operand::Reference(addr3),
operand1: ep::Operand::Reference(addr2),
},
destination: Address::ZERO.offset(4),
}
]
);
}
#[test]
fn use_kcl_functions_zero_params() {
let (plan, scope) = must_plan(
"fn triple = () => { return 123 }
let x = triple()",
);
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 123i64.into()
}]
);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &Address::ZERO);
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}");
}
}
}
#[test]
fn use_kcl_functions_with_optional_params() {
for (i, program) in ["fn triple = (x, y?) => { return x*3 }
let x = triple(1, 888)"]
.into_iter()
.enumerate()
{
let (plan, scope) = must_plan(program);
let destination = Address::ZERO + 3;
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 888i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 2,
value: 3i64.into(),
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Mul,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO + 2)
},
destination,
}
],
"failed test {i}"
);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &destination, "failed test {i}");
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test {i}");
}
}
}
}
#[test]
fn use_kcl_functions_with_too_many_params() {
let program = "fn triple = (x, y?) => { return x*3 }
let x = triple(1, 2, 3)";
let err = should_not_compile(program);
assert!(matches!(
err,
CompileError::TooManyArgs {
maximum: 2,
actual: 3,
..
}
))
}
#[test]
fn use_kcl_function_as_return_value() {
let program = "fn twotwotwo = () => {
return () => { return 222 }
}
let f = twotwotwo()
let x = f()";
let (plan, scope) = must_plan(program);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &Address::ZERO);
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test");
}
}
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 222i64.into()
}]
)
}
#[test]
fn define_recursive_function() {
let program = "fn add_infinitely = (i) => {
return add_infinitely(i+1)
}";
let (plan, _scope) = must_plan(program);
assert_eq!(plan, Vec::new())
}
#[test]
fn use_kcl_function_as_param() {
let program = "fn wrapper = (f) => {
return f()
}
fn twotwotwo = () => {
return 222
}
let x = wrapper(twotwotwo)";
let (plan, scope) = must_plan(program);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &Address::ZERO);
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test");
}
}
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 222i64.into()
}]
)
}
#[test]
fn use_kcl_functions_with_params() {
for (i, program) in [
"fn triple = (x) => { return x*3 }
let x = triple(1)",
"fn triple = (x,y?) => { return x*3 }
let x = triple(1)",
]
.into_iter()
.enumerate()
{
let (plan, scope) = must_plan(program);
let destination = Address::ZERO + 2;
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 3i64.into(),
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Mul,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO.offset(1))
},
destination,
}
],
"failed test {i}"
);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &destination, "failed test {i}");
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test {i}");
}
}
}
}
#[test]
fn define_kcl_functions() {
let (plan, scope) = must_plan("fn triple = (x) => { return x * 3 }");
assert!(plan.is_empty());
match scope.get("triple").unwrap() {
EpBinding::Function(KclFunction::UserDefined(expr)) => {
assert!(expr.params_optional.is_empty());
assert_eq!(expr.params_required.len(), 1);
}
other => {
panic!("expected 'triple' bound to a user-defined KCL function but it was bound to {other:?}");
}
}
}
#[test]
fn aliases_dont_affect_plans() {
let (plan1, _) = must_plan(
"let one = 1
let two = 2
let x = one + two",
);
let (plan2, _) = must_plan(
"let one = 1
let two = 2
let y = two
let x = one + y",
);
assert_eq!(plan1, plan2);
}
#[test]
fn store_object() {
let program = "const x0 = {a: 1, b: 2, c: {d: 3}}";
let (actual, bindings) = must_plan(program);
let expected = vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 2i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(2),
value: 3i64.into(),
},
];
assert_eq!(actual, expected);
eprintln!("{bindings:#?}");
assert_eq!(
bindings.get("x0").unwrap(),
&EpBinding::Map(HashMap::from([
("a".to_owned(), EpBinding::Single(Address::ZERO),),
("b".to_owned(), EpBinding::Single(Address::ZERO.offset(1))),
(
"c".to_owned(),
EpBinding::Map(HashMap::from([(
"d".to_owned(),
EpBinding::Single(Address::ZERO.offset(2))
)]))
),
]))
)
}
#[test]
fn store_object_with_array_property() {
let program = "const x0 = {a: 1, b: [2, 3]}";
let (actual, bindings) = must_plan(program);
let expected = vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(1),
value: 2i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO.offset(2),
value: 3i64.into(),
},
];
assert_eq!(actual, expected);
eprintln!("{bindings:#?}");
assert_eq!(
bindings.get("x0").unwrap(),
&EpBinding::Map(HashMap::from([
("a".to_owned(), EpBinding::Single(Address::ZERO),),
(
"b".to_owned(),
EpBinding::Sequence(vec![
EpBinding::Single(Address::ZERO.offset(1)),
EpBinding::Single(Address::ZERO.offset(2)),
])
),
]))
)
}
#[ignore = "haven't done API calls or stdlib yet"]
#[test]
fn stdlib_api_calls() {
let program = "const x0 = startSketchAt([0, 0])
const x1 = line([0, 10], x0)
const x2 = line([10, 0], x1)
const x3 = line([0, -10], x2)
const x4 = line([0, 0], x3)
const x5 = close(x4)
const x6 = extrude(20, x5)
show(x6)";
must_plan(program);
}