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