diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index 06b87f8e22..01f1c56cc8 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -2007,7 +2007,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan" version = "0.1.0" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#c7722adf9744b9e4eead0a7a88662ad5e7e3adbf" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#f38096b3893fd36c9792216f3b87ced2254d2d04" dependencies = [ "bytes", "insta", @@ -2027,7 +2027,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan-macros" version = "0.1.8" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#c7722adf9744b9e4eead0a7a88662ad5e7e3adbf" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#f38096b3893fd36c9792216f3b87ced2254d2d04" dependencies = [ "proc-macro2", "quote", @@ -2037,7 +2037,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan-traits" version = "0.1.12" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#c7722adf9744b9e4eead0a7a88662ad5e7e3adbf" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#f38096b3893fd36c9792216f3b87ced2254d2d04" dependencies = [ "serde", "thiserror", @@ -2046,8 +2046,8 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds" -version = "0.1.28" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#c7722adf9744b9e4eead0a7a88662ad5e7e3adbf" +version = "0.1.29" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#f38096b3893fd36c9792216f3b87ced2254d2d04" dependencies = [ "anyhow", "chrono", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds-macros" version = "0.1.2" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#c7722adf9744b9e4eead0a7a88662ad5e7e3adbf" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#f38096b3893fd36c9792216f3b87ced2254d2d04" dependencies = [ "proc-macro2", "quote", @@ -2085,7 +2085,7 @@ dependencies = [ [[package]] name = "kittycad-modeling-session" version = "0.1.1" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#c7722adf9744b9e4eead0a7a88662ad5e7e3adbf" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#f38096b3893fd36c9792216f3b87ced2254d2d04" dependencies = [ "futures", "kittycad", diff --git a/src/wasm-lib/grackle/src/binding_scope.rs b/src/wasm-lib/grackle/src/binding_scope.rs index 85fe65b1b7..9b461938fe 100644 --- a/src/wasm-lib/grackle/src/binding_scope.rs +++ b/src/wasm-lib/grackle/src/binding_scope.rs @@ -24,6 +24,8 @@ pub enum EpBinding { }, /// Not associated with a KCEP address. Function(KclFunction), + /// SketchGroups have their own storage. + SketchGroup { index: usize }, } impl From for EpBinding { @@ -47,6 +49,7 @@ impl EpBinding { .ok_or(CompileError::IndexOutOfBounds { i, len: elements.len() }) } EpBinding::Map { .. } => Err(CompileError::CannotIndex), + EpBinding::SketchGroup { .. } => Err(CompileError::CannotIndex), EpBinding::Single(_) => Err(CompileError::CannotIndex), EpBinding::Function(_) => Err(CompileError::CannotIndex), }, @@ -54,6 +57,7 @@ impl EpBinding { LiteralValue::String(property) => match self { EpBinding::Single(_) => Err(CompileError::NoProperties), EpBinding::Function(_) => Err(CompileError::NoProperties), + EpBinding::SketchGroup { .. } => Err(CompileError::NoProperties), EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties), EpBinding::Map { properties, diff --git a/src/wasm-lib/grackle/src/error.rs b/src/wasm-lib/grackle/src/error.rs index f5e32d0364..143d194545 100644 --- a/src/wasm-lib/grackle/src/error.rs +++ b/src/wasm-lib/grackle/src/error.rs @@ -1,5 +1,5 @@ use kcl_lib::ast::types::RequiredParamAfterOptionalParam; -use kittycad_execution_plan::ExecutionError; +use kittycad_execution_plan::{ExecutionError, ExecutionFailed, Instruction}; use crate::String2; @@ -58,6 +58,26 @@ pub enum CompileError { pub enum Error { #[error("{0}")] Compile(#[from] CompileError), - #[error("{0}")] - Execution(#[from] ExecutionError), + #[error("Failed on instruction {instruction_index}:\n{error}\n\nInstruction contents were {instruction:#?}")] + Execution { + error: ExecutionError, + instruction: Instruction, + instruction_index: usize, + }, +} + +impl From for Error { + fn from( + ExecutionFailed { + error, + instruction, + instruction_index, + }: ExecutionFailed, + ) -> Self { + Self::Execution { + error, + instruction, + instruction_index, + } + } } diff --git a/src/wasm-lib/grackle/src/lib.rs b/src/wasm-lib/grackle/src/lib.rs index 0e7fe3c064..177b7251c5 100644 --- a/src/wasm-lib/grackle/src/lib.rs +++ b/src/wasm-lib/grackle/src/lib.rs @@ -33,11 +33,14 @@ pub async fn execute(ast: Program, session: Option) -> Result f.call(&mut self.next_addr, args)?, - KclFunction::StartSketchAt(f) => f.call(&mut self.next_addr, args)?, - KclFunction::LineTo(f) => f.call(&mut self.next_addr, args)?, - KclFunction::Add(f) => f.call(&mut self.next_addr, args)?, + KclFunction::Id(f) => f.call(&mut ctx, args)?, + KclFunction::StartSketchAt(f) => f.call(&mut ctx, args)?, + KclFunction::LineTo(f) => f.call(&mut ctx, args)?, + KclFunction::Add(f) => f.call(&mut ctx, args)?, KclFunction::UserDefined(f) => { let UserDefinedFunction { params_optional, diff --git a/src/wasm-lib/grackle/src/native_functions.rs b/src/wasm-lib/grackle/src/native_functions.rs index 64774f9995..d9295e03a7 100644 --- a/src/wasm-lib/grackle/src/native_functions.rs +++ b/src/wasm-lib/grackle/src/native_functions.rs @@ -15,11 +15,25 @@ pub mod sketch; pub struct Id; pub trait Callable { - fn call(&self, next_addr: &mut Address, args: Vec) -> Result; + fn call(&self, ctx: &mut Context<'_>, args: Vec) -> Result; +} + +#[derive(Debug)] +pub struct Context<'a> { + pub next_address: &'a mut Address, + pub next_sketch_group: &'a mut usize, +} + +impl<'a> Context<'a> { + pub fn assign_sketch_group(&mut self) -> usize { + let out = *self.next_sketch_group; + *self.next_sketch_group += 1; + out + } } impl Callable for Id { - fn call(&self, _: &mut Address, args: Vec) -> Result { + fn call(&self, _: &mut Context<'_>, args: Vec) -> Result { if args.len() > 1 { return Err(CompileError::TooManyArgs { fn_name: "id".into(), @@ -48,7 +62,7 @@ impl Callable for Id { pub struct Add; impl Callable for Add { - fn call(&self, next_address: &mut Address, mut args: Vec) -> Result { + fn call(&self, ctx: &mut Context<'_>, mut args: Vec) -> Result { let len = args.len(); if len > 2 { return Err(CompileError::TooManyArgs { @@ -69,7 +83,7 @@ impl Callable for Add { let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args)? else { return Err(CompileError::InvalidOperand(ERR)); }; - let destination = next_address.offset_by(1); + let destination = ctx.next_address.offset_by(1); Ok(EvalPlan { instructions: vec![Instruction::BinaryArithmetic { arithmetic: BinaryArithmetic { diff --git a/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs b/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs index 9a2636d53b..58b377aabf 100644 --- a/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs +++ b/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs @@ -35,6 +35,40 @@ pub fn stack_api_call( })) } +pub fn sg_binding( + b: EpBinding, + fn_name: &'static str, + expected: &'static str, + arg_number: usize, +) -> Result { + match b { + EpBinding::SketchGroup { index } => Ok(index), + EpBinding::Single(_) => Err(CompileError::ArgWrongType { + fn_name, + expected, + actual: "single".to_owned(), + arg_number, + }), + EpBinding::Sequence { .. } => Err(CompileError::ArgWrongType { + fn_name, + expected, + actual: "array".to_owned(), + arg_number, + }), + EpBinding::Map { .. } => Err(CompileError::ArgWrongType { + fn_name, + expected, + actual: "object".to_owned(), + arg_number, + }), + EpBinding::Function(_) => Err(CompileError::ArgWrongType { + fn_name, + expected, + actual: "function".to_owned(), + arg_number, + }), + } +} pub fn single_binding( b: EpBinding, fn_name: &'static str, @@ -43,6 +77,12 @@ pub fn single_binding( ) -> Result { match b { EpBinding::Single(a) => Ok(a), + EpBinding::SketchGroup { .. } => Err(CompileError::ArgWrongType { + fn_name, + expected, + actual: "SketchGroup".to_owned(), + arg_number, + }), EpBinding::Sequence { .. } => Err(CompileError::ArgWrongType { fn_name, expected, @@ -78,6 +118,12 @@ pub fn sequence_binding( actual: "single".to_owned(), arg_number, }), + EpBinding::SketchGroup { .. } => Err(CompileError::ArgWrongType { + fn_name, + expected, + actual: "SketchGroup".to_owned(), + arg_number, + }), EpBinding::Map { .. } => Err(CompileError::ArgWrongType { fn_name, expected, @@ -98,7 +144,7 @@ pub fn arg_point2d( arg: EpBinding, fn_name: &'static str, instructions: &mut Vec, - next_addr: &mut Address, + ctx: &mut crate::native_functions::Context<'_>, arg_number: usize, ) -> Result { let expected = "2D point (array with length 2)"; @@ -113,7 +159,7 @@ pub fn arg_point2d( } // KCL stores points as an array. // KC API stores them as Rust objects laid flat out in memory. - let start = next_addr.offset_by(2); + let start = ctx.next_address.offset_by(2); let start_x = start; let start_y = start + 1; let start_z = start + 2; @@ -133,5 +179,6 @@ pub fn arg_point2d( value: 0.0.into(), }, ]); + ctx.next_address.offset_by(1); // After we pushed 0.0 here, just above. Ok(start) } diff --git a/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs b/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs index 42cbc00fe5..c092091a5b 100644 --- a/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs +++ b/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs @@ -3,14 +3,14 @@ use kittycad_execution_plan::{ sketch_types::{self, Axes, BasePath, Plane, SketchGroup}, Destination, Instruction, }; -use kittycad_execution_plan_traits::{Address, InMemory, Value}; +use kittycad_execution_plan_traits::{InMemory, Primitive, Value}; use kittycad_modeling_cmds::{ shared::{Point3d, Point4d}, ModelingCmdEndpoint, }; use uuid::Uuid; -use super::helpers::{arg_point2d, no_arg_api_call, single_binding, stack_api_call}; +use super::helpers::{arg_point2d, no_arg_api_call, sg_binding, single_binding, stack_api_call}; use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan}; #[derive(Debug, Clone)] @@ -18,7 +18,11 @@ use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Cal pub struct LineTo; impl Callable for LineTo { - fn call(&self, next_addr: &mut Address, args: Vec) -> Result { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { let mut instructions = Vec::new(); let fn_name = "lineTo"; // Get both required params. @@ -37,12 +41,29 @@ impl Callable for LineTo { actual: 1, }); }; - // Check the type of both required params. - let to = arg_point2d(to, fn_name, &mut instructions, next_addr, 0)?; - let sg = single_binding(sketch_group, fn_name, "sketch group", 1)?; + let tag = match args_iter.next() { + Some(a) => a, + None => { + // Write an empty string and use that. + let empty_string_addr = ctx.next_address.offset_by(1); + instructions.push(Instruction::SetPrimitive { + address: empty_string_addr, + value: String::new().into(), + }); + EpBinding::Single(empty_string_addr) + } + }; + // Check the type of required params. + let to = arg_point2d(to, fn_name, &mut instructions, ctx, 0)?; + let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?; + let tag = single_binding(tag, fn_name, "string tag", 2)?; let id = Uuid::new_v4(); - let start_of_line = next_addr.offset(1); + // Start of the path segment (which is a straight line). let length_of_3d_point = Point3d::::default().into_parts().len(); + let start_of_line = ctx.next_address.offset_by(1); + // Reserve space for the line's end, and the `relative: bool` field. + ctx.next_address.offset_by(length_of_3d_point + 1); + let new_sg_index = ctx.assign_sketch_group(); instructions.extend([ // Push the `to` 2D point onto the stack. Instruction::Copy { @@ -60,31 +81,65 @@ impl Callable for LineTo { }, // Then its end Instruction::StackPop { - destination: Some(start_of_line + 1), + destination: Some(Destination::Address(start_of_line + 1)), }, // Then its `relative` field. Instruction::SetPrimitive { address: start_of_line + 1 + length_of_3d_point, value: false.into(), }, + // Push the path ID onto the stack. + Instruction::SketchGroupCopyFrom { + destination: Destination::StackPush, + length: 1, + source: sg, + offset: SketchGroup::path_id_offset(), + }, // Send the ExtendPath request Instruction::ApiRequest(ApiRequest { endpoint: ModelingCmdEndpoint::ExtendPath, store_response: None, arguments: vec![ // Path ID - InMemory::Address(sg + SketchGroup::path_id_offset()), + InMemory::StackPop, // Segment InMemory::Address(start_of_line), ], cmd_id: id.into(), }), + // Push the new segment in SketchGroup format. + // Path tag. + Instruction::StackPush { + data: vec![Primitive::from("ToPoint".to_owned())], + }, + // `BasePath::from` point. + Instruction::SketchGroupGetLastPoint { + source: sg, + destination: Destination::StackExtend, + }, + // `BasePath::to` point. + Instruction::Copy { + source: start_of_line + 1, + length: 2, + destination: Destination::StackExtend, + }, + // `BasePath::name` string. + Instruction::Copy { + source: tag, + length: 1, + destination: Destination::StackExtend, + }, + // Update the SketchGroup with its new segment. + Instruction::SketchGroupAddSegment { + destination: new_sg_index, + segment: InMemory::StackPop, + source: sg, + }, ]); - // TODO: Create a new SketchGroup from the old one + add the new path, then store it. Ok(EvalPlan { instructions, - binding: EpBinding::Single(Address::ZERO + 9999), + binding: EpBinding::SketchGroup { index: new_sg_index }, }) } } @@ -94,7 +149,11 @@ impl Callable for LineTo { pub struct StartSketchAt; impl Callable for StartSketchAt { - fn call(&self, next_addr: &mut Address, args: Vec) -> Result { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { let mut instructions = Vec::new(); // First, before we send any API calls, let's validate the arguments to this function. let mut args_iter = args.into_iter(); @@ -105,7 +164,7 @@ impl Callable for StartSketchAt { actual: 0, }); }; - let start_point = arg_point2d(start, "startSketchAt", &mut instructions, next_addr, 0)?; + let start_point = arg_point2d(start, "startSketchAt", &mut instructions, ctx, 0)?; let tag = match args_iter.next() { None => None, Some(b) => Some(single_binding(b, "startSketchAt", "a single string", 1)?), @@ -177,7 +236,7 @@ impl Callable for StartSketchAt { z: 0.0, w: 1.0, }, - // TODO: Must copy the existing data (from the arguments to this KCL function) + // Below: Must copy the existing data (from the arguments to this KCL function) // over these values after writing to memory. path_first: BasePath { from: Default::default(), @@ -194,18 +253,26 @@ impl Callable for StartSketchAt { axes, entity_id: Some(plane_id), }; - let sketch_group_primitives = sketch_group.clone().into_parts(); - - let sketch_group_addr = next_addr.offset_by(sketch_group_primitives.len()); - instructions.push(Instruction::SetValue { - address: sketch_group_addr, - value_parts: sketch_group_primitives, - }); - instructions.extend(sketch_group.set_base_path(sketch_group_addr, start_point, tag)); + let sketch_group_index = ctx.assign_sketch_group(); + instructions.extend([ + Instruction::SketchGroupSet { + sketch_group, + destination: sketch_group_index, + }, + // As mentioned above: Copy the existing data over the `path_first`. + Instruction::SketchGroupSetBasePath { + source: sketch_group_index, + from: InMemory::Address(start_point), + to: InMemory::Address(start_point), + name: tag.map(InMemory::Address), + }, + ]); Ok(EvalPlan { instructions, - binding: EpBinding::Single(sketch_group_addr), + binding: EpBinding::SketchGroup { + index: sketch_group_index, + }, }) } } diff --git a/src/wasm-lib/grackle/src/tests.rs b/src/wasm-lib/grackle/src/tests.rs index 9e3bdcb602..5adc4738d2 100644 --- a/src/wasm-lib/grackle/src/tests.rs +++ b/src/wasm-lib/grackle/src/tests.rs @@ -1,19 +1,20 @@ use std::{collections::HashMap, env}; -use ep::{Destination, UnaryArithmetic}; +use ep::{sketch_types, Destination, UnaryArithmetic}; use ept::{ListHeader, ObjectHeader}; +use kittycad_modeling_cmds::shared::Point2d; use kittycad_modeling_session::SessionBuilder; use pretty_assertions::assert_eq; use super::*; -fn must_plan(program: &str) -> (Vec, BindingScope) { +fn must_plan(program: &str) -> (Vec, BindingScope, Address) { let tokens = kcl_lib::token::lexer(program); let parser = kcl_lib::parser::Parser::new(tokens); let ast = parser.ast().unwrap(); let mut p = Planner::new(); let (instrs, _) = p.build_plan(ast).unwrap(); - (instrs, p.binding_scope) + (instrs, p.binding_scope, p.next_addr) } fn should_not_compile(program: &str) -> CompileError { @@ -29,7 +30,7 @@ fn assignments() { let program = " let x = 1 let y = 2"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![ @@ -48,7 +49,7 @@ fn assignments() { #[test] fn bind_array_simple() { let program = r#"let x = [44, 55, "sixty-six"]"#; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![ @@ -98,7 +99,7 @@ fn bind_array_simple() { #[test] fn bind_nested_array() { let program = r#"let x = [44, [55, "sixty-six"]]"#; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![ @@ -154,7 +155,7 @@ fn bind_nested_array() { #[test] fn bind_arrays_with_objects_elements() { let program = r#"let x = [44, {a: 55, b: "sixty-six"}]"#; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![ @@ -223,7 +224,7 @@ fn assign_bool() { // Check that Grackle properly compiles KCL bools to EP bools. for (str, val) in [("true", true), ("false", false)] { let program = format!("let x = {str}"); - let (plan, scope) = must_plan(&program); + let (plan, scope, _) = must_plan(&program); assert_eq!( plan, vec![Instruction::SetPrimitive { @@ -240,7 +241,7 @@ fn aliases() { let program = " let x = 1 let y = x"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![Instruction::SetPrimitive { @@ -253,7 +254,7 @@ fn aliases() { #[test] fn use_native_function_add() { let program = "let x = add(1,2)"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![ @@ -280,7 +281,7 @@ fn use_native_function_add() { #[test] fn use_native_function_id() { let program = "let x = id(2)"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![Instruction::SetPrimitive { @@ -297,7 +298,7 @@ async fn computed_object_property() { let prop0 = "a" let val = obj[prop0] // should be `true` "#; - let (_plan, scope) = must_plan(program); + let (_plan, scope, _) = must_plan(program); let Some(EpBinding::Single(address_of_val)) = scope.get("val") else { panic!("Unexpected binding for variable 'val': {:?}", scope.get("val")); }; @@ -319,7 +320,7 @@ async fn computed_array_in_object() { let prop1 = "b" let val = complicated[prop0][i][prop1] // should be `true` "#; - let (_plan, scope) = must_plan(program); + let (_plan, scope, _) = must_plan(program); let Some(EpBinding::Single(address_of_val)) = scope.get("val") else { panic!("Unexpected binding for variable 'val': {:?}", scope.get("val")); }; @@ -341,7 +342,7 @@ async fn computed_object_in_array() { let prop1 = "b" let val = complicated[i][prop0][prop1] // should be `true` "#; - let (_plan, scope) = must_plan(program); + let (_plan, scope, _) = must_plan(program); let Some(EpBinding::Single(address_of_val)) = scope.get("val") else { panic!("Unexpected binding for variable 'val': {:?}", scope.get("val")); }; @@ -362,7 +363,7 @@ async fn computed_nested_object_property() { let prop1 = "b" let val = obj[prop0][prop1] // should be `true` "#; - let (_plan, scope) = must_plan(program); + let (_plan, scope, _) = must_plan(program); let Some(EpBinding::Single(address_of_val)) = scope.get("val") else { panic!("Unexpected binding for variable 'val': {:?}", scope.get("val")); }; @@ -382,7 +383,7 @@ async fn computed_array_index() { let index = 1+1 // should be "c" let prop = array[index] "#; - let (plan, scope) = must_plan(program); + 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); @@ -479,7 +480,7 @@ fn member_expressions_object() { let obj = {x: 1, y: 2} let prop = obj["y"] "#; - let (_plan, scope) = must_plan(program); + let (_plan, scope, _) = must_plan(program); match scope.get("prop").unwrap() { EpBinding::Single(addr) => { assert_eq!(*addr, Address::ZERO + 4); @@ -511,7 +512,7 @@ fn member_expressions_array() { 1 d */ - let (_plan, scope) = must_plan(program); + let (_plan, scope, _) = must_plan(program); match scope.get("first").unwrap() { EpBinding::Single(addr) => { assert_eq!(*addr, Address::ZERO + 3); @@ -534,7 +535,7 @@ fn member_expressions_array() { fn compile_flipped_sign() { let program = "let x = 3 let y = -x"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); let expected = vec![ Instruction::SetPrimitive { address: Address::ZERO, @@ -554,7 +555,7 @@ fn compile_flipped_sign() { #[test] fn add_literals() { let program = "let x = 1 + 2"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!( plan, vec![ @@ -584,7 +585,7 @@ fn add_vars() { let one = 1 let two = 2 let x = one + two"; - let (plan, _bindings) = must_plan(program); + let (plan, _bindings, _) = must_plan(program); let addr0 = Address::ZERO; let addr1 = Address::ZERO.offset(1); assert_eq!( @@ -618,7 +619,7 @@ fn composite_binary_exprs() { let z = 3 let six = x + y + z "; - let (plan, _bindings) = must_plan(program); + let (plan, _bindings, _) = must_plan(program); let addr0 = Address::ZERO; let addr1 = Address::ZERO.offset(1); let addr2 = Address::ZERO.offset(2); @@ -662,7 +663,7 @@ fn composite_binary_exprs() { #[test] fn use_kcl_functions_zero_params() { - let (plan, scope) = must_plan( + let (plan, scope, _) = must_plan( "fn triple = () => { return 123 } let x = triple()", ); @@ -690,7 +691,7 @@ fn use_kcl_functions_with_optional_params() { .into_iter() .enumerate() { - let (plan, scope) = must_plan(program); + let (plan, scope, _) = must_plan(program); let destination = Address::ZERO + 3; assert_eq!( plan, @@ -751,7 +752,7 @@ fn use_kcl_function_as_return_value() { } let f = twotwotwo() let x = f()"; - let (plan, scope) = must_plan(program); + let (plan, scope, _) = must_plan(program); match scope.get("x").unwrap() { EpBinding::Single(addr) => { assert_eq!(addr, &Address::ZERO); @@ -774,7 +775,7 @@ fn define_recursive_function() { let program = "fn add_infinitely = (i) => { return add_infinitely(i+1) }"; - let (plan, _scope) = must_plan(program); + let (plan, _scope, _) = must_plan(program); assert_eq!(plan, Vec::new()) } #[test] @@ -786,7 +787,7 @@ fn use_kcl_function_as_param() { return 222 } let x = wrapper(twotwotwo)"; - let (plan, scope) = must_plan(program); + let (plan, scope, _) = must_plan(program); match scope.get("x").unwrap() { EpBinding::Single(addr) => { assert_eq!(addr, &Address::ZERO); @@ -815,7 +816,7 @@ fn use_kcl_functions_with_params() { .into_iter() .enumerate() { - let (plan, scope) = must_plan(program); + let (plan, scope, _) = must_plan(program); let destination = Address::ZERO + 2; assert_eq!( plan, @@ -872,14 +873,14 @@ fn unsugar_pipe_expressions() { let x = triple(double(1)) // should be 6 "; // So, check that they are. - let (plan1, _) = must_plan(program1); - let (plan2, _) = must_plan(program2); + let (plan1, _, _) = must_plan(program1); + let (plan2, _, _) = must_plan(program2); assert_eq!(plan1, plan2); } #[test] fn define_kcl_functions() { - let (plan, scope) = must_plan("fn triple = (x) => { return x * 3 }"); + let (plan, scope, _) = must_plan("fn triple = (x) => { return x * 3 }"); assert!(plan.is_empty()); match scope.get("triple").unwrap() { EpBinding::Function(KclFunction::UserDefined(expr)) => { @@ -894,12 +895,12 @@ fn define_kcl_functions() { #[test] fn aliases_dont_affect_plans() { - let (plan1, _) = must_plan( + let (plan1, _scope, _) = must_plan( "let one = 1 let two = 2 let x = one + two", ); - let (plan2, _) = must_plan( + let (plan2, _scope, _) = must_plan( "let one = 1 let two = 2 let y = two @@ -911,7 +912,7 @@ fn aliases_dont_affect_plans() { #[test] fn store_object() { let program = "const x0 = {a: 1, b: 2, c: {d: 3}}"; - let (actual, bindings) = must_plan(program); + let (actual, bindings, _) = must_plan(program); let expected = vec![ Instruction::SetPrimitive { address: Address::ZERO, @@ -983,7 +984,7 @@ fn store_object() { #[test] fn store_object_with_array_property() { let program = "const x0 = {a: 1, b: [2, 3]}"; - let (actual, bindings) = must_plan(program); + let (actual, bindings, _) = must_plan(program); let expected = vec![ Instruction::SetPrimitive { address: Address::ZERO, @@ -1045,18 +1046,71 @@ fn store_object_with_array_property() { ) } +/// Write the program's plan to the KCVM debugger's normal input file. +#[allow(unused)] +fn kcvm_dbg(kcl_program: &str) { + let (plan, _scope, _) = must_plan(kcl_program); + let plan_json = serde_json::to_string_pretty(&plan).unwrap(); + std::fs::write( + "/Users/adamchalmers/kc-repos/modeling-api/execution-plan-debugger/test_input.json", + plan_json, + ) + .unwrap(); +} + #[tokio::test] async fn stdlib_cube_partial() { let program = r#" - let cube = startSketchAt([0.0, 0.0]) - |> lineTo([4.0, 0.0], %) + let cube = startSketchAt([1.0, 1.0], "adam") + |> lineTo([21.0 , 1.0], %, "side0") + |> lineTo([21.0 , 21.0], %, "side1") + |> lineTo([ 1.0 , 21.0], %, "side2") + |> lineTo([ 1.0 , 1.0], %, "side3") "#; - let (_plan, _scope) = must_plan(program); + let (_plan, _scope, last_address) = must_plan(program); + assert_eq!(last_address, Address::ZERO + 65); let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); let client = test_client().await; - let _mem = crate::execute(ast, Some(client)).await.unwrap(); + let mem = match crate::execute(ast, Some(client)).await { + Ok(mem) => mem, + Err(e) => panic!("{e}"), + }; + let sg = &mem.sketch_groups.last().unwrap(); + assert_eq!( + sg.path_rest, + vec![ + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 1.0, y: 1.0 }, + to: Point2d { x: 21.0, y: 1.0 }, + name: "side0".into(), + } + }, + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 21.0, y: 1.0 }, + to: Point2d { x: 21.0, y: 21.0 }, + name: "side1".into(), + } + }, + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 21.0, y: 21.0 }, + to: Point2d { x: 1.0, y: 21.0 }, + name: "side2".into(), + } + }, + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 1.0, y: 21.0 }, + to: Point2d { x: 1.0, y: 1.0 }, + name: "side3".into(), + } + }, + ] + ); // use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat, ModelingCmd}; // let out = client // .run_command( @@ -1136,7 +1190,7 @@ fn stdlib_api_calls() { fn objects_as_parameters() { let program = "fn identity = (x) => { return x } let obj = identity({x: 1})"; - let (plan, scope) = must_plan(program); + let (plan, scope, _) = must_plan(program); let expected_plan = vec![ // Object contents Instruction::SetPrimitive { @@ -1170,7 +1224,7 @@ fn objects_as_parameters() { fn arrays_as_parameters() { let program = r#"fn identity = (x) => { return x } let array = identity(["a","b","c"])"#; - let (plan, scope) = must_plan(program); + let (plan, scope, _) = must_plan(program); const INDEX_OF_A: usize = 2; const INDEX_OF_B: usize = 4; const INDEX_OF_C: usize = 6; @@ -1227,7 +1281,7 @@ fn mod_and_pow() { let y = x^3 let z = y % 5 "; - let (plan, _bindings) = must_plan(program); + let (plan, _bindings, _) = must_plan(program); let addr0 = Address::ZERO; let addr1 = Address::ZERO.offset(1); let addr2 = Address::ZERO.offset(2);