From 4ca341e1322622bb760fdadd54457c85bf5d75dd Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Tue, 30 Jan 2024 15:18:45 +1100 Subject: [PATCH] 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 --- src/wasm-lib/Cargo.lock | 12 +- src/wasm-lib/Cargo.toml | 2 +- src/wasm-lib/grackle/src/binding_scope.rs | 14 +- src/wasm-lib/grackle/src/kcl_value_group.rs | 94 +-------- src/wasm-lib/grackle/src/lib.rs | 220 +++++++++----------- src/wasm-lib/grackle/src/tests.rs | 163 +++++++++++---- 6 files changed, 244 insertions(+), 261 deletions(-) diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index aff07ce0e..d9e67af76 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -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", diff --git a/src/wasm-lib/Cargo.toml b/src/wasm-lib/Cargo.toml index 3d6a80519..dd824b473 100644 --- a/src/wasm-lib/Cargo.toml +++ b/src/wasm-lib/Cargo.toml @@ -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" } diff --git a/src/wasm-lib/grackle/src/binding_scope.rs b/src/wasm-lib/grackle/src/binding_scope.rs index 2850f2732..f5daf401a 100644 --- a/src/wasm-lib/grackle/src/binding_scope.rs +++ b/src/wasm-lib/grackle/src/binding_scope.rs @@ -22,7 +22,10 @@ pub enum EpBinding { elements: Vec, }, /// A sequence of KCL values, indexed by their identifier. - Map(HashMap), + Map { + length_at: Address, + properties: HashMap, + }, /// 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())), diff --git a/src/wasm-lib/grackle/src/kcl_value_group.rs b/src/wasm-lib/grackle/src/kcl_value_group.rs index 3cc6fd278..575768fc5 100644 --- a/src/wasm-lib/grackle/src/kcl_value_group.rs +++ b/src/wasm-lib/grackle/src/kcl_value_group.rs @@ -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), -} +pub type SingleValue = kcl_lib::ast::types::Value; -#[derive(Debug)] -pub enum SingleValue { - Literal(Box), - Identifier(Box), - BinaryExpression(Box), - CallExpression(Box), - PipeExpression(Box), - UnaryExpression(Box), - KclNoneExpression(ast::types::KclNone), - MemberExpression(Box), - FunctionExpression(Box), - PipeSubstitution(Box), - ArrayExpression(Box), -} - -impl From 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 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 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 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), } } diff --git a/src/wasm-lib/grackle/src/lib.rs b/src/wasm-lib/grackle/src/lib.rs index f10bb61cc..72de40d6e 100644 --- a/src/wasm-lib/grackle/src/lib.rs +++ b/src/wasm-lib/grackle/src/lib.rs @@ -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) -> Result { @@ -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 { 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 { - 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 { - // 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 diff --git a/src/wasm-lib/grackle/src/tests.rs b/src/wasm-lib/grackle/src/tests.rs index 90a2dc81b..e04fdb9ed 100644 --- a/src/wasm-lib/grackle/src/tests.rs +++ b/src/wasm-lib/grackle/src/tests.rs @@ -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))]) + } ) }