Compare commits
6 Commits
v0.31.0
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
09dc17f994 | |||
4fa9eb4a0e | |||
c2d3808f7c | |||
e77c83a7b8 | |||
189099bce5 | |||
628310952f |
@ -17,7 +17,12 @@ pub enum EpBinding {
|
||||
/// A KCL value which gets stored in a particular address in KCEP memory.
|
||||
Single(Address),
|
||||
/// A sequence of KCL values, indexed by their position in the sequence.
|
||||
Sequence(Vec<EpBinding>),
|
||||
Sequence {
|
||||
/// Address where the length of the array is stored.
|
||||
length_at: Address,
|
||||
/// Where is each element in the array bound?
|
||||
elements: Vec<EpBinding>,
|
||||
},
|
||||
/// A sequence of KCL values, indexed by their identifier.
|
||||
Map(HashMap<String, EpBinding>),
|
||||
/// Not associated with a KCEP address.
|
||||
@ -38,9 +43,11 @@ impl EpBinding {
|
||||
LiteralIdentifier::Literal(litval) => match litval.value {
|
||||
// Arrays can be indexed by integers.
|
||||
LiteralValue::IInteger(i) => match self {
|
||||
EpBinding::Sequence(seq) => {
|
||||
EpBinding::Sequence { length_at: _, elements } => {
|
||||
let i = usize::try_from(i).map_err(|_| CompileError::InvalidIndex(i.to_string()))?;
|
||||
seq.get(i).ok_or(CompileError::IndexOutOfBounds { i, len: seq.len() })
|
||||
elements
|
||||
.get(i)
|
||||
.ok_or(CompileError::IndexOutOfBounds { i, len: elements.len() })
|
||||
}
|
||||
EpBinding::Map(_) => Err(CompileError::CannotIndex),
|
||||
EpBinding::Single(_) => Err(CompileError::CannotIndex),
|
||||
@ -50,7 +57,7 @@ impl EpBinding {
|
||||
LiteralValue::String(property) => match self {
|
||||
EpBinding::Single(_) => Err(CompileError::NoProperties),
|
||||
EpBinding::Function(_) => Err(CompileError::NoProperties),
|
||||
EpBinding::Sequence(_) => Err(CompileError::ArrayDoesNotHaveProperties),
|
||||
EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties),
|
||||
EpBinding::Map(map) => map.get(&property).ok_or(CompileError::UndefinedProperty { property }),
|
||||
},
|
||||
// It's never valid to index by a fractional number.
|
||||
|
@ -231,7 +231,7 @@ impl Planner {
|
||||
binding: arg,
|
||||
} = match KclValueGroup::from(argument) {
|
||||
KclValueGroup::Single(value) => self.plan_to_compute_single(ctx, value)?,
|
||||
KclValueGroup::ArrayExpression(_) => todo!(),
|
||||
KclValueGroup::ArrayExpression(expr) => self.plan_to_bind_array(ctx, *expr)?,
|
||||
KclValueGroup::ObjectExpression(_) => todo!(),
|
||||
};
|
||||
acc_instrs.extend(new_instructions);
|
||||
@ -411,9 +411,9 @@ impl Planner {
|
||||
.declarations
|
||||
.into_iter()
|
||||
.try_fold(Vec::new(), |mut acc, declaration| {
|
||||
let (instrs, binding) = self.plan_to_bind_one(&mut ctx, declaration.init)?;
|
||||
let EvalPlan { instructions, binding } = self.plan_to_bind_one(&mut ctx, declaration.init)?;
|
||||
self.binding_scope.bind(declaration.id.name, binding);
|
||||
acc.extend(instrs);
|
||||
acc.extend(instructions);
|
||||
Ok(acc)
|
||||
})
|
||||
}
|
||||
@ -422,69 +422,14 @@ impl Planner {
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
value_being_bound: ast::types::Value,
|
||||
) -> Result<(Vec<Instruction>, EpBinding), CompileError> {
|
||||
) -> Result<EvalPlan, CompileError> {
|
||||
match KclValueGroup::from(value_being_bound) {
|
||||
KclValueGroup::Single(init_value) => {
|
||||
// Simple! Just evaluate it, note where the final value will be stored in KCEP memory,
|
||||
// and bind it to the KCL identifier.
|
||||
let EvalPlan { instructions, binding } = self.plan_to_compute_single(ctx, init_value)?;
|
||||
Ok((instructions, binding))
|
||||
}
|
||||
KclValueGroup::ArrayExpression(expr) => {
|
||||
// First, emit a plan to compute each element of the array.
|
||||
// Collect all the bindings from each element too.
|
||||
let (instructions, bindings) = expr.elements.into_iter().try_fold(
|
||||
(Vec::new(), Vec::new()),
|
||||
|(mut acc_instrs, mut acc_bindings), element| {
|
||||
match KclValueGroup::from(element) {
|
||||
KclValueGroup::Single(value) => {
|
||||
// If this element of the array is a single value, then binding it is
|
||||
// straightforward -- you got a single binding, no need to change anything.
|
||||
let EvalPlan { instructions, binding } = self.plan_to_compute_single(ctx, value)?;
|
||||
acc_instrs.extend(instructions);
|
||||
acc_bindings.push(binding);
|
||||
}
|
||||
KclValueGroup::ArrayExpression(expr) => {
|
||||
// If this element of the array is _itself_ an array, then we need to
|
||||
// emit a plan to calculate each element of this child array.
|
||||
// Then we collect the child array's bindings, and bind them to one
|
||||
// element of the parent array.
|
||||
let binding = expr
|
||||
.elements
|
||||
.into_iter()
|
||||
.try_fold(Vec::new(), |mut seq, child_element| {
|
||||
let (instructions, binding) = self.plan_to_bind_one(ctx, child_element)?;
|
||||
acc_instrs.extend(instructions);
|
||||
seq.push(binding);
|
||||
Ok(seq)
|
||||
})
|
||||
.map(EpBinding::Sequence)?;
|
||||
acc_bindings.push(binding);
|
||||
}
|
||||
KclValueGroup::ObjectExpression(expr) => {
|
||||
// If this element of the array is an object, then we need to
|
||||
// emit a plan to calculate each value of each property of the object.
|
||||
// Then we collect the bindings for each child value, and bind them to one
|
||||
// element of the parent array.
|
||||
let map = HashMap::with_capacity(expr.properties.len());
|
||||
let binding = expr
|
||||
.properties
|
||||
.into_iter()
|
||||
.try_fold(map, |mut map, property| {
|
||||
let (instructions, binding) = self.plan_to_bind_one(ctx, property.value)?;
|
||||
map.insert(property.key.name, binding);
|
||||
acc_instrs.extend(instructions);
|
||||
Ok(map)
|
||||
})
|
||||
.map(EpBinding::Map)?;
|
||||
acc_bindings.push(binding);
|
||||
}
|
||||
};
|
||||
Ok((acc_instrs, acc_bindings))
|
||||
},
|
||||
)?;
|
||||
Ok((instructions, EpBinding::Sequence(bindings)))
|
||||
self.plan_to_compute_single(ctx, init_value)
|
||||
}
|
||||
KclValueGroup::ArrayExpression(expr) => self.plan_to_bind_array(ctx, *expr),
|
||||
KclValueGroup::ObjectExpression(expr) => {
|
||||
// Convert the object to a sequence of key-value pairs.
|
||||
let mut kvs = expr.properties.into_iter().map(|prop| (prop.key, prop.value));
|
||||
@ -502,16 +447,22 @@ impl Planner {
|
||||
// each element of that array. Collect their bindings, and bind them all
|
||||
// under one property of the parent object.
|
||||
let n = expr.elements.len();
|
||||
let length_at = self.next_addr.offset_by(1);
|
||||
acc_instrs.push(Instruction::SetPrimitive {
|
||||
address: length_at,
|
||||
value: n.into(),
|
||||
});
|
||||
let binding = expr
|
||||
.elements
|
||||
.into_iter()
|
||||
.try_fold(Vec::with_capacity(n), |mut seq, child_element| {
|
||||
let (instructions, binding) = self.plan_to_bind_one(ctx, child_element)?;
|
||||
let EvalPlan { instructions, binding } =
|
||||
self.plan_to_bind_one(ctx, child_element)?;
|
||||
seq.push(binding);
|
||||
acc_instrs.extend(instructions);
|
||||
Ok(seq)
|
||||
})
|
||||
.map(EpBinding::Sequence)?;
|
||||
.map(|elements| EpBinding::Sequence { length_at, elements })?;
|
||||
acc_bindings.insert(key.name, binding);
|
||||
}
|
||||
KclValueGroup::ObjectExpression(expr) => {
|
||||
@ -524,7 +475,8 @@ impl Planner {
|
||||
.properties
|
||||
.into_iter()
|
||||
.try_fold(HashMap::with_capacity(n), |mut map, property| {
|
||||
let (instructions, binding) = self.plan_to_bind_one(ctx, property.value)?;
|
||||
let EvalPlan { instructions, binding } =
|
||||
self.plan_to_bind_one(ctx, property.value)?;
|
||||
map.insert(property.key.name, binding);
|
||||
acc_instrs.extend(instructions);
|
||||
Ok(map)
|
||||
@ -536,10 +488,90 @@ impl Planner {
|
||||
Ok((acc_instrs, acc_bindings))
|
||||
},
|
||||
)?;
|
||||
Ok((instructions, EpBinding::Map(each_property_binding)))
|
||||
Ok(EvalPlan {
|
||||
instructions,
|
||||
binding: EpBinding::Map(each_property_binding),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_to_bind_array(
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
expr: ast::types::ArrayExpression,
|
||||
) -> Result<EvalPlan, CompileError> {
|
||||
let length_at = self.next_addr.offset_by(1);
|
||||
let mut instructions = vec![Instruction::SetPrimitive {
|
||||
address: length_at,
|
||||
value: expr.elements.len().into(),
|
||||
}];
|
||||
// First, emit a plan to compute each element of the array.
|
||||
// Collect all the bindings from each element too.
|
||||
let (instrs, bindings) = expr.elements.into_iter().try_fold(
|
||||
(Vec::new(), Vec::new()),
|
||||
|(mut acc_instrs, mut acc_bindings), element| {
|
||||
match KclValueGroup::from(element) {
|
||||
KclValueGroup::Single(value) => {
|
||||
// If this element of the array is a single value, then binding it is
|
||||
// straightforward -- you got a single binding, no need to change anything.
|
||||
let EvalPlan { instructions, binding } = self.plan_to_compute_single(ctx, value)?;
|
||||
acc_instrs.extend(instructions);
|
||||
acc_bindings.push(binding);
|
||||
}
|
||||
KclValueGroup::ArrayExpression(expr) => {
|
||||
// If this element of the array is _itself_ an array, then we need to
|
||||
// emit a plan to calculate each element of this child array.
|
||||
// Then we collect the child array's bindings, and bind them to one
|
||||
// element of the parent array.
|
||||
let length_at = self.next_addr.offset_by(1);
|
||||
acc_instrs.push(Instruction::SetPrimitive {
|
||||
address: length_at,
|
||||
value: expr.elements.len().into(),
|
||||
});
|
||||
let binding = expr
|
||||
.elements
|
||||
.into_iter()
|
||||
.try_fold(Vec::new(), |mut seq, child_element| {
|
||||
let EvalPlan { instructions, binding } = self.plan_to_bind_one(ctx, child_element)?;
|
||||
acc_instrs.extend(instructions);
|
||||
seq.push(binding);
|
||||
Ok(seq)
|
||||
})
|
||||
.map(|elements| EpBinding::Sequence { length_at, elements })?;
|
||||
acc_bindings.push(binding);
|
||||
}
|
||||
KclValueGroup::ObjectExpression(expr) => {
|
||||
// If this element of the array is an object, then we need to
|
||||
// emit a plan to calculate each value of each property of the object.
|
||||
// Then we collect the bindings for each child value, and bind them to one
|
||||
// element of the parent array.
|
||||
let map = HashMap::with_capacity(expr.properties.len());
|
||||
let binding = expr
|
||||
.properties
|
||||
.into_iter()
|
||||
.try_fold(map, |mut map, property| {
|
||||
let EvalPlan { instructions, binding } = self.plan_to_bind_one(ctx, property.value)?;
|
||||
map.insert(property.key.name, binding);
|
||||
acc_instrs.extend(instructions);
|
||||
Ok(map)
|
||||
})
|
||||
.map(EpBinding::Map)?;
|
||||
acc_bindings.push(binding);
|
||||
}
|
||||
};
|
||||
Ok((acc_instrs, acc_bindings))
|
||||
},
|
||||
)?;
|
||||
instructions.extend(instrs);
|
||||
Ok(EvalPlan {
|
||||
instructions,
|
||||
binding: EpBinding::Sequence {
|
||||
length_at,
|
||||
elements: bindings,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Every KCL literal value is equivalent to an Execution Plan value, and therefore can be
|
||||
|
@ -48,16 +48,22 @@ fn bind_array() {
|
||||
assert_eq!(
|
||||
plan,
|
||||
vec![
|
||||
// Arrays start with the length.
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: 3usize.into(),
|
||||
},
|
||||
// Then the elements follow.
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 44i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(1),
|
||||
address: Address::ZERO + 2,
|
||||
value: 55i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(2),
|
||||
address: Address::ZERO + 3,
|
||||
value: "sixty-six".to_owned().into(),
|
||||
}
|
||||
]
|
||||
@ -73,14 +79,22 @@ fn bind_nested_array() {
|
||||
vec![
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: 2usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 44i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(1),
|
||||
address: Address::ZERO + 2,
|
||||
value: 2usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 3,
|
||||
value: 55i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(2),
|
||||
address: Address::ZERO + 4,
|
||||
value: "sixty-six".to_owned().into(),
|
||||
}
|
||||
]
|
||||
@ -96,14 +110,18 @@ fn bind_arrays_with_objects_elements() {
|
||||
vec![
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: 2usize.into()
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 44i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(1),
|
||||
address: Address::ZERO + 2,
|
||||
value: 55i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(2),
|
||||
address: Address::ZERO + 3,
|
||||
value: "sixty-six".to_owned().into(),
|
||||
}
|
||||
]
|
||||
@ -187,6 +205,45 @@ fn use_native_function_add() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrays_as_parameters() {
|
||||
let program = "fn identity = (x) => { return x }
|
||||
let array = identity([1,2,3])";
|
||||
let (plan, scope) = must_plan(program);
|
||||
let expected_plan = vec![
|
||||
// Array length
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: 3usize.into(),
|
||||
},
|
||||
// Array contents
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 1i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 2,
|
||||
value: 2i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 3,
|
||||
value: 3i64.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(plan, expected_plan);
|
||||
assert_eq!(
|
||||
scope.get("array").unwrap(),
|
||||
&EpBinding::Sequence {
|
||||
length_at: Address::ZERO,
|
||||
elements: vec![
|
||||
EpBinding::Single(Address::ZERO + 1),
|
||||
EpBinding::Single(Address::ZERO + 2),
|
||||
EpBinding::Single(Address::ZERO + 3),
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_native_function_id() {
|
||||
let program = "let x = id(2)";
|
||||
@ -265,7 +322,7 @@ fn member_expressions_array() {
|
||||
let (_plan, scope) = must_plan(program);
|
||||
match scope.get("first").unwrap() {
|
||||
EpBinding::Single(addr) => {
|
||||
assert_eq!(*addr, Address::ZERO);
|
||||
assert_eq!(*addr, Address::ZERO + 2);
|
||||
}
|
||||
other => {
|
||||
panic!("expected 'number' bound to 0x0 but it was bound to {other:?}");
|
||||
@ -273,7 +330,7 @@ fn member_expressions_array() {
|
||||
}
|
||||
match scope.get("last").unwrap() {
|
||||
EpBinding::Single(addr) => {
|
||||
assert_eq!(*addr, Address::ZERO + 3);
|
||||
assert_eq!(*addr, Address::ZERO + 6);
|
||||
}
|
||||
other => {
|
||||
panic!("expected 'number' bound to 0x3 but it was bound to {other:?}");
|
||||
@ -682,14 +739,11 @@ fn store_object() {
|
||||
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))),
|
||||
("a".to_owned(), EpBinding::Single(Address::ZERO)),
|
||||
("b".to_owned(), EpBinding::Single(Address::ZERO + 1)),
|
||||
(
|
||||
"c".to_owned(),
|
||||
EpBinding::Map(HashMap::from([(
|
||||
"d".to_owned(),
|
||||
EpBinding::Single(Address::ZERO.offset(2))
|
||||
)]))
|
||||
EpBinding::Map(HashMap::from([("d".to_owned(), EpBinding::Single(Address::ZERO + 2))]))
|
||||
),
|
||||
]))
|
||||
)
|
||||
@ -697,7 +751,7 @@ fn store_object() {
|
||||
|
||||
#[test]
|
||||
fn store_object_with_array_property() {
|
||||
let program = "const x0 = {a: 1, b: [2, 3]}";
|
||||
let program = "const x0 = {a: 1, b: [22, 33]}";
|
||||
let (actual, bindings) = must_plan(program);
|
||||
let expected = vec![
|
||||
Instruction::SetPrimitive {
|
||||
@ -705,12 +759,16 @@ fn store_object_with_array_property() {
|
||||
value: 1i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(1),
|
||||
value: 2i64.into(),
|
||||
address: Address::ZERO + 1,
|
||||
value: 2usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(2),
|
||||
value: 3i64.into(),
|
||||
address: Address::ZERO + 2,
|
||||
value: 22i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 3,
|
||||
value: 33i64.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
@ -721,10 +779,13 @@ fn store_object_with_array_property() {
|
||||
("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)),
|
||||
])
|
||||
EpBinding::Sequence {
|
||||
length_at: Address::ZERO.offset(1),
|
||||
elements: vec![
|
||||
EpBinding::Single(Address::ZERO.offset(2)),
|
||||
EpBinding::Single(Address::ZERO.offset(3)),
|
||||
]
|
||||
}
|
||||
),
|
||||
]))
|
||||
)
|
||||
|
Reference in New Issue
Block a user