Grackle: Store KCL objects as KCEP objects (#1333)
* Grackle: Store KCL objects as KCEP objects * Remove KCL SingleValue * Fix a test, update map bindings * Fix tests
This commit is contained in:
12
src/wasm-lib/Cargo.lock
generated
12
src/wasm-lib/Cargo.lock
generated
@ -1950,7 +1950,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kittycad-execution-plan"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0d1fb31b8624abea0d270365cc459e78577d26f8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"insta",
|
||||
@ -1980,7 +1980,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kittycad-execution-plan-macros"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0d1fb31b8624abea0d270365cc459e78577d26f8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1989,9 +1989,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-execution-plan-traits"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29dc558ca98b726d998fe9617f7cab0c427f54b49b42fb68d0a69d85e1b52dc7"
|
||||
checksum = "d6f893f7837d95bb669781581842511307e42622221c6d241031f1c35c11a2af"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
@ -2001,7 +2001,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.1.12"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0d1fb31b8624abea0d270365cc459e78577d26f8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -2028,7 +2028,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kittycad-modeling-session"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0d1fb31b8624abea0d270365cc459e78577d26f8"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"kittycad",
|
||||
|
||||
@ -60,7 +60,7 @@ members = [
|
||||
[workspace.dependencies]
|
||||
kittycad = { version = "0.2.45", default-features = false, features = ["js"] }
|
||||
kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
||||
kittycad-execution-plan-traits = "0.1.5"
|
||||
kittycad-execution-plan-traits = "0.1.6"
|
||||
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
||||
kittycad-execution-plan-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
||||
|
||||
|
||||
@ -22,7 +22,10 @@ pub enum EpBinding {
|
||||
elements: Vec<EpBinding>,
|
||||
},
|
||||
/// A sequence of KCL values, indexed by their identifier.
|
||||
Map(HashMap<String, EpBinding>),
|
||||
Map {
|
||||
length_at: Address,
|
||||
properties: HashMap<String, EpBinding>,
|
||||
},
|
||||
/// Not associated with a KCEP address.
|
||||
Function(KclFunction),
|
||||
}
|
||||
@ -47,7 +50,7 @@ impl EpBinding {
|
||||
.get(i)
|
||||
.ok_or(CompileError::IndexOutOfBounds { i, len: elements.len() })
|
||||
}
|
||||
EpBinding::Map(_) => Err(CompileError::CannotIndex),
|
||||
EpBinding::Map { .. } => Err(CompileError::CannotIndex),
|
||||
EpBinding::Single(_) => Err(CompileError::CannotIndex),
|
||||
EpBinding::Function(_) => Err(CompileError::CannotIndex),
|
||||
},
|
||||
@ -56,7 +59,12 @@ impl EpBinding {
|
||||
EpBinding::Single(_) => Err(CompileError::NoProperties),
|
||||
EpBinding::Function(_) => Err(CompileError::NoProperties),
|
||||
EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties),
|
||||
EpBinding::Map(map) => map.get(&property).ok_or(CompileError::UndefinedProperty { property }),
|
||||
EpBinding::Map {
|
||||
properties,
|
||||
length_at: _,
|
||||
} => properties
|
||||
.get(&property)
|
||||
.ok_or(CompileError::UndefinedProperty { property }),
|
||||
},
|
||||
// It's never valid to index by a fractional number.
|
||||
LiteralValue::Fractional(num) => Err(CompileError::InvalidIndex(num.to_string())),
|
||||
|
||||
@ -1,90 +1,14 @@
|
||||
use kcl_lib::ast::{self, types::BinaryPart};
|
||||
|
||||
/// Basically the same enum as `kcl_lib::ast::types::Value`, but grouped according to whether the
|
||||
/// value is singular or composite.
|
||||
/// You can convert losslessly between KclValueGroup and `kcl_lib::ast::types::Value` with From/Into.
|
||||
pub enum KclValueGroup {
|
||||
Single(SingleValue),
|
||||
ObjectExpression(Box<ast::types::ObjectExpression>),
|
||||
}
|
||||
pub type SingleValue = kcl_lib::ast::types::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SingleValue {
|
||||
Literal(Box<ast::types::Literal>),
|
||||
Identifier(Box<ast::types::Identifier>),
|
||||
BinaryExpression(Box<ast::types::BinaryExpression>),
|
||||
CallExpression(Box<ast::types::CallExpression>),
|
||||
PipeExpression(Box<ast::types::PipeExpression>),
|
||||
UnaryExpression(Box<ast::types::UnaryExpression>),
|
||||
KclNoneExpression(ast::types::KclNone),
|
||||
MemberExpression(Box<ast::types::MemberExpression>),
|
||||
FunctionExpression(Box<ast::types::FunctionExpression>),
|
||||
PipeSubstitution(Box<ast::types::PipeSubstitution>),
|
||||
ArrayExpression(Box<ast::types::ArrayExpression>),
|
||||
}
|
||||
|
||||
impl From<ast::types::BinaryPart> for KclValueGroup {
|
||||
fn from(value: ast::types::BinaryPart) -> Self {
|
||||
match value {
|
||||
BinaryPart::Literal(e) => Self::Single(SingleValue::Literal(e)),
|
||||
BinaryPart::Identifier(e) => Self::Single(SingleValue::Identifier(e)),
|
||||
BinaryPart::BinaryExpression(e) => Self::Single(SingleValue::BinaryExpression(e)),
|
||||
BinaryPart::CallExpression(e) => Self::Single(SingleValue::CallExpression(e)),
|
||||
BinaryPart::UnaryExpression(e) => Self::Single(SingleValue::UnaryExpression(e)),
|
||||
BinaryPart::MemberExpression(e) => Self::Single(SingleValue::MemberExpression(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::types::BinaryPart> for SingleValue {
|
||||
fn from(value: ast::types::BinaryPart) -> Self {
|
||||
match value {
|
||||
BinaryPart::Literal(e) => Self::Literal(e),
|
||||
BinaryPart::Identifier(e) => Self::Identifier(e),
|
||||
BinaryPart::BinaryExpression(e) => Self::BinaryExpression(e),
|
||||
BinaryPart::CallExpression(e) => Self::CallExpression(e),
|
||||
BinaryPart::UnaryExpression(e) => Self::UnaryExpression(e),
|
||||
BinaryPart::MemberExpression(e) => Self::MemberExpression(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::types::Value> for KclValueGroup {
|
||||
fn from(value: ast::types::Value) -> Self {
|
||||
match value {
|
||||
ast::types::Value::Literal(e) => Self::Single(SingleValue::Literal(e)),
|
||||
ast::types::Value::Identifier(e) => Self::Single(SingleValue::Identifier(e)),
|
||||
ast::types::Value::BinaryExpression(e) => Self::Single(SingleValue::BinaryExpression(e)),
|
||||
ast::types::Value::CallExpression(e) => Self::Single(SingleValue::CallExpression(e)),
|
||||
ast::types::Value::PipeExpression(e) => Self::Single(SingleValue::PipeExpression(e)),
|
||||
ast::types::Value::None(e) => Self::Single(SingleValue::KclNoneExpression(e)),
|
||||
ast::types::Value::UnaryExpression(e) => Self::Single(SingleValue::UnaryExpression(e)),
|
||||
ast::types::Value::ArrayExpression(e) => Self::Single(SingleValue::ArrayExpression(e)),
|
||||
ast::types::Value::ObjectExpression(e) => Self::ObjectExpression(e),
|
||||
ast::types::Value::MemberExpression(e) => Self::Single(SingleValue::MemberExpression(e)),
|
||||
ast::types::Value::FunctionExpression(e) => Self::Single(SingleValue::FunctionExpression(e)),
|
||||
ast::types::Value::PipeSubstitution(e) => Self::Single(SingleValue::PipeSubstitution(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KclValueGroup> for ast::types::Value {
|
||||
fn from(value: KclValueGroup) -> Self {
|
||||
match value {
|
||||
KclValueGroup::Single(e) => match e {
|
||||
SingleValue::Literal(e) => ast::types::Value::Literal(e),
|
||||
SingleValue::Identifier(e) => ast::types::Value::Identifier(e),
|
||||
SingleValue::BinaryExpression(e) => ast::types::Value::BinaryExpression(e),
|
||||
SingleValue::CallExpression(e) => ast::types::Value::CallExpression(e),
|
||||
SingleValue::PipeExpression(e) => ast::types::Value::PipeExpression(e),
|
||||
SingleValue::UnaryExpression(e) => ast::types::Value::UnaryExpression(e),
|
||||
SingleValue::KclNoneExpression(e) => ast::types::Value::None(e),
|
||||
SingleValue::MemberExpression(e) => ast::types::Value::MemberExpression(e),
|
||||
SingleValue::FunctionExpression(e) => ast::types::Value::FunctionExpression(e),
|
||||
SingleValue::PipeSubstitution(e) => ast::types::Value::PipeSubstitution(e),
|
||||
SingleValue::ArrayExpression(e) => ast::types::Value::ArrayExpression(e),
|
||||
},
|
||||
KclValueGroup::ObjectExpression(e) => ast::types::Value::ObjectExpression(e),
|
||||
}
|
||||
pub fn into_single_value(value: ast::types::BinaryPart) -> SingleValue {
|
||||
match value {
|
||||
BinaryPart::Literal(e) => SingleValue::Literal(e),
|
||||
BinaryPart::Identifier(e) => SingleValue::Identifier(e),
|
||||
BinaryPart::BinaryExpression(e) => SingleValue::BinaryExpression(e),
|
||||
BinaryPart::CallExpression(e) => SingleValue::CallExpression(e),
|
||||
BinaryPart::UnaryExpression(e) => SingleValue::UnaryExpression(e),
|
||||
BinaryPart::MemberExpression(e) => SingleValue::MemberExpression(e),
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ use kcl_lib::{
|
||||
ast,
|
||||
ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program},
|
||||
};
|
||||
use kcl_value_group::into_single_value;
|
||||
use kittycad_execution_plan::{self as ep, Address, Destination, Instruction};
|
||||
use kittycad_execution_plan_traits as ept;
|
||||
use kittycad_execution_plan_traits::NumericPrimitive;
|
||||
@ -18,7 +19,7 @@ use kittycad_modeling_session::Session;
|
||||
|
||||
use self::binding_scope::{BindingScope, EpBinding, GetFnResult};
|
||||
use self::error::{CompileError, Error};
|
||||
use self::kcl_value_group::{KclValueGroup, SingleValue};
|
||||
use self::kcl_value_group::SingleValue;
|
||||
|
||||
/// Execute a KCL program by compiling into an execution plan, then running that.
|
||||
pub async fn execute(ast: Program, session: Option<Session>) -> Result<ep::Memory, Error> {
|
||||
@ -57,19 +58,17 @@ impl Planner {
|
||||
}
|
||||
let mut ctx = Context::default();
|
||||
let instructions_for_this_node = match item {
|
||||
BodyItem::ExpressionStatement(node) => match KclValueGroup::from(node.expression) {
|
||||
KclValueGroup::Single(value) => self.plan_to_compute_single(&mut ctx, value)?.instructions,
|
||||
KclValueGroup::ObjectExpression(_) => todo!(),
|
||||
},
|
||||
BodyItem::ExpressionStatement(node) => {
|
||||
self.plan_to_compute_single(&mut ctx, SingleValue::from(node.expression))?
|
||||
.instructions
|
||||
}
|
||||
BodyItem::VariableDeclaration(node) => self.plan_to_bind(node)?,
|
||||
BodyItem::ReturnStatement(node) => match KclValueGroup::from(node.argument) {
|
||||
KclValueGroup::Single(value) => {
|
||||
let EvalPlan { instructions, binding } = self.plan_to_compute_single(&mut ctx, value)?;
|
||||
retval = Some(binding);
|
||||
instructions
|
||||
}
|
||||
KclValueGroup::ObjectExpression(_) => todo!(),
|
||||
},
|
||||
BodyItem::ReturnStatement(node) => {
|
||||
let EvalPlan { instructions, binding } =
|
||||
self.plan_to_compute_single(&mut ctx, SingleValue::from(node.argument))?;
|
||||
retval = Some(binding);
|
||||
instructions
|
||||
}
|
||||
};
|
||||
instructions.extend(instructions_for_this_node);
|
||||
Ok((instructions, retval))
|
||||
@ -80,7 +79,7 @@ impl Planner {
|
||||
/// Returns the instructions, and the destination address of the value.
|
||||
fn plan_to_compute_single(&mut self, ctx: &mut Context, value: SingleValue) -> Result<EvalPlan, CompileError> {
|
||||
match value {
|
||||
SingleValue::KclNoneExpression(KclNone { start: _, end: _ }) => {
|
||||
SingleValue::None(KclNone { start: _, end: _ }) => {
|
||||
let address = self.next_addr.offset_by(1);
|
||||
Ok(EvalPlan {
|
||||
instructions: vec![Instruction::SetPrimitive {
|
||||
@ -154,7 +153,7 @@ impl Planner {
|
||||
})
|
||||
}
|
||||
SingleValue::UnaryExpression(expr) => {
|
||||
let operand = self.plan_to_compute_single(ctx, SingleValue::from(expr.argument))?;
|
||||
let operand = self.plan_to_compute_single(ctx, into_single_value(expr.argument))?;
|
||||
let EpBinding::Single(binding) = operand.binding else {
|
||||
return Err(CompileError::InvalidOperand(
|
||||
"you tried to use a composite value (e.g. array or object) as the operand to some math",
|
||||
@ -178,8 +177,8 @@ impl Planner {
|
||||
})
|
||||
}
|
||||
SingleValue::BinaryExpression(expr) => {
|
||||
let l = self.plan_to_compute_single(ctx, SingleValue::from(expr.left))?;
|
||||
let r = self.plan_to_compute_single(ctx, SingleValue::from(expr.right))?;
|
||||
let l = self.plan_to_compute_single(ctx, into_single_value(expr.left))?;
|
||||
let r = self.plan_to_compute_single(ctx, into_single_value(expr.right))?;
|
||||
let EpBinding::Single(l_binding) = l.binding else {
|
||||
return Err(CompileError::InvalidOperand(
|
||||
"you tried to use a composite value (e.g. array or object) as the operand to some math",
|
||||
@ -226,10 +225,7 @@ impl Planner {
|
||||
let EvalPlan {
|
||||
instructions: new_instructions,
|
||||
binding: arg,
|
||||
} = match KclValueGroup::from(argument) {
|
||||
KclValueGroup::Single(value) => self.plan_to_compute_single(ctx, value)?,
|
||||
KclValueGroup::ObjectExpression(expr) => self.plan_to_bind_object(ctx, *expr)?,
|
||||
};
|
||||
} = self.plan_to_compute_single(ctx, SingleValue::from(argument))?;
|
||||
acc_instrs.extend(new_instructions);
|
||||
acc_args.push(arg);
|
||||
Ok((acc_instrs, acc_args))
|
||||
@ -405,17 +401,11 @@ impl Planner {
|
||||
let EvalPlan {
|
||||
mut instructions,
|
||||
binding: mut current_value,
|
||||
} = match KclValueGroup::from(first) {
|
||||
KclValueGroup::Single(v) => self.plan_to_compute_single(ctx, v)?,
|
||||
KclValueGroup::ObjectExpression(_) => todo!(),
|
||||
};
|
||||
} = self.plan_to_compute_single(ctx, SingleValue::from(first))?;
|
||||
|
||||
// Handle the remaining bodies.
|
||||
for body in bodies {
|
||||
let value = match KclValueGroup::from(body) {
|
||||
KclValueGroup::Single(v) => v,
|
||||
KclValueGroup::ObjectExpression(_) => todo!(),
|
||||
};
|
||||
let value = SingleValue::from(body);
|
||||
// This body will probably contain a % (pipe substitution character).
|
||||
// So it needs to know what the previous pipeline body's value is,
|
||||
// to replace the % with that value.
|
||||
@ -435,6 +425,66 @@ impl Planner {
|
||||
binding: current_value,
|
||||
})
|
||||
}
|
||||
SingleValue::ObjectExpression(expr) => {
|
||||
let length_at = self.next_addr.offset_by(1);
|
||||
let key_count = expr.properties.len();
|
||||
// Compute elements
|
||||
let (instructions_for_each_element, bindings, keys) = expr.properties.into_iter().try_fold(
|
||||
(Vec::new(), HashMap::new(), Vec::with_capacity(key_count)),
|
||||
|(mut acc_instrs, mut acc_properties, mut acc_keys), property| {
|
||||
let key = property.key.name;
|
||||
acc_keys.push(key.clone());
|
||||
|
||||
// Some elements will have their own length header (e.g. arrays).
|
||||
// For all other elements, we'll need to add a length header.
|
||||
let element_has_its_own_header = matches!(
|
||||
SingleValue::from(property.value.clone()),
|
||||
SingleValue::ArrayExpression(_) | SingleValue::ObjectExpression(_)
|
||||
);
|
||||
let element_needs_its_own_header = !element_has_its_own_header;
|
||||
let length_at = element_needs_its_own_header.then(|| self.next_addr.offset_by(1));
|
||||
|
||||
let instrs_for_this_element = {
|
||||
// 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, SingleValue::from(property.value))?;
|
||||
acc_properties.insert(key, binding);
|
||||
instructions
|
||||
};
|
||||
// If we decided to add a length header for this element,
|
||||
// this is where we actually add it.
|
||||
if let Some(length_at) = length_at {
|
||||
let length_of_this_element = (self.next_addr - length_at) - 1;
|
||||
// Append element's length
|
||||
acc_instrs.push(Instruction::SetPrimitive {
|
||||
address: length_at,
|
||||
value: length_of_this_element.into(),
|
||||
});
|
||||
}
|
||||
// Append element's value
|
||||
acc_instrs.extend(instrs_for_this_element);
|
||||
Ok((acc_instrs, acc_properties, acc_keys))
|
||||
},
|
||||
)?;
|
||||
// The array's overall instructions are:
|
||||
// - Write a length header
|
||||
// - Write everything to calculate its elements.
|
||||
let mut instructions = vec![Instruction::SetPrimitive {
|
||||
address: length_at,
|
||||
value: ept::ObjectHeader {
|
||||
properties: keys,
|
||||
size: (self.next_addr - length_at) - 1,
|
||||
}
|
||||
.into(),
|
||||
}];
|
||||
instructions.extend(instructions_for_each_element);
|
||||
let binding = EpBinding::Map {
|
||||
length_at,
|
||||
properties: bindings,
|
||||
};
|
||||
Ok(EvalPlan { instructions, binding })
|
||||
}
|
||||
SingleValue::ArrayExpression(expr) => {
|
||||
let length_at = self.next_addr.offset_by(1);
|
||||
let element_count = expr.elements.len();
|
||||
@ -442,44 +492,22 @@ impl Planner {
|
||||
let (instructions_for_each_element, bindings) = expr.elements.into_iter().try_fold(
|
||||
(Vec::new(), Vec::new()),
|
||||
|(mut acc_instrs, mut acc_bindings), element| {
|
||||
// Only write this element's length to memory if the element isn't an array.
|
||||
// Why?
|
||||
// Because if the element is an array, then it'll have an array header
|
||||
// which shows its length instead.
|
||||
let elem_is_array = matches!(
|
||||
KclValueGroup::from(element.clone()),
|
||||
KclValueGroup::Single(SingleValue::ArrayExpression(_))
|
||||
// Some elements will have their own length header (e.g. arrays).
|
||||
// For all other elements, we'll need to add a length header.
|
||||
let element_has_its_own_header = matches!(
|
||||
SingleValue::from(element.clone()),
|
||||
SingleValue::ArrayExpression(_) | SingleValue::ObjectExpression(_)
|
||||
);
|
||||
let length_at = (!elem_is_array).then(|| self.next_addr.offset_by(1));
|
||||
let element_needs_its_own_header = !element_has_its_own_header;
|
||||
let length_at = element_needs_its_own_header.then(|| self.next_addr.offset_by(1));
|
||||
|
||||
let instrs_for_this_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_bindings.push(binding);
|
||||
instructions
|
||||
}
|
||||
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 (instrs, binding) = expr
|
||||
.properties
|
||||
.into_iter()
|
||||
.try_fold((Vec::new(), map), |(mut instrs, mut map), property| {
|
||||
let EvalPlan { instructions, binding } =
|
||||
self.plan_to_bind_one(ctx, property.value)?;
|
||||
map.insert(property.key.name, binding);
|
||||
instrs.extend(instructions);
|
||||
Ok((instrs, map))
|
||||
})
|
||||
.map(|(ins, b)| (ins, EpBinding::Map(b)))?;
|
||||
acc_bindings.push(binding);
|
||||
instrs
|
||||
}
|
||||
let instrs_for_this_element = {
|
||||
// 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, SingleValue::from(element))?;
|
||||
acc_bindings.push(binding);
|
||||
instructions
|
||||
};
|
||||
// If we decided to add a length header for this element,
|
||||
// this is where we actually add it.
|
||||
@ -529,71 +557,13 @@ impl Planner {
|
||||
.declarations
|
||||
.into_iter()
|
||||
.try_fold(Vec::new(), |mut acc, declaration| {
|
||||
let EvalPlan { instructions, binding } = self.plan_to_bind_one(&mut ctx, declaration.init)?;
|
||||
let EvalPlan { instructions, binding } =
|
||||
self.plan_to_compute_single(&mut ctx, SingleValue::from(declaration.init))?;
|
||||
self.binding_scope.bind(declaration.id.name, binding);
|
||||
acc.extend(instructions);
|
||||
Ok(acc)
|
||||
})
|
||||
}
|
||||
|
||||
fn plan_to_bind_one(
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
value_being_bound: ast::types::Value,
|
||||
) -> 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.
|
||||
self.plan_to_compute_single(ctx, init_value)
|
||||
}
|
||||
KclValueGroup::ObjectExpression(expr) => self.plan_to_bind_object(ctx, *expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_to_bind_object(
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
expr: ast::types::ObjectExpression,
|
||||
) -> Result<EvalPlan, CompileError> {
|
||||
// Convert the object to a sequence of key-value pairs.
|
||||
let mut kvs = expr.properties.into_iter().map(|prop| (prop.key, prop.value));
|
||||
let (instructions, each_property_binding) = kvs.try_fold(
|
||||
(Vec::new(), HashMap::new()),
|
||||
|(mut acc_instrs, mut acc_bindings), (key, value)| {
|
||||
match KclValueGroup::from(value) {
|
||||
KclValueGroup::Single(value) => {
|
||||
let EvalPlan { instructions, binding } = self.plan_to_compute_single(ctx, value)?;
|
||||
acc_instrs.extend(instructions);
|
||||
acc_bindings.insert(key.name, binding);
|
||||
}
|
||||
KclValueGroup::ObjectExpression(expr) => {
|
||||
// If this value of the object is _itself_ an object, then we need to
|
||||
// emit a plan to calculate each value of each property of the child object.
|
||||
// Then we collect the bindings for each child value, and bind them to one
|
||||
// property of the parent object.
|
||||
let n = expr.properties.len();
|
||||
let binding = expr
|
||||
.properties
|
||||
.into_iter()
|
||||
.try_fold(HashMap::with_capacity(n), |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.insert(key.name, binding);
|
||||
}
|
||||
};
|
||||
Ok((acc_instrs, acc_bindings))
|
||||
},
|
||||
)?;
|
||||
Ok(EvalPlan {
|
||||
instructions,
|
||||
binding: EpBinding::Map(each_property_binding),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Every KCL literal value is equivalent to an Execution Plan value, and therefore can be
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ep::{Destination, UnaryArithmetic};
|
||||
use ept::ListHeader;
|
||||
use ept::{ListHeader, ObjectHeader};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
@ -158,7 +160,7 @@ fn bind_arrays_with_objects_elements() {
|
||||
// List header
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: ListHeader { count: 2, size: 5 }.into()
|
||||
value: ListHeader { count: 2, size: 7 }.into()
|
||||
},
|
||||
// Array contents
|
||||
Instruction::SetPrimitive {
|
||||
@ -171,14 +173,26 @@ fn bind_arrays_with_objects_elements() {
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 3,
|
||||
value: 2usize.into()
|
||||
value: ObjectHeader {
|
||||
size: 4,
|
||||
properties: vec!["a".to_owned(), "b".to_owned(),]
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 4,
|
||||
value: 55i64.into()
|
||||
value: 1usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 5,
|
||||
value: 55i64.into()
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 6,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 7,
|
||||
value: "sixty-six".to_owned().into()
|
||||
},
|
||||
]
|
||||
@ -400,7 +414,7 @@ fn member_expressions_object() {
|
||||
let (_plan, scope) = must_plan(program);
|
||||
match scope.get("prop").unwrap() {
|
||||
EpBinding::Single(addr) => {
|
||||
assert_eq!(*addr, Address::ZERO + 1);
|
||||
assert_eq!(*addr, Address::ZERO + 4);
|
||||
}
|
||||
other => {
|
||||
panic!("expected 'prop' bound to 0x0 but it was bound to {other:?}");
|
||||
@ -833,32 +847,69 @@ fn store_object() {
|
||||
let expected = vec![
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: ObjectHeader {
|
||||
properties: vec!["a".to_owned(), "b".to_owned(), "c".to_owned()],
|
||||
size: 7,
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
// Key a header
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
// Key a value
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 2,
|
||||
value: 1i64.into(),
|
||||
},
|
||||
// Key b header
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(1),
|
||||
address: Address::ZERO + 3,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
// Key b value
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 4,
|
||||
value: 2i64.into(),
|
||||
},
|
||||
// Inner object (i.e. key c) header
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO.offset(2),
|
||||
address: Address::ZERO + 5,
|
||||
value: ObjectHeader {
|
||||
properties: vec!["d".to_owned()],
|
||||
size: 2,
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
// Key d header
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 6,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
// Key d value
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 7,
|
||||
value: 3i64.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
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))),
|
||||
let actual = bindings.get("x0").unwrap();
|
||||
let expected = EpBinding::Map {
|
||||
length_at: Address::ZERO,
|
||||
properties: HashMap::from([
|
||||
("a".to_owned(), EpBinding::Single(Address::ZERO + 2)),
|
||||
("b".to_owned(), EpBinding::Single(Address::ZERO + 4)),
|
||||
(
|
||||
"c".to_owned(),
|
||||
EpBinding::Map(HashMap::from([(
|
||||
"d".to_owned(),
|
||||
EpBinding::Single(Address::ZERO.offset(2))
|
||||
)]))
|
||||
EpBinding::Map {
|
||||
length_at: Address::ZERO + 5,
|
||||
properties: HashMap::from([("d".to_owned(), EpBinding::Single(Address::ZERO + 7))]),
|
||||
},
|
||||
),
|
||||
]))
|
||||
)
|
||||
]),
|
||||
};
|
||||
assert_eq!(actual, &expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -868,20 +919,24 @@ fn store_object_with_array_property() {
|
||||
let expected = vec![
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: ObjectHeader {
|
||||
properties: vec!["a".to_owned(), "b".to_owned()],
|
||||
size: 7,
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 2,
|
||||
value: 1i64.into(),
|
||||
},
|
||||
// Array header
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: ListHeader { count: 2, size: 4 }.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 2,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 3,
|
||||
value: 2i64.into(),
|
||||
value: ListHeader { count: 2, size: 4 }.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 4,
|
||||
@ -889,25 +944,36 @@ fn store_object_with_array_property() {
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 5,
|
||||
value: 2i64.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 6,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 7,
|
||||
value: 3i64.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(
|
||||
bindings.get("x0").unwrap(),
|
||||
&EpBinding::Map(HashMap::from([
|
||||
("a".to_owned(), EpBinding::Single(Address::ZERO),),
|
||||
(
|
||||
"b".to_owned(),
|
||||
EpBinding::Sequence {
|
||||
length_at: Address::ZERO + 1,
|
||||
elements: vec![
|
||||
EpBinding::Single(Address::ZERO + 3),
|
||||
EpBinding::Single(Address::ZERO + 5),
|
||||
]
|
||||
}
|
||||
),
|
||||
]))
|
||||
&EpBinding::Map {
|
||||
length_at: Address::ZERO,
|
||||
properties: HashMap::from([
|
||||
("a".to_owned(), EpBinding::Single(Address::ZERO + 2)),
|
||||
(
|
||||
"b".to_owned(),
|
||||
EpBinding::Sequence {
|
||||
length_at: Address::ZERO + 3,
|
||||
elements: vec![
|
||||
EpBinding::Single(Address::ZERO + 5),
|
||||
EpBinding::Single(Address::ZERO + 7),
|
||||
]
|
||||
}
|
||||
),
|
||||
])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -934,13 +1000,28 @@ fn objects_as_parameters() {
|
||||
// Object contents
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO,
|
||||
value: ObjectHeader {
|
||||
properties: vec!["x".to_owned()],
|
||||
size: 2,
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 1,
|
||||
value: 1usize.into(),
|
||||
},
|
||||
Instruction::SetPrimitive {
|
||||
address: Address::ZERO + 2,
|
||||
value: 1i64.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(plan, expected_plan);
|
||||
assert_eq!(
|
||||
scope.get("obj").unwrap(),
|
||||
&EpBinding::Map(HashMap::from([("x".to_owned(), EpBinding::Single(Address::ZERO)),]))
|
||||
&EpBinding::Map {
|
||||
length_at: Address::ZERO,
|
||||
properties: HashMap::from([("x".to_owned(), EpBinding::Single(Address::ZERO + 2))])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user