2023-08-24 15:34:51 -07:00
//! The executor for the AST.
2024-12-07 07:16:04 +13:00
use std ::{ path ::PathBuf , sync ::Arc } ;
2023-08-24 15:34:51 -07:00
use anyhow ::Result ;
2023-11-09 09:58:20 -06:00
use async_recursion ::async_recursion ;
2024-11-07 11:23:41 -05:00
use indexmap ::IndexMap ;
2024-09-19 14:06:29 -07:00
use kcmc ::{
each_cmd as mcmd ,
ok_response ::{ output ::TakeSnapshot , OkModelingCmdResponse } ,
websocket ::{ ModelingSessionData , OkWebSocketResponseData } ,
ImageFormat , ModelingCmd ,
} ;
2024-09-18 17:04:04 -05:00
use kittycad_modeling_cmds as kcmc ;
use kittycad_modeling_cmds ::length_unit ::LengthUnit ;
2023-10-05 14:27:48 -07:00
use parse_display ::{ Display , FromStr } ;
2023-08-25 13:41:04 -07:00
use schemars ::JsonSchema ;
2023-08-24 15:34:51 -07:00
use serde ::{ Deserialize , Serialize } ;
2023-08-25 13:41:04 -07:00
2024-09-18 17:04:04 -05:00
type Point2D = kcmc ::shared ::Point2d < f64 > ;
type Point3D = kcmc ::shared ::Point3d < f64 > ;
2024-12-07 07:16:04 +13:00
pub use function_param ::FunctionParam ;
pub use kcl_value ::{ KclObjectFields , KclValue } ;
2024-12-10 18:50:22 -08:00
pub ( crate ) mod cache ;
2024-12-16 13:10:31 -05:00
mod cad_op ;
2024-12-10 18:50:22 -08:00
mod exec_ast ;
mod function_param ;
mod kcl_value ;
2023-08-24 15:34:51 -07:00
use crate ::{
2024-10-17 00:48:33 -04:00
engine ::{ EngineManager , ExecutionKind } ,
2023-08-24 15:34:51 -07:00
errors ::{ KclError , KclErrorDetails } ,
2024-12-10 18:50:22 -08:00
execution ::cache ::{ CacheInformation , CacheResult } ,
2024-10-17 00:48:33 -04:00
fs ::{ FileManager , FileSystem } ,
2024-12-10 18:50:22 -08:00
parsing ::ast ::types ::{
BodyItem , Expr , FunctionExpression , ImportSelector , ItemVisibility , Node , NodeRef , TagDeclarator , TagNode ,
2024-12-05 19:51:06 -08:00
} ,
2024-06-18 14:38:25 -05:00
settings ::types ::UnitLength ,
2024-12-03 16:39:51 +13:00
source_range ::{ ModuleId , SourceRange } ,
2024-11-21 13:10:03 -05:00
std ::{ args ::Arg , StdLib } ,
2024-11-25 14:06:23 -06:00
ExecError , Program ,
2023-08-24 15:34:51 -07:00
} ;
2024-12-16 13:10:31 -05:00
// Re-exports.
pub use cad_op ::Operation ;
2024-09-16 15:10:33 -04:00
/// State for executing a program.
#[ derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ExecState {
/// Program variable bindings.
pub memory : ProgramMemory ,
2024-10-09 19:38:40 -04:00
/// The stable artifact ID generator.
pub id_generator : IdGenerator ,
2024-09-16 15:10:33 -04:00
/// Dynamic state that follows dynamic flow of the program.
pub dynamic_state : DynamicState ,
/// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value : Option < KclValue > ,
2024-10-17 00:48:33 -04:00
/// Identifiers that have been exported from the current module.
2024-12-07 07:16:04 +13:00
pub module_exports : Vec < String > ,
2024-10-17 00:48:33 -04:00
/// The stack of import statements for detecting circular module imports.
/// If this is empty, we're not currently executing an import statement.
pub import_stack : Vec < std ::path ::PathBuf > ,
2024-11-07 11:23:41 -05:00
/// Map from source file absolute path to module ID.
pub path_to_source_id : IndexMap < std ::path ::PathBuf , ModuleId > ,
/// Map from module ID to module info.
pub module_infos : IndexMap < ModuleId , ModuleInfo > ,
2024-12-16 13:10:31 -05:00
/// Operations that have been performed in execution order, for display in
/// the Feature Tree.
pub operations : Vec < Operation > ,
2024-09-16 15:10:33 -04:00
}
2024-11-07 11:23:41 -05:00
impl ExecState {
2024-12-07 07:16:04 +13:00
fn add_module ( & mut self , path : std ::path ::PathBuf ) -> ModuleId {
2024-11-07 11:23:41 -05:00
// Need to avoid borrowing self in the closure.
let new_module_id = ModuleId ::from_usize ( self . path_to_source_id . len ( ) ) ;
let mut is_new = false ;
let id = * self . path_to_source_id . entry ( path . clone ( ) ) . or_insert_with ( | | {
is_new = true ;
new_module_id
} ) ;
if is_new {
let module_info = ModuleInfo { id , path } ;
self . module_infos . insert ( id , module_info ) ;
}
id
}
}
2023-08-25 13:41:04 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ProgramMemory {
2024-07-22 19:43:40 -04:00
pub environments : Vec < Environment > ,
pub current_env : EnvironmentRef ,
2023-08-24 15:34:51 -07:00
#[ serde(rename = " return " ) ]
2024-08-12 17:55:05 -05:00
pub return_ : Option < KclValue > ,
2023-08-24 15:34:51 -07:00
}
impl ProgramMemory {
pub fn new ( ) -> Self {
Self {
2024-07-22 19:43:40 -04:00
environments : vec ! [ Environment ::root ( ) ] ,
current_env : EnvironmentRef ::root ( ) ,
return_ : None ,
}
}
pub fn new_env_for_call ( & mut self , parent : EnvironmentRef ) -> EnvironmentRef {
let new_env_ref = EnvironmentRef ( self . environments . len ( ) ) ;
let new_env = Environment ::new ( parent ) ;
self . environments . push ( new_env ) ;
new_env_ref
}
/// Add to the program memory in the current scope.
2024-08-12 16:53:24 -05:00
pub fn add ( & mut self , key : & str , value : KclValue , source_range : SourceRange ) -> Result < ( ) , KclError > {
2024-07-22 19:43:40 -04:00
if self . environments [ self . current_env . index ( ) ] . contains_key ( key ) {
return Err ( KclError ::ValueAlreadyDefined ( KclErrorDetails {
message : format ! ( " Cannot redefine `{}` " , key ) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ;
}
self . environments [ self . current_env . index ( ) ] . insert ( key . to_string ( ) , value ) ;
Ok ( ( ) )
}
2024-12-13 16:39:40 -05:00
pub fn update_tag ( & mut self , tag : & str , value : TagIdentifier ) -> Result < ( ) , KclError > {
2024-12-09 20:47:34 -05:00
self . environments [ self . current_env . index ( ) ] . insert ( tag . to_string ( ) , KclValue ::TagIdentifier ( Box ::new ( value ) ) ) ;
2024-12-13 16:39:40 -05:00
Ok ( ( ) )
2024-07-27 22:56:46 -07:00
}
2024-07-22 19:43:40 -04:00
/// Get a value from the program memory.
/// Return Err if not found.
2024-08-12 16:53:24 -05:00
pub fn get ( & self , var : & str , source_range : SourceRange ) -> Result < & KclValue , KclError > {
2024-07-22 19:43:40 -04:00
let mut env_ref = self . current_env ;
loop {
let env = & self . environments [ env_ref . index ( ) ] ;
if let Some ( item ) = env . bindings . get ( var ) {
return Ok ( item ) ;
}
if let Some ( parent ) = env . parent {
env_ref = parent ;
} else {
break ;
}
}
Err ( KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " memory item key `{}` is not defined " , var ) ,
source_ranges : vec ! [ source_range ] ,
} ) )
}
2024-09-27 15:44:44 -07:00
/// Find all solids in the memory that are on a specific sketch id.
2024-07-22 19:43:40 -04:00
/// This does not look inside closures. But as long as we do not allow
/// mutation of variables in KCL, closure memory should be a subset of this.
2024-11-20 15:19:25 +13:00
#[ allow(clippy::vec_box) ]
2024-09-27 15:44:44 -07:00
pub fn find_solids_on_sketch ( & self , sketch_id : uuid ::Uuid ) -> Vec < Box < Solid > > {
2024-07-22 19:43:40 -04:00
self . environments
. iter ( )
. flat_map ( | env | {
env . bindings
. values ( )
. filter_map ( | item | match item {
2024-09-27 15:44:44 -07:00
KclValue ::Solid ( eg ) if eg . sketch . id = = sketch_id = > Some ( eg . clone ( ) ) ,
2024-07-22 19:43:40 -04:00
_ = > None ,
} )
. collect ::< Vec < _ > > ( )
} )
. collect ( )
}
}
impl Default for ProgramMemory {
fn default ( ) -> Self {
Self ::new ( )
}
}
/// An index pointing to an environment.
#[ derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2024-10-30 16:52:17 -04:00
#[ schemars(transparent) ]
2024-07-22 19:43:40 -04:00
pub struct EnvironmentRef ( usize ) ;
impl EnvironmentRef {
pub fn root ( ) -> Self {
Self ( 0 )
}
pub fn index ( & self ) -> usize {
self . 0
}
}
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
pub struct Environment {
2024-12-05 12:09:35 -05:00
bindings : IndexMap < String , KclValue > ,
2024-07-22 19:43:40 -04:00
parent : Option < EnvironmentRef > ,
}
2024-11-26 12:27:09 -06:00
const NO_META : Vec < Metadata > = Vec ::new ( ) ;
2024-07-22 19:43:40 -04:00
impl Environment {
pub fn root ( ) -> Self {
Self {
// Prelude
2024-12-05 12:09:35 -05:00
bindings : IndexMap ::from ( [
2024-11-26 12:27:09 -06:00
( " ZERO " . to_string ( ) , KclValue ::from_number ( 0.0 , NO_META ) ) ,
( " QUARTER_TURN " . to_string ( ) , KclValue ::from_number ( 90.0 , NO_META ) ) ,
( " HALF_TURN " . to_string ( ) , KclValue ::from_number ( 180.0 , NO_META ) ) ,
( " THREE_QUARTER_TURN " . to_string ( ) , KclValue ::from_number ( 270.0 , NO_META ) ) ,
2024-02-11 18:26:09 -08:00
] ) ,
2024-07-22 19:43:40 -04:00
parent : None ,
2023-08-24 15:34:51 -07:00
}
}
2024-07-22 19:43:40 -04:00
pub fn new ( parent : EnvironmentRef ) -> Self {
Self {
2024-12-05 12:09:35 -05:00
bindings : IndexMap ::new ( ) ,
2024-07-22 19:43:40 -04:00
parent : Some ( parent ) ,
2023-08-24 15:34:51 -07:00
}
}
2024-08-12 16:53:24 -05:00
pub fn get ( & self , key : & str , source_range : SourceRange ) -> Result < & KclValue , KclError > {
2024-07-22 19:43:40 -04:00
self . bindings . get ( key ) . ok_or_else ( | | {
2023-08-24 15:34:51 -07:00
KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " memory item key `{}` is not defined " , key ) ,
source_ranges : vec ! [ source_range ] ,
} )
} )
}
2024-06-23 19:19:24 -07:00
2024-08-12 16:53:24 -05:00
pub fn insert ( & mut self , key : String , value : KclValue ) {
2024-07-22 19:43:40 -04:00
self . bindings . insert ( key , value ) ;
2024-06-23 19:19:24 -07:00
}
2023-08-24 15:34:51 -07:00
2024-07-22 19:43:40 -04:00
pub fn contains_key ( & self , key : & str ) -> bool {
self . bindings . contains_key ( key )
2023-08-24 15:34:51 -07:00
}
2024-07-29 21:30:25 -07:00
2024-09-27 15:44:44 -07:00
pub fn update_sketch_tags ( & mut self , sg : & Sketch ) {
2024-07-29 21:30:25 -07:00
if sg . tags . is_empty ( ) {
return ;
}
for ( _ , val ) in self . bindings . iter_mut ( ) {
2024-11-14 17:27:19 -06:00
let KclValue ::Sketch { value } = val else { continue } ;
let mut sketch = value . to_owned ( ) ;
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
2024-09-27 15:44:44 -07:00
if sketch . original_id = = sg . original_id {
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
for tag in sg . tags . iter ( ) {
2024-09-27 15:44:44 -07:00
sketch . tags . insert ( tag . 0. clone ( ) , tag . 1. clone ( ) ) ;
2024-07-29 21:30:25 -07:00
}
}
2024-11-14 17:27:19 -06:00
* val = KclValue ::Sketch { value : sketch } ;
2024-07-29 21:30:25 -07:00
}
}
2023-08-24 15:34:51 -07:00
}
2024-07-29 23:22:52 -04:00
/// Dynamic state that depends on the dynamic flow of the program, like the call
/// stack. If the language had exceptions, for example, you could store the
/// stack of exception handlers here.
2024-09-16 15:10:33 -04:00
#[ derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema) ]
2024-07-29 23:22:52 -04:00
pub struct DynamicState {
2024-09-27 15:44:44 -07:00
pub solid_ids : Vec < SolidLazyIds > ,
2024-07-29 23:22:52 -04:00
}
impl DynamicState {
pub fn new ( ) -> Self {
Self ::default ( )
}
#[ must_use ]
pub fn merge ( & self , memory : & ProgramMemory ) -> Self {
let mut merged = self . clone ( ) ;
merged . append ( memory ) ;
merged
}
pub fn append ( & mut self , memory : & ProgramMemory ) {
for env in & memory . environments {
for item in env . bindings . values ( ) {
2024-09-27 15:44:44 -07:00
if let KclValue ::Solid ( eg ) = item {
self . solid_ids . push ( SolidLazyIds ::from ( eg . as_ref ( ) ) ) ;
2024-07-29 23:22:52 -04:00
}
}
}
}
2024-09-27 15:44:44 -07:00
pub fn edge_cut_ids_on_sketch ( & self , sketch_id : uuid ::Uuid ) -> Vec < uuid ::Uuid > {
self . solid_ids
2024-07-29 23:22:52 -04:00
. iter ( )
. flat_map ( | eg | {
2024-09-27 15:44:44 -07:00
if eg . sketch_id = = sketch_id {
2024-08-12 17:56:45 -05:00
eg . edge_cuts . clone ( )
2024-07-29 23:22:52 -04:00
} else {
Vec ::new ( )
}
} )
. collect ::< Vec < _ > > ( )
}
}
2024-10-09 19:38:40 -04:00
/// A generator for ArtifactIds that can be stable across executions.
#[ derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct IdGenerator {
next_id : usize ,
ids : Vec < uuid ::Uuid > ,
}
impl IdGenerator {
pub fn new ( ) -> Self {
Self ::default ( )
}
pub fn next_uuid ( & mut self ) -> uuid ::Uuid {
if let Some ( id ) = self . ids . get ( self . next_id ) {
self . next_id + = 1 ;
* id
} else {
let id = uuid ::Uuid ::new_v4 ( ) ;
self . ids . push ( id ) ;
self . next_id + = 1 ;
id
}
}
}
2024-02-11 15:08:54 -08:00
/// A geometry.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " ) ]
pub enum Geometry {
2024-09-27 15:44:44 -07:00
Sketch ( Box < Sketch > ) ,
Solid ( Box < Solid > ) ,
2024-02-11 15:08:54 -08:00
}
impl Geometry {
pub fn id ( & self ) -> uuid ::Uuid {
match self {
2024-09-27 15:44:44 -07:00
Geometry ::Sketch ( s ) = > s . id ,
Geometry ::Solid ( e ) = > e . id ,
2024-02-11 15:08:54 -08:00
}
}
}
/// A set of geometry.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " ) ]
2024-11-20 15:19:25 +13:00
#[ allow(clippy::vec_box) ]
2024-02-11 15:08:54 -08:00
pub enum Geometries {
2024-09-27 15:44:44 -07:00
Sketches ( Vec < Box < Sketch > > ) ,
Solids ( Vec < Box < Solid > > ) ,
2024-02-11 15:08:54 -08:00
}
2024-10-15 13:25:03 -07:00
impl From < Geometry > for Geometries {
fn from ( value : Geometry ) -> Self {
match value {
Geometry ::Sketch ( x ) = > Self ::Sketches ( vec! [ x ] ) ,
Geometry ::Solid ( x ) = > Self ::Solids ( vec! [ x ] ) ,
}
}
}
2024-09-27 15:44:44 -07:00
/// A sketch or a group of sketches.
2024-02-11 15:08:54 -08:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
2024-11-20 15:19:25 +13:00
#[ allow(clippy::vec_box) ]
2024-09-27 15:44:44 -07:00
pub enum SketchSet {
Sketch ( Box < Sketch > ) ,
Sketches ( Vec < Box < Sketch > > ) ,
2024-02-11 15:08:54 -08:00
}
2024-09-27 15:44:44 -07:00
impl SketchSet {
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
pub fn meta ( & self ) -> Vec < Metadata > {
match self {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketch ( sg ) = > sg . meta . clone ( ) ,
SketchSet ::Sketches ( sg ) = > sg . iter ( ) . flat_map ( | sg | sg . meta . clone ( ) ) . collect ( ) ,
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < SketchSet > for Vec < Sketch > {
fn from ( value : SketchSet ) -> Self {
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
match value {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketch ( sg ) = > vec! [ * sg ] ,
SketchSet ::Sketches ( sgs ) = > sgs . into_iter ( ) . map ( | sg | * sg ) . collect ( ) ,
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < Sketch > for SketchSet {
fn from ( sg : Sketch ) -> Self {
SketchSet ::Sketch ( Box ::new ( sg ) )
2024-06-23 23:04:32 -07:00
}
}
2024-09-27 15:44:44 -07:00
impl From < Box < Sketch > > for SketchSet {
fn from ( sg : Box < Sketch > ) -> Self {
SketchSet ::Sketch ( sg )
2024-06-23 23:04:32 -07:00
}
}
2024-09-27 15:44:44 -07:00
impl From < Vec < Sketch > > for SketchSet {
fn from ( sg : Vec < Sketch > ) -> Self {
2024-06-23 23:04:32 -07:00
if sg . len ( ) = = 1 {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketch ( Box ::new ( sg [ 0 ] . clone ( ) ) )
2024-06-23 23:04:32 -07:00
} else {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketches ( sg . into_iter ( ) . map ( Box ::new ) . collect ( ) )
2024-06-23 23:04:32 -07:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < Vec < Box < Sketch > > > for SketchSet {
fn from ( sg : Vec < Box < Sketch > > ) -> Self {
2024-06-23 23:04:32 -07:00
if sg . len ( ) = = 1 {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketch ( sg [ 0 ] . clone ( ) )
2024-06-23 23:04:32 -07:00
} else {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketches ( sg )
2024-06-23 23:04:32 -07:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < SketchSet > for Vec < Box < Sketch > > {
fn from ( sg : SketchSet ) -> Self {
2024-06-23 23:04:32 -07:00
match sg {
2024-09-27 15:44:44 -07:00
SketchSet ::Sketch ( sg ) = > vec! [ sg ] ,
SketchSet ::Sketches ( sgs ) = > sgs ,
2024-06-23 23:04:32 -07:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < & Sketch > for Vec < Box < Sketch > > {
fn from ( sg : & Sketch ) -> Self {
2024-06-23 23:04:32 -07:00
vec! [ Box ::new ( sg . clone ( ) ) ]
}
}
2024-09-27 15:44:44 -07:00
impl From < Box < Sketch > > for Vec < Box < Sketch > > {
fn from ( sg : Box < Sketch > ) -> Self {
2024-06-23 23:04:32 -07:00
vec! [ sg ]
}
}
2024-09-27 15:44:44 -07:00
/// A solid or a group of solids.
2024-04-23 10:31:20 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
2024-11-20 15:19:25 +13:00
#[ allow(clippy::vec_box) ]
2024-09-27 15:44:44 -07:00
pub enum SolidSet {
Solid ( Box < Solid > ) ,
Solids ( Vec < Box < Solid > > ) ,
2024-04-23 10:31:20 -07:00
}
2024-09-27 15:44:44 -07:00
impl From < Solid > for SolidSet {
fn from ( eg : Solid ) -> Self {
SolidSet ::Solid ( Box ::new ( eg ) )
2024-06-23 23:04:32 -07:00
}
}
2024-09-27 15:44:44 -07:00
impl From < Box < Solid > > for SolidSet {
fn from ( eg : Box < Solid > ) -> Self {
SolidSet ::Solid ( eg )
2024-06-23 23:04:32 -07:00
}
}
2024-09-27 15:44:44 -07:00
impl From < Vec < Solid > > for SolidSet {
fn from ( eg : Vec < Solid > ) -> Self {
2024-06-23 23:04:32 -07:00
if eg . len ( ) = = 1 {
2024-09-27 15:44:44 -07:00
SolidSet ::Solid ( Box ::new ( eg [ 0 ] . clone ( ) ) )
2024-06-23 23:04:32 -07:00
} else {
2024-09-27 15:44:44 -07:00
SolidSet ::Solids ( eg . into_iter ( ) . map ( Box ::new ) . collect ( ) )
2024-06-23 23:04:32 -07:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < Vec < Box < Solid > > > for SolidSet {
fn from ( eg : Vec < Box < Solid > > ) -> Self {
2024-06-23 23:04:32 -07:00
if eg . len ( ) = = 1 {
2024-09-27 15:44:44 -07:00
SolidSet ::Solid ( eg [ 0 ] . clone ( ) )
2024-06-23 23:04:32 -07:00
} else {
2024-09-27 15:44:44 -07:00
SolidSet ::Solids ( eg )
2024-06-23 23:04:32 -07:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < SolidSet > for Vec < Box < Solid > > {
fn from ( eg : SolidSet ) -> Self {
2024-06-23 23:04:32 -07:00
match eg {
2024-09-27 15:44:44 -07:00
SolidSet ::Solid ( eg ) = > vec! [ eg ] ,
SolidSet ::Solids ( egs ) = > egs ,
2024-06-23 23:04:32 -07:00
}
}
}
2024-09-27 15:44:44 -07:00
impl From < & Solid > for Vec < Box < Solid > > {
fn from ( eg : & Solid ) -> Self {
2024-06-23 23:04:32 -07:00
vec! [ Box ::new ( eg . clone ( ) ) ]
}
}
2024-09-27 15:44:44 -07:00
impl From < Box < Solid > > for Vec < Box < Solid > > {
fn from ( eg : Box < Solid > ) -> Self {
2024-06-23 23:04:32 -07:00
vec! [ eg ]
}
}
2024-02-12 12:18:37 -08:00
/// Data for an imported geometry.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ImportedGeometry {
/// The ID of the imported geometry.
pub id : uuid ::Uuid ,
/// The original file paths.
pub value : Vec < String > ,
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
2023-10-05 14:27:48 -07:00
/// A plane.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Plane {
/// The id of the plane.
pub id : uuid ::Uuid ,
// The code for the plane either a string or custom.
pub value : PlaneType ,
/// Origin of the plane.
pub origin : Point3d ,
/// What should the plane’ s X axis be?
pub x_axis : Point3d ,
/// What should the plane’ s Y axis be?
pub y_axis : Point3d ,
/// The z-axis (normal).
pub z_axis : Point3d ,
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
2024-10-09 19:38:40 -04:00
impl Plane {
pub ( crate ) fn from_plane_data ( value : crate ::std ::sketch ::PlaneData , exec_state : & mut ExecState ) -> Self {
let id = exec_state . id_generator . next_uuid ( ) ;
match value {
crate ::std ::sketch ::PlaneData ::XY = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
z_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
value : PlaneType ::XY ,
meta : vec ! [ ] ,
} ,
crate ::std ::sketch ::PlaneData ::NegXY = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
z_axis : Point3d ::new ( 0.0 , 0.0 , - 1.0 ) ,
value : PlaneType ::XY ,
meta : vec ! [ ] ,
} ,
crate ::std ::sketch ::PlaneData ::XZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( 0.0 , - 1.0 , 0.0 ) ,
value : PlaneType ::XZ ,
meta : vec ! [ ] ,
} ,
crate ::std ::sketch ::PlaneData ::NegXZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( - 1.0 , 0.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
value : PlaneType ::XZ ,
meta : vec ! [ ] ,
} ,
crate ::std ::sketch ::PlaneData ::YZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( 1.0 , 0.0 , 0.0 ) ,
value : PlaneType ::YZ ,
meta : vec ! [ ] ,
} ,
crate ::std ::sketch ::PlaneData ::NegYZ = > Plane {
id ,
origin : Point3d ::new ( 0.0 , 0.0 , 0.0 ) ,
x_axis : Point3d ::new ( 0.0 , 1.0 , 0.0 ) ,
y_axis : Point3d ::new ( 0.0 , 0.0 , 1.0 ) ,
z_axis : Point3d ::new ( - 1.0 , 0.0 , 0.0 ) ,
value : PlaneType ::YZ ,
meta : vec ! [ ] ,
} ,
crate ::std ::sketch ::PlaneData ::Plane {
origin ,
x_axis ,
y_axis ,
z_axis ,
} = > Plane {
id ,
origin : * origin ,
x_axis : * x_axis ,
y_axis : * y_axis ,
z_axis : * z_axis ,
value : PlaneType ::Custom ,
meta : vec ! [ ] ,
} ,
}
}
2024-11-18 16:25:25 -05:00
/// The standard planes are XY, YZ and XZ (in both positive and negative)
pub fn is_standard ( & self ) -> bool {
! self . is_custom ( )
}
/// The standard planes are XY, YZ and XZ (in both positive and negative)
/// Custom planes are any other plane that the user might specify.
pub fn is_custom ( & self ) -> bool {
matches! ( self . value , PlaneType ::Custom )
}
2024-10-09 19:38:40 -04:00
}
2024-04-15 17:18:32 -07:00
#[ derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DefaultPlanes {
pub xy : uuid ::Uuid ,
pub xz : uuid ::Uuid ,
pub yz : uuid ::Uuid ,
pub neg_xy : uuid ::Uuid ,
pub neg_xz : uuid ::Uuid ,
pub neg_yz : uuid ::Uuid ,
}
2024-02-12 18:08:42 -08:00
/// A face.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Face {
/// The id of the face.
pub id : uuid ::Uuid ,
/// The tag of the face.
pub value : String ,
/// What should the face’ s X axis be?
pub x_axis : Point3d ,
/// What should the face’ s Y axis be?
pub y_axis : Point3d ,
/// The z-axis (normal).
pub z_axis : Point3d ,
2024-09-27 15:44:44 -07:00
/// The solid the face is on.
pub solid : Box < Solid > ,
2024-02-12 18:08:42 -08:00
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
2023-10-05 14:27:48 -07:00
/// Type for a plane.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
#[ display(style = " camelCase " ) ]
pub enum PlaneType {
#[ serde(rename = " XY " , alias = " xy " ) ]
#[ display( " XY " ) ]
XY ,
#[ serde(rename = " XZ " , alias = " xz " ) ]
#[ display( " XZ " ) ]
XZ ,
#[ serde(rename = " YZ " , alias = " yz " ) ]
#[ display( " YZ " ) ]
YZ ,
/// A custom plane.
#[ serde(rename = " Custom " ) ]
#[ display( " Custom " ) ]
Custom ,
}
2024-07-27 22:56:46 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema) ]
2024-06-24 14:45:07 -07:00
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub struct TagIdentifier {
pub value : String ,
2024-07-27 22:56:46 -07:00
pub info : Option < TagEngineInfo > ,
2024-06-24 14:45:07 -07:00
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
2024-07-27 22:56:46 -07:00
impl Eq for TagIdentifier { }
2024-06-24 14:45:07 -07:00
impl std ::fmt ::Display for TagIdentifier {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
write! ( f , " {} " , self . value )
}
}
impl std ::str ::FromStr for TagIdentifier {
type Err = KclError ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Ok ( Self {
value : s . to_string ( ) ,
2024-07-27 22:56:46 -07:00
info : None ,
2024-06-24 14:45:07 -07:00
meta : Default ::default ( ) ,
} )
}
}
impl Ord for TagIdentifier {
fn cmp ( & self , other : & Self ) -> std ::cmp ::Ordering {
self . value . cmp ( & other . value )
}
}
impl PartialOrd for TagIdentifier {
fn partial_cmp ( & self , other : & Self ) -> Option < std ::cmp ::Ordering > {
Some ( self . cmp ( other ) )
}
}
impl std ::hash ::Hash for TagIdentifier {
fn hash < H : std ::hash ::Hasher > ( & self , state : & mut H ) {
self . value . hash ( state ) ;
}
}
2024-07-05 16:53:13 -07:00
pub type MemoryFunction =
fn (
2024-11-21 13:10:03 -05:00
s : Vec < Arg > ,
2024-07-05 16:53:13 -07:00
memory : ProgramMemory ,
2024-12-05 17:56:49 +13:00
expression : crate ::parsing ::ast ::types ::BoxNode < FunctionExpression > ,
2024-07-05 16:53:13 -07:00
metadata : Vec < Metadata > ,
2024-09-16 15:10:33 -04:00
exec_state : & ExecState ,
2024-07-05 16:53:13 -07:00
ctx : ExecutorContext ,
2024-08-12 17:55:05 -05:00
) -> std ::pin ::Pin < Box < dyn std ::future ::Future < Output = Result < Option < KclValue > , KclError > > + Send > > ;
2023-09-20 18:27:08 -07:00
2024-07-27 22:56:46 -07:00
/// Engine information for a tag.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub struct TagEngineInfo {
/// The id of the tagged object.
pub id : uuid ::Uuid ,
2024-09-27 15:44:44 -07:00
/// The sketch the tag is on.
pub sketch : uuid ::Uuid ,
2024-07-27 22:56:46 -07:00
/// The path the tag is on.
2024-10-28 20:20:45 -04:00
pub path : Option < Path > ,
2024-07-27 22:56:46 -07:00
/// The surface information for the tag.
2024-10-28 20:20:45 -04:00
pub surface : Option < ExtrudeSurface > ,
2024-07-27 22:56:46 -07:00
}
2024-09-27 15:44:44 -07:00
/// A sketch is a collection of paths.
2023-08-25 13:41:04 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
2023-09-12 18:10:27 -07:00
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
2024-09-27 15:44:44 -07:00
pub struct Sketch {
2024-10-23 12:42:54 -05:00
/// The id of the sketch (this will change when the engine's reference to it changes).
2023-08-24 15:34:51 -07:00
pub id : uuid ::Uuid ,
2024-09-27 15:44:44 -07:00
/// The paths in the sketch.
2024-10-23 12:42:54 -05:00
pub paths : Vec < Path > ,
2024-02-16 16:42:01 -08:00
/// What the sketch is on (can be a plane or a face).
pub on : SketchSurface ,
2023-08-25 13:41:04 -07:00
/// The starting path.
2023-08-24 15:34:51 -07:00
pub start : BasePath ,
2024-09-27 15:44:44 -07:00
/// Tag identifiers that have been declared in this sketch.
2024-12-05 12:09:35 -05:00
#[ serde(default, skip_serializing_if = " IndexMap::is_empty " ) ]
pub tags : IndexMap < String , TagIdentifier > ,
2024-09-27 15:44:44 -07:00
/// The original id of the sketch. This stays the same even if the sketch is
2024-07-29 21:30:25 -07:00
/// is sketched on face etc.
#[ serde(skip) ]
pub original_id : uuid ::Uuid ,
2023-08-25 13:41:04 -07:00
/// Metadata.
2023-08-24 15:34:51 -07:00
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
2024-09-27 15:44:44 -07:00
/// A sketch type.
2024-02-16 16:42:01 -08:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub enum SketchSurface {
Plane ( Box < Plane > ) ,
Face ( Box < Face > ) ,
}
impl SketchSurface {
2024-07-27 22:56:46 -07:00
pub ( crate ) fn id ( & self ) -> uuid ::Uuid {
2024-02-16 16:42:01 -08:00
match self {
SketchSurface ::Plane ( plane ) = > plane . id ,
SketchSurface ::Face ( face ) = > face . id ,
}
}
2024-07-27 22:56:46 -07:00
pub ( crate ) fn x_axis ( & self ) -> Point3d {
2024-02-16 16:42:01 -08:00
match self {
2024-09-04 23:27:12 -05:00
SketchSurface ::Plane ( plane ) = > plane . x_axis ,
SketchSurface ::Face ( face ) = > face . x_axis ,
2024-02-16 16:42:01 -08:00
}
}
2024-07-27 22:56:46 -07:00
pub ( crate ) fn y_axis ( & self ) -> Point3d {
2024-02-16 16:42:01 -08:00
match self {
2024-09-04 23:27:12 -05:00
SketchSurface ::Plane ( plane ) = > plane . y_axis ,
SketchSurface ::Face ( face ) = > face . y_axis ,
2024-02-16 16:42:01 -08:00
}
}
2024-07-27 22:56:46 -07:00
pub ( crate ) fn z_axis ( & self ) -> Point3d {
2024-02-16 16:42:01 -08:00
match self {
2024-09-04 23:27:12 -05:00
SketchSurface ::Plane ( plane ) = > plane . z_axis ,
SketchSurface ::Face ( face ) = > face . z_axis ,
2024-02-16 16:42:01 -08:00
}
}
}
2024-11-25 13:07:03 -05:00
#[ derive(Debug, Clone) ]
pub ( crate ) enum GetTangentialInfoFromPathsResult {
PreviousPoint ( [ f64 ; 2 ] ) ,
Arc { center : [ f64 ; 2 ] , ccw : bool } ,
Circle { center : [ f64 ; 2 ] , ccw : bool , radius : f64 } ,
}
impl GetTangentialInfoFromPathsResult {
pub ( crate ) fn tan_previous_point ( & self , last_arc_end : crate ::std ::utils ::Coords2d ) -> [ f64 ; 2 ] {
match self {
GetTangentialInfoFromPathsResult ::PreviousPoint ( p ) = > * p ,
GetTangentialInfoFromPathsResult ::Arc { center , ccw , .. } = > {
crate ::std ::utils ::get_tangent_point_from_previous_arc ( * center , * ccw , last_arc_end )
}
// The circle always starts at 0 degrees, so a suitable tangent
// point is either directly above or below.
GetTangentialInfoFromPathsResult ::Circle {
center , radius , ccw , ..
} = > [ center [ 0 ] + radius , center [ 1 ] + if * ccw { - 1.0 } else { 1.0 } ] ,
}
}
2024-02-11 12:59:00 +11:00
}
2024-09-27 15:44:44 -07:00
impl Sketch {
2024-10-30 16:52:17 -04:00
pub ( crate ) fn add_tag ( & mut self , tag : NodeRef < '_ , TagDeclarator > , current_path : & Path ) {
2024-12-13 16:39:40 -05:00
let mut tag_identifier : TagIdentifier = tag . into ( ) ;
2024-07-27 22:56:46 -07:00
let base = current_path . get_base ( ) ;
tag_identifier . info = Some ( TagEngineInfo {
id : base . geo_meta . id ,
2024-09-27 15:44:44 -07:00
sketch : self . id ,
2024-10-28 20:20:45 -04:00
path : Some ( current_path . clone ( ) ) ,
surface : None ,
2024-07-27 22:56:46 -07:00
} ) ;
2024-06-24 14:45:07 -07:00
2024-07-27 22:56:46 -07:00
self . tags . insert ( tag . name . to_string ( ) , tag_identifier ) ;
2023-08-24 15:34:51 -07:00
}
2024-05-30 17:48:59 -05:00
/// Get the path most recently sketched.
2024-07-27 22:56:46 -07:00
pub ( crate ) fn latest_path ( & self ) -> Option < & Path > {
2024-10-23 12:42:54 -05:00
self . paths . last ( )
2024-05-30 17:48:59 -05:00
}
/// The "pen" is an imaginary pen drawing the path.
/// This gets the current point the pen is hovering over, i.e. the point
/// where the last path segment ends, and the next path segment will begin.
2024-07-27 22:56:46 -07:00
pub ( crate ) fn current_pen_position ( & self ) -> Result < Point2d , KclError > {
2024-05-30 17:48:59 -05:00
let Some ( path ) = self . latest_path ( ) else {
2023-08-24 15:34:51 -07:00
return Ok ( self . start . to . into ( ) ) ;
2024-05-30 17:48:59 -05:00
} ;
2023-08-24 15:34:51 -07:00
2024-05-30 17:48:59 -05:00
let base = path . get_base ( ) ;
Ok ( base . to . into ( ) )
2023-08-24 15:34:51 -07:00
}
2024-02-11 12:59:00 +11:00
2024-07-27 22:56:46 -07:00
pub ( crate ) fn get_tangential_info_from_paths ( & self ) -> GetTangentialInfoFromPathsResult {
2024-05-30 17:48:59 -05:00
let Some ( path ) = self . latest_path ( ) else {
2024-11-25 13:07:03 -05:00
return GetTangentialInfoFromPathsResult ::PreviousPoint ( self . start . to ) ;
2024-05-30 17:48:59 -05:00
} ;
2024-11-25 13:07:03 -05:00
path . get_tangential_info ( )
2024-02-11 12:59:00 +11:00
}
2023-08-24 15:34:51 -07:00
}
2024-09-27 15:44:44 -07:00
/// An solid is a collection of extrude surfaces.
2023-08-25 13:41:04 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
2023-09-12 18:10:27 -07:00
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
2024-09-27 15:44:44 -07:00
pub struct Solid {
/// The id of the solid.
2023-08-24 15:34:51 -07:00
pub id : uuid ::Uuid ,
2023-08-25 13:41:04 -07:00
/// The extrude surfaces.
2023-08-24 15:34:51 -07:00
pub value : Vec < ExtrudeSurface > ,
2024-09-27 15:44:44 -07:00
/// The sketch.
pub sketch : Sketch ,
/// The height of the solid.
2023-08-24 15:34:51 -07:00
pub height : f64 ,
2024-02-12 18:08:42 -08:00
/// The id of the extrusion start cap
pub start_cap_id : Option < uuid ::Uuid > ,
/// The id of the extrusion end cap
pub end_cap_id : Option < uuid ::Uuid > ,
2024-09-27 15:44:44 -07:00
/// Chamfers or fillets on this solid.
2024-06-23 19:19:24 -07:00
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
2024-08-12 17:56:45 -05:00
pub edge_cuts : Vec < EdgeCut > ,
2023-08-25 13:41:04 -07:00
/// Metadata.
2023-08-24 15:34:51 -07:00
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
2024-09-27 15:44:44 -07:00
impl Solid {
2024-08-12 17:56:45 -05:00
pub ( crate ) fn get_all_edge_cut_ids ( & self ) -> Vec < uuid ::Uuid > {
self . edge_cuts . iter ( ) . map ( | foc | foc . id ( ) ) . collect ( )
2024-06-23 19:19:24 -07:00
}
}
2024-09-27 15:44:44 -07:00
/// An solid ID and its fillet and chamfer IDs. This is needed for lazy
2024-07-29 23:22:52 -04:00
/// fillet evaluation.
#[ derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema) ]
2024-09-27 15:44:44 -07:00
pub struct SolidLazyIds {
pub solid_id : uuid ::Uuid ,
pub sketch_id : uuid ::Uuid ,
/// Chamfers or fillets on this solid.
2024-07-29 23:22:52 -04:00
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
2024-08-12 17:56:45 -05:00
pub edge_cuts : Vec < uuid ::Uuid > ,
2024-07-29 23:22:52 -04:00
}
2024-09-27 15:44:44 -07:00
impl From < & Solid > for SolidLazyIds {
fn from ( eg : & Solid ) -> Self {
2024-07-29 23:22:52 -04:00
Self {
2024-09-27 15:44:44 -07:00
solid_id : eg . id ,
sketch_id : eg . sketch . id ,
2024-08-12 17:56:45 -05:00
edge_cuts : eg . edge_cuts . iter ( ) . map ( | foc | foc . id ( ) ) . collect ( ) ,
2024-07-29 23:22:52 -04:00
}
}
}
2024-06-23 19:19:24 -07:00
/// A fillet or a chamfer.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
2024-08-12 17:56:45 -05:00
pub enum EdgeCut {
2024-06-23 19:19:24 -07:00
/// A fillet.
Fillet {
/// The id of the engine command that called this fillet.
id : uuid ::Uuid ,
radius : f64 ,
/// The engine id of the edge to fillet.
2024-07-29 13:18:55 -07:00
#[ serde(rename = " edgeId " ) ]
2024-06-23 19:19:24 -07:00
edge_id : uuid ::Uuid ,
2024-10-30 16:52:17 -04:00
tag : Box < Option < TagNode > > ,
2024-06-23 19:19:24 -07:00
} ,
/// A chamfer.
Chamfer {
/// The id of the engine command that called this chamfer.
id : uuid ::Uuid ,
length : f64 ,
/// The engine id of the edge to chamfer.
2024-07-29 13:18:55 -07:00
#[ serde(rename = " edgeId " ) ]
2024-06-23 19:19:24 -07:00
edge_id : uuid ::Uuid ,
2024-10-30 16:52:17 -04:00
tag : Box < Option < TagNode > > ,
2024-06-23 19:19:24 -07:00
} ,
}
2024-08-12 17:56:45 -05:00
impl EdgeCut {
2024-06-23 19:19:24 -07:00
pub fn id ( & self ) -> uuid ::Uuid {
match self {
2024-08-12 17:56:45 -05:00
EdgeCut ::Fillet { id , .. } = > * id ,
EdgeCut ::Chamfer { id , .. } = > * id ,
2024-06-23 19:19:24 -07:00
}
}
pub fn edge_id ( & self ) -> uuid ::Uuid {
match self {
2024-08-12 17:56:45 -05:00
EdgeCut ::Fillet { edge_id , .. } = > * edge_id ,
EdgeCut ::Chamfer { edge_id , .. } = > * edge_id ,
2024-06-23 19:19:24 -07:00
}
}
2024-06-23 23:04:32 -07:00
2024-10-30 16:52:17 -04:00
pub fn tag ( & self ) -> Option < TagNode > {
2024-06-23 23:04:32 -07:00
match self {
2024-08-12 17:56:45 -05:00
EdgeCut ::Fillet { tag , .. } = > * tag . clone ( ) ,
EdgeCut ::Chamfer { tag , .. } = > * tag . clone ( ) ,
2024-06-23 23:04:32 -07:00
}
}
2023-08-24 15:34:51 -07:00
}
2023-08-25 13:41:04 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum BodyType {
Root ,
Sketch ,
Block ,
}
2024-11-07 11:23:41 -05:00
/// Info about a module. Right now, this is pretty minimal. We hope to cache
/// modules here in the future.
#[ derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
pub struct ModuleInfo {
/// The ID of the module.
id : ModuleId ,
/// Absolute path of the module's source file.
path : std ::path ::PathBuf ,
}
2023-09-14 15:51:26 -06:00
#[ derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
pub struct Point2d {
pub x : f64 ,
pub y : f64 ,
}
impl From < [ f64 ; 2 ] > for Point2d {
fn from ( p : [ f64 ; 2 ] ) -> Self {
Self { x : p [ 0 ] , y : p [ 1 ] }
}
}
2023-08-31 22:19:23 -07:00
impl From < & [ f64 ; 2 ] > for Point2d {
fn from ( p : & [ f64 ; 2 ] ) -> Self {
Self { x : p [ 0 ] , y : p [ 1 ] }
}
}
2023-08-24 15:34:51 -07:00
impl From < Point2d > for [ f64 ; 2 ] {
fn from ( p : Point2d ) -> Self {
[ p . x , p . y ]
}
}
2024-09-18 17:04:04 -05:00
impl From < Point2d > for Point2D {
2023-08-31 22:19:23 -07:00
fn from ( p : Point2d ) -> Self {
Self { x : p . x , y : p . y }
}
}
2023-09-14 15:51:26 -06:00
impl Point2d {
pub const ZERO : Self = Self { x : 0.0 , y : 0.0 } ;
pub fn scale ( self , scalar : f64 ) -> Self {
Self {
x : self . x * scalar ,
y : self . y * scalar ,
}
}
}
2024-09-04 23:27:12 -05:00
#[ derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
pub struct Point3d {
pub x : f64 ,
pub y : f64 ,
pub z : f64 ,
}
2023-10-05 14:27:48 -07:00
impl Point3d {
2024-09-04 23:27:12 -05:00
pub const ZERO : Self = Self { x : 0.0 , y : 0.0 , z : 0.0 } ;
2023-10-05 14:27:48 -07:00
pub fn new ( x : f64 , y : f64 , z : f64 ) -> Self {
Self { x , y , z }
}
}
2024-09-18 17:04:04 -05:00
impl From < Point3d > for Point3D {
2023-10-05 14:27:48 -07:00
fn from ( p : Point3d ) -> Self {
Self { x : p . x , y : p . y , z : p . z }
}
}
2024-09-18 17:04:04 -05:00
impl From < Point3d > for kittycad_modeling_cmds ::shared ::Point3d < LengthUnit > {
fn from ( p : Point3d ) -> Self {
Self {
x : LengthUnit ( p . x ) ,
y : LengthUnit ( p . y ) ,
z : LengthUnit ( p . z ) ,
}
}
}
2023-10-05 14:27:48 -07:00
2023-08-25 13:41:04 -07:00
/// Metadata.
2024-11-14 17:27:19 -06:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq, Copy) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Metadata {
2023-08-25 13:41:04 -07:00
/// The source range.
2023-08-24 15:34:51 -07:00
pub source_range : SourceRange ,
}
2024-11-14 17:27:19 -06:00
impl From < Metadata > for Vec < SourceRange > {
fn from ( meta : Metadata ) -> Self {
vec! [ meta . source_range ]
}
}
2023-08-24 15:34:51 -07:00
impl From < SourceRange > for Metadata {
fn from ( source_range : SourceRange ) -> Self {
Self { source_range }
}
}
2024-10-30 16:52:17 -04:00
impl < T > From < NodeRef < '_ , T > > for Metadata {
fn from ( node : NodeRef < '_ , T > ) -> Self {
2024-10-17 00:48:33 -04:00
Self {
2024-11-07 11:23:41 -05:00
source_range : SourceRange ::new ( node . start , node . end , node . module_id ) ,
2024-08-14 01:57:03 -04:00
}
}
}
2024-09-16 15:10:33 -04:00
impl From < & Expr > for Metadata {
fn from ( expr : & Expr ) -> Self {
Self {
source_range : SourceRange ::from ( expr ) ,
}
}
}
2023-08-25 13:41:04 -07:00
/// A base path.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct BasePath {
2023-08-25 13:41:04 -07:00
/// The from point.
2023-09-12 18:10:27 -07:00
#[ ts(type = " [number, number] " ) ]
2023-08-24 15:34:51 -07:00
pub from : [ f64 ; 2 ] ,
2023-08-25 13:41:04 -07:00
/// The to point.
2023-09-12 18:10:27 -07:00
#[ ts(type = " [number, number] " ) ]
2023-08-24 15:34:51 -07:00
pub to : [ f64 ; 2 ] ,
2024-06-24 14:45:07 -07:00
/// The tag of the path.
2024-10-30 16:52:17 -04:00
pub tag : Option < TagNode > ,
2023-08-25 13:41:04 -07:00
/// Metadata.
2023-08-24 15:34:51 -07:00
#[ serde(rename = " __geoMeta " ) ]
pub geo_meta : GeoMeta ,
}
2023-08-25 13:41:04 -07:00
/// Geometry metadata.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct GeoMeta {
2023-08-25 13:41:04 -07:00
/// The id of the geometry.
2023-08-24 15:34:51 -07:00
pub id : uuid ::Uuid ,
2023-08-25 13:41:04 -07:00
/// Metadata.
2023-08-24 15:34:51 -07:00
#[ serde(flatten) ]
pub metadata : Metadata ,
}
2023-08-25 13:41:04 -07:00
/// A path.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
2024-02-11 12:59:00 +11:00
#[ serde(tag = " type " ) ]
2023-08-24 15:34:51 -07:00
pub enum Path {
2023-08-25 13:41:04 -07:00
/// A path that goes to a point.
2023-08-24 15:34:51 -07:00
ToPoint {
#[ serde(flatten) ]
base : BasePath ,
} ,
2024-02-11 12:59:00 +11:00
/// A arc that is tangential to the last path segment that goes to a point
TangentialArcTo {
#[ serde(flatten) ]
base : BasePath ,
/// the arc's center
#[ ts(type = " [number, number] " ) ]
center : [ f64 ; 2 ] ,
/// arc's direction
ccw : bool ,
} ,
2024-02-22 19:07:17 -08:00
/// A arc that is tangential to the last path segment
TangentialArc {
#[ serde(flatten) ]
base : BasePath ,
2024-08-07 18:35:41 -05:00
/// the arc's center
#[ ts(type = " [number, number] " ) ]
center : [ f64 ; 2 ] ,
/// arc's direction
ccw : bool ,
2024-02-22 19:07:17 -08:00
} ,
2024-09-23 22:42:51 +10:00
// TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940
/// a complete arc
Circle {
#[ serde(flatten) ]
base : BasePath ,
/// the arc's center
#[ ts(type = " [number, number] " ) ]
center : [ f64 ; 2 ] ,
/// the arc's radius
radius : f64 ,
/// arc's direction
2024-11-25 13:07:03 -05:00
/// This is used to compute the tangential angle.
2024-09-23 22:42:51 +10:00
ccw : bool ,
} ,
2023-08-25 13:41:04 -07:00
/// A path that is horizontal.
2023-08-24 15:34:51 -07:00
Horizontal {
#[ serde(flatten) ]
base : BasePath ,
2023-08-25 13:41:04 -07:00
/// The x coordinate.
2023-08-24 15:34:51 -07:00
x : f64 ,
} ,
2023-08-25 13:41:04 -07:00
/// An angled line to.
2023-08-24 15:34:51 -07:00
AngledLineTo {
#[ serde(flatten) ]
base : BasePath ,
2023-08-25 13:41:04 -07:00
/// The x coordinate.
2023-08-24 15:34:51 -07:00
x : Option < f64 > ,
2023-08-25 13:41:04 -07:00
/// The y coordinate.
2023-08-24 15:34:51 -07:00
y : Option < f64 > ,
} ,
2023-08-25 13:41:04 -07:00
/// A base path.
2023-08-24 15:34:51 -07:00
Base {
#[ serde(flatten) ]
base : BasePath ,
} ,
2024-10-25 17:49:30 -05:00
/// A circular arc, not necessarily tangential to the current point.
Arc {
#[ serde(flatten) ]
base : BasePath ,
/// Center of the circle that this arc is drawn on.
center : [ f64 ; 2 ] ,
/// Radius of the circle that this arc is drawn on.
radius : f64 ,
2024-11-25 13:07:03 -05:00
/// True if the arc is counterclockwise.
ccw : bool ,
2024-10-25 17:49:30 -05:00
} ,
2023-08-24 15:34:51 -07:00
}
2024-10-25 09:27:40 -05:00
/// What kind of path is this?
#[ derive(Display) ]
enum PathType {
ToPoint ,
Base ,
TangentialArc ,
TangentialArcTo ,
Circle ,
Horizontal ,
AngledLineTo ,
2024-10-25 17:49:30 -05:00
Arc ,
2024-10-25 09:27:40 -05:00
}
2024-10-25 17:49:30 -05:00
impl From < & Path > for PathType {
fn from ( value : & Path ) -> Self {
2024-10-25 09:27:40 -05:00
match value {
Path ::ToPoint { .. } = > Self ::ToPoint ,
Path ::TangentialArcTo { .. } = > Self ::TangentialArcTo ,
Path ::TangentialArc { .. } = > Self ::TangentialArc ,
Path ::Circle { .. } = > Self ::Circle ,
Path ::Horizontal { .. } = > Self ::Horizontal ,
Path ::AngledLineTo { .. } = > Self ::AngledLineTo ,
Path ::Base { .. } = > Self ::Base ,
2024-10-25 17:49:30 -05:00
Path ::Arc { .. } = > Self ::Arc ,
2024-10-25 09:27:40 -05:00
}
}
}
2023-08-24 15:34:51 -07:00
impl Path {
pub fn get_id ( & self ) -> uuid ::Uuid {
match self {
Path ::ToPoint { base } = > base . geo_meta . id ,
Path ::Horizontal { base , .. } = > base . geo_meta . id ,
Path ::AngledLineTo { base , .. } = > base . geo_meta . id ,
Path ::Base { base } = > base . geo_meta . id ,
2024-02-11 12:59:00 +11:00
Path ::TangentialArcTo { base , .. } = > base . geo_meta . id ,
2024-08-07 18:35:41 -05:00
Path ::TangentialArc { base , .. } = > base . geo_meta . id ,
2024-09-23 22:42:51 +10:00
Path ::Circle { base , .. } = > base . geo_meta . id ,
2024-10-25 17:49:30 -05:00
Path ::Arc { base , .. } = > base . geo_meta . id ,
2023-08-24 15:34:51 -07:00
}
}
2024-10-30 16:52:17 -04:00
pub fn get_tag ( & self ) -> Option < TagNode > {
2023-08-24 15:34:51 -07:00
match self {
2024-06-24 14:45:07 -07:00
Path ::ToPoint { base } = > base . tag . clone ( ) ,
Path ::Horizontal { base , .. } = > base . tag . clone ( ) ,
Path ::AngledLineTo { base , .. } = > base . tag . clone ( ) ,
Path ::Base { base } = > base . tag . clone ( ) ,
Path ::TangentialArcTo { base , .. } = > base . tag . clone ( ) ,
2024-08-07 18:35:41 -05:00
Path ::TangentialArc { base , .. } = > base . tag . clone ( ) ,
2024-09-23 22:42:51 +10:00
Path ::Circle { base , .. } = > base . tag . clone ( ) ,
2024-10-25 17:49:30 -05:00
Path ::Arc { base , .. } = > base . tag . clone ( ) ,
2023-08-24 15:34:51 -07:00
}
}
pub fn get_base ( & self ) -> & BasePath {
match self {
Path ::ToPoint { base } = > base ,
Path ::Horizontal { base , .. } = > base ,
Path ::AngledLineTo { base , .. } = > base ,
Path ::Base { base } = > base ,
2024-02-11 12:59:00 +11:00
Path ::TangentialArcTo { base , .. } = > base ,
2024-08-07 18:35:41 -05:00
Path ::TangentialArc { base , .. } = > base ,
2024-09-23 22:42:51 +10:00
Path ::Circle { base , .. } = > base ,
2024-10-25 17:49:30 -05:00
Path ::Arc { base , .. } = > base ,
2023-08-24 15:34:51 -07:00
}
}
2024-02-12 18:08:42 -08:00
2024-10-25 09:27:40 -05:00
/// Where does this path segment start?
pub fn get_from ( & self ) -> & [ f64 ; 2 ] {
& self . get_base ( ) . from
}
/// Where does this path segment end?
pub fn get_to ( & self ) -> & [ f64 ; 2 ] {
& self . get_base ( ) . to
}
/// Length of this path segment, in cartesian plane.
pub fn length ( & self ) -> f64 {
2024-10-25 17:49:30 -05:00
match self {
Self ::ToPoint { .. } | Self ::Base { .. } | Self ::Horizontal { .. } | Self ::AngledLineTo { .. } = > {
linear_distance ( self . get_from ( ) , self . get_to ( ) )
}
Self ::TangentialArc {
base : _ ,
center ,
ccw : _ ,
}
| Self ::TangentialArcTo {
base : _ ,
center ,
ccw : _ ,
} = > {
// The radius can be calculated as the linear distance between `to` and `center`,
// or between `from` and `center`. They should be the same.
let radius = linear_distance ( self . get_from ( ) , center ) ;
debug_assert_eq! ( radius , linear_distance ( self . get_to ( ) , center ) ) ;
// TODO: Call engine utils to figure this out.
linear_distance ( self . get_from ( ) , self . get_to ( ) )
}
Self ::Circle { radius , .. } = > 2.0 * std ::f64 ::consts ::PI * radius ,
Self ::Arc { .. } = > {
// TODO: Call engine utils to figure this out.
linear_distance ( self . get_from ( ) , self . get_to ( ) )
}
}
2024-10-25 09:27:40 -05:00
}
2024-02-12 18:08:42 -08:00
pub fn get_base_mut ( & mut self ) -> Option < & mut BasePath > {
match self {
Path ::ToPoint { base } = > Some ( base ) ,
Path ::Horizontal { base , .. } = > Some ( base ) ,
Path ::AngledLineTo { base , .. } = > Some ( base ) ,
Path ::Base { base } = > Some ( base ) ,
Path ::TangentialArcTo { base , .. } = > Some ( base ) ,
2024-08-07 18:35:41 -05:00
Path ::TangentialArc { base , .. } = > Some ( base ) ,
2024-09-23 22:42:51 +10:00
Path ::Circle { base , .. } = > Some ( base ) ,
2024-10-25 17:49:30 -05:00
Path ::Arc { base , .. } = > Some ( base ) ,
2024-02-12 18:08:42 -08:00
}
}
2024-11-25 13:07:03 -05:00
pub ( crate ) fn get_tangential_info ( & self ) -> GetTangentialInfoFromPathsResult {
match self {
Path ::TangentialArc { center , ccw , .. }
| Path ::TangentialArcTo { center , ccw , .. }
| Path ::Arc { center , ccw , .. } = > GetTangentialInfoFromPathsResult ::Arc {
center : * center ,
ccw : * ccw ,
} ,
Path ::Circle {
center , ccw , radius , ..
} = > GetTangentialInfoFromPathsResult ::Circle {
center : * center ,
ccw : * ccw ,
radius : * radius ,
} ,
Path ::ToPoint { .. } | Path ::Horizontal { .. } | Path ::AngledLineTo { .. } | Path ::Base { .. } = > {
let base = self . get_base ( ) ;
GetTangentialInfoFromPathsResult ::PreviousPoint ( base . from )
}
}
}
2023-08-24 15:34:51 -07:00
}
2024-10-25 17:49:30 -05:00
/// Compute the straight-line distance between a pair of (2D) points.
#[ rustfmt::skip ]
fn linear_distance (
[ x0 , y0 ] : & [ f64 ; 2 ] ,
[ x1 , y1 ] : & [ f64 ; 2 ]
) -> f64 {
let y_sq = ( y1 - y0 ) . powi ( 2 ) ;
let x_sq = ( x1 - x0 ) . powi ( 2 ) ;
( y_sq + x_sq ) . sqrt ( )
}
2023-08-25 13:41:04 -07:00
/// An extrude surface.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub enum ExtrudeSurface {
2023-08-25 13:41:04 -07:00
/// An extrude plane.
2024-02-12 18:08:42 -08:00
ExtrudePlane ( ExtrudePlane ) ,
2024-02-22 19:07:17 -08:00
ExtrudeArc ( ExtrudeArc ) ,
2024-07-28 00:30:04 -07:00
Chamfer ( ChamferSurface ) ,
Fillet ( FilletSurface ) ,
}
// Chamfer surface.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ChamferSurface {
/// The id for the chamfer surface.
pub face_id : uuid ::Uuid ,
/// The tag.
2024-10-30 16:52:17 -04:00
pub tag : Option < Node < TagDeclarator > > ,
2024-07-28 00:30:04 -07:00
/// Metadata.
#[ serde(flatten) ]
pub geo_meta : GeoMeta ,
}
// Fillet surface.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct FilletSurface {
/// The id for the fillet surface.
pub face_id : uuid ::Uuid ,
/// The tag.
2024-10-30 16:52:17 -04:00
pub tag : Option < Node < TagDeclarator > > ,
2024-07-28 00:30:04 -07:00
/// Metadata.
#[ serde(flatten) ]
pub geo_meta : GeoMeta ,
2024-02-12 18:08:42 -08:00
}
/// An extruded plane.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ExtrudePlane {
/// The face id for the extrude plane.
pub face_id : uuid ::Uuid ,
2024-06-24 14:45:07 -07:00
/// The tag.
2024-10-30 16:52:17 -04:00
pub tag : Option < Node < TagDeclarator > > ,
2024-02-12 18:08:42 -08:00
/// Metadata.
#[ serde(flatten) ]
pub geo_meta : GeoMeta ,
2023-08-24 15:34:51 -07:00
}
2024-02-22 19:07:17 -08:00
/// An extruded arc.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ExtrudeArc {
/// The face id for the extrude plane.
pub face_id : uuid ::Uuid ,
2024-06-24 14:45:07 -07:00
/// The tag.
2024-10-30 16:52:17 -04:00
pub tag : Option < Node < TagDeclarator > > ,
2024-02-22 19:07:17 -08:00
/// Metadata.
#[ serde(flatten) ]
pub geo_meta : GeoMeta ,
}
2023-08-24 15:34:51 -07:00
impl ExtrudeSurface {
pub fn get_id ( & self ) -> uuid ::Uuid {
match self {
2024-02-12 18:08:42 -08:00
ExtrudeSurface ::ExtrudePlane ( ep ) = > ep . geo_meta . id ,
2024-02-22 19:07:17 -08:00
ExtrudeSurface ::ExtrudeArc ( ea ) = > ea . geo_meta . id ,
2024-07-28 00:30:04 -07:00
ExtrudeSurface ::Fillet ( f ) = > f . geo_meta . id ,
ExtrudeSurface ::Chamfer ( c ) = > c . geo_meta . id ,
2023-08-24 15:34:51 -07:00
}
}
2024-10-30 16:52:17 -04:00
pub fn get_tag ( & self ) -> Option < Node < TagDeclarator > > {
2023-08-24 15:34:51 -07:00
match self {
2024-06-24 14:45:07 -07:00
ExtrudeSurface ::ExtrudePlane ( ep ) = > ep . tag . clone ( ) ,
ExtrudeSurface ::ExtrudeArc ( ea ) = > ea . tag . clone ( ) ,
2024-07-28 00:30:04 -07:00
ExtrudeSurface ::Fillet ( f ) = > f . tag . clone ( ) ,
ExtrudeSurface ::Chamfer ( c ) = > c . tag . clone ( ) ,
2023-08-24 15:34:51 -07:00
}
}
}
2024-10-01 12:45:01 -07:00
/// The type of ExecutorContext being used
#[ derive(PartialEq, Debug, Default, Clone) ]
pub enum ContextType {
/// Live engine connection
#[ default ]
Live ,
/// Completely mocked connection
/// Mock mode is only for the modeling app when they just want to mock engine calls and not
/// actually make them.
Mock ,
/// Handled by some other interpreter/conversion system
MockCustomForwarded ,
}
2023-10-05 14:27:48 -07:00
/// The executor context.
2024-06-06 16:01:41 -05:00
/// Cloning will return another handle to the same engine connection/session,
/// as this uses `Arc` under the hood.
2023-10-05 14:27:48 -07:00
#[ derive(Debug, Clone) ]
pub struct ExecutorContext {
2024-03-13 12:56:46 -07:00
pub engine : Arc < Box < dyn EngineManager > > ,
2024-04-15 17:18:32 -07:00
pub fs : Arc < FileManager > ,
2023-11-08 20:23:59 -06:00
pub stdlib : Arc < StdLib > ,
2024-04-25 00:13:09 -07:00
pub settings : ExecutorSettings ,
2024-10-01 12:45:01 -07:00
pub context_type : ContextType ,
2023-10-05 14:27:48 -07:00
}
2024-04-25 00:13:09 -07:00
/// The executor settings.
2024-12-05 19:51:06 -08:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
2024-04-25 00:13:09 -07:00
pub struct ExecutorSettings {
/// The unit to use in modeling dimensions.
2024-06-18 14:38:25 -05:00
pub units : UnitLength ,
2024-04-25 00:13:09 -07:00
/// Highlight edges of 3D objects?
pub highlight_edges : bool ,
2024-04-25 02:31:18 -07:00
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
pub enable_ssao : bool ,
2024-08-23 17:40:30 -05:00
/// Show grid?
2024-07-05 12:39:58 -07:00
pub show_grid : bool ,
2024-08-23 17:40:30 -05:00
/// Should engine store this for replay?
/// If so, under what name?
pub replay : Option < String > ,
2024-12-07 07:16:04 +13:00
/// The directory of the current project. This is used for resolving import
/// paths. If None is given, the current working directory is used.
pub project_directory : Option < PathBuf > ,
2024-04-25 00:13:09 -07:00
}
impl Default for ExecutorSettings {
fn default ( ) -> Self {
Self {
units : Default ::default ( ) ,
highlight_edges : true ,
2024-04-25 02:31:18 -07:00
enable_ssao : false ,
2024-07-05 12:39:58 -07:00
show_grid : false ,
2024-08-23 17:40:30 -05:00
replay : None ,
2024-12-07 07:16:04 +13:00
project_directory : None ,
2024-04-25 02:31:18 -07:00
}
}
}
impl From < crate ::settings ::types ::Configuration > for ExecutorSettings {
fn from ( config : crate ::settings ::types ::Configuration ) -> Self {
Self {
units : config . settings . modeling . base_unit ,
highlight_edges : config . settings . modeling . highlight_edges . into ( ) ,
enable_ssao : config . settings . modeling . enable_ssao . into ( ) ,
2024-07-05 12:39:58 -07:00
show_grid : config . settings . modeling . show_scale_grid ,
2024-08-23 17:40:30 -05:00
replay : None ,
2024-12-07 07:16:04 +13:00
project_directory : None ,
2024-04-25 02:31:18 -07:00
}
}
}
impl From < crate ::settings ::types ::project ::ProjectConfiguration > for ExecutorSettings {
fn from ( config : crate ::settings ::types ::project ::ProjectConfiguration ) -> Self {
Self {
units : config . settings . modeling . base_unit ,
highlight_edges : config . settings . modeling . highlight_edges . into ( ) ,
enable_ssao : config . settings . modeling . enable_ssao . into ( ) ,
2024-07-05 12:39:58 -07:00
show_grid : config . settings . modeling . show_scale_grid ,
2024-08-23 17:40:30 -05:00
replay : None ,
2024-12-07 07:16:04 +13:00
project_directory : None ,
2024-04-25 02:31:18 -07:00
}
}
}
impl From < crate ::settings ::types ::ModelingSettings > for ExecutorSettings {
fn from ( modeling : crate ::settings ::types ::ModelingSettings ) -> Self {
Self {
units : modeling . base_unit ,
highlight_edges : modeling . highlight_edges . into ( ) ,
enable_ssao : modeling . enable_ssao . into ( ) ,
2024-07-05 12:39:58 -07:00
show_grid : modeling . show_scale_grid ,
2024-08-23 17:40:30 -05:00
replay : None ,
2024-12-07 07:16:04 +13:00
project_directory : None ,
2024-04-25 00:13:09 -07:00
}
}
}
2024-10-25 20:06:32 -07:00
/// Create a new zoo api client.
#[ cfg(not(target_arch = " wasm32 " )) ]
pub fn new_zoo_client ( token : Option < String > , engine_addr : Option < String > ) -> Result < kittycad ::Client > {
let user_agent = concat! ( env! ( " CARGO_PKG_NAME " ) , " .rs/ " , env! ( " CARGO_PKG_VERSION " ) , ) ;
let http_client = reqwest ::Client ::builder ( )
. user_agent ( user_agent )
// For file conversions we need this to be long.
. timeout ( std ::time ::Duration ::from_secs ( 600 ) )
. connect_timeout ( std ::time ::Duration ::from_secs ( 60 ) ) ;
let ws_client = reqwest ::Client ::builder ( )
. user_agent ( user_agent )
// For file conversions we need this to be long.
. timeout ( std ::time ::Duration ::from_secs ( 600 ) )
. connect_timeout ( std ::time ::Duration ::from_secs ( 60 ) )
. connection_verbose ( true )
. tcp_keepalive ( std ::time ::Duration ::from_secs ( 600 ) )
. http1_only ( ) ;
let zoo_token_env = std ::env ::var ( " ZOO_API_TOKEN " ) ;
let token = if let Some ( token ) = token {
token
} else if let Ok ( token ) = std ::env ::var ( " KITTYCAD_API_TOKEN " ) {
if let Ok ( zoo_token ) = zoo_token_env {
if zoo_token ! = token {
return Err ( anyhow ::anyhow! (
" Both environment variables KITTYCAD_API_TOKEN=`{}` and ZOO_API_TOKEN=`{}` are set. Use only one. " ,
token ,
zoo_token
) ) ;
}
}
token
} else if let Ok ( token ) = zoo_token_env {
token
} else {
return Err ( anyhow ::anyhow! (
" No API token found in environment variables. Use KITTYCAD_API_TOKEN or ZOO_API_TOKEN "
) ) ;
} ;
// Create the client.
let mut client = kittycad ::Client ::new_from_reqwest ( token , http_client , ws_client ) ;
// Set an engine address if it's set.
let kittycad_host_env = std ::env ::var ( " KITTYCAD_HOST " ) ;
if let Some ( addr ) = engine_addr {
client . set_base_url ( addr ) ;
} else if let Ok ( addr ) = std ::env ::var ( " ZOO_HOST " ) {
if let Ok ( kittycad_host ) = kittycad_host_env {
if kittycad_host ! = addr {
return Err ( anyhow ::anyhow! (
" Both environment variables KITTYCAD_HOST=`{}` and ZOO_HOST=`{}` are set. Use only one. " ,
kittycad_host ,
addr
) ) ;
}
}
client . set_base_url ( addr ) ;
} else if let Ok ( addr ) = kittycad_host_env {
client . set_base_url ( addr ) ;
}
Ok ( client )
}
2024-02-12 12:18:37 -08:00
impl ExecutorContext {
/// Create a new default executor context.
#[ cfg(not(target_arch = " wasm32 " )) ]
2024-08-23 17:40:30 -05:00
pub async fn new ( client : & kittycad ::Client , settings : ExecutorSettings ) -> Result < Self > {
let ( ws , _headers ) = client
2024-04-25 02:31:18 -07:00
. modeling ( )
. commands_ws (
None ,
None ,
if settings . enable_ssao {
Some ( kittycad ::types ::PostEffectType ::Ssao )
} else {
None
} ,
2024-08-23 17:40:30 -05:00
settings . replay . clone ( ) ,
2024-07-05 12:39:58 -07:00
if settings . show_grid { Some ( true ) } else { None } ,
2024-04-25 02:31:18 -07:00
None ,
None ,
None ,
Some ( false ) ,
)
. await ? ;
let engine : Arc < Box < dyn EngineManager > > =
Arc ::new ( Box ::new ( crate ::engine ::conn ::EngineConnection ::new ( ws ) . await ? ) ) ;
2024-08-23 17:40:30 -05:00
Ok ( Self {
2024-04-25 02:31:18 -07:00
engine ,
2024-04-15 17:18:32 -07:00
fs : Arc ::new ( FileManager ::new ( ) ) ,
2024-02-12 12:18:37 -08:00
stdlib : Arc ::new ( StdLib ::new ( ) ) ,
2024-04-25 00:13:09 -07:00
settings ,
2024-10-01 12:45:01 -07:00
context_type : ContextType ::Live ,
2024-08-23 17:40:30 -05:00
} )
2024-02-12 12:18:37 -08:00
}
2024-11-20 15:19:25 +13:00
#[ cfg(not(target_arch = " wasm32 " )) ]
pub async fn new_mock ( ) -> Self {
ExecutorContext {
engine : Arc ::new ( Box ::new (
crate ::engine ::conn_mock ::EngineConnection ::new ( ) . await . unwrap ( ) ,
) ) ,
fs : Arc ::new ( FileManager ::new ( ) ) ,
stdlib : Arc ::new ( StdLib ::new ( ) ) ,
settings : Default ::default ( ) ,
context_type : ContextType ::Mock ,
}
}
#[ cfg(target_arch = " wasm32 " ) ]
pub async fn new (
engine_manager : crate ::engine ::conn_wasm ::EngineCommandManager ,
fs_manager : crate ::fs ::wasm ::FileSystemManager ,
2024-12-10 18:50:22 -08:00
settings : ExecutorSettings ,
2024-11-20 15:19:25 +13:00
) -> Result < Self , String > {
Ok ( ExecutorContext {
engine : Arc ::new ( Box ::new (
crate ::engine ::conn_wasm ::EngineConnection ::new ( engine_manager )
. await
. map_err ( | e | format! ( " {:?} " , e ) ) ? ,
) ) ,
fs : Arc ::new ( FileManager ::new ( fs_manager ) ) ,
stdlib : Arc ::new ( StdLib ::new ( ) ) ,
2024-12-10 18:50:22 -08:00
settings ,
2024-11-20 15:19:25 +13:00
context_type : ContextType ::Live ,
} )
}
#[ cfg(target_arch = " wasm32 " ) ]
2024-12-10 18:50:22 -08:00
pub async fn new_mock (
fs_manager : crate ::fs ::wasm ::FileSystemManager ,
settings : ExecutorSettings ,
) -> Result < Self , String > {
2024-11-20 15:19:25 +13:00
Ok ( ExecutorContext {
engine : Arc ::new ( Box ::new (
crate ::engine ::conn_mock ::EngineConnection ::new ( )
. await
. map_err ( | e | format! ( " {:?} " , e ) ) ? ,
) ) ,
fs : Arc ::new ( FileManager ::new ( fs_manager ) ) ,
stdlib : Arc ::new ( StdLib ::new ( ) ) ,
2024-12-10 18:50:22 -08:00
settings ,
2024-11-20 15:19:25 +13:00
context_type : ContextType ::Mock ,
} )
}
#[ cfg(not(target_arch = " wasm32 " )) ]
pub fn new_forwarded_mock ( engine : Arc < Box < dyn EngineManager > > ) -> Self {
ExecutorContext {
engine ,
fs : Arc ::new ( FileManager ::new ( ) ) ,
stdlib : Arc ::new ( StdLib ::new ( ) ) ,
settings : Default ::default ( ) ,
context_type : ContextType ::MockCustomForwarded ,
}
}
2024-10-25 20:06:32 -07:00
/// Create a new default executor context.
/// With a kittycad client.
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
/// variables.
/// But also allows for passing in a token and engine address directly.
#[ cfg(not(target_arch = " wasm32 " )) ]
pub async fn new_with_client (
settings : ExecutorSettings ,
token : Option < String > ,
engine_addr : Option < String > ,
) -> Result < Self > {
// Create the client.
let client = new_zoo_client ( token , engine_addr ) ? ;
let ctx = Self ::new ( & client , settings ) . await ? ;
Ok ( ctx )
}
/// Create a new default executor context.
/// With the default kittycad client.
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
/// variables.
#[ cfg(not(target_arch = " wasm32 " )) ]
2024-11-20 15:19:25 +13:00
pub async fn new_with_default_client ( units : UnitLength ) -> Result < Self > {
2024-10-25 20:06:32 -07:00
// Create the client.
2024-11-20 15:19:25 +13:00
let ctx = Self ::new_with_client (
ExecutorSettings {
units ,
.. Default ::default ( )
} ,
None ,
None ,
)
. await ? ;
2024-10-25 20:06:32 -07:00
Ok ( ctx )
}
2024-10-01 12:45:01 -07:00
pub fn is_mock ( & self ) -> bool {
self . context_type = = ContextType ::Mock | | self . context_type = = ContextType ::MockCustomForwarded
}
2024-06-18 14:38:25 -05:00
/// For executing unit tests.
#[ cfg(not(target_arch = " wasm32 " )) ]
2024-07-29 13:43:27 -05:00
pub async fn new_for_unit_test ( units : UnitLength , engine_addr : Option < String > ) -> Result < Self > {
2024-10-25 20:06:32 -07:00
let ctx = ExecutorContext ::new_with_client (
2024-06-18 14:38:25 -05:00
ExecutorSettings {
units ,
highlight_edges : true ,
enable_ssao : false ,
2024-07-05 12:39:58 -07:00
show_grid : false ,
2024-08-23 17:40:30 -05:00
replay : None ,
2024-12-07 07:16:04 +13:00
project_directory : None ,
2024-06-18 14:38:25 -05:00
} ,
2024-10-25 20:06:32 -07:00
None ,
engine_addr ,
2024-06-18 14:38:25 -05:00
)
. await ? ;
Ok ( ctx )
}
2024-10-09 19:38:40 -04:00
pub async fn reset_scene (
& self ,
2024-11-20 15:19:25 +13:00
exec_state : & mut ExecState ,
2024-12-07 07:16:04 +13:00
source_range : crate ::execution ::SourceRange ,
2024-12-05 19:51:06 -08:00
) -> Result < ( ) , KclError > {
2024-11-20 15:19:25 +13:00
self . engine
. clear_scene ( & mut exec_state . id_generator , source_range )
. await ? ;
2024-12-05 19:51:06 -08:00
// We do not create the planes here as the post hook in wasm will do that
// AND if we aren't in wasm it doesn't really matter.
2024-06-18 14:38:25 -05:00
Ok ( ( ) )
}
2024-12-10 18:50:22 -08:00
// Given an old ast, old program memory and new ast, find the parts of the code that need to be
// re-executed.
// This function should never error, because in the case of any internal error, we should just pop
// the cache.
pub async fn get_changed_program ( & self , info : CacheInformation ) -> Option < CacheResult > {
let Some ( old ) = info . old else {
// We have no old info, we need to re-execute the whole thing.
return Some ( CacheResult {
clear_scene : true ,
program : info . new_ast ,
} ) ;
} ;
// If the settings are different we might need to bust the cache.
// We specifically do this before checking if they are the exact same.
if old . settings ! = self . settings {
// If the units are different we need to re-execute the whole thing.
if old . settings . units ! = self . settings . units {
return Some ( CacheResult {
clear_scene : true ,
program : info . new_ast ,
} ) ;
}
// If anything else is different we do not need to re-execute, but rather just
// run the settings again.
if self
. engine
. reapply_settings ( & self . settings , Default ::default ( ) )
. await
. is_err ( )
{
// Bust the cache, we errored.
return Some ( CacheResult {
clear_scene : true ,
program : info . new_ast ,
} ) ;
}
}
// If the ASTs are the EXACT same we return None.
// We don't even need to waste time computing the digests.
if old . ast = = info . new_ast {
return None ;
}
let mut old_ast = old . ast . inner ;
old_ast . compute_digest ( ) ;
let mut new_ast = info . new_ast . inner . clone ( ) ;
new_ast . compute_digest ( ) ;
// Check if the digest is the same.
if old_ast . digest = = new_ast . digest {
return None ;
}
// Check if the changes were only to Non-code areas, like comments or whitespace.
// For any unhandled cases just re-execute the whole thing.
Some ( CacheResult {
clear_scene : true ,
program : info . new_ast ,
} )
}
2024-06-18 14:38:25 -05:00
2024-04-12 21:32:57 -07:00
/// Perform the execution of a program.
/// You can optionally pass in some initialization memory.
/// Kurt uses this for partial execution.
2024-12-05 19:51:06 -08:00
pub async fn run ( & self , cache_info : CacheInformation , exec_state : & mut ExecState ) -> Result < ( ) , KclError > {
self . run_with_session_data ( cache_info , exec_state ) . await ? ;
2024-11-20 15:19:25 +13:00
Ok ( ( ) )
2024-08-23 17:40:30 -05:00
}
2024-11-20 15:19:25 +13:00
2024-08-23 17:40:30 -05:00
/// Perform the execution of a program.
/// You can optionally pass in some initialization memory.
/// Kurt uses this for partial execution.
pub async fn run_with_session_data (
& self ,
2024-12-05 19:51:06 -08:00
cache_info : CacheInformation ,
2024-11-20 15:19:25 +13:00
exec_state : & mut ExecState ,
) -> Result < Option < ModelingSessionData > , KclError > {
2024-11-28 11:27:17 +13:00
let _stats = crate ::log ::LogPerfStats ::new ( " Interpretation " ) ;
2024-12-05 19:51:06 -08:00
// Get the program that actually changed from the old and new information.
2024-12-10 18:50:22 -08:00
let cache_result = self . get_changed_program ( cache_info . clone ( ) ) . await ;
2024-12-05 19:51:06 -08:00
// Check if we don't need to re-execute.
let Some ( cache_result ) = cache_result else {
return Ok ( None ) ;
} ;
if cache_result . clear_scene & & ! self . is_mock ( ) {
2024-12-12 18:06:26 -08:00
// Pop the execution state, since we are starting fresh.
let mut id_generator = exec_state . id_generator . clone ( ) ;
// We do not pop the ids, since we want to keep the same id generator.
// This is for the front end to keep track of the ids.
id_generator . next_id = 0 ;
* exec_state = ExecState {
id_generator ,
.. Default ::default ( )
} ;
2024-12-05 19:51:06 -08:00
// We don't do this in mock mode since there is no engine connection
// anyways and from the TS side we override memory and don't want to clear it.
self . reset_scene ( exec_state , Default ::default ( ) ) . await ? ;
}
2024-11-07 11:23:41 -05:00
// TODO: Use the top-level file's path.
exec_state . add_module ( std ::path ::PathBuf ::from ( " " ) ) ;
2024-12-10 18:50:22 -08:00
// Re-apply the settings, in case the cache was busted.
self . engine . reapply_settings ( & self . settings , Default ::default ( ) ) . await ? ;
2024-10-09 19:38:40 -04:00
2024-12-07 07:16:04 +13:00
self . inner_execute ( & cache_result . program , exec_state , crate ::execution ::BodyType ::Root )
2024-08-23 17:40:30 -05:00
. await ? ;
let session_data = self . engine . get_session_data ( ) ;
2024-11-20 15:19:25 +13:00
Ok ( session_data )
2024-04-12 21:32:57 -07:00
}
2024-02-20 17:55:06 -08:00
2024-04-12 21:32:57 -07:00
/// Execute an AST's program.
#[ async_recursion ]
2024-10-30 16:52:17 -04:00
pub ( crate ) async fn inner_execute < ' a > (
& ' a self ,
2024-12-05 17:56:49 +13:00
program : NodeRef < ' a , crate ::parsing ::ast ::types ::Program > ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2024-05-22 18:50:54 -05:00
body_type : BodyType ,
2024-09-30 15:40:50 -05:00
) -> Result < Option < KclValue > , KclError > {
let mut last_expr = None ;
2024-04-12 21:32:57 -07:00
// Iterate over the body of the program.
for statement in & program . body {
match statement {
2024-10-17 00:48:33 -04:00
BodyItem ::ImportStatement ( import_stmt ) = > {
let source_range = SourceRange ::from ( import_stmt ) ;
2024-12-07 07:16:04 +13:00
let ( module_memory , module_exports ) =
self . open_module ( & import_stmt . path , exec_state , source_range ) . await ? ;
match & import_stmt . selector {
ImportSelector ::List { items } = > {
for import_item in items {
// Extract the item from the module.
let item =
module_memory
. get ( & import_item . name . name , import_item . into ( ) )
. map_err ( | _err | {
KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " {} is not defined in module " , import_item . name . name ) ,
source_ranges : vec ! [ SourceRange ::from ( & import_item . name ) ] ,
} )
} ) ? ;
// Check that the item is allowed to be imported.
if ! module_exports . contains ( & import_item . name . name ) {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Cannot import \" {} \" from module because it is not exported. Add \" export \" before the definition to export it. " ,
import_item . name . name
) ,
source_ranges : vec ! [ SourceRange ::from ( & import_item . name ) ] ,
} ) ) ;
}
// Add the item to the current module.
exec_state . memory . add (
import_item . identifier ( ) ,
item . clone ( ) ,
SourceRange ::from ( & import_item . name ) ,
) ? ;
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state . module_exports . push ( import_item . identifier ( ) . to_owned ( ) ) ;
}
}
}
ImportSelector ::Glob ( _ ) = > {
for name in module_exports . iter ( ) {
let item = module_memory . get ( name , source_range ) . map_err ( | _err | {
KclError ::Internal ( KclErrorDetails {
message : format ! ( " {} is not defined in module (but was exported?) " , name ) ,
source_ranges : vec ! [ source_range ] ,
} )
} ) ? ;
exec_state . memory . add ( name , item . clone ( ) , source_range ) ? ;
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state . module_exports . push ( name . clone ( ) ) ;
}
2024-10-17 00:48:33 -04:00
}
2024-12-07 07:16:04 +13:00
}
ImportSelector ::None ( _ ) = > {
2024-10-17 00:48:33 -04:00
return Err ( KclError ::Semantic ( KclErrorDetails {
2024-12-07 07:16:04 +13:00
message : " Importing whole module is not yet implemented, sorry. " . to_owned ( ) ,
source_ranges : vec ! [ source_range ] ,
2024-10-17 00:48:33 -04:00
} ) ) ;
}
}
last_expr = None ;
}
2024-04-12 21:32:57 -07:00
BodyItem ::ExpressionStatement ( expression_statement ) = > {
2024-08-14 01:57:03 -04:00
let metadata = Metadata ::from ( expression_statement ) ;
2024-09-30 15:40:50 -05:00
last_expr = Some (
self . execute_expr (
& expression_statement . expression ,
exec_state ,
& metadata ,
StatementKind ::Expression ,
)
. await ? ,
) ;
2024-04-12 21:32:57 -07:00
}
BodyItem ::VariableDeclaration ( variable_declaration ) = > {
2024-12-07 07:16:04 +13:00
let var_name = variable_declaration . declaration . id . name . to_string ( ) ;
let source_range = SourceRange ::from ( & variable_declaration . declaration . init ) ;
let metadata = Metadata { source_range } ;
let memory_item = self
. execute_expr (
& variable_declaration . declaration . init ,
exec_state ,
& metadata ,
StatementKind ::Declaration { name : & var_name } ,
)
. await ? ;
exec_state . memory . add ( & var_name , memory_item , source_range ) ? ;
// Track exports.
if let ItemVisibility ::Export = variable_declaration . visibility {
exec_state . module_exports . push ( var_name ) ;
2023-08-24 15:34:51 -07:00
}
2024-09-30 15:40:50 -05:00
last_expr = None ;
2023-08-24 15:34:51 -07:00
}
2024-08-14 01:57:03 -04:00
BodyItem ::ReturnStatement ( return_statement ) = > {
let metadata = Metadata ::from ( return_statement ) ;
let value = self
. execute_expr (
& return_statement . argument ,
2024-09-16 15:10:33 -04:00
exec_state ,
2024-08-14 01:57:03 -04:00
& metadata ,
StatementKind ::Expression ,
)
. await ? ;
2024-09-16 15:10:33 -04:00
exec_state . memory . return_ = Some ( value ) ;
2024-09-30 15:40:50 -05:00
last_expr = None ;
2024-08-14 01:57:03 -04:00
}
2023-08-24 15:34:51 -07:00
}
}
2024-05-22 18:50:54 -05:00
if BodyType ::Root = = body_type {
// Flush the batch queue.
2024-06-22 14:31:37 -07:00
self . engine
. flush_batch (
// True here tells the engine to flush all the end commands as well like fillets
// and chamfers where the engine would otherwise eat the ID of the segments.
true ,
2024-12-03 16:39:51 +13:00
SourceRange ::new ( program . end , program . end , program . module_id ) ,
2024-06-22 14:31:37 -07:00
)
. await ? ;
2024-05-22 18:50:54 -05:00
}
2024-04-12 21:32:57 -07:00
2024-09-30 15:40:50 -05:00
Ok ( last_expr )
2024-04-12 21:32:57 -07:00
}
2024-03-23 15:45:55 -07:00
2024-12-07 07:16:04 +13:00
async fn open_module (
& self ,
path : & str ,
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < ( ProgramMemory , Vec < String > ) , KclError > {
let resolved_path = if let Some ( project_dir ) = & self . settings . project_directory {
project_dir . join ( path )
} else {
std ::path ::PathBuf ::from ( & path )
} ;
if exec_state . import_stack . contains ( & resolved_path ) {
return Err ( KclError ::ImportCycle ( KclErrorDetails {
message : format ! (
" circular import of modules is not allowed: {} -> {} " ,
exec_state
. import_stack
. iter ( )
. map ( | p | p . as_path ( ) . to_string_lossy ( ) )
. collect ::< Vec < _ > > ( )
. join ( " -> " ) ,
resolved_path . to_string_lossy ( )
) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ;
}
let module_id = exec_state . add_module ( resolved_path . clone ( ) ) ;
let source = self . fs . read_to_string ( & resolved_path , source_range ) . await ? ;
// TODO handle parsing errors properly
let program = crate ::parsing ::parse_str ( & source , module_id ) . parse_errs_as_err ( ) ? ;
exec_state . import_stack . push ( resolved_path . clone ( ) ) ;
let original_execution = self . engine . replace_execution_kind ( ExecutionKind ::Isolated ) ;
let original_memory = std ::mem ::take ( & mut exec_state . memory ) ;
let original_exports = std ::mem ::take ( & mut exec_state . module_exports ) ;
let result = self
. inner_execute ( & program , exec_state , crate ::execution ::BodyType ::Root )
. await ;
let module_exports = std ::mem ::replace ( & mut exec_state . module_exports , original_exports ) ;
let module_memory = std ::mem ::replace ( & mut exec_state . memory , original_memory ) ;
self . engine . replace_execution_kind ( original_execution ) ;
exec_state . import_stack . pop ( ) ;
result . map_err ( | err | {
if let KclError ::ImportCycle ( _ ) = err {
// It was an import cycle. Keep the original message.
err . override_source_ranges ( vec! [ source_range ] )
} else {
KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Error loading imported file. Open it to view more details. {path}: {} " ,
err . message ( )
) ,
source_ranges : vec ! [ source_range ] ,
} )
}
} ) ? ;
Ok ( ( module_memory , module_exports ) )
}
2024-12-11 21:26:42 +13:00
#[ async_recursion ]
pub async fn execute_expr < ' a : ' async_recursion > (
2024-05-23 14:50:22 -05:00
& self ,
2024-08-12 15:38:42 -05:00
init : & Expr ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2024-05-23 14:50:22 -05:00
metadata : & Metadata ,
statement_kind : StatementKind < ' a > ,
2024-08-12 16:53:24 -05:00
) -> Result < KclValue , KclError > {
2024-05-23 14:50:22 -05:00
let item = match init {
2024-08-14 01:57:03 -04:00
Expr ::None ( none ) = > KclValue ::from ( none ) ,
Expr ::Literal ( literal ) = > KclValue ::from ( literal ) ,
2024-12-13 16:39:40 -05:00
Expr ::TagDeclarator ( tag ) = > tag . execute ( exec_state ) . await ? ,
2024-08-12 15:38:42 -05:00
Expr ::Identifier ( identifier ) = > {
2024-09-16 15:10:33 -04:00
let value = exec_state . memory . get ( & identifier . name , identifier . into ( ) ) ? ;
2024-05-23 14:50:22 -05:00
value . clone ( )
}
2024-09-16 15:10:33 -04:00
Expr ::BinaryExpression ( binary_expression ) = > binary_expression . get_result ( exec_state , self ) . await ? ,
2024-08-12 15:38:42 -05:00
Expr ::FunctionExpression ( function_expression ) = > {
2024-07-22 19:43:40 -04:00
// Cloning memory here is crucial for semantics so that we close
// over variables. Variables defined lexically later shouldn't
// be available to the function body.
2024-08-12 16:53:24 -05:00
KclValue ::Function {
2024-05-23 14:50:22 -05:00
expression : function_expression . clone ( ) ,
meta : vec ! [ metadata . to_owned ( ) ] ,
2024-09-16 15:10:33 -04:00
func : None ,
memory : Box ::new ( exec_state . memory . clone ( ) ) ,
2024-05-23 14:50:22 -05:00
}
}
2024-09-16 15:10:33 -04:00
Expr ::CallExpression ( call_expression ) = > call_expression . execute ( exec_state , self ) . await ? ,
2024-12-02 15:23:18 -06:00
Expr ::CallExpressionKw ( call_expression ) = > call_expression . execute ( exec_state , self ) . await ? ,
2024-09-16 15:10:33 -04:00
Expr ::PipeExpression ( pipe_expression ) = > pipe_expression . get_result ( exec_state , self ) . await ? ,
2024-08-12 15:38:42 -05:00
Expr ::PipeSubstitution ( pipe_substitution ) = > match statement_kind {
2024-05-23 14:50:22 -05:00
StatementKind ::Declaration { name } = > {
let message = format! (
" you cannot declare variable {name} as %, because % can only be used in function calls "
) ;
return Err ( KclError ::Semantic ( KclErrorDetails {
message ,
source_ranges : vec ! [ pipe_substitution . into ( ) ] ,
} ) ) ;
}
2024-09-16 15:10:33 -04:00
StatementKind ::Expression = > match exec_state . pipe_value . clone ( ) {
2024-05-23 14:50:22 -05:00
Some ( x ) = > x ,
None = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " cannot use % outside a pipe expression " . to_owned ( ) ,
source_ranges : vec ! [ pipe_substitution . into ( ) ] ,
} ) ) ;
}
} ,
} ,
2024-09-16 15:10:33 -04:00
Expr ::ArrayExpression ( array_expression ) = > array_expression . execute ( exec_state , self ) . await ? ,
2024-10-17 02:58:04 +13:00
Expr ::ArrayRangeExpression ( range_expression ) = > range_expression . execute ( exec_state , self ) . await ? ,
2024-09-16 15:10:33 -04:00
Expr ::ObjectExpression ( object_expression ) = > object_expression . execute ( exec_state , self ) . await ? ,
Expr ::MemberExpression ( member_expression ) = > member_expression . get_result ( exec_state ) ? ,
Expr ::UnaryExpression ( unary_expression ) = > unary_expression . get_result ( exec_state , self ) . await ? ,
2024-09-30 15:40:50 -05:00
Expr ::IfExpression ( expr ) = > expr . get_result ( exec_state , self ) . await ? ,
2024-12-11 21:26:42 +13:00
Expr ::LabelledExpression ( expr ) = > {
let result = self
. execute_expr ( & expr . expr , exec_state , metadata , statement_kind )
. await ? ;
exec_state . memory . add ( & expr . label . name , result . clone ( ) , init . into ( ) ) ? ;
// TODO this lets us use the label as a variable name, but not as a tag in most cases
result
}
2024-05-23 14:50:22 -05:00
} ;
Ok ( item )
}
2024-04-12 21:32:57 -07:00
/// Update the units for the executor.
2024-06-18 14:38:25 -05:00
pub fn update_units ( & mut self , units : UnitLength ) {
2024-04-25 00:13:09 -07:00
self . settings . units = units ;
2024-04-12 21:32:57 -07:00
}
2024-06-06 16:01:41 -05:00
2024-12-10 18:50:22 -08:00
/// Get a snapshot of the current scene.
pub async fn prepare_snapshot ( & self ) -> std ::result ::Result < TakeSnapshot , ExecError > {
2024-06-06 16:01:41 -05:00
// Zoom to fit.
self . engine
. send_modeling_cmd (
uuid ::Uuid ::new_v4 ( ) ,
2024-12-07 07:16:04 +13:00
crate ::execution ::SourceRange ::default ( ) ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::ZoomToFit {
2024-06-06 16:01:41 -05:00
object_ids : Default ::default ( ) ,
2024-09-18 17:04:04 -05:00
animated : false ,
2024-06-06 16:01:41 -05:00
padding : 0.1 ,
2024-09-18 17:04:04 -05:00
} ) ,
2024-06-06 16:01:41 -05:00
)
. await ? ;
// Send a snapshot request to the engine.
let resp = self
. engine
. send_modeling_cmd (
uuid ::Uuid ::new_v4 ( ) ,
2024-12-07 07:16:04 +13:00
crate ::execution ::SourceRange ::default ( ) ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::TakeSnapshot {
format : ImageFormat ::Png ,
} ) ,
2024-06-06 16:01:41 -05:00
)
. await ? ;
2024-09-18 17:04:04 -05:00
let OkWebSocketResponseData ::Modeling {
modeling_response : OkModelingCmdResponse ::TakeSnapshot ( contents ) ,
2024-06-06 16:01:41 -05:00
} = resp
else {
2024-11-25 14:06:23 -06:00
return Err ( ExecError ::BadPng ( format! (
" Instead of a TakeSnapshot response, the engine returned {resp:?} "
) ) ) ;
2024-06-06 16:01:41 -05:00
} ;
2024-11-20 15:19:25 +13:00
Ok ( contents )
2024-06-06 16:01:41 -05:00
}
2024-12-10 18:50:22 -08:00
/// Execute the program, then get a PNG screenshot.
pub async fn execute_and_prepare_snapshot (
& self ,
program : & Program ,
exec_state : & mut ExecState ,
) -> std ::result ::Result < TakeSnapshot , ExecError > {
self . run ( program . clone ( ) . into ( ) , exec_state ) . await ? ;
self . prepare_snapshot ( ) . await
}
2023-08-24 15:34:51 -07:00
}
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
/// For each argument given,
/// assign it to a parameter of the function, in the given block of function memory.
/// Returns Err if too few/too many arguments were given for the function.
fn assign_args_to_params (
2024-10-30 16:52:17 -04:00
function_expression : NodeRef < '_ , FunctionExpression > ,
2024-11-21 13:10:03 -05:00
args : Vec < Arg > ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
mut fn_memory : ProgramMemory ,
) -> Result < ProgramMemory , KclError > {
let num_args = function_expression . number_of_args ( ) ;
let ( min_params , max_params ) = num_args . into_inner ( ) ;
let n = args . len ( ) ;
// Check if the user supplied too many arguments
// (we'll check for too few arguments below).
let err_wrong_number_args = KclError ::Semantic ( KclErrorDetails {
message : if min_params = = max_params {
format! ( " Expected {min_params} arguments, got {n} " )
} else {
format! ( " Expected {min_params} - {max_params} arguments, got {n} " )
} ,
source_ranges : vec ! [ function_expression . into ( ) ] ,
} ) ;
if n > max_params {
return Err ( err_wrong_number_args ) ;
}
2024-07-22 19:43:40 -04:00
// Add the arguments to the memory. A new call frame should have already
// been created.
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
for ( index , param ) in function_expression . params . iter ( ) . enumerate ( ) {
if let Some ( arg ) = args . get ( index ) {
// Argument was provided.
2024-11-21 13:10:03 -05:00
fn_memory . add ( & param . identifier . name , arg . value . clone ( ) , ( & param . identifier ) . into ( ) ) ? ;
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
} else {
// Argument was not provided.
2024-12-05 21:04:40 -06:00
if let Some ( ref default_val ) = param . default_value {
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
// If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it.
fn_memory . add (
& param . identifier . name ,
2024-12-05 21:04:40 -06:00
default_val . clone ( ) . into ( ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
( & param . identifier ) . into ( ) ,
) ? ;
} else {
// But if the corresponding parameter was required,
// then the user has called with too few arguments.
return Err ( err_wrong_number_args ) ;
}
}
}
Ok ( fn_memory )
}
2024-12-09 22:11:16 -06:00
fn assign_args_to_params_kw (
function_expression : NodeRef < '_ , FunctionExpression > ,
mut args : crate ::std ::args ::KwArgs ,
mut fn_memory : ProgramMemory ,
) -> Result < ProgramMemory , KclError > {
// Add the arguments to the memory. A new call frame should have already
// been created.
let source_ranges = vec! [ function_expression . into ( ) ] ;
for param in function_expression . params . iter ( ) {
if param . labeled {
let arg = args . labeled . get ( & param . identifier . name ) ;
let arg_val = match arg {
Some ( arg ) = > arg . value . clone ( ) ,
None = > match param . default_value {
Some ( ref default_val ) = > KclValue ::from ( default_val . clone ( ) ) ,
None = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : format ! (
" This function requires a parameter {}, but you haven't passed it one. " ,
param . identifier . name
) ,
} ) ) ;
}
} ,
} ;
fn_memory . add ( & param . identifier . name , arg_val , ( & param . identifier ) . into ( ) ) ? ;
} else {
let Some ( unlabeled ) = args . unlabeled . take ( ) else {
let param_name = & param . identifier . name ;
return Err ( if args . labeled . contains_key ( param_name ) {
KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : format ! ( " The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:` " ) ,
} )
} else {
KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : " This function expects an unlabeled first parameter, but you haven't passed it one. "
. to_owned ( ) ,
} )
} ) ;
} ;
fn_memory . add (
& param . identifier . name ,
unlabeled . value . clone ( ) ,
( & param . identifier ) . into ( ) ,
) ? ;
}
}
Ok ( fn_memory )
}
2024-09-16 15:10:33 -04:00
pub ( crate ) async fn call_user_defined_function (
2024-11-21 13:10:03 -05:00
args : Vec < Arg > ,
2024-09-16 15:10:33 -04:00
memory : & ProgramMemory ,
2024-10-30 16:52:17 -04:00
function_expression : NodeRef < '_ , FunctionExpression > ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < Option < KclValue > , KclError > {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
let mut body_memory = memory . clone ( ) ;
let body_env = body_memory . new_env_for_call ( memory . current_env ) ;
body_memory . current_env = body_env ;
let fn_memory = assign_args_to_params ( function_expression , args , body_memory ) ? ;
// Execute the function body using the memory we just created.
let ( result , fn_memory ) = {
2024-12-09 22:11:16 -06:00
let previous_memory = std ::mem ::replace ( & mut exec_state . memory , fn_memory ) ;
let result = ctx
. inner_execute ( & function_expression . body , exec_state , BodyType ::Block )
. await ;
// Restore the previous memory.
let fn_memory = std ::mem ::replace ( & mut exec_state . memory , previous_memory ) ;
( result , fn_memory )
} ;
result . map ( | _ | fn_memory . return_ )
}
pub ( crate ) async fn call_user_defined_function_kw (
args : crate ::std ::args ::KwArgs ,
memory : & ProgramMemory ,
function_expression : NodeRef < '_ , FunctionExpression > ,
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < Option < KclValue > , KclError > {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
let mut body_memory = memory . clone ( ) ;
let body_env = body_memory . new_env_for_call ( memory . current_env ) ;
body_memory . current_env = body_env ;
let fn_memory = assign_args_to_params_kw ( function_expression , args , body_memory ) ? ;
// Execute the function body using the memory we just created.
let ( result , fn_memory ) = {
2024-09-16 15:10:33 -04:00
let previous_memory = std ::mem ::replace ( & mut exec_state . memory , fn_memory ) ;
let result = ctx
. inner_execute ( & function_expression . body , exec_state , BodyType ::Block )
. await ;
// Restore the previous memory.
let fn_memory = std ::mem ::replace ( & mut exec_state . memory , previous_memory ) ;
( result , fn_memory )
} ;
2024-09-30 15:40:50 -05:00
result . map ( | _ | fn_memory . return_ )
2024-09-16 15:10:33 -04:00
}
2024-05-23 14:50:22 -05:00
pub enum StatementKind < ' a > {
Declaration { name : & ' a str } ,
Expression ,
}
2023-08-24 15:34:51 -07:00
#[ cfg(test) ]
mod tests {
2024-03-13 12:56:46 -07:00
use std ::sync ::Arc ;
2023-08-24 15:34:51 -07:00
use pretty_assertions ::assert_eq ;
2023-08-29 14:12:48 -07:00
use super ::* ;
2024-12-10 18:50:22 -08:00
use crate ::{
parsing ::ast ::types ::{ DefaultParamVal , Identifier , Node , Parameter } ,
OldAstState ,
} ;
2023-08-29 14:12:48 -07:00
2024-12-10 18:50:22 -08:00
pub async fn parse_execute ( code : & str ) -> Result < ( Program , ExecutorContext , ExecState ) > {
2024-12-06 13:57:31 +13:00
let program = Program ::parse_no_errs ( code ) ? ;
2024-11-07 11:23:41 -05:00
2024-03-13 12:56:46 -07:00
let ctx = ExecutorContext {
engine : Arc ::new ( Box ::new ( crate ::engine ::conn_mock ::EngineConnection ::new ( ) . await ? ) ) ,
2024-04-15 17:18:32 -07:00
fs : Arc ::new ( crate ::fs ::FileManager ::new ( ) ) ,
2024-03-13 12:56:46 -07:00
stdlib : Arc ::new ( crate ::std ::StdLib ::new ( ) ) ,
2024-04-25 00:13:09 -07:00
settings : Default ::default ( ) ,
2024-10-01 12:45:01 -07:00
context_type : ContextType ::Mock ,
2024-03-13 12:56:46 -07:00
} ;
2024-11-20 15:19:25 +13:00
let mut exec_state = ExecState ::default ( ) ;
2024-12-10 18:50:22 -08:00
ctx . run ( program . clone ( ) . into ( ) , & mut exec_state ) . await ? ;
2023-08-24 15:34:51 -07:00
2024-12-10 18:50:22 -08:00
Ok ( ( program , ctx , exec_state ) )
2023-08-24 15:34:51 -07:00
}
2024-08-14 02:38:37 -04:00
/// Convenience function to get a JSON value from memory and unwrap.
2024-11-14 17:27:19 -06:00
fn mem_get_json ( memory : & ProgramMemory , name : & str ) -> KclValue {
memory . get ( name , SourceRange ::default ( ) ) . unwrap ( ) . to_owned ( )
2023-08-24 15:34:51 -07:00
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_fn_definitions ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn def = (x) => {
2023-08-24 15:34:51 -07:00
return x
}
2023-09-13 11:42:09 -07:00
fn ghi = ( x ) = > {
2023-08-24 15:34:51 -07:00
return x
}
2023-09-13 11:42:09 -07:00
fn jkl = ( x ) = > {
2023-08-24 15:34:51 -07:00
return x
}
2023-09-13 11:42:09 -07:00
fn hmm = ( x ) = > {
2023-08-24 15:34:51 -07:00
return x
}
const yo = 5 + 6
const abc = 3
const identifierGuy = 5
2023-10-05 14:27:48 -07:00
const part001 = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 1.2 , 4.83 ] , % )
2023-08-24 15:34:51 -07:00
| > line ( [ 2.8 , 0 ] , % )
| > angledLine ( [ 100 + 100 , 3.01 ] , % )
| > angledLine ( [ abc , 3.02 ] , % )
| > angledLine ( [ def ( yo ) , 3.03 ] , % )
| > angledLine ( [ ghi ( 2 ) , 3.04 ] , % )
| > angledLine ( [ jkl ( yo ) + 2 , 3.05 ] , % )
| > close ( % )
2024-03-01 17:16:18 -08:00
const yo2 = hmm ( [ identifierGuy + 5 ] ) " #;
2023-08-24 15:34:51 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_pipe_substitutions_unary ( ) {
let ast = r #" const myVar = 3
2023-10-05 14:27:48 -07:00
const part001 = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2024-07-27 17:59:41 -07:00
| > line ( [ 3 , 4 ] , % , $seg01 )
2023-08-24 15:34:51 -07:00
| > line ( [
2024-07-27 22:56:46 -07:00
min ( segLen ( seg01 ) , myVar ) ,
- legLen ( segLen ( seg01 ) , myVar )
2023-08-24 15:34:51 -07:00
] , % )
2024-03-01 17:16:18 -08:00
" #;
2023-08-24 15:34:51 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_pipe_substitutions ( ) {
let ast = r #" const myVar = 3
2023-10-05 14:27:48 -07:00
const part001 = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2024-07-27 17:59:41 -07:00
| > line ( [ 3 , 4 ] , % , $seg01 )
2023-08-24 15:34:51 -07:00
| > line ( [
2024-07-27 22:56:46 -07:00
min ( segLen ( seg01 ) , myVar ) ,
legLen ( segLen ( seg01 ) , myVar )
2023-08-24 15:34:51 -07:00
] , % )
2024-03-01 17:16:18 -08:00
" #;
2023-09-05 16:02:27 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_inline_comment ( ) {
let ast = r #" const baseThick = 1
const armAngle = 60
const baseThickHalf = baseThick / 2
const halfArmAngle = armAngle / 2
const arrExpShouldNotBeIncluded = [ 1 , 2 , 3 ]
const objExpShouldNotBeIncluded = { a : 1 , b : 2 , c : 3 }
2023-10-05 14:27:48 -07:00
const part001 = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-05 16:02:27 -07:00
| > yLineTo ( 1 , % )
| > xLine ( 3.84 , % ) // selection-range-7ish-before-this
const variableBelowShouldNotBeIncluded = 3
2024-03-01 17:16:18 -08:00
" #;
2023-08-24 15:34:51 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
2023-09-11 15:15:37 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_function_literal_in_pipe ( ) {
let ast = r #" const w = 20
const l = 8
const h = 10
fn thing = ( ) = > {
return - 8
}
2023-10-05 14:27:48 -07:00
const firstExtrude = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-11 15:15:37 -07:00
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , thing ( ) ] , % )
| > close ( % )
2024-03-01 17:16:18 -08:00
| > extrude ( h , % ) " #;
2023-09-11 15:15:37 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_function_unary_in_pipe ( ) {
let ast = r #" const w = 20
const l = 8
const h = 10
fn thing = ( x ) = > {
return - x
}
2023-10-05 14:27:48 -07:00
const firstExtrude = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-11 15:15:37 -07:00
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , thing ( 8 ) ] , % )
| > close ( % )
2024-03-01 17:16:18 -08:00
| > extrude ( h , % ) " #;
2023-09-11 15:15:37 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_function_array_in_pipe ( ) {
let ast = r #" const w = 20
const l = 8
const h = 10
fn thing = ( x ) = > {
return [ 0 , - x ]
}
2023-10-05 14:27:48 -07:00
const firstExtrude = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-11 15:15:37 -07:00
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( thing ( 8 ) , % )
| > close ( % )
2024-03-01 17:16:18 -08:00
| > extrude ( h , % ) " #;
2023-09-11 15:15:37 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_function_call_in_pipe ( ) {
let ast = r #" const w = 20
const l = 8
const h = 10
fn other_thing = ( y ) = > {
return - y
}
fn thing = ( x ) = > {
return other_thing ( x )
}
2023-10-05 14:27:48 -07:00
const firstExtrude = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-11 15:15:37 -07:00
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , thing ( 8 ) ] , % )
| > close ( % )
2024-03-01 17:16:18 -08:00
| > extrude ( h , % ) " #;
2023-09-11 15:15:37 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_with_function_sketch ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (h, l, w) => {
2023-10-05 14:27:48 -07:00
const myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-11 15:15:37 -07:00
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , - l ] , % )
| > close ( % )
| > extrude ( h , % )
return myBox
}
2024-03-01 17:16:18 -08:00
const fnBox = box ( 3 , 6 , 10 ) " #;
2023-09-11 15:15:37 -07:00
parse_execute ( ast ) . await . unwrap ( ) ;
}
2023-09-13 07:23:14 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_member_of_object_with_function_period ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (obj) => {
2023-10-05 14:27:48 -07:00
let myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( obj . start , % )
2023-09-13 07:23:14 -07:00
| > line ( [ 0 , obj . l ] , % )
| > line ( [ obj . w , 0 ] , % )
| > line ( [ 0 , - obj . l ] , % )
| > close ( % )
| > extrude ( obj . h , % )
return myBox
}
const thisBox = box ( { start : [ 0 , 0 ] , l : 6 , w : 10 , h : 3 } )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_member_of_object_with_function_brace ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (obj) => {
2023-10-05 14:27:48 -07:00
let myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( obj [ " start " ] , % )
2023-09-13 07:23:14 -07:00
| > line ( [ 0 , obj [ " l " ] ] , % )
| > line ( [ obj [ " w " ] , 0 ] , % )
| > line ( [ 0 , - obj [ " l " ] ] , % )
| > close ( % )
| > extrude ( obj [ " h " ] , % )
return myBox
}
const thisBox = box ( { start : [ 0 , 0 ] , l : 6 , w : 10 , h : 3 } )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_member_of_object_with_function_mix_period_brace ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (obj) => {
2023-10-05 14:27:48 -07:00
let myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( obj [ " start " ] , % )
2023-09-13 07:23:14 -07:00
| > line ( [ 0 , obj [ " l " ] ] , % )
| > line ( [ obj [ " w " ] , 0 ] , % )
| > line ( [ 10 - obj [ " w " ] , - obj . l ] , % )
| > close ( % )
| > extrude ( obj [ " h " ] , % )
return myBox
}
const thisBox = box ( { start : [ 0 , 0 ] , l : 6 , w : 10 , h : 3 } )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2024-08-13 16:25:09 -04:00
#[ tokio::test(flavor = " multi_thread " ) ]
#[ ignore ] // https://github.com/KittyCAD/modeling-app/issues/3338
async fn test_object_member_starting_pipeline ( ) {
let ast = r #"
fn test2 = ( ) = > {
return {
thing : startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
| > line ( [ 0 , 1 ] , % )
| > line ( [ 1 , 0 ] , % )
| > line ( [ 0 , - 1 ] , % )
| > close ( % )
}
}
const x2 = test2 ( )
x2 . thing
| > extrude ( 10 , % )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2023-09-13 07:23:14 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
#[ ignore ] // ignore til we get loops
async fn test_execute_with_function_sketch_loop_objects ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (obj) => {
2023-10-05 14:27:48 -07:00
let myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( obj . start , % )
2023-09-13 07:23:14 -07:00
| > line ( [ 0 , obj . l ] , % )
| > line ( [ obj . w , 0 ] , % )
| > line ( [ 0 , - obj . l ] , % )
| > close ( % )
| > extrude ( obj . h , % )
return myBox
}
for var in [ { start : [ 0 , 0 ] , l : 6 , w : 10 , h : 3 } , { start : [ - 10 , - 10 ] , l : 3 , w : 5 , h : 1.5 } ] {
const thisBox = box ( var )
} " #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
#[ ignore ] // ignore til we get loops
async fn test_execute_with_function_sketch_loop_array ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (h, l, w, start) => {
2023-10-05 14:27:48 -07:00
const myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-13 07:23:14 -07:00
| > line ( [ 0 , l ] , % )
| > line ( [ w , 0 ] , % )
| > line ( [ 0 , - l ] , % )
| > close ( % )
| > extrude ( h , % )
return myBox
}
for var in [ [ 3 , 6 , 10 , [ 0 , 0 ] ] , [ 1.5 , 3 , 5 , [ - 10 , - 10 ] ] ] {
const thisBox = box ( var [ 0 ] , var [ 1 ] , var [ 2 ] , var [ 3 ] )
} " #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_member_of_array_with_function ( ) {
2024-11-18 19:54:25 -05:00
let ast = r #" fn box = (arr) => {
2023-10-05 14:27:48 -07:00
let myBox = startSketchOn ( ' XY ' )
2024-11-18 19:54:25 -05:00
| > startProfileAt ( arr [ 0 ] , % )
| > line ( [ 0 , arr [ 1 ] ] , % )
| > line ( [ arr [ 2 ] , 0 ] , % )
| > line ( [ 0 , - arr [ 1 ] ] , % )
2023-09-13 07:23:14 -07:00
| > close ( % )
2024-11-18 19:54:25 -05:00
| > extrude ( arr [ 3 ] , % )
2023-09-13 07:23:14 -07:00
return myBox
}
const thisBox = box ( [ [ 0 , 0 ] , 6 , 10 , 3 ] )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2024-07-22 19:43:40 -04:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_function_cannot_access_future_definitions ( ) {
let ast = r #"
fn returnX = ( ) = > {
// x shouldn't be defined yet.
return x
}
const x = 5
const answer = returnX ( ) " #;
let result = parse_execute ( ast ) . await ;
let err = result . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ;
assert_eq! (
err ,
KclError ::UndefinedValue ( KclErrorDetails {
message : " memory item key `x` is not defined " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [
SourceRange ::new ( 64 , 65 , ModuleId ::default ( ) ) ,
SourceRange ::new ( 97 , 106 , ModuleId ::default ( ) )
] ,
2024-07-22 19:43:40 -04:00
} ) ,
) ;
}
2024-12-13 12:10:33 -08:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_cannot_shebang_in_fn ( ) {
let ast = r #"
fn foo ( ) {
#! hello
return true
}
foo
" #;
let result = parse_execute ( ast ) . await ;
let err = result . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ;
assert_eq! (
err ,
KclError ::Syntax ( KclErrorDetails {
message : " Unexpected token: # " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ::new ( 15 , 16 , ModuleId ::default ( ) ) ] ,
} ) ,
) ;
}
2024-07-22 19:43:40 -04:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_pattern_transform_function_cannot_access_future_definitions ( ) {
let ast = r #"
fn transform = ( replicaId ) = > {
// x shouldn't be defined yet.
let scale = x
return {
translate : [ 0 , 0 , replicaId * 10 ] ,
scale : [ scale , 1 , 0 ] ,
}
}
fn layer = ( ) = > {
return startSketchOn ( " XY " )
2024-09-23 22:42:51 +10:00
| > circle ( { center : [ 0 , 0 ] , radius : 1 } , % , $tag1 )
2024-07-22 19:43:40 -04:00
| > extrude ( 10 , % )
}
const x = 5
// The 10 layers are replicas of each other, with a transform applied to each.
let shape = layer ( ) | > patternTransform ( 10 , transform , % )
" #;
let result = parse_execute ( ast ) . await ;
let err = result . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ;
assert_eq! (
err ,
KclError ::UndefinedValue ( KclErrorDetails {
message : " memory item key `x` is not defined " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 80 , 81 , ModuleId ::default ( ) ) ] ,
2024-07-22 19:43:40 -04:00
} ) ,
) ;
}
2024-11-14 17:27:19 -06:00
// ADAM: Move some of these into simulation tests.
2024-07-22 19:43:40 -04:00
2023-09-13 07:23:14 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_execute_with_functions ( ) {
let ast = r # "const myVar = 2 + min(100, -1 + legLen(5, 3))"# ;
2024-12-10 18:50:22 -08:00
let ( _ , _ , exec_state ) = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( 5.0 , mem_get_json ( & exec_state . memory , " myVar " ) . as_f64 ( ) . unwrap ( ) ) ;
2023-09-13 07:23:14 -07:00
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_execute ( ) {
let ast = r # "const myVar = 1 + 2 * (3 - 4) / -5 + 6"# ;
2024-12-10 18:50:22 -08:00
let ( _ , _ , exec_state ) = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( 7.4 , mem_get_json ( & exec_state . memory , " myVar " ) . as_f64 ( ) . unwrap ( ) ) ;
2023-09-13 07:23:14 -07:00
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_execute_start_negative ( ) {
let ast = r # "const myVar = -5 + 6"# ;
2024-12-10 18:50:22 -08:00
let ( _ , _ , exec_state ) = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( 1.0 , mem_get_json ( & exec_state . memory , " myVar " ) . as_f64 ( ) . unwrap ( ) ) ;
2023-09-13 07:23:14 -07:00
}
2023-09-13 13:10:55 -07:00
2023-09-19 16:05:53 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_execute_with_pi ( ) {
let ast = r # "const myVar = pi() * 2"# ;
2024-12-10 18:50:22 -08:00
let ( _ , _ , exec_state ) = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
std ::f64 ::consts ::TAU ,
mem_get_json ( & exec_state . memory , " myVar " ) . as_f64 ( ) . unwrap ( )
) ;
2023-09-19 16:05:53 -07:00
}
2023-09-13 13:10:55 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_define_decimal_without_leading_zero ( ) {
let ast = r # "let thing = .4 + 7"# ;
2024-12-10 18:50:22 -08:00
let ( _ , _ , exec_state ) = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( 7.4 , mem_get_json ( & exec_state . memory , " thing " ) . as_f64 ( ) . unwrap ( ) ) ;
2023-09-13 13:10:55 -07:00
}
2023-09-15 13:19:53 -07:00
2023-09-20 10:51:49 -05:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_zero_param_fn ( ) {
let ast = r #" const sigmaAllow = 35000 // psi
const leg1 = 5 // inches
const leg2 = 8 // inches
fn thickness = ( ) = > { return 0.56 }
2023-10-05 14:27:48 -07:00
const bracket = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-20 10:51:49 -05:00
| > line ( [ 0 , leg1 ] , % )
| > line ( [ leg2 , 0 ] , % )
| > line ( [ 0 , - thickness ( ) ] , % )
| > line ( [ - leg2 + thickness ( ) , 0 ] , % )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2024-08-14 02:38:37 -04:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_unary_operator_not_succeeds ( ) {
let ast = r #"
fn returnTrue = ( ) = > { return ! false }
const t = true
const f = false
let notTrue = ! t
let notFalse = ! f
let c = ! ! true
let d = ! returnTrue ( )
assert ( ! false , " expected to pass " )
fn check = ( x ) = > {
assert ( ! x , " expected argument to be false " )
return true
}
check ( false )
" #;
2024-12-10 18:50:22 -08:00
let ( _ , _ , exec_state ) = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( false , mem_get_json ( & exec_state . memory , " notTrue " ) . as_bool ( ) . unwrap ( ) ) ;
assert_eq! ( true , mem_get_json ( & exec_state . memory , " notFalse " ) . as_bool ( ) . unwrap ( ) ) ;
assert_eq! ( true , mem_get_json ( & exec_state . memory , " c " ) . as_bool ( ) . unwrap ( ) ) ;
assert_eq! ( false , mem_get_json ( & exec_state . memory , " d " ) . as_bool ( ) . unwrap ( ) ) ;
2024-08-14 02:38:37 -04:00
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_unary_operator_not_on_non_bool_fails ( ) {
let code1 = r #"
// Yup, this is null.
let myNull = 0 / 0
let notNull = ! myNull
" #;
assert_eq! (
parse_execute ( code1 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
2024-11-14 17:27:19 -06:00
message : " Cannot apply unary operator ! to non-boolean value: number " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 56 , 63 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code2 = " let notZero = !0 " ;
assert_eq! (
parse_execute ( code2 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
2024-11-25 10:50:43 +13:00
message : " Cannot apply unary operator ! to non-boolean value: number " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 14 , 16 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code3 = r #"
let notEmptyString = ! " "
" #;
assert_eq! (
parse_execute ( code3 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
2024-11-14 17:27:19 -06:00
message : " Cannot apply unary operator ! to non-boolean value: string (text) " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 22 , 25 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code4 = r #"
let obj = { a : 1 }
let notMember = ! obj . a
" #;
assert_eq! (
parse_execute ( code4 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
2024-11-25 10:50:43 +13:00
message : " Cannot apply unary operator ! to non-boolean value: number " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 36 , 42 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code5 = "
let a = [ ]
let notArray = ! a " ;
assert_eq! (
parse_execute ( code5 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
2024-11-14 17:27:19 -06:00
message : " Cannot apply unary operator ! to non-boolean value: array (list) " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 27 , 29 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code6 = "
let x = { }
let notObject = ! x " ;
assert_eq! (
parse_execute ( code6 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
2024-11-14 17:27:19 -06:00
message : " Cannot apply unary operator ! to non-boolean value: object " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 28 , 30 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code7 = "
fn x = ( ) = > { return 1 }
let notFunction = ! x " ;
let fn_err = parse_execute ( code7 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ;
// These are currently printed out as JSON objects, so we don't want to
// check the full error.
assert! (
fn_err
. message ( )
. starts_with ( " Cannot apply unary operator ! to non-boolean value: " ) ,
" Actual error: {:?} " ,
fn_err
) ;
let code8 = "
let myTagDeclarator = $myTag
let notTagDeclarator = ! myTagDeclarator " ;
let tag_declarator_err = parse_execute ( code8 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ;
// These are currently printed out as JSON objects, so we don't want to
// check the full error.
assert! (
tag_declarator_err
. message ( )
2024-11-14 17:27:19 -06:00
. starts_with ( " Cannot apply unary operator ! to non-boolean value: TagDeclarator " ) ,
2024-08-14 02:38:37 -04:00
" Actual error: {:?} " ,
tag_declarator_err
) ;
let code9 = "
2024-12-13 16:39:40 -05:00
let myTagDeclarator = $myTag
let notTagIdentifier = ! myTag " ;
2024-08-14 02:38:37 -04:00
let tag_identifier_err = parse_execute ( code9 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ;
// These are currently printed out as JSON objects, so we don't want to
// check the full error.
assert! (
tag_identifier_err
. message ( )
2024-11-14 17:27:19 -06:00
. starts_with ( " Cannot apply unary operator ! to non-boolean value: TagIdentifier " ) ,
2024-08-14 02:38:37 -04:00
" Actual error: {:?} " ,
tag_identifier_err
) ;
let code10 = " let notPipe = !(1 |> 2) " ;
assert_eq! (
// TODO: We don't currently parse this, but we should. It should be
// a runtime error instead.
parse_execute ( code10 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Syntax ( KclErrorDetails {
2024-08-20 23:49:19 -04:00
message : " Unexpected token: ! " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 14 , 15 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
let code11 = "
fn identity = ( x ) = > { return x }
let notPipeSub = 1 | > identity ( ! % ) ) " ;
assert_eq! (
// TODO: We don't currently parse this, but we should. It should be
// a runtime error instead.
parse_execute ( code11 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Syntax ( KclErrorDetails {
2024-08-20 23:49:19 -04:00
message : " Unexpected token: |> " . to_owned ( ) ,
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::new ( 54 , 56 , ModuleId ::default ( ) ) ] ,
2024-08-14 02:38:37 -04:00
} )
) ;
// TODO: Add these tests when we support these types.
// let notNan = !NaN
// let notInfinity = !Infinity
}
2023-09-15 13:19:53 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_negative_variable_in_binary_expression ( ) {
let ast = r #" const sigmaAllow = 35000 // psi
const width = 1 // inch
const p = 150 // lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
const thickness = 0.56 // inches. App does not support square root function yet
2023-10-05 14:27:48 -07:00
const bracket = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-15 13:19:53 -07:00
| > line ( [ 0 , leg1 ] , % )
| > line ( [ leg2 , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - leg2 + thickness , 0 ] , % )
2023-09-15 17:40:57 -07:00
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2024-08-01 19:40:22 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_function_no_return ( ) {
let ast = r #" fn test = (origin) => {
origin
}
test ( [ 0 , 0 ] )
" #;
let result = parse_execute ( ast ) . await ;
assert! ( result . is_err ( ) ) ;
2024-12-03 16:39:51 +13:00
assert! ( result
. unwrap_err ( )
. to_string ( )
. contains ( " Result of user-defined function test is undefined " ) , ) ;
2024-08-01 19:40:22 -07:00
}
2023-09-15 17:40:57 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_doubly_nested_parens ( ) {
let ast = r #" const sigmaAllow = 35000 // psi
const width = 4 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = ( distance * p * FOS * 6 / ( sigmaAllow - width ) )
const thickness = 0.32 // inches. App does not support square root function yet
2023-10-05 14:27:48 -07:00
const bracket = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-15 17:40:57 -07:00
| > line ( [ 0 , leg1 ] , % )
| > line ( [ leg2 , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - 1 * leg2 + thickness , 0 ] , % )
| > line ( [ 0 , - 1 * leg1 + thickness ] , % )
| > close ( % )
| > extrude ( width , % )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_math_nested_parens_one_less ( ) {
let ast = r #" const sigmaAllow = 35000 // psi
const width = 4 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / ( sigmaAllow - width )
const thickness = 0.32 // inches. App does not support square root function yet
2023-10-05 14:27:48 -07:00
const bracket = startSketchOn ( ' XY ' )
| > startProfileAt ( [ 0 , 0 ] , % )
2023-09-15 17:40:57 -07:00
| > line ( [ 0 , leg1 ] , % )
| > line ( [ leg2 , 0 ] , % )
| > line ( [ 0 , - thickness ] , % )
| > line ( [ - 1 * leg2 + thickness , 0 ] , % )
| > line ( [ 0 , - 1 * leg1 + thickness ] , % )
| > close ( % )
| > extrude ( width , % )
2023-09-15 13:19:53 -07:00
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
2024-07-29 00:33:31 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_fn_as_operand ( ) {
let ast = r #" fn f = () => { return 1 }
let x = f ( )
let y = x + 1
let z = f ( ) + 1
let w = f ( ) + f ( )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
#[ test ]
fn test_assign_args_to_params ( ) {
// Set up a little framework for this test.
2024-08-12 16:53:24 -05:00
fn mem ( number : usize ) -> KclValue {
2024-11-14 17:27:19 -06:00
KclValue ::Int {
value : number as i64 ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
meta : Default ::default ( ) ,
2024-11-14 17:27:19 -06:00
}
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
}
2024-10-30 16:52:17 -04:00
fn ident ( s : & 'static str ) -> Node < Identifier > {
Node ::no_src ( Identifier {
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
name : s . to_owned ( ) ,
2024-07-09 12:24:42 -04:00
digest : None ,
2024-10-30 16:52:17 -04:00
} )
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
}
fn opt_param ( s : & 'static str ) -> Parameter {
Parameter {
identifier : ident ( s ) ,
2024-03-21 17:14:30 -07:00
type_ : None ,
2024-12-05 21:04:40 -06:00
default_value : Some ( DefaultParamVal ::none ( ) ) ,
labeled : true ,
2024-07-09 12:24:42 -04:00
digest : None ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
}
}
fn req_param ( s : & 'static str ) -> Parameter {
Parameter {
identifier : ident ( s ) ,
2024-03-21 17:14:30 -07:00
type_ : None ,
2024-12-05 21:04:40 -06:00
default_value : None ,
labeled : true ,
2024-07-09 12:24:42 -04:00
digest : None ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
}
}
2024-08-12 16:53:24 -05:00
fn additional_program_memory ( items : & [ ( String , KclValue ) ] ) -> ProgramMemory {
2024-02-11 18:26:09 -08:00
let mut program_memory = ProgramMemory ::new ( ) ;
for ( name , item ) in items {
2024-07-22 19:43:40 -04:00
program_memory
. add ( name . as_str ( ) , item . clone ( ) , SourceRange ::default ( ) )
. unwrap ( ) ;
2024-02-11 18:26:09 -08:00
}
program_memory
}
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
// Declare the test cases.
for ( test_name , params , args , expected ) in [
( " empty " , Vec ::new ( ) , Vec ::new ( ) , Ok ( ProgramMemory ::new ( ) ) ) ,
(
" all params required, and all given, should be OK " ,
vec! [ req_param ( " x " ) ] ,
vec! [ mem ( 1 ) ] ,
2024-02-11 18:26:09 -08:00
Ok ( additional_program_memory ( & [ ( " x " . to_owned ( ) , mem ( 1 ) ) ] ) ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
) ,
(
" all params required, none given, should error " ,
vec! [ req_param ( " x " ) ] ,
vec! [ ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
message : " Expected 1 arguments, got 0 " . to_owned ( ) ,
} ) ) ,
) ,
(
" all params optional, none given, should be OK " ,
vec! [ opt_param ( " x " ) ] ,
vec! [ ] ,
2024-12-05 21:04:40 -06:00
Ok ( additional_program_memory ( & [ ( " x " . to_owned ( ) , KclValue ::none ( ) ) ] ) ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
) ,
(
" mixed params, too few given " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
message : " Expected 1-2 arguments, got 0 " . to_owned ( ) ,
} ) ) ,
) ,
(
" mixed params, minimum given, should be OK " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ mem ( 1 ) ] ,
2024-02-11 18:26:09 -08:00
Ok ( additional_program_memory ( & [
( " x " . to_owned ( ) , mem ( 1 ) ) ,
2024-12-05 21:04:40 -06:00
( " y " . to_owned ( ) , KclValue ::none ( ) ) ,
2024-02-11 18:26:09 -08:00
] ) ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
) ,
(
" mixed params, maximum given, should be OK " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ mem ( 1 ) , mem ( 2 ) ] ,
2024-02-11 18:26:09 -08:00
Ok ( additional_program_memory ( & [
( " x " . to_owned ( ) , mem ( 1 ) ) ,
( " y " . to_owned ( ) , mem ( 2 ) ) ,
] ) ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
) ,
(
" mixed params, too many given " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ mem ( 1 ) , mem ( 2 ) , mem ( 3 ) ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
2024-12-03 16:39:51 +13:00
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
message : " Expected 1-2 arguments, got 3 " . to_owned ( ) ,
} ) ) ,
) ,
] {
// Run each test.
2024-10-30 16:52:17 -04:00
let func_expr = & Node ::no_src ( FunctionExpression {
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
params ,
2024-10-30 16:52:17 -04:00
body : Node {
2024-12-05 17:56:49 +13:00
inner : crate ::parsing ::ast ::types ::Program {
2024-10-30 16:52:17 -04:00
body : Vec ::new ( ) ,
non_code_meta : Default ::default ( ) ,
2024-11-26 16:39:57 +13:00
shebang : None ,
2024-10-30 16:52:17 -04:00
digest : None ,
} ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
start : 0 ,
end : 0 ,
2024-11-07 11:23:41 -05:00
module_id : ModuleId ::default ( ) ,
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
} ,
2024-03-21 17:14:30 -07:00
return_type : None ,
2024-07-09 12:24:42 -04:00
digest : None ,
2024-10-30 16:52:17 -04:00
} ) ;
2024-11-21 13:10:03 -05:00
let args = args . into_iter ( ) . map ( Arg ::synthetic ) . collect ( ) ;
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
let actual = assign_args_to_params ( func_expr , args , ProgramMemory ::new ( ) ) ;
assert_eq! (
actual , expected ,
" failed test '{test_name}': \n got {actual:?} \n but expected \n {expected:?} "
) ;
}
}
2024-02-11 15:08:54 -08:00
#[ test ]
fn test_serialize_memory_item ( ) {
2024-09-27 15:44:44 -07:00
let mem = KclValue ::Solids {
2024-02-11 15:08:54 -08:00
value : Default ::default ( ) ,
} ;
let json = serde_json ::to_string ( & mem ) . unwrap ( ) ;
2024-09-27 15:44:44 -07:00
assert_eq! ( json , r # "{"type":"Solids","value":[]}"# ) ;
2024-02-11 15:08:54 -08:00
}
2024-12-10 18:50:22 -08:00
// Easy case where we have no old ast and memory.
// We need to re-execute everything.
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_no_old_information ( ) {
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , ctx , _ ) = parse_execute ( new ) . await . unwrap ( ) ;
let result = ctx
. get_changed_program ( CacheInformation {
old : None ,
new_ast : program . ast . clone ( ) ,
} )
. await ;
assert! ( result . is_some ( ) ) ;
let result = result . unwrap ( ) ;
assert_eq! ( result . program , program . ast ) ;
assert! ( result . clear_scene ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code ( ) {
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , ctx , exec_state ) = parse_execute ( new ) . await . unwrap ( ) ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program . ast . clone ( ) ,
} )
. await ;
assert_eq! ( result , None ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code_changed_whitespace ( ) {
let old = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program_old , ctx , exec_state ) = parse_execute ( old ) . await . unwrap ( ) ;
let program_new = crate ::Program ::parse_no_errs ( new ) . unwrap ( ) ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program_old . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program_new . ast . clone ( ) ,
} )
. await ;
assert_eq! ( result , None ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code_changed_code_comment_start_of_program ( ) {
let old = r #" // Removed the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , ctx , exec_state ) = parse_execute ( old ) . await . unwrap ( ) ;
let program_new = crate ::Program ::parse_no_errs ( new ) . unwrap ( ) ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program_new . ast . clone ( ) ,
} )
. await ;
assert_eq! ( result , None ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code_changed_code_comments ( ) {
let old = r #" // Removed the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % ) // my thing
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , ctx , exec_state ) = parse_execute ( old ) . await . unwrap ( ) ;
let program_new = crate ::Program ::parse_no_errs ( new ) . unwrap ( ) ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program_new . ast . clone ( ) ,
} )
. await ;
Remove non code from Digests (#4772)
Remove non code from Digests
@jessfraz and I talked it over; for the time being we're going to remove
comments from the AST digest. We already exclude source position, so
this is just increasing the degree to which we're going to ignore things
that are not germane to execution.
Before, we'd digest *some* but not all of the comments in the AST.
Silly, I know, right?
So, this code:
```
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line([-24, 0], %) // my thing
|> close(%)
|> extrude(6, %)
```
Would digest differently than:
```
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line([-24, 0], %)
|> close(%)
|> extrude(6, %)
```
Which is wrong. We've fully divested of hashing code comments, so this
will now hash to be the same. Hooray.
2024-12-12 13:55:09 -05:00
assert! ( result . is_none ( ) ) ;
2024-12-10 18:50:22 -08:00
}
// Changing the units with the exact same file should bust the cache.
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code_but_different_units ( ) {
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , mut ctx , exec_state ) = parse_execute ( new ) . await . unwrap ( ) ;
// Change the settings to cm.
ctx . settings . units = crate ::UnitLength ::Cm ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program . ast . clone ( ) ,
} )
. await ;
assert! ( result . is_some ( ) ) ;
let result = result . unwrap ( ) ;
assert_eq! ( result . program , program . ast ) ;
assert! ( result . clear_scene ) ;
}
// Changing the grid settings with the exact same file should NOT bust the cache.
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code_but_different_grid_setting ( ) {
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , mut ctx , exec_state ) = parse_execute ( new ) . await . unwrap ( ) ;
// Change the settings.
ctx . settings . show_grid = ! ctx . settings . show_grid ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program . ast . clone ( ) ,
} )
. await ;
assert_eq! ( result , None ) ;
}
// Changing the edge visibility settings with the exact same file should NOT bust the cache.
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_get_changed_program_same_code_but_different_edge_visiblity_setting ( ) {
let new = r #" // Remove the end face for the extrusion.
firstSketch = startSketchOn ( ' XY ' )
| > startProfileAt ( [ - 12 , 12 ] , % )
| > line ( [ 24 , 0 ] , % )
| > line ( [ 0 , - 24 ] , % )
| > line ( [ - 24 , 0 ] , % )
| > close ( % )
| > extrude ( 6 , % )
// Remove the end face for the extrusion.
shell ( { faces = [ ' end ' ] , thickness = 0.25 } , firstSketch ) " #;
let ( program , mut ctx , exec_state ) = parse_execute ( new ) . await . unwrap ( ) ;
// Change the settings.
ctx . settings . highlight_edges = ! ctx . settings . highlight_edges ;
let result = ctx
. get_changed_program ( CacheInformation {
old : Some ( OldAstState {
ast : program . ast . clone ( ) ,
exec_state ,
settings : Default ::default ( ) ,
} ) ,
new_ast : program . ast . clone ( ) ,
} )
. await ;
assert_eq! ( result , None ) ;
}
2023-08-24 15:34:51 -07:00
}