Grackle: Runtime computed array indices (#1331)
This commit is contained in:
		
							
								
								
									
										55
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										55
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -430,6 +430,12 @@ version = "3.14.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytecount"
 | 
			
		||||
version = "0.6.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytemuck"
 | 
			
		||||
version = "1.14.0"
 | 
			
		||||
@ -1426,6 +1432,7 @@ dependencies = [
 | 
			
		||||
 "kittycad-modeling-session",
 | 
			
		||||
 "pretty_assertions",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "tokio",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -1943,7 +1950,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad-execution-plan"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#cd3e2c339b4794478e995247ca693d17c6b8ac4d"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "insta",
 | 
			
		||||
@ -1953,6 +1960,7 @@ dependencies = [
 | 
			
		||||
 "kittycad-modeling-session",
 | 
			
		||||
 "parse-display-derive",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "tabled",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "uuid",
 | 
			
		||||
@ -1972,7 +1980,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad-execution-plan-macros"
 | 
			
		||||
version = "0.1.2"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#cd3e2c339b4794478e995247ca693d17c6b8ac4d"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@ -1981,9 +1989,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad-execution-plan-traits"
 | 
			
		||||
version = "0.1.3"
 | 
			
		||||
version = "0.1.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1486416eacecf481188a253199461fde5dbbc9bccaf176d60c48181cd0465273"
 | 
			
		||||
checksum = "29dc558ca98b726d998fe9617f7cab0c427f54b49b42fb68d0a69d85e1b52dc7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
@ -1993,7 +2001,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad-modeling-cmds"
 | 
			
		||||
version = "0.1.12"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#cd3e2c339b4794478e995247ca693d17c6b8ac4d"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "chrono",
 | 
			
		||||
@ -2020,7 +2028,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad-modeling-session"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#cd3e2c339b4794478e995247ca693d17c6b8ac4d"
 | 
			
		||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0a891ad3d4a189410f457e00b644ff4e116e7175"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "futures",
 | 
			
		||||
 "kittycad",
 | 
			
		||||
@ -2527,6 +2535,17 @@ dependencies = [
 | 
			
		||||
 "sha2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "papergrid"
 | 
			
		||||
version = "0.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytecount",
 | 
			
		||||
 "fnv",
 | 
			
		||||
 "unicode-width",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "parking_lot"
 | 
			
		||||
version = "0.11.2"
 | 
			
		||||
@ -3920,6 +3939,30 @@ dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tabled"
 | 
			
		||||
version = "0.14.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dfe9c3632da101aba5131ed63f9eed38665f8b3c68703a6bb18124835c1a5d22"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "papergrid",
 | 
			
		||||
 "tabled_derive",
 | 
			
		||||
 "unicode-width",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tabled_derive"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "heck",
 | 
			
		||||
 "proc-macro-error",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 1.0.109",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "take_mut"
 | 
			
		||||
version = "0.2.2"
 | 
			
		||||
 | 
			
		||||
@ -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.2"
 | 
			
		||||
kittycad-execution-plan-traits = "0.1.5"
 | 
			
		||||
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" }
 | 
			
		||||
 | 
			
		||||
@ -71,3 +71,8 @@ path = "tests/executor/main.rs"
 | 
			
		||||
[[test]]
 | 
			
		||||
name = "modify"
 | 
			
		||||
path = "tests/modify/main.rs"
 | 
			
		||||
 | 
			
		||||
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
 | 
			
		||||
# [patch."https://github.com/KittyCAD/modeling-api"]
 | 
			
		||||
# kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
 | 
			
		||||
# kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
 | 
			
		||||
@ -12,7 +12,8 @@ kittycad-execution-plan = { workspace = true }
 | 
			
		||||
kittycad-execution-plan-traits = { workspace = true }
 | 
			
		||||
kittycad-modeling-session = { workspace = true }
 | 
			
		||||
thiserror = "1.0.56"
 | 
			
		||||
tokio = { version = "1.35.1", features = ["macros", "rt"] }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
pretty_assertions = "1"
 | 
			
		||||
pretty_assertions = "1"
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@ use kcl_lib::ast::{self, types::BinaryPart};
 | 
			
		||||
/// You can convert losslessly between KclValueGroup and `kcl_lib::ast::types::Value` with From/Into.
 | 
			
		||||
pub enum KclValueGroup {
 | 
			
		||||
    Single(SingleValue),
 | 
			
		||||
    ArrayExpression(Box<ast::types::ArrayExpression>),
 | 
			
		||||
    ObjectExpression(Box<ast::types::ObjectExpression>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +20,7 @@ pub enum SingleValue {
 | 
			
		||||
    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 {
 | 
			
		||||
@ -59,7 +59,7 @@ impl From<ast::types::Value> for KclValueGroup {
 | 
			
		||||
            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::ArrayExpression(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)),
 | 
			
		||||
@ -82,8 +82,8 @@ impl From<KclValueGroup> for ast::types::Value {
 | 
			
		||||
                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::ArrayExpression(e) => ast::types::Value::ArrayExpression(e),
 | 
			
		||||
            KclValueGroup::ObjectExpression(e) => ast::types::Value::ObjectExpression(e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -7,13 +7,11 @@ mod tests;
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use ep::Destination;
 | 
			
		||||
use kcl_lib::{
 | 
			
		||||
    ast,
 | 
			
		||||
    ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program},
 | 
			
		||||
};
 | 
			
		||||
use kittycad_execution_plan as ep;
 | 
			
		||||
use kittycad_execution_plan::{Address, Instruction};
 | 
			
		||||
use kittycad_execution_plan::{self as ep, Address, Destination, Instruction};
 | 
			
		||||
use kittycad_execution_plan_traits as ept;
 | 
			
		||||
use kittycad_execution_plan_traits::NumericPrimitive;
 | 
			
		||||
use kittycad_modeling_session::Session;
 | 
			
		||||
@ -23,12 +21,12 @@ use self::error::{CompileError, Error};
 | 
			
		||||
use self::kcl_value_group::{KclValueGroup, SingleValue};
 | 
			
		||||
 | 
			
		||||
/// Execute a KCL program by compiling into an execution plan, then running that.
 | 
			
		||||
pub async fn execute(ast: Program, session: Session) -> Result<(), Error> {
 | 
			
		||||
pub async fn execute(ast: Program, session: Option<Session>) -> Result<ep::Memory, Error> {
 | 
			
		||||
    let mut planner = Planner::new();
 | 
			
		||||
    let (plan, _retval) = planner.build_plan(ast)?;
 | 
			
		||||
    let mut mem = kittycad_execution_plan::Memory::default();
 | 
			
		||||
    kittycad_execution_plan::execute(&mut mem, plan, session).await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
    let mut mem = ep::Memory::default();
 | 
			
		||||
    ep::execute(&mut mem, plan, session).await?;
 | 
			
		||||
    Ok(mem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compiles KCL programs into Execution Plans.
 | 
			
		||||
@ -61,7 +59,6 @@ impl Planner {
 | 
			
		||||
                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::ArrayExpression(_) => todo!(),
 | 
			
		||||
                        KclValueGroup::ObjectExpression(_) => todo!(),
 | 
			
		||||
                    },
 | 
			
		||||
                    BodyItem::VariableDeclaration(node) => self.plan_to_bind(node)?,
 | 
			
		||||
@ -71,7 +68,6 @@ impl Planner {
 | 
			
		||||
                            retval = Some(binding);
 | 
			
		||||
                            instructions
 | 
			
		||||
                        }
 | 
			
		||||
                        KclValueGroup::ArrayExpression(_) => todo!(),
 | 
			
		||||
                        KclValueGroup::ObjectExpression(_) => todo!(),
 | 
			
		||||
                    },
 | 
			
		||||
                };
 | 
			
		||||
@ -232,7 +228,6 @@ impl Planner {
 | 
			
		||||
                            binding: arg,
 | 
			
		||||
                        } = match KclValueGroup::from(argument) {
 | 
			
		||||
                            KclValueGroup::Single(value) => self.plan_to_compute_single(ctx, value)?,
 | 
			
		||||
                            KclValueGroup::ArrayExpression(expr) => self.plan_to_bind_array(ctx, *expr)?,
 | 
			
		||||
                            KclValueGroup::ObjectExpression(expr) => self.plan_to_bind_object(ctx, *expr)?,
 | 
			
		||||
                        };
 | 
			
		||||
                        acc_instrs.extend(new_instructions);
 | 
			
		||||
@ -335,17 +330,62 @@ impl Planner {
 | 
			
		||||
                let (properties, id) = parse();
 | 
			
		||||
                let name = id.name;
 | 
			
		||||
                let mut binding = self.binding_scope.get(&name).ok_or(CompileError::Undefined { name })?;
 | 
			
		||||
                for (property, computed) in properties {
 | 
			
		||||
                    if computed {
 | 
			
		||||
                        todo!("Support computed properties like '{:?}'", property);
 | 
			
		||||
                    } else {
 | 
			
		||||
                if properties.iter().any(|(_property, computed)| *computed) {
 | 
			
		||||
                    // There's a computed property, so the property/index can only be determined at runtime.
 | 
			
		||||
                    let mut instructions: Vec<Instruction> = Vec::new();
 | 
			
		||||
                    // For now we can only handle computed properties of *arrays*, no other type.
 | 
			
		||||
                    let mut start_of_array = match binding {
 | 
			
		||||
                        EpBinding::Sequence { length_at, elements: _ } => *length_at,
 | 
			
		||||
                        _ => todo!("handle computed properties besides arrays"),
 | 
			
		||||
                    };
 | 
			
		||||
                    for (property, _computed) in properties {
 | 
			
		||||
                        // Where is the index stored?
 | 
			
		||||
                        let index = match property {
 | 
			
		||||
                            // If it's some identifier, then look up where that identifier will be stored.
 | 
			
		||||
                            // That's the memory address the index should be in.
 | 
			
		||||
                            ast::types::LiteralIdentifier::Identifier(id) => {
 | 
			
		||||
                                let b = self
 | 
			
		||||
                                    .binding_scope
 | 
			
		||||
                                    .get(&id.name)
 | 
			
		||||
                                    .ok_or(CompileError::Undefined { name: id.name })?;
 | 
			
		||||
                                match b {
 | 
			
		||||
                                    EpBinding::Single(addr) => ep::Operand::Reference(*addr),
 | 
			
		||||
                                    // TODO use a better error message here
 | 
			
		||||
                                    other => return Err(CompileError::InvalidIndex(format!("{other:?}"))),
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            // If the index is a literal, then just use it.
 | 
			
		||||
                            ast::types::LiteralIdentifier::Literal(litval) => {
 | 
			
		||||
                                ep::Operand::Literal(kcl_literal_to_kcep_literal(litval.value))
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                        instructions.push(Instruction::GetElement {
 | 
			
		||||
                            index,
 | 
			
		||||
                            start: start_of_array,
 | 
			
		||||
                        });
 | 
			
		||||
                        // Point `start_of_array` to the location we just indexed, so that if there's
 | 
			
		||||
                        // another index expression to a child array, `start_of_array` is in the right place.
 | 
			
		||||
                        start_of_array = self.next_addr.offset_by(1);
 | 
			
		||||
                        instructions.push(Instruction::StackPop {
 | 
			
		||||
                            destination: Some(start_of_array),
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    Ok(EvalPlan {
 | 
			
		||||
                        instructions,
 | 
			
		||||
                        binding: EpBinding::Single(start_of_array),
 | 
			
		||||
                    })
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Compiler optimization:
 | 
			
		||||
                    // Because there are no computed properties, we can resolve the property chain
 | 
			
		||||
                    // at compile-time. Just jump to the right property at each step in the chain.
 | 
			
		||||
                    for (property, _) in properties {
 | 
			
		||||
                        binding = binding.property_of(property)?;
 | 
			
		||||
                    }
 | 
			
		||||
                    Ok(EvalPlan {
 | 
			
		||||
                        instructions: Vec::new(),
 | 
			
		||||
                        binding: binding.clone(),
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                Ok(EvalPlan {
 | 
			
		||||
                    instructions: Vec::new(),
 | 
			
		||||
                    binding: binding.clone(),
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            SingleValue::PipeSubstitution(_expr) => {
 | 
			
		||||
                if let Some(ref binding) = ctx.pipe_substitution {
 | 
			
		||||
@ -367,7 +407,6 @@ impl Planner {
 | 
			
		||||
                    binding: mut current_value,
 | 
			
		||||
                } = match KclValueGroup::from(first) {
 | 
			
		||||
                    KclValueGroup::Single(v) => self.plan_to_compute_single(ctx, v)?,
 | 
			
		||||
                    KclValueGroup::ArrayExpression(_) => todo!(),
 | 
			
		||||
                    KclValueGroup::ObjectExpression(_) => todo!(),
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
@ -375,7 +414,6 @@ impl Planner {
 | 
			
		||||
                for body in bodies {
 | 
			
		||||
                    let value = match KclValueGroup::from(body) {
 | 
			
		||||
                        KclValueGroup::Single(v) => v,
 | 
			
		||||
                        KclValueGroup::ArrayExpression(_) => todo!(),
 | 
			
		||||
                        KclValueGroup::ObjectExpression(_) => todo!(),
 | 
			
		||||
                    };
 | 
			
		||||
                    // This body will probably contain a % (pipe substitution character).
 | 
			
		||||
@ -397,6 +435,85 @@ impl Planner {
 | 
			
		||||
                    binding: current_value,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            SingleValue::ArrayExpression(expr) => {
 | 
			
		||||
                let length_at = self.next_addr.offset_by(1);
 | 
			
		||||
                let element_count = expr.elements.len();
 | 
			
		||||
                // Compute elements
 | 
			
		||||
                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(_))
 | 
			
		||||
                        );
 | 
			
		||||
                        let length_at = (!elem_is_array).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
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                        // 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_bindings))
 | 
			
		||||
                    },
 | 
			
		||||
                )?;
 | 
			
		||||
                // 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::ListHeader {
 | 
			
		||||
                        count: element_count,
 | 
			
		||||
                        size: (self.next_addr - length_at) - 1,
 | 
			
		||||
                    }
 | 
			
		||||
                    .into(),
 | 
			
		||||
                }];
 | 
			
		||||
                instructions.extend(instructions_for_each_element);
 | 
			
		||||
                let binding = EpBinding::Sequence {
 | 
			
		||||
                    length_at,
 | 
			
		||||
                    elements: bindings,
 | 
			
		||||
                };
 | 
			
		||||
                Ok(EvalPlan { instructions, binding })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -430,7 +547,6 @@ impl Planner {
 | 
			
		||||
                // and bind it to the KCL identifier.
 | 
			
		||||
                self.plan_to_compute_single(ctx, init_value)
 | 
			
		||||
            }
 | 
			
		||||
            KclValueGroup::ArrayExpression(expr) => self.plan_to_bind_array(ctx, *expr),
 | 
			
		||||
            KclValueGroup::ObjectExpression(expr) => self.plan_to_bind_object(ctx, *expr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -451,28 +567,6 @@ impl Planner {
 | 
			
		||||
                        acc_instrs.extend(instructions);
 | 
			
		||||
                        acc_bindings.insert(key.name, binding);
 | 
			
		||||
                    }
 | 
			
		||||
                    KclValueGroup::ArrayExpression(expr) => {
 | 
			
		||||
                        // If this value of the object is an array, then emit a plan to calculate
 | 
			
		||||
                        // 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: expr.elements.len().into(),
 | 
			
		||||
                        });
 | 
			
		||||
                        let binding = expr
 | 
			
		||||
                            .elements
 | 
			
		||||
                            .into_iter()
 | 
			
		||||
                            .try_fold(Vec::with_capacity(n), |mut seq, child_element| {
 | 
			
		||||
                                let EvalPlan { instructions, binding } = self.plan_to_bind_one(ctx, child_element)?;
 | 
			
		||||
                                seq.push(binding);
 | 
			
		||||
                                acc_instrs.extend(instructions);
 | 
			
		||||
                                Ok(seq)
 | 
			
		||||
                            })
 | 
			
		||||
                            .map(|elements| EpBinding::Sequence { length_at, elements })?;
 | 
			
		||||
                        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.
 | 
			
		||||
@ -500,86 +594,6 @@ impl Planner {
 | 
			
		||||
            binding: EpBinding::Map(each_property_binding),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn plan_to_bind_array(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        ctx: &mut Context,
 | 
			
		||||
        expr: ast::types::ArrayExpression,
 | 
			
		||||
    ) -> Result<EvalPlan, CompileError> {
 | 
			
		||||
        // First, emit a plan to compute each element of the array.
 | 
			
		||||
        // Collect all the bindings from each element too.
 | 
			
		||||
        let length_at = self.next_addr.offset_by(1);
 | 
			
		||||
        let mut instructions = vec![Instruction::SetPrimitive {
 | 
			
		||||
            address: length_at,
 | 
			
		||||
            value: expr.elements.len().into(),
 | 
			
		||||
        }];
 | 
			
		||||
        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 sublength_at = self.next_addr.offset_by(1);
 | 
			
		||||
                        acc_instrs.push(Instruction::SetPrimitive {
 | 
			
		||||
                            address: sublength_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: sublength_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
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
use ep::{Destination, UnaryArithmetic};
 | 
			
		||||
use ept::ListHeader;
 | 
			
		||||
use pretty_assertions::assert_eq;
 | 
			
		||||
 | 
			
		||||
use super::*;
 | 
			
		||||
@ -42,7 +43,7 @@ fn assignments() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn bind_array() {
 | 
			
		||||
fn bind_array_simple() {
 | 
			
		||||
    let program = r#"let x = [44, 55, "sixty-six"]"#;
 | 
			
		||||
    let (plan, _scope) = must_plan(program);
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
@ -51,19 +52,40 @@ fn bind_array() {
 | 
			
		||||
            // Array length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO,
 | 
			
		||||
                value: 3usize.into()
 | 
			
		||||
                value: ListHeader {
 | 
			
		||||
                    // The list has 3 elements
 | 
			
		||||
                    count: 3,
 | 
			
		||||
                    // The 3 elements each take 2 primitives (one for length, one for value),
 | 
			
		||||
                    // so 6 in total.
 | 
			
		||||
                    size: 6
 | 
			
		||||
                }
 | 
			
		||||
                .into()
 | 
			
		||||
            },
 | 
			
		||||
            // Array contents
 | 
			
		||||
            // Elem 0
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 1,
 | 
			
		||||
                value: 44i64.into(),
 | 
			
		||||
                value: 1usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 2,
 | 
			
		||||
                value: 55i64.into(),
 | 
			
		||||
                value: 44i64.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 1
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 3,
 | 
			
		||||
                value: 1usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 4,
 | 
			
		||||
                value: 55i64.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 2
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 5,
 | 
			
		||||
                value: 1usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 6,
 | 
			
		||||
                value: "sixty-six".to_owned().into(),
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
@ -80,27 +102,48 @@ fn bind_nested_array() {
 | 
			
		||||
            // Outer array length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO,
 | 
			
		||||
                value: 2usize.into(),
 | 
			
		||||
                value: ListHeader {
 | 
			
		||||
                    count: 2,
 | 
			
		||||
                    // 2 for each of the 3 elements, plus 1 for the inner array header.
 | 
			
		||||
                    size: 2 + 2 + 2 + 1,
 | 
			
		||||
                }
 | 
			
		||||
                .into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Outer array contents
 | 
			
		||||
            // Outer array element 0 length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 1,
 | 
			
		||||
                value: 44i64.into(),
 | 
			
		||||
                value: 1usize.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Inner array length
 | 
			
		||||
            // Outer array element 0 value
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 2,
 | 
			
		||||
                value: 2usize.into(),
 | 
			
		||||
                value: 44i64.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Inner array length
 | 
			
		||||
            // Outer array element 1 length (i.e. inner array header)
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 3,
 | 
			
		||||
                value: 55i64.into(),
 | 
			
		||||
                value: ListHeader { count: 2, size: 4 }.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Inner array elem0 length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 4,
 | 
			
		||||
                value: 1usize.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Inner array elem0 value
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 5,
 | 
			
		||||
                value: 55i64.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Inner array elem1 length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 6,
 | 
			
		||||
                value: 1usize.into(),
 | 
			
		||||
            },
 | 
			
		||||
            // Inner array elem1 value
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 7,
 | 
			
		||||
                value: "sixty-six".to_owned().into(),
 | 
			
		||||
            }
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@ -112,24 +155,32 @@ fn bind_arrays_with_objects_elements() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        plan,
 | 
			
		||||
        vec![
 | 
			
		||||
            // Array length
 | 
			
		||||
            // List header
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO,
 | 
			
		||||
                value: 2usize.into()
 | 
			
		||||
                value: ListHeader { count: 2, size: 5 }.into()
 | 
			
		||||
            },
 | 
			
		||||
            // Array contents
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 1,
 | 
			
		||||
                value: 44i64.into(),
 | 
			
		||||
                value: 1usize.into(),
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 2,
 | 
			
		||||
                value: 55i64.into(),
 | 
			
		||||
                value: 44i64.into(),
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 3,
 | 
			
		||||
                value: "sixty-six".to_owned().into(),
 | 
			
		||||
            }
 | 
			
		||||
                value: 2usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 4,
 | 
			
		||||
                value: 55i64.into()
 | 
			
		||||
            },
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 5,
 | 
			
		||||
                value: "sixty-six".to_owned().into()
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@ -224,23 +275,101 @@ fn use_native_function_id() {
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
#[ignore = "haven't done computed properties yet"]
 | 
			
		||||
fn computed_array_index() {
 | 
			
		||||
#[tokio::test]
 | 
			
		||||
async fn computed_array_index() {
 | 
			
		||||
    let program = r#"
 | 
			
		||||
    let array = ["a", "b", "c"]
 | 
			
		||||
    let index = 1+1
 | 
			
		||||
    let index = 1+1 // should be "c"
 | 
			
		||||
    let prop = array[index]
 | 
			
		||||
    "#;
 | 
			
		||||
    let (_plan, scope) = must_plan(program);
 | 
			
		||||
    match scope.get("prop").unwrap() {
 | 
			
		||||
        EpBinding::Single(addr) => {
 | 
			
		||||
            assert_eq!(*addr, Address::ZERO + 1);
 | 
			
		||||
        }
 | 
			
		||||
        other => {
 | 
			
		||||
            panic!("expected 'prop' bound to 0x0 but it was bound to {other:?}");
 | 
			
		||||
        }
 | 
			
		||||
    let (plan, scope) = must_plan(program);
 | 
			
		||||
    let expected_address_of_prop = Address::ZERO + 10;
 | 
			
		||||
    if let Some(EpBinding::Single(addr)) = scope.get("prop") {
 | 
			
		||||
        assert_eq!(*addr, expected_address_of_prop);
 | 
			
		||||
    } else {
 | 
			
		||||
        panic!("expected 'prop' bound to 0 but it was {:?}", scope.get("prop"));
 | 
			
		||||
    }
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        plan,
 | 
			
		||||
        vec![
 | 
			
		||||
            // Setting the array
 | 
			
		||||
            // First, the length of the array (number of elements).
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO,
 | 
			
		||||
                value: ListHeader { count: 3, size: 6 }.into()
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 0 length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 1,
 | 
			
		||||
                value: 1usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 0 value
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 2,
 | 
			
		||||
                value: "a".to_owned().into()
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 1 length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 3,
 | 
			
		||||
                value: 1usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 1 value
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 4,
 | 
			
		||||
                value: "b".to_owned().into()
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 2 length
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 5,
 | 
			
		||||
                value: 1usize.into()
 | 
			
		||||
            },
 | 
			
		||||
            // Elem 2 value
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 6,
 | 
			
		||||
                value: "c".to_owned().into()
 | 
			
		||||
            },
 | 
			
		||||
            // Calculate the index (1+1)
 | 
			
		||||
            // First, the left operand
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 7,
 | 
			
		||||
                value: 1i64.to_owned().into()
 | 
			
		||||
            },
 | 
			
		||||
            // Then the right operand
 | 
			
		||||
            Instruction::SetPrimitive {
 | 
			
		||||
                address: Address::ZERO + 8,
 | 
			
		||||
                value: 1i64.to_owned().into()
 | 
			
		||||
            },
 | 
			
		||||
            // Then index, which is left operand + right operand
 | 
			
		||||
            Instruction::BinaryArithmetic {
 | 
			
		||||
                arithmetic: ep::BinaryArithmetic {
 | 
			
		||||
                    operation: ep::BinaryOperation::Add,
 | 
			
		||||
                    operand0: ep::Operand::Reference(Address::ZERO + 7),
 | 
			
		||||
                    operand1: ep::Operand::Reference(Address::ZERO + 8)
 | 
			
		||||
                },
 | 
			
		||||
                destination: Destination::Address(Address::ZERO + 9)
 | 
			
		||||
            },
 | 
			
		||||
            // Get the element at the index
 | 
			
		||||
            Instruction::GetElement {
 | 
			
		||||
                start: Address::ZERO,
 | 
			
		||||
                index: ep::Operand::Reference(Address::ZERO + 9)
 | 
			
		||||
            },
 | 
			
		||||
            // Write it to the next free address.
 | 
			
		||||
            Instruction::StackPop {
 | 
			
		||||
                destination: Some(expected_address_of_prop)
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
    // Now let's run the program and check what's actually in the memory afterwards.
 | 
			
		||||
    let tokens = kcl_lib::token::lexer(program);
 | 
			
		||||
    let parser = kcl_lib::parser::Parser::new(tokens);
 | 
			
		||||
    let ast = parser.ast().unwrap();
 | 
			
		||||
    let mem = crate::execute(ast, None).await.unwrap();
 | 
			
		||||
    use ept::ReadMemory;
 | 
			
		||||
    // Should be "b", as pointed out in the KCL program's comment.
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        mem.get(&expected_address_of_prop).unwrap(),
 | 
			
		||||
        &ept::Primitive::String("c".to_owned())
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
@ -281,26 +410,40 @@ fn member_expressions_object() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn member_expressions_array() {
 | 
			
		||||
    let program = "
 | 
			
		||||
    let array = [[1,2],[3,4]]
 | 
			
		||||
    let program = r#"
 | 
			
		||||
    let array = [["a", "b"],["c", "d"]]
 | 
			
		||||
    let first = array[0][0]
 | 
			
		||||
    let last = array[1][1]
 | 
			
		||||
    ";
 | 
			
		||||
    "#;
 | 
			
		||||
    /*
 | 
			
		||||
    Memory layout:
 | 
			
		||||
    Header(2, 10) // outer array
 | 
			
		||||
    Header(2, 4) // first inner array
 | 
			
		||||
    1
 | 
			
		||||
    a
 | 
			
		||||
    1
 | 
			
		||||
    b
 | 
			
		||||
    Header(2,4) // second inner array
 | 
			
		||||
    1
 | 
			
		||||
    c
 | 
			
		||||
    1
 | 
			
		||||
    d
 | 
			
		||||
     */
 | 
			
		||||
    let (_plan, scope) = must_plan(program);
 | 
			
		||||
    match scope.get("first").unwrap() {
 | 
			
		||||
        EpBinding::Single(addr) => {
 | 
			
		||||
            assert_eq!(*addr, Address::ZERO + 2);
 | 
			
		||||
            assert_eq!(*addr, Address::ZERO + 3);
 | 
			
		||||
        }
 | 
			
		||||
        other => {
 | 
			
		||||
            panic!("expected 'number' bound to 0x0 but it was bound to {other:?}");
 | 
			
		||||
            panic!("expected 'number' bound to addr 3 but it was bound to {other:?}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    match scope.get("last").unwrap() {
 | 
			
		||||
        EpBinding::Single(addr) => {
 | 
			
		||||
            assert_eq!(*addr, Address::ZERO + 6);
 | 
			
		||||
            assert_eq!(*addr, Address::ZERO + 10);
 | 
			
		||||
        }
 | 
			
		||||
        other => {
 | 
			
		||||
            panic!("expected 'number' bound to 0x3 but it was bound to {other:?}");
 | 
			
		||||
            panic!("expected 'number' bound to addr 10 but it was bound to {other:?}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -702,7 +845,6 @@ fn store_object() {
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
    assert_eq!(actual, expected);
 | 
			
		||||
    eprintln!("{bindings:#?}");
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        bindings.get("x0").unwrap(),
 | 
			
		||||
        &EpBinding::Map(HashMap::from([
 | 
			
		||||
@ -728,17 +870,25 @@ fn store_object_with_array_property() {
 | 
			
		||||
            address: Address::ZERO,
 | 
			
		||||
            value: 1i64.into(),
 | 
			
		||||
        },
 | 
			
		||||
        // Array length
 | 
			
		||||
        // Array header
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 1,
 | 
			
		||||
            value: 2usize.into(),
 | 
			
		||||
            value: ListHeader { count: 2, size: 4 }.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 2,
 | 
			
		||||
            value: 2i64.into(),
 | 
			
		||||
            value: 1usize.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 3,
 | 
			
		||||
            value: 2i64.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 4,
 | 
			
		||||
            value: 1usize.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 5,
 | 
			
		||||
            value: 3i64.into(),
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
@ -752,8 +902,8 @@ fn store_object_with_array_property() {
 | 
			
		||||
                EpBinding::Sequence {
 | 
			
		||||
                    length_at: Address::ZERO + 1,
 | 
			
		||||
                    elements: vec![
 | 
			
		||||
                        EpBinding::Single(Address::ZERO + 2),
 | 
			
		||||
                        EpBinding::Single(Address::ZERO + 3),
 | 
			
		||||
                        EpBinding::Single(Address::ZERO + 5),
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
@ -796,27 +946,42 @@ fn objects_as_parameters() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn arrays_as_parameters() {
 | 
			
		||||
    let program = "fn identity = (x) => { return x }
 | 
			
		||||
    let array = identity([1,2,3])";
 | 
			
		||||
    let program = r#"fn identity = (x) => { return x }
 | 
			
		||||
    let array = identity(["a","b","c"])"#;
 | 
			
		||||
    let (plan, scope) = must_plan(program);
 | 
			
		||||
    const INDEX_OF_A: usize = 2;
 | 
			
		||||
    const INDEX_OF_B: usize = 4;
 | 
			
		||||
    const INDEX_OF_C: usize = 6;
 | 
			
		||||
    let expected_plan = vec![
 | 
			
		||||
        // Array length
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO,
 | 
			
		||||
            value: 3usize.into(),
 | 
			
		||||
            value: ListHeader { count: 3, size: 6 }.into(),
 | 
			
		||||
        },
 | 
			
		||||
        // Array contents
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 1,
 | 
			
		||||
            value: 1i64.into(),
 | 
			
		||||
            value: 1usize.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 2,
 | 
			
		||||
            value: 2i64.into(),
 | 
			
		||||
            address: Address::ZERO + INDEX_OF_A,
 | 
			
		||||
            value: "a".to_owned().into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 3,
 | 
			
		||||
            value: 3i64.into(),
 | 
			
		||||
            value: 1usize.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + INDEX_OF_B,
 | 
			
		||||
            value: "b".to_owned().into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + 5,
 | 
			
		||||
            value: 1usize.into(),
 | 
			
		||||
        },
 | 
			
		||||
        Instruction::SetPrimitive {
 | 
			
		||||
            address: Address::ZERO + INDEX_OF_C,
 | 
			
		||||
            value: "c".to_owned().into(),
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
    assert_eq!(plan, expected_plan);
 | 
			
		||||
@ -825,9 +990,9 @@ fn arrays_as_parameters() {
 | 
			
		||||
        &EpBinding::Sequence {
 | 
			
		||||
            length_at: Address::ZERO,
 | 
			
		||||
            elements: vec![
 | 
			
		||||
                EpBinding::Single(Address::ZERO + 1),
 | 
			
		||||
                EpBinding::Single(Address::ZERO + 2),
 | 
			
		||||
                EpBinding::Single(Address::ZERO + 3),
 | 
			
		||||
                EpBinding::Single(Address::ZERO + INDEX_OF_A),
 | 
			
		||||
                EpBinding::Single(Address::ZERO + INDEX_OF_B),
 | 
			
		||||
                EpBinding::Single(Address::ZERO + INDEX_OF_C),
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user