Compare commits
	
		
			3 Commits
		
	
	
		
			import-fix
			...
			angled-lin
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 58b9382990 | |||
| 0947d970a6 | |||
| e8769eb543 | 
| @ -8,6 +8,7 @@ description = "A new executor for KCL which compiles to Execution Plans" | ||||
| [dependencies] | ||||
| image = { version = "0.24.7", default-features = false, features = ["png"] } | ||||
| kcl-lib = { path = "../kcl" } | ||||
| kcl-macros = { path = "../kcl-macros/" } | ||||
| kittycad = { workspace = true } | ||||
| kittycad-execution-plan = { workspace = true } | ||||
| kittycad-execution-plan-traits = { workspace = true } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								src/wasm-lib/grackle/fixtures/tri_angledLineToX.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/wasm-lib/grackle/fixtures/tri_angledLineToX.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 91 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/wasm-lib/grackle/fixtures/tri_angledLineX.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/wasm-lib/grackle/fixtures/tri_angledLineX.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 77 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/wasm-lib/grackle/fixtures/zigzag_angledLine.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/wasm-lib/grackle/fixtures/zigzag_angledLine.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 76 KiB | 
| @ -43,7 +43,12 @@ impl EpBinding { | ||||
|     /// Look up the given property of this binding. | ||||
|     pub fn property_of(&self, property: LiteralIdentifier) -> Result<&Self, CompileError> { | ||||
|         match property { | ||||
|             LiteralIdentifier::Identifier(_) => todo!("Support identifier properties"), | ||||
|             LiteralIdentifier::Identifier(ident) => { | ||||
|               let EpBinding::Map { length_at: _, properties } = self else { | ||||
|                 return Err(CompileError::CannotIndex) | ||||
|               }; | ||||
|               properties.get(&ident.name).ok_or(CompileError::CannotIndex) | ||||
|             } | ||||
|             LiteralIdentifier::Literal(litval) => match litval.value { | ||||
|                 // Arrays can be indexed by integers. | ||||
|                 LiteralValue::IInteger(i) => match self { | ||||
|  | ||||
| @ -11,6 +11,8 @@ use kcl_lib::{ | ||||
|     ast, | ||||
|     ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program}, | ||||
| }; | ||||
| extern crate alloc; | ||||
| use kcl_macros::parse_file; | ||||
| use kcl_value_group::into_single_value; | ||||
| use kittycad_execution_plan::{self as ep, Destination, Instruction}; | ||||
| use kittycad_execution_plan_traits as ept; | ||||
| @ -24,9 +26,11 @@ use self::{ | ||||
| }; | ||||
|  | ||||
| /// Execute a KCL program by compiling into an execution plan, then running that. | ||||
| pub async fn execute(ast: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> { | ||||
| /// Include a `prelude.kcl` inlined as a `Program` struct thanks to a proc_macro `parse!`. | ||||
| /// This makes additional functions available to the user. | ||||
| pub async fn execute(ast_user: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> { | ||||
|     let mut planner = Planner::new(); | ||||
|     let (plan, _retval) = planner.build_plan(ast)?; | ||||
|     let (plan, _retval) = planner.build_plan(ast_user)?; | ||||
|     let mut mem = ep::Memory::default(); | ||||
|     ep::execute(&mut mem, plan, session).await?; | ||||
|     Ok(mem) | ||||
| @ -54,7 +58,9 @@ impl Planner { | ||||
|  | ||||
|     /// If successful, return the KCEP instructions for executing the given program. | ||||
|     /// If the program is a function with a return, then it also returns the KCL function's return value. | ||||
|     fn build_plan(&mut self, program: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> { | ||||
|     fn build_plan(&mut self, ast_user: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> { | ||||
|         let ast_prelude = parse_file!("./prelude.kcl"); | ||||
|         let program = ast_prelude.merge(ast_user); | ||||
|         program | ||||
|             .body | ||||
|             .into_iter() | ||||
|  | ||||
| @ -32,23 +32,23 @@ impl<'a> Context<'a> { | ||||
| /// Unary operator macro to quickly create new bindings. | ||||
| macro_rules! define_unary { | ||||
|   () => {}; | ||||
|   ($h:ident$( $r:ident)*) => { | ||||
|   ($fn_name:ident$( $rest:ident)*) => { | ||||
|     #[derive(Debug, Clone)] | ||||
|     #[cfg_attr(test, derive(Eq, PartialEq))] | ||||
|     pub struct $h; | ||||
|     pub struct $fn_name; | ||||
|  | ||||
|     impl Callable for $h { | ||||
|     impl Callable for $fn_name { | ||||
|         fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> { | ||||
|             if args.len() > 1 { | ||||
|                 return Err(CompileError::TooManyArgs { | ||||
|                     fn_name: "$h".into(), | ||||
|                     fn_name: "$fn_name".into(), | ||||
|                     maximum: 1, | ||||
|                     actual: args.len(), | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             let not_enough_args = CompileError::NotEnoughArgs { | ||||
|                 fn_name: "$h".into(), | ||||
|                 fn_name: "$fn_name".into(), | ||||
|                 required: 1, | ||||
|                 actual: args.len(), | ||||
|             }; | ||||
| @ -61,7 +61,7 @@ macro_rules! define_unary { | ||||
|             let instructions = vec![ | ||||
|               Instruction::UnaryArithmetic { | ||||
|                 arithmetic: UnaryArithmetic { | ||||
|                   operation: UnaryOperation::$h, | ||||
|                   operation: UnaryOperation::$fn_name, | ||||
|                   operand: Operand::Reference(arg0) | ||||
|                 }, | ||||
|                 destination: Destination::Address(destination) | ||||
| @ -75,7 +75,7 @@ macro_rules! define_unary { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     define_unary!($($r)*); | ||||
|     define_unary!($($rest)*); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -115,27 +115,27 @@ impl Callable for Id { | ||||
| /// Binary operator macro to quickly create new bindings. | ||||
| macro_rules! define_binary { | ||||
|   () => {}; | ||||
|   ($h:ident$( $r:ident)*) => { | ||||
|   ($fn_name:ident$( $rest:ident)*) => { | ||||
|     #[derive(Debug, Clone)] | ||||
|     #[cfg_attr(test, derive(Eq, PartialEq))] | ||||
|     pub struct $h; | ||||
|     pub struct $fn_name; | ||||
|  | ||||
|     impl Callable for $h { | ||||
|     impl Callable for $fn_name { | ||||
|         fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> { | ||||
|           let len = args.len(); | ||||
|           if len > 2 { | ||||
|               return Err(CompileError::TooManyArgs { | ||||
|                   fn_name: "$h".into(), | ||||
|                   fn_name: "$fn_name".into(), | ||||
|                   maximum: 2, | ||||
|                   actual: len, | ||||
|               }); | ||||
|           } | ||||
|           let not_enough_args = CompileError::NotEnoughArgs { | ||||
|               fn_name: "$h".into(), | ||||
|               fn_name: "$fn_name".into(), | ||||
|               required: 2, | ||||
|               actual: len, | ||||
|           }; | ||||
|           const ERR: &str = "cannot use composite values (e.g. array) as arguments to $h"; | ||||
|           const ERR: &str = "cannot use composite values (e.g. array) as arguments to $fn_name"; | ||||
|           let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else { | ||||
|               return Err(CompileError::InvalidOperand(ERR)); | ||||
|           }; | ||||
| @ -146,7 +146,7 @@ macro_rules! define_binary { | ||||
|           Ok(EvalPlan { | ||||
|               instructions: vec![Instruction::BinaryArithmetic { | ||||
|                   arithmetic: BinaryArithmetic { | ||||
|                       operation: BinaryOperation::$h, | ||||
|                       operation: BinaryOperation::$fn_name, | ||||
|                       operand0: Operand::Reference(arg0), | ||||
|                       operand1: Operand::Reference(arg1), | ||||
|                   }, | ||||
| @ -157,7 +157,7 @@ macro_rules! define_binary { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     define_binary!($($r)*); | ||||
|     define_binary!($($rest)*); | ||||
|   }; | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -1144,10 +1144,6 @@ async fn stdlib_cube_xline_yline() { | ||||
|         |> close(%) | ||||
|         |> extrude(100.0, %) | ||||
|     "#; | ||||
|     kcvm_dbg( | ||||
|         program, | ||||
|         "/home/lee/Code/Zoo/modeling-api/execution-plan-debugger/cube_xyline.json", | ||||
|     ); | ||||
|     let (_plan, _scope, _last_address) = must_plan(program); | ||||
|  | ||||
|     let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) | ||||
| @ -1442,3 +1438,154 @@ async fn cos_sin_pi() { | ||||
|     // Constants don't live in memory. | ||||
|     assert_eq!(*z, constants::PI); | ||||
| } | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn kcl_prelude() { | ||||
|     let program = " | ||||
|         let the_answer_to_the_universe_is = hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this() | ||||
|     "; | ||||
|     let (_plan, scope, _) = must_plan(program); | ||||
|     let Some(EpBinding::Single(the_answer_to_the_universe_is)) = scope.get("the_answer_to_the_universe_is") else { | ||||
|         panic!( | ||||
|             "Unexpected binding for variable 'the_answer_to_the_universe_is': {:?}", | ||||
|             scope.get("the_answer_to_the_universe_is") | ||||
|         ); | ||||
|     }; | ||||
|     let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) | ||||
|         .ast() | ||||
|         .unwrap(); | ||||
|     let mem = crate::execute(ast, &mut None).await.unwrap(); | ||||
|     use ept::ReadMemory; | ||||
|     assert_eq!(*mem.get(the_answer_to_the_universe_is).unwrap(), Primitive::from(42i64)); | ||||
| } | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn angled_line() { | ||||
|     let program = r#" | ||||
|     let zigzag = startSketchAt([0.0, 0.0], "test") | ||||
|         |> angledLine({ angle: 45.0, length: 100 }, %, "") | ||||
|         |> angledLine({ angle: 45.0 + 90.0, length: 100 }, %, "") | ||||
|         |> angledLine({ angle: 45.0, length: 100 }, %, "") | ||||
|         |> angledLine({ angle: 45.0 + 90.0, length: 100 }, %, "") | ||||
|         |> close(%) | ||||
|         |> extrude(100.0, %) | ||||
|     "#; | ||||
|     let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) | ||||
|         .ast() | ||||
|         .unwrap(); | ||||
|     let mut client = Some(test_client().await); | ||||
|     let mem = match crate::execute(ast, &mut client).await { | ||||
|         Ok(mem) => mem, | ||||
|         Err(e) => panic!("{e}"), | ||||
|     }; | ||||
|     use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; | ||||
|     let out = client | ||||
|         .unwrap() | ||||
|         .run_command( | ||||
|             uuid::Uuid::new_v4().into(), | ||||
|             kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot { | ||||
|                 format: ImageFormat::Png, | ||||
|             }), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|     let out = match out { | ||||
|         OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b, | ||||
|         other => panic!("wrong output: {other:?}"), | ||||
|     }; | ||||
|  | ||||
|     use image::io::Reader as ImageReader; | ||||
|     let img = ImageReader::new(std::io::Cursor::new(out)) | ||||
|         .with_guessed_format() | ||||
|         .unwrap() | ||||
|         .decode() | ||||
|         .unwrap(); | ||||
|     twenty_twenty::assert_image("fixtures/zigzag_angledLine.png", &img, 0.9999); | ||||
| } | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn angled_line_to_x() { | ||||
|     let program = r#" | ||||
|     let zigzag = startSketchAt([0.0, 0.0], "test") | ||||
|         |> angledLineToX({ angle: toRadians(85.0), to: 300.0 }, %, "") | ||||
|         |> angledLineToX({ angle: toRadians(35.0), to: 550.0 }, %, "") | ||||
|         |> close(%) | ||||
|         |> extrude(100.0, %) | ||||
|     "#; | ||||
|     let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) | ||||
|         .ast() | ||||
|         .unwrap(); | ||||
|     let mut client = Some(test_client().await); | ||||
|     let mem = match crate::execute(ast, &mut client).await { | ||||
|         Ok(mem) => mem, | ||||
|         Err(e) => panic!("{e}"), | ||||
|     }; | ||||
|     use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; | ||||
|     let out = client | ||||
|         .unwrap() | ||||
|         .run_command( | ||||
|             uuid::Uuid::new_v4().into(), | ||||
|             kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot { | ||||
|                 format: ImageFormat::Png, | ||||
|             }), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|     let out = match out { | ||||
|         OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b, | ||||
|         other => panic!("wrong output: {other:?}"), | ||||
|     }; | ||||
|  | ||||
|     use image::io::Reader as ImageReader; | ||||
|     let img = ImageReader::new(std::io::Cursor::new(out)) | ||||
|         .with_guessed_format() | ||||
|         .unwrap() | ||||
|         .decode() | ||||
|         .unwrap(); | ||||
|     twenty_twenty::assert_image("fixtures/tri_angledLineToX.png", &img, 0.9999); | ||||
| } | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn angled_line_x() { | ||||
|     let program = r#" | ||||
|     let zigzag = startSketchAt([0.0, 0.0], "test") | ||||
|         |> angledLineX({ angle: toRadians(85.0), to: 300.0 }, %, "") | ||||
|         |> angledLineX({ angle: toRadians(35.0), to: 550.0 }, %, "") | ||||
|         |> close(%) | ||||
|         |> extrude(100.0, %) | ||||
|     "#; | ||||
|     let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) | ||||
|         .ast() | ||||
|         .unwrap(); | ||||
|     let mut client = Some(test_client().await); | ||||
|     let mem = match crate::execute(ast, &mut client).await { | ||||
|         Ok(mem) => mem, | ||||
|         Err(e) => panic!("{e}"), | ||||
|     }; | ||||
|     use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; | ||||
|     let out = client | ||||
|         .unwrap() | ||||
|         .run_command( | ||||
|             uuid::Uuid::new_v4().into(), | ||||
|             kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot { | ||||
|                 format: ImageFormat::Png, | ||||
|             }), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|     let out = match out { | ||||
|         OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b, | ||||
|         other => panic!("wrong output: {other:?}"), | ||||
|     }; | ||||
|  | ||||
|     use image::io::Reader as ImageReader; | ||||
|     let img = ImageReader::new(std::io::Cursor::new(out)) | ||||
|         .with_guessed_format() | ||||
|         .unwrap() | ||||
|         .decode() | ||||
|         .unwrap(); | ||||
|     twenty_twenty::assert_image("fixtures/tri_angledLineX.png", &img, 0.9999); | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,6 @@ use syn::{parse_macro_input, LitStr}; | ||||
| /// This macro takes exactly one argument: A string literal containing KCL. | ||||
| /// # Examples | ||||
| /// ``` | ||||
| /// extern crate alloc; | ||||
| /// use kcl_compile_macro::parse_kcl; | ||||
| /// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4"); | ||||
| /// ``` | ||||
| @ -21,3 +20,14 @@ pub fn parse(input: TokenStream) -> TokenStream { | ||||
|     let ast_struct = ast.bake(&Default::default()); | ||||
|     quote!(#ast_struct).into() | ||||
| } | ||||
|  | ||||
| /// Same as `parse!` but read in a file. | ||||
| #[proc_macro] | ||||
| pub fn parse_file(input: TokenStream) -> TokenStream { | ||||
|     let file_name = parse_macro_input!(input as LitStr); | ||||
|     let kcl_src = std::fs::read_to_string(file_name.value()).unwrap(); | ||||
|     let tokens = kcl_lib::token::lexer(&kcl_src); | ||||
|     let ast = kcl_lib::parser::Parser::new(tokens).ast().unwrap(); | ||||
|     let ast_struct = ast.bake(&Default::default()); | ||||
|     quote!(#ast_struct).into() | ||||
| } | ||||
|  | ||||
| @ -351,6 +351,31 @@ impl Program { | ||||
|  | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     /// Merge two `Program`s together. | ||||
|     pub fn merge(self, ast_other: Program) -> Program { | ||||
|       let mut body_new: Vec<BodyItem> = vec![]; | ||||
|       body_new.extend(self.body); | ||||
|       body_new.extend(ast_other.body); | ||||
|  | ||||
|       let mut non_code_nodes_new: HashMap<usize, Vec<NonCodeNode>> = HashMap::new(); | ||||
|       non_code_nodes_new.extend(self.non_code_meta.non_code_nodes); | ||||
|       non_code_nodes_new.extend(ast_other.non_code_meta.non_code_nodes); | ||||
|  | ||||
|       let mut start_new: Vec<NonCodeNode> = vec![]; | ||||
|       start_new.extend(self.non_code_meta.start); | ||||
|       start_new.extend(ast_other.non_code_meta.start); | ||||
|  | ||||
|       Program { | ||||
|         start: self.start, | ||||
|         end: self.end + (ast_other.end - ast_other.start), | ||||
|         body: body_new, | ||||
|         non_code_meta: NonCodeMeta { | ||||
|           non_code_nodes: non_code_nodes_new, | ||||
|           start: start_new, | ||||
|         }, | ||||
|       } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub trait ValueMeta { | ||||
|  | ||||
| @ -958,7 +958,7 @@ impl TryFrom<Token> for Identifier { | ||||
|             Err(KclError::Syntax(KclErrorDetails { | ||||
|                 source_ranges: token.as_source_ranges(), | ||||
|                 message: format!( | ||||
|                     "Cannot assign a variable to a reserved keyword: {}", | ||||
|                     "Word Cannot assign a variable to a reserved keyword: {}", | ||||
|                     token.value.as_str() | ||||
|                 ), | ||||
|             })) | ||||
| @ -1283,13 +1283,6 @@ fn optional_after_required(params: &[Parameter]) -> Result<(), KclError> { | ||||
|  | ||||
| impl Identifier { | ||||
|     fn into_valid_binding_name(self) -> Result<Identifier, KclError> { | ||||
|         // Make sure they are not assigning a variable to a stdlib function. | ||||
|         if crate::std::name_in_stdlib(&self.name) { | ||||
|             return Err(KclError::Syntax(KclErrorDetails { | ||||
|                 source_ranges: vec![SourceRange([self.start, self.end])], | ||||
|                 message: format!("Cannot assign a variable to a reserved keyword: {}", self.name), | ||||
|             })); | ||||
|         } | ||||
|         Ok(self) | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/wasm-lib/prelude.kcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/wasm-lib/prelude.kcl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| fn hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this = () => { | ||||
|   return 42 | ||||
| } | ||||
|  | ||||
| let TAU = PI * 2.0 | ||||
|  | ||||
| fn angledLine = (args, segment_group, tag?) => { | ||||
|   let x = cos(args.angle) * args.length | ||||
|   let y = sin(args.angle) * args.length | ||||
|   return line([x, y], segment_group, tag) | ||||
| } | ||||
|  | ||||
| fn angledLineTo_ = (args, axis) => { | ||||
|   let fn = { X: [cos, sin], Y: [sin, cos] } | ||||
|   let a = args.to | ||||
|   let length = args.to / fn[axis][0](args.angle) | ||||
|   let b = fn[axis][1](args.angle) * length | ||||
|   return [x, y] | ||||
| } | ||||
|  | ||||
| fn angledLineToX = (args, segment_group, tag?) => { | ||||
|   return lineTo(angledLineTo_(args, "X"), segment_group, tag) | ||||
| } | ||||
|  | ||||
| fn angledLineX = (args, segment_group, tag?) => { | ||||
|   return line(angledLineTo_(args, "X"), segment_group, tag) | ||||
| } | ||||
|  | ||||
| fn angledLineToY = (args, segment_group, tag?) => { | ||||
|   return lineTo(angledLineTo_(args, "Y"), segment_group, tag) | ||||
| } | ||||
|  | ||||
| fn angledLineY = (args, segment_group, tag?) => { | ||||
|   return line(angledLineTo_(args, "Y"), segment_group, tag) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	