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:
Adam Chalmers
2024-01-30 15:18:45 +11:00
committed by GitHub
parent c6249f36d2
commit 4ca341e132
6 changed files with 244 additions and 261 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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