2023-08-24 15:34:51 -07:00
//! The executor for the AST.
2023-11-08 20:23:59 -06:00
use std ::{ collections ::HashMap , 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-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 } ;
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
use serde_json ::Value as JValue ;
2023-09-05 16:02:27 -07:00
use tower_lsp ::lsp_types ::{ Position as LspPosition , Range as LspRange } ;
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 > ;
2023-08-24 15:34:51 -07:00
use crate ::{
2024-08-14 01:57:03 -04:00
ast ::types ::{
human_friendly_type , BodyItem , Expr , ExpressionStatement , FunctionExpression , KclNone , Program ,
ReturnStatement , TagDeclarator ,
} ,
2024-03-13 12:56:46 -07:00
engine ::EngineManager ,
2023-08-24 15:34:51 -07:00
errors ::{ KclError , KclErrorDetails } ,
2024-02-12 12:18:37 -08:00
fs ::FileManager ,
2024-06-18 14:38:25 -05:00
settings ::types ::UnitLength ,
2024-07-28 23:49:28 -07:00
std ::{ FnAsArg , StdLib } ,
2023-08-24 15:34:51 -07:00
} ;
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 ,
/// 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 > ,
}
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-07-27 22:56:46 -07:00
pub fn update_tag ( & mut self , tag : & str , value : TagIdentifier ) -> Result < ( ) , KclError > {
2024-08-12 16:53:24 -05:00
self . environments [ self . current_env . index ( ) ] . insert ( tag . to_string ( ) , KclValue ::TagIdentifier ( Box ::new ( value ) ) ) ;
2024-07-27 22:56:46 -07:00
Ok ( ( ) )
}
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 ] ,
} ) )
}
/// Find all extrude groups in the memory that are on a specific sketch group id.
/// 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.
pub fn find_extrude_groups_on_sketch_group ( & self , sketch_group_id : uuid ::Uuid ) -> Vec < Box < ExtrudeGroup > > {
self . environments
. iter ( )
. flat_map ( | env | {
env . bindings
. values ( )
. filter_map ( | item | match item {
2024-08-12 16:53:24 -05:00
KclValue ::ExtrudeGroup ( eg ) if eg . sketch_group . id = = sketch_group_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) ]
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-08-12 16:53:24 -05:00
bindings : HashMap < String , KclValue > ,
2024-07-22 19:43:40 -04:00
parent : Option < EnvironmentRef > ,
}
impl Environment {
pub fn root ( ) -> Self {
Self {
// Prelude
bindings : HashMap ::from ( [
2024-02-11 18:26:09 -08:00
(
" ZERO " . to_string ( ) ,
2024-08-12 16:53:24 -05:00
KclValue ::UserVal ( UserVal {
2024-02-11 18:26:09 -08:00
value : serde_json ::Value ::Number ( serde_json ::value ::Number ::from ( 0 ) ) ,
meta : Default ::default ( ) ,
} ) ,
) ,
(
" QUARTER_TURN " . to_string ( ) ,
2024-08-12 16:53:24 -05:00
KclValue ::UserVal ( UserVal {
2024-02-11 18:26:09 -08:00
value : serde_json ::Value ::Number ( serde_json ::value ::Number ::from ( 90 ) ) ,
meta : Default ::default ( ) ,
} ) ,
) ,
(
" HALF_TURN " . to_string ( ) ,
2024-08-12 16:53:24 -05:00
KclValue ::UserVal ( UserVal {
2024-02-11 18:26:09 -08:00
value : serde_json ::Value ::Number ( serde_json ::value ::Number ::from ( 180 ) ) ,
meta : Default ::default ( ) ,
} ) ,
) ,
(
" THREE_QUARTER_TURN " . to_string ( ) ,
2024-08-12 16:53:24 -05:00
KclValue ::UserVal ( UserVal {
2024-02-11 18:26:09 -08:00
value : serde_json ::Value ::Number ( serde_json ::value ::Number ::from ( 270 ) ) ,
meta : Default ::default ( ) ,
} ) ,
) ,
] ) ,
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 {
bindings : HashMap ::new ( ) ,
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
pub fn update_sketch_group_tags ( & mut self , sg : & SketchGroup ) {
if sg . tags . is_empty ( ) {
return ;
}
for ( _ , val ) in self . bindings . iter_mut ( ) {
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
let KclValue ::UserVal ( v ) = val else { continue } ;
let meta = v . meta . clone ( ) ;
let maybe_sg : Result < SketchGroup , _ > = serde_json ::from_value ( v . value . clone ( ) ) ;
let Ok ( mut sketch_group ) = maybe_sg else {
continue ;
} ;
if sketch_group . original_id = = sg . original_id {
for tag in sg . tags . iter ( ) {
sketch_group . tags . insert ( tag . 0. clone ( ) , tag . 1. clone ( ) ) ;
2024-07-29 21:30:25 -07:00
}
}
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
* val = KclValue ::UserVal ( UserVal {
meta ,
value : serde_json ::to_value ( sketch_group ) . expect ( " can always turn SketchGroup into JSON " ) ,
} ) ;
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 {
pub extrude_group_ids : Vec < ExtrudeGroupLazyIds > ,
}
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-08-12 16:53:24 -05:00
if let KclValue ::ExtrudeGroup ( eg ) = item {
2024-07-29 23:22:52 -04:00
self . extrude_group_ids . push ( ExtrudeGroupLazyIds ::from ( eg . as_ref ( ) ) ) ;
}
}
}
}
2024-08-12 17:56:45 -05:00
pub fn edge_cut_ids_on_sketch_group ( & self , sketch_group_id : uuid ::Uuid ) -> Vec < uuid ::Uuid > {
2024-07-29 23:22:52 -04:00
self . extrude_group_ids
. iter ( )
. flat_map ( | eg | {
if eg . sketch_group_id = = sketch_group_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-02-11 15:08:54 -08:00
/// A memory item.
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 " ) ]
2024-08-12 16:53:24 -05:00
pub enum KclValue {
2023-09-12 18:10:27 -07:00
UserVal ( UserVal ) ,
2024-06-24 14:45:07 -07:00
TagIdentifier ( Box < TagIdentifier > ) ,
TagDeclarator ( Box < TagDeclarator > ) ,
2023-10-05 14:27:48 -07:00
Plane ( Box < Plane > ) ,
2024-02-12 18:08:42 -08:00
Face ( Box < Face > ) ,
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
2023-09-19 14:20:14 -07:00
ExtrudeGroup ( Box < ExtrudeGroup > ) ,
2024-02-11 15:08:54 -08:00
ExtrudeGroups {
value : Vec < Box < ExtrudeGroup > > ,
} ,
2024-02-12 12:18:37 -08:00
ImportedGeometry ( ImportedGeometry ) ,
2023-09-12 18:10:27 -07:00
#[ ts(skip) ]
2023-08-24 15:34:51 -07:00
Function {
#[ serde(skip) ]
func : Option < MemoryFunction > ,
expression : Box < FunctionExpression > ,
2024-07-22 19:43:40 -04:00
memory : Box < ProgramMemory > ,
2023-08-24 15:34:51 -07:00
#[ serde(rename = " __meta " ) ]
meta : Vec < Metadata > ,
} ,
}
2024-08-12 16:53:24 -05:00
impl KclValue {
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 ( crate ) fn new_user_val < T : Serialize > ( meta : Vec < Metadata > , val : T ) -> Self {
2024-09-18 20:23:19 -04:00
Self ::UserVal ( UserVal ::new ( meta , val ) )
2024-06-21 16:44:31 -07:00
}
2024-07-27 22:56:46 -07:00
pub ( crate ) fn get_extrude_group_set ( & self ) -> Result < ExtrudeGroupSet > {
2024-06-21 16:44:31 -07:00
match self {
2024-08-12 16:53:24 -05:00
KclValue ::ExtrudeGroup ( e ) = > Ok ( ExtrudeGroupSet ::ExtrudeGroup ( e . clone ( ) ) ) ,
KclValue ::ExtrudeGroups { value } = > Ok ( ExtrudeGroupSet ::ExtrudeGroups ( value . clone ( ) ) ) ,
KclValue ::UserVal ( value ) = > {
2024-08-15 01:37:33 -04:00
let value = value . value . clone ( ) ;
match value {
JValue ::Null | JValue ::Bool ( _ ) | JValue ::Number ( _ ) | JValue ::String ( _ ) = > Err ( anyhow ::anyhow! (
" Failed to deserialize extrude group set from JSON {} " ,
human_friendly_type ( & value )
) ) ,
JValue ::Array ( _ ) = > serde_json ::from_value ::< Vec < Box < ExtrudeGroup > > > ( value )
. map ( ExtrudeGroupSet ::from )
. map_err ( | e | anyhow ::anyhow! ( " Failed to deserialize array of extrude groups from JSON: {} " , e ) ) ,
JValue ::Object ( _ ) = > serde_json ::from_value ::< Box < ExtrudeGroup > > ( value )
. map ( ExtrudeGroupSet ::from )
. map_err ( | e | anyhow ::anyhow! ( " Failed to deserialize extrude group from JSON: {} " , e ) ) ,
}
2024-06-21 16:44:31 -07:00
}
_ = > anyhow ::bail! ( " Not a extrude group or extrude groups: {:?} " , self ) ,
}
}
2024-08-13 16:25:09 -04:00
/// Human readable type name used in error messages. Should not be relied
/// on for program logic.
pub ( crate ) fn human_friendly_type ( & self ) -> & 'static str {
match self {
KclValue ::UserVal ( u ) = > human_friendly_type ( & u . value ) ,
KclValue ::TagDeclarator ( _ ) = > " TagDeclarator " ,
KclValue ::TagIdentifier ( _ ) = > " TagIdentifier " ,
KclValue ::ExtrudeGroup ( _ ) = > " ExtrudeGroup " ,
KclValue ::ExtrudeGroups { .. } = > " ExtrudeGroups " ,
KclValue ::ImportedGeometry ( _ ) = > " ImportedGeometry " ,
KclValue ::Function { .. } = > " Function " ,
KclValue ::Plane ( _ ) = > " Plane " ,
KclValue ::Face ( _ ) = > " Face " ,
}
}
2024-06-21 16:44:31 -07:00
}
2024-08-12 16:53:24 -05:00
impl From < SketchGroupSet > for KclValue {
2024-06-23 23:04:32 -07:00
fn from ( sg : SketchGroupSet ) -> Self {
2024-09-18 20:23:19 -04:00
KclValue ::UserVal ( UserVal ::new ( sg . meta ( ) , sg ) )
2024-06-23 23:04:32 -07:00
}
}
2024-08-12 16:53:24 -05:00
impl From < Vec < Box < SketchGroup > > > for KclValue {
2024-06-23 23:04:32 -07:00
fn from ( sg : Vec < Box < SketchGroup > > ) -> 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
let meta = sg . iter ( ) . flat_map ( | sg | sg . meta . clone ( ) ) . collect ( ) ;
2024-09-18 20:23:19 -04:00
KclValue ::UserVal ( UserVal ::new ( meta , sg ) )
2024-06-23 23:04:32 -07:00
}
}
2024-08-12 16:53:24 -05:00
impl From < ExtrudeGroupSet > for KclValue {
2024-06-23 23:04:32 -07:00
fn from ( eg : ExtrudeGroupSet ) -> Self {
match eg {
2024-08-12 16:53:24 -05:00
ExtrudeGroupSet ::ExtrudeGroup ( eg ) = > KclValue ::ExtrudeGroup ( eg ) ,
ExtrudeGroupSet ::ExtrudeGroups ( egs ) = > KclValue ::ExtrudeGroups { value : egs } ,
2024-06-23 23:04:32 -07:00
}
}
}
2024-08-12 16:53:24 -05:00
impl From < Vec < Box < ExtrudeGroup > > > for KclValue {
2024-06-23 23:04:32 -07:00
fn from ( eg : Vec < Box < ExtrudeGroup > > ) -> Self {
if eg . len ( ) = = 1 {
2024-08-12 16:53:24 -05:00
KclValue ::ExtrudeGroup ( eg [ 0 ] . clone ( ) )
2024-06-23 23:04:32 -07:00
} else {
2024-08-12 16:53:24 -05:00
KclValue ::ExtrudeGroups { value : eg }
2024-06-23 23:04:32 -07:00
}
}
}
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 {
SketchGroup ( Box < SketchGroup > ) ,
ExtrudeGroup ( Box < ExtrudeGroup > ) ,
}
impl Geometry {
pub fn id ( & self ) -> uuid ::Uuid {
match self {
Geometry ::SketchGroup ( s ) = > s . id ,
Geometry ::ExtrudeGroup ( e ) = > e . id ,
}
}
}
/// A set of geometry.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " ) ]
pub enum Geometries {
SketchGroups ( Vec < Box < SketchGroup > > ) ,
ExtrudeGroups ( Vec < Box < ExtrudeGroup > > ) ,
}
/// A sketch group or a group of sketch groups.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub enum SketchGroupSet {
SketchGroup ( Box < SketchGroup > ) ,
SketchGroups ( Vec < Box < SketchGroup > > ) ,
}
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
impl SketchGroupSet {
pub fn meta ( & self ) -> Vec < Metadata > {
match self {
SketchGroupSet ::SketchGroup ( sg ) = > sg . meta . clone ( ) ,
SketchGroupSet ::SketchGroups ( sg ) = > sg . iter ( ) . flat_map ( | sg | sg . meta . clone ( ) ) . collect ( ) ,
}
}
}
impl From < SketchGroupSet > for Vec < SketchGroup > {
fn from ( value : SketchGroupSet ) -> Self {
match value {
SketchGroupSet ::SketchGroup ( sg ) = > vec! [ * sg ] ,
SketchGroupSet ::SketchGroups ( sgs ) = > sgs . into_iter ( ) . map ( | sg | * sg ) . collect ( ) ,
}
}
}
2024-06-23 23:04:32 -07:00
impl From < SketchGroup > for SketchGroupSet {
fn from ( sg : SketchGroup ) -> Self {
SketchGroupSet ::SketchGroup ( Box ::new ( sg ) )
}
}
impl From < Box < SketchGroup > > for SketchGroupSet {
fn from ( sg : Box < SketchGroup > ) -> Self {
SketchGroupSet ::SketchGroup ( sg )
}
}
impl From < Vec < SketchGroup > > for SketchGroupSet {
fn from ( sg : Vec < SketchGroup > ) -> Self {
if sg . len ( ) = = 1 {
SketchGroupSet ::SketchGroup ( Box ::new ( sg [ 0 ] . clone ( ) ) )
} else {
SketchGroupSet ::SketchGroups ( sg . into_iter ( ) . map ( Box ::new ) . collect ( ) )
}
}
}
impl From < Vec < Box < SketchGroup > > > for SketchGroupSet {
fn from ( sg : Vec < Box < SketchGroup > > ) -> Self {
if sg . len ( ) = = 1 {
SketchGroupSet ::SketchGroup ( sg [ 0 ] . clone ( ) )
} else {
SketchGroupSet ::SketchGroups ( sg )
}
}
}
impl From < SketchGroupSet > for Vec < Box < SketchGroup > > {
fn from ( sg : SketchGroupSet ) -> Self {
match sg {
SketchGroupSet ::SketchGroup ( sg ) = > vec! [ sg ] ,
SketchGroupSet ::SketchGroups ( sgs ) = > sgs ,
}
}
}
impl From < & SketchGroup > for Vec < Box < SketchGroup > > {
fn from ( sg : & SketchGroup ) -> Self {
vec! [ Box ::new ( sg . clone ( ) ) ]
}
}
impl From < Box < SketchGroup > > for Vec < Box < SketchGroup > > {
fn from ( sg : Box < SketchGroup > ) -> Self {
vec! [ sg ]
}
}
2024-04-23 10:31:20 -07:00
/// A extrude group or a group of extrude groups.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub enum ExtrudeGroupSet {
ExtrudeGroup ( Box < ExtrudeGroup > ) ,
ExtrudeGroups ( Vec < Box < ExtrudeGroup > > ) ,
}
2024-06-23 23:04:32 -07:00
impl From < ExtrudeGroup > for ExtrudeGroupSet {
fn from ( eg : ExtrudeGroup ) -> Self {
ExtrudeGroupSet ::ExtrudeGroup ( Box ::new ( eg ) )
}
}
impl From < Box < ExtrudeGroup > > for ExtrudeGroupSet {
fn from ( eg : Box < ExtrudeGroup > ) -> Self {
ExtrudeGroupSet ::ExtrudeGroup ( eg )
}
}
impl From < Vec < ExtrudeGroup > > for ExtrudeGroupSet {
fn from ( eg : Vec < ExtrudeGroup > ) -> Self {
if eg . len ( ) = = 1 {
ExtrudeGroupSet ::ExtrudeGroup ( Box ::new ( eg [ 0 ] . clone ( ) ) )
} else {
ExtrudeGroupSet ::ExtrudeGroups ( eg . into_iter ( ) . map ( Box ::new ) . collect ( ) )
}
}
}
impl From < Vec < Box < ExtrudeGroup > > > for ExtrudeGroupSet {
fn from ( eg : Vec < Box < ExtrudeGroup > > ) -> Self {
if eg . len ( ) = = 1 {
ExtrudeGroupSet ::ExtrudeGroup ( eg [ 0 ] . clone ( ) )
} else {
ExtrudeGroupSet ::ExtrudeGroups ( eg )
}
}
}
impl From < ExtrudeGroupSet > for Vec < Box < ExtrudeGroup > > {
fn from ( eg : ExtrudeGroupSet ) -> Self {
match eg {
ExtrudeGroupSet ::ExtrudeGroup ( eg ) = > vec! [ eg ] ,
ExtrudeGroupSet ::ExtrudeGroups ( egs ) = > egs ,
}
}
}
impl From < & ExtrudeGroup > for Vec < Box < ExtrudeGroup > > {
fn from ( eg : & ExtrudeGroup ) -> Self {
vec! [ Box ::new ( eg . clone ( ) ) ]
}
}
impl From < Box < ExtrudeGroup > > for Vec < Box < ExtrudeGroup > > {
fn from ( eg : Box < ExtrudeGroup > ) -> Self {
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-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-06-23 19:19:24 -07:00
/// The extrude group the face is on.
pub extrude_group : Box < ExtrudeGroup > ,
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 ,
}
2023-08-25 13:41:04 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
2023-09-12 18:10:27 -07:00
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub struct UserVal {
2024-01-11 15:31:35 -08:00
#[ ts(type = " any " ) ]
2023-09-12 18:10:27 -07:00
pub value : serde_json ::Value ,
#[ serde(rename = " __meta " ) ]
pub meta : Vec < Metadata > ,
}
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
impl UserVal {
2024-09-18 20:23:19 -04:00
pub fn new < T : serde ::Serialize > ( meta : Vec < Metadata > , val : T ) -> Self {
Self {
meta ,
value : serde_json ::to_value ( val ) . expect ( " all KCL values should be compatible with JSON " ) ,
}
}
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
/// If the UserVal matches the type `T`, return it.
pub fn get < T : serde ::de ::DeserializeOwned > ( & self ) -> Option < ( T , Vec < Metadata > ) > {
let meta = self . meta . clone ( ) ;
// TODO: This clone might cause performance problems, it'll happen a lot.
let res : Result < T , _ > = serde_json ::from_value ( self . value . clone ( ) ) ;
if let Ok ( t ) = res {
Some ( ( t , meta ) )
} else {
None
}
}
/// If the UserVal matches the type `T`, then mutate it via the given closure.
/// If the closure returns Err, the mutation won't be applied.
pub fn mutate < T , F , E > ( & mut self , mutate : F ) -> Result < ( ) , E >
where
T : serde ::de ::DeserializeOwned + Serialize ,
F : FnOnce ( & mut T ) -> Result < ( ) , E > ,
{
let Some ( ( mut val , meta ) ) = self . get ::< T > ( ) else {
return Ok ( ( ) ) ;
} ;
mutate ( & mut val ) ? ;
2024-09-18 20:23:19 -04:00
* self = Self ::new ( meta , val ) ;
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
Ok ( ( ) )
}
}
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-08-12 16:53:24 -05:00
s : Vec < KclValue > ,
2024-07-05 16:53:13 -07:00
memory : ProgramMemory ,
expression : Box < FunctionExpression > ,
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-08-12 16:53:24 -05:00
impl From < KclValue > for Vec < SourceRange > {
fn from ( item : KclValue ) -> Self {
2023-08-24 15:34:51 -07:00
match item {
2024-08-12 16:53:24 -05:00
KclValue ::UserVal ( u ) = > u . meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
KclValue ::TagDeclarator ( t ) = > t . into ( ) ,
KclValue ::TagIdentifier ( t ) = > t . meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
KclValue ::ExtrudeGroup ( e ) = > e . meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
KclValue ::ExtrudeGroups { value } = > value
2024-02-11 15:08:54 -08:00
. iter ( )
. flat_map ( | eg | eg . meta . iter ( ) . map ( | m | m . source_range ) )
. collect ( ) ,
2024-08-12 16:53:24 -05:00
KclValue ::ImportedGeometry ( i ) = > i . meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
KclValue ::Function { meta , .. } = > meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
KclValue ::Plane ( p ) = > p . meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
KclValue ::Face ( f ) = > f . meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ,
2023-08-24 15:34:51 -07:00
}
}
}
2024-08-12 16:53:24 -05:00
impl KclValue {
2023-08-24 15:34:51 -07:00
pub fn get_json_value ( & self ) -> Result < serde_json ::Value , KclError > {
2024-08-12 16:53:24 -05:00
if let KclValue ::UserVal ( user_val ) = self {
2023-09-12 18:10:27 -07:00
Ok ( user_val . value . clone ( ) )
2023-08-24 15:34:51 -07:00
} else {
2023-09-19 16:05:53 -07:00
serde_json ::to_value ( self ) . map_err ( | err | {
KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Cannot convert memory item to json value: {:?} " , err ) ,
source_ranges : self . clone ( ) . into ( ) ,
} )
} )
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
/// Get a JSON value and deserialize it into some concrete type.
pub fn get_json < T : serde ::de ::DeserializeOwned > ( & self ) -> Result < T , KclError > {
let json = self . get_json_value ( ) ? ;
serde_json ::from_value ( json ) . map_err ( | e | {
KclError ::Type ( KclErrorDetails {
message : format ! ( " Failed to deserialize struct from JSON: {} " , e ) ,
source_ranges : self . clone ( ) . into ( ) ,
} )
} )
}
/// Get a JSON value and deserialize it into some concrete type.
/// If it's a KCL None, return None. Otherwise return Some.
pub fn get_json_opt < T : serde ::de ::DeserializeOwned > ( & self ) -> Result < Option < T > , KclError > {
let json = self . get_json_value ( ) ? ;
if let JValue ::Object ( ref o ) = json {
if let Some ( JValue ::String ( s ) ) = o . get ( " type " ) {
if s = = " KclNone " {
return Ok ( None ) ;
}
}
}
serde_json ::from_value ( json )
. map_err ( | e | {
KclError ::Type ( KclErrorDetails {
message : format ! ( " Failed to deserialize struct from JSON: {} " , e ) ,
source_ranges : self . clone ( ) . into ( ) ,
} )
} )
. map ( Some )
}
2024-07-19 20:30:13 -05:00
pub fn as_user_val ( & self ) -> Option < & UserVal > {
2024-08-12 16:53:24 -05:00
if let KclValue ::UserVal ( x ) = self {
2024-06-27 22:20:51 -05:00
Some ( x )
} else {
None
}
}
/// If this value is of type u32, return it.
pub fn get_u32 ( & self , source_ranges : Vec < SourceRange > ) -> Result < u32 , KclError > {
let err = KclError ::Semantic ( KclErrorDetails {
message : " Expected an integer >= 0 " . to_owned ( ) ,
source_ranges ,
} ) ;
self . as_user_val ( )
. and_then ( | uv | uv . value . as_number ( ) )
. and_then ( | n | n . as_u64 ( ) )
. and_then ( | n | u32 ::try_from ( n ) . ok ( ) )
. ok_or ( err )
}
/// If this value is of type function, return it.
2024-07-19 20:30:13 -05:00
pub fn get_function ( & self ) -> Option < FnAsArg < '_ > > {
2024-08-12 16:53:24 -05:00
let KclValue ::Function {
2024-06-27 22:20:51 -05:00
func ,
expression ,
2024-07-22 19:43:40 -04:00
memory ,
2024-06-27 22:20:51 -05:00
meta : _ ,
} = & self
else {
2024-07-19 20:30:13 -05:00
return None ;
2024-06-27 22:20:51 -05:00
} ;
2024-07-19 20:30:13 -05:00
Some ( FnAsArg {
2024-09-16 15:10:33 -04:00
func : func . as_ref ( ) ,
2024-06-27 22:20:51 -05:00
expr : expression . to_owned ( ) ,
2024-07-22 19:43:40 -04:00
memory : memory . to_owned ( ) ,
2024-06-27 22:20:51 -05:00
} )
}
2024-07-27 17:59:41 -07:00
/// Get a tag identifier from a memory item.
2024-06-24 14:45:07 -07:00
pub fn get_tag_identifier ( & self ) -> Result < TagIdentifier , KclError > {
match self {
2024-08-12 16:53:24 -05:00
KclValue ::TagIdentifier ( t ) = > Ok ( * t . clone ( ) ) ,
KclValue ::UserVal ( _ ) = > {
2024-07-05 16:53:13 -07:00
if let Some ( identifier ) = self . get_json_opt ::< TagIdentifier > ( ) ? {
Ok ( identifier )
} else {
2024-07-27 17:59:41 -07:00
Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Not a tag identifier: {:?} " , self ) ,
source_ranges : self . clone ( ) . into ( ) ,
} ) )
2024-07-05 16:53:13 -07:00
}
2024-06-24 14:45:07 -07:00
}
_ = > Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Not a tag identifier: {:?} " , self ) ,
source_ranges : self . clone ( ) . into ( ) ,
} ) ) ,
}
}
2024-07-27 17:59:41 -07:00
/// Get a tag declarator from a memory item.
2024-06-24 14:45:07 -07:00
pub fn get_tag_declarator ( & self ) -> Result < TagDeclarator , KclError > {
match self {
2024-08-12 16:53:24 -05:00
KclValue ::TagDeclarator ( t ) = > Ok ( * t . clone ( ) ) ,
2024-06-24 14:45:07 -07:00
_ = > Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Not a tag declarator: {:?} " , self ) ,
source_ranges : self . clone ( ) . into ( ) ,
} ) ) ,
}
}
2024-07-27 17:59:41 -07:00
/// Get an optional tag from a memory item.
2024-06-24 14:45:07 -07:00
pub fn get_tag_declarator_opt ( & self ) -> Result < Option < TagDeclarator > , KclError > {
match self {
2024-08-12 16:53:24 -05:00
KclValue ::TagDeclarator ( t ) = > Ok ( Some ( * t . clone ( ) ) ) ,
2024-06-24 14:45:07 -07:00
_ = > Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Not a tag declarator: {:?} " , self ) ,
source_ranges : self . clone ( ) . into ( ) ,
} ) ) ,
}
}
2023-11-08 13:44:31 -06:00
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
/// If it's not a function, return Err.
2023-09-20 18:27:08 -07:00
pub async fn call_fn (
2023-08-24 15:34:51 -07:00
& self ,
2024-08-12 16:53:24 -05:00
args : Vec < KclValue > ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2023-10-05 14:27:48 -07:00
ctx : ExecutorContext ,
2024-08-12 17:55:05 -05:00
) -> Result < Option < KclValue > , KclError > {
2024-08-12 16:53:24 -05:00
let KclValue ::Function {
2024-07-22 19:43:40 -04:00
func ,
expression ,
memory : closure_memory ,
meta ,
} = & self
else {
2023-11-08 13:44:31 -06:00
return Err ( KclError ::Semantic ( KclErrorDetails {
2023-09-20 18:27:08 -07:00
message : " not a in memory function " . to_string ( ) ,
2023-08-24 15:34:51 -07:00
source_ranges : vec ! [ ] ,
2023-11-08 13:44:31 -06:00
} ) ) ;
} ;
2024-09-16 15:10:33 -04:00
if let Some ( func ) = func {
func (
args ,
closure_memory . as_ref ( ) . clone ( ) ,
expression . clone ( ) ,
meta . clone ( ) ,
exec_state ,
ctx ,
)
. await
} else {
call_user_defined_function ( args , closure_memory . as_ref ( ) , expression . as_ref ( ) , exec_state , & ctx ) . await
}
2023-08-24 15:34:51 -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 ,
/// The sketch group the tag is on.
pub sketch_group : uuid ::Uuid ,
/// The path the tag is on.
2024-07-28 00:30:04 -07:00
pub path : Option < BasePath > ,
2024-07-27 22:56:46 -07:00
/// The surface information for the tag.
pub surface : Option < ExtrudeSurface > ,
}
2023-08-25 13:41:04 -07:00
/// A sketch group is a collection of paths.
#[ 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 " ) ]
2023-08-24 15:34:51 -07:00
pub struct SketchGroup {
2024-07-29 21:30:25 -07:00
/// The id of the sketch group (this will change when the engine's reference to it changes.
2023-08-24 15:34:51 -07:00
pub id : uuid ::Uuid ,
2023-08-25 13:41:04 -07:00
/// The paths in the sketch group.
2023-08-24 15:34:51 -07:00
pub value : 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-07-05 16:53:13 -07:00
/// Tag identifiers that have been declared in this sketch group.
#[ serde(default, skip_serializing_if = " HashMap::is_empty " ) ]
pub tags : HashMap < String , TagIdentifier > ,
2024-07-29 21:30:25 -07:00
/// The original id of the sketch group. This stays the same even if the sketch group is
/// 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-02-16 16:42:01 -08:00
/// A sketch group type.
#[ 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-02-11 12:59:00 +11:00
pub struct GetTangentialInfoFromPathsResult {
pub center_or_tangent_point : [ f64 ; 2 ] ,
pub is_center : bool ,
pub ccw : bool ,
}
2023-08-24 15:34:51 -07:00
impl SketchGroup {
2024-07-27 22:56:46 -07:00
pub ( crate ) fn add_tag ( & mut self , tag : & TagDeclarator , current_path : & Path ) {
let mut tag_identifier : TagIdentifier = tag . into ( ) ;
let base = current_path . get_base ( ) ;
tag_identifier . info = Some ( TagEngineInfo {
id : base . geo_meta . id ,
sketch_group : self . id ,
2024-07-28 00:30:04 -07:00
path : Some ( base . clone ( ) ) ,
2024-07-27 22:56:46 -07:00
surface : None ,
} ) ;
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-05-30 17:48:59 -05:00
self . value . last ( )
}
/// 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-02-11 12:59:00 +11:00
return GetTangentialInfoFromPathsResult {
center_or_tangent_point : self . start . to ,
is_center : false ,
ccw : false ,
} ;
2024-05-30 17:48:59 -05:00
} ;
match path {
2024-08-07 18:35:41 -05:00
Path ::TangentialArc { center , ccw , .. } = > GetTangentialInfoFromPathsResult {
center_or_tangent_point : * center ,
is_center : true ,
ccw : * ccw ,
} ,
2024-05-30 17:48:59 -05:00
Path ::TangentialArcTo { center , ccw , .. } = > GetTangentialInfoFromPathsResult {
center_or_tangent_point : * center ,
is_center : true ,
ccw : * ccw ,
} ,
_ = > {
let base = path . get_base ( ) ;
GetTangentialInfoFromPathsResult {
center_or_tangent_point : base . from ,
is_center : false ,
ccw : false ,
2024-02-11 12:59:00 +11:00
}
}
}
}
2023-08-24 15:34:51 -07:00
}
2023-08-25 13:41:04 -07:00
/// An extrude group is a collection of extrude surfaces.
#[ 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 " ) ]
2023-08-24 15:34:51 -07:00
pub struct ExtrudeGroup {
2023-08-25 13:41:04 -07:00
/// The id of the extrude group.
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-06-21 23:50:30 -07:00
/// The sketch group.
pub sketch_group : SketchGroup ,
2023-08-25 13:41:04 -07:00
/// The height of the extrude group.
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-06-23 19:19:24 -07:00
/// Chamfers or fillets on this extrude group.
#[ 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 > ,
}
impl ExtrudeGroup {
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-07-29 23:22:52 -04:00
/// An extrude group ID and its fillet and chamfer IDs. This is needed for lazy
/// fillet evaluation.
#[ derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema) ]
pub struct ExtrudeGroupLazyIds {
pub extrude_group_id : uuid ::Uuid ,
pub sketch_group_id : uuid ::Uuid ,
/// Chamfers or fillets on this extrude group.
#[ 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
}
impl From < & ExtrudeGroup > for ExtrudeGroupLazyIds {
fn from ( eg : & ExtrudeGroup ) -> Self {
Self {
extrude_group_id : eg . id ,
sketch_group_id : eg . sketch_group . 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-07-28 00:30:04 -07:00
tag : Box < Option < TagDeclarator > > ,
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-07-09 12:24:42 -04:00
tag : Box < Option < TagDeclarator > > ,
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-06-24 14:45:07 -07:00
pub fn tag ( & self ) -> Option < TagDeclarator > {
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 ,
}
2023-09-05 16:02:27 -07:00
#[ derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq) ]
2024-06-19 17:32:08 -07:00
#[ cfg_attr(feature = " pyo3 " , pyo3::pyclass) ]
2023-08-24 15:34:51 -07:00
#[ ts(export) ]
2023-09-12 18:10:27 -07:00
pub struct SourceRange ( #[ ts(type = " [number, number] " ) ] pub [ usize ; 2 ] ) ;
2023-08-24 15:34:51 -07:00
2024-07-29 20:40:07 -05:00
impl From < [ usize ; 2 ] > for SourceRange {
fn from ( value : [ usize ; 2 ] ) -> Self {
Self ( value )
}
}
2023-09-05 16:02:27 -07:00
impl SourceRange {
/// Create a new source range.
pub fn new ( start : usize , end : usize ) -> Self {
Self ( [ start , end ] )
}
/// Get the start of the range.
pub fn start ( & self ) -> usize {
self . 0 [ 0 ]
}
/// Get the end of the range.
pub fn end ( & self ) -> usize {
self . 0 [ 1 ]
}
/// Check if the range contains a position.
pub fn contains ( & self , pos : usize ) -> bool {
pos > = self . start ( ) & & pos < = self . end ( )
}
pub fn start_to_lsp_position ( & self , code : & str ) -> LspPosition {
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
2024-06-21 15:06:01 -07:00
let mut line = code . get ( .. self . start ( ) ) . unwrap_or_default ( ) . lines ( ) . count ( ) ;
2023-09-05 16:02:27 -07:00
if line > 0 {
line = line . saturating_sub ( 1 ) ;
}
let column = code [ .. self . start ( ) ] . lines ( ) . last ( ) . map ( | l | l . len ( ) ) . unwrap_or_default ( ) ;
LspPosition {
line : line as u32 ,
character : column as u32 ,
}
}
pub fn end_to_lsp_position ( & self , code : & str ) -> LspPosition {
2024-06-21 15:06:01 -07:00
let lines = code . get ( .. self . end ( ) ) . unwrap_or_default ( ) . lines ( ) ;
2024-04-15 17:18:32 -07:00
if lines . clone ( ) . count ( ) = = 0 {
return LspPosition { line : 0 , character : 0 } ;
}
2023-09-05 16:02:27 -07:00
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
2024-04-15 17:18:32 -07:00
let line = lines . clone ( ) . count ( ) - 1 ;
let column = lines . last ( ) . map ( | l | l . len ( ) ) . unwrap_or_default ( ) ;
2023-09-05 16:02:27 -07:00
LspPosition {
line : line as u32 ,
character : column as u32 ,
}
}
pub fn to_lsp_range ( & self , code : & str ) -> LspRange {
let start = self . start_to_lsp_position ( code ) ;
let end = self . end_to_lsp_position ( code ) ;
LspRange { start , end }
}
}
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-06-24 14:45:07 -07:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq) ]
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 ,
}
impl From < SourceRange > for Metadata {
fn from ( source_range : SourceRange ) -> Self {
Self { source_range }
}
}
2024-08-14 01:57:03 -04:00
impl From < & ExpressionStatement > for Metadata {
fn from ( exp_statement : & ExpressionStatement ) -> Self {
Self {
source_range : SourceRange ::new ( exp_statement . start , exp_statement . end ) ,
}
}
}
impl From < & ReturnStatement > for Metadata {
fn from ( return_statement : & ReturnStatement ) -> Self {
Self {
source_range : SourceRange ::new ( return_statement . start , return_statement . end ) ,
}
}
}
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.
pub tag : Option < TagDeclarator > ,
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
// Maybe this one's not needed since it's a full revolution?
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 ,
} ,
}
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 ,
2023-08-24 15:34:51 -07:00
}
}
2024-06-24 14:45:07 -07:00
pub fn get_tag ( & self ) -> Option < TagDeclarator > {
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 ( ) ,
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 ,
2023-08-24 15:34:51 -07: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-02-12 18:08:42 -08:00
}
}
2023-08-24 15:34:51 -07:00
}
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.
pub tag : Option < TagDeclarator > ,
/// 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.
pub tag : Option < TagDeclarator > ,
/// 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.
pub tag : Option < 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.
pub tag : Option < 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-06-24 14:45:07 -07:00
pub fn get_tag ( & self ) -> Option < 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
}
}
}
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-03-26 19:32:31 -07:00
/// Mock mode is only for the modeling app when they just want to mock engine calls and not
/// actually make them.
pub is_mock : bool ,
2023-10-05 14:27:48 -07:00
}
2024-04-25 00:13:09 -07:00
/// The executor settings.
#[ derive(Debug, Clone) ]
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-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-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-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-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-04-25 00:13:09 -07:00
}
}
}
2024-02-12 12:18:37 -08:00
impl ExecutorContext {
/// Create a new default executor context.
2024-08-23 07:50:30 -05:00
/// Also returns the response HTTP headers from the server.
2024-02-12 12:18:37 -08:00
#[ 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 ? ) ) ;
// Set the edge visibility.
engine
2024-06-19 13:57:50 -07:00
. batch_modeling_cmd (
2024-04-25 02:31:18 -07:00
uuid ::Uuid ::new_v4 ( ) ,
SourceRange ::default ( ) ,
2024-09-18 17:04:04 -05:00
& ModelingCmd ::from ( mcmd ::EdgeLinesVisible {
2024-04-25 02:31:18 -07:00
hidden : ! settings . highlight_edges ,
2024-09-18 17:04:04 -05:00
} ) ,
2024-04-25 02:31:18 -07:00
)
. 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-03-26 19:32:31 -07:00
is_mock : false ,
2024-08-23 17:40:30 -05:00
} )
2024-02-12 12:18:37 -08:00
}
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 > {
let user_agent = concat! ( env! ( " CARGO_PKG_NAME " ) , " .rs/ " , env! ( " CARGO_PKG_VERSION " ) ) ;
2024-06-18 14:38:25 -05:00
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 token = std ::env ::var ( " KITTYCAD_API_TOKEN " ) . expect ( " KITTYCAD_API_TOKEN not set " ) ;
// Create the client.
let mut client = kittycad ::Client ::new_from_reqwest ( token , http_client , ws_client ) ;
// Set a local engine address if it's set.
if let Ok ( addr ) = std ::env ::var ( " LOCAL_ENGINE_ADDR " ) {
client . set_base_url ( addr ) ;
}
2024-07-29 13:43:27 -05:00
if let Some ( addr ) = engine_addr {
client . set_base_url ( addr ) ;
}
2024-06-18 14:38:25 -05:00
let ctx = ExecutorContext ::new (
& client ,
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-06-18 14:38:25 -05:00
} ,
)
. await ? ;
Ok ( ctx )
}
2024-07-29 20:40:07 -05:00
pub async fn reset_scene ( & self , source_range : crate ::executor ::SourceRange ) -> Result < ( ) > {
self . engine . clear_scene ( source_range ) . await ? ;
2024-06-18 14:38:25 -05:00
Ok ( ( ) )
}
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.
pub async fn run (
& self ,
2024-06-26 14:51:47 -07:00
program : & crate ::ast ::types ::Program ,
2024-04-12 21:32:57 -07:00
memory : Option < ProgramMemory > ,
2024-09-16 15:10:33 -04:00
) -> Result < ExecState , KclError > {
2024-08-23 17:40:30 -05:00
self . run_with_session_data ( program , memory ) . await . map ( | x | x . 0 )
}
/// 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 ,
program : & crate ::ast ::types ::Program ,
memory : Option < ProgramMemory > ,
2024-09-16 15:10:33 -04:00
) -> Result < ( ExecState , Option < ModelingSessionData > ) , KclError > {
2024-04-12 21:32:57 -07:00
// Before we even start executing the program, set the units.
self . engine
2024-06-19 13:57:50 -07:00
. batch_modeling_cmd (
2024-04-12 21:32:57 -07:00
uuid ::Uuid ::new_v4 ( ) ,
SourceRange ::default ( ) ,
2024-09-18 17:04:04 -05:00
& ModelingCmd ::from ( mcmd ::SetSceneUnits {
unit : match self . settings . units {
UnitLength ::Cm = > kcmc ::units ::UnitLength ::Centimeters ,
UnitLength ::Ft = > kcmc ::units ::UnitLength ::Feet ,
UnitLength ::In = > kcmc ::units ::UnitLength ::Inches ,
UnitLength ::M = > kcmc ::units ::UnitLength ::Meters ,
UnitLength ::Mm = > kcmc ::units ::UnitLength ::Millimeters ,
UnitLength ::Yd = > kcmc ::units ::UnitLength ::Yards ,
} ,
} ) ,
2024-04-12 21:32:57 -07:00
)
. await ? ;
2024-09-16 15:10:33 -04:00
let memory = if let Some ( memory ) = memory {
2024-04-12 21:32:57 -07:00
memory . clone ( )
} else {
Default ::default ( )
} ;
2024-09-16 15:10:33 -04:00
let mut exec_state = ExecState {
memory ,
.. Default ::default ( )
} ;
self . inner_execute ( program , & mut exec_state , crate ::executor ::BodyType ::Root )
2024-08-23 17:40:30 -05:00
. await ? ;
let session_data = self . engine . get_session_data ( ) ;
2024-09-16 15:10:33 -04:00
Ok ( ( exec_state , 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 ]
pub ( crate ) async fn inner_execute (
& self ,
2024-06-26 14:51:47 -07:00
program : & crate ::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-16 15:10:33 -04:00
) -> Result < ( ) , KclError > {
2024-04-12 21:32:57 -07:00
// Iterate over the body of the program.
for statement in & program . body {
match statement {
BodyItem ::ExpressionStatement ( expression_statement ) = > {
2024-08-14 01:57:03 -04:00
let metadata = Metadata ::from ( expression_statement ) ;
// Discard return value.
self . execute_expr (
& expression_statement . expression ,
2024-09-16 15:10:33 -04:00
exec_state ,
2024-08-14 01:57:03 -04:00
& metadata ,
StatementKind ::Expression ,
)
. await ? ;
2024-04-12 21:32:57 -07:00
}
BodyItem ::VariableDeclaration ( variable_declaration ) = > {
for declaration in & variable_declaration . declarations {
let var_name = declaration . id . name . to_string ( ) ;
2024-08-14 01:57:03 -04:00
let source_range = SourceRange ::from ( & declaration . init ) ;
2024-04-12 21:32:57 -07:00
let metadata = Metadata { source_range } ;
2024-05-23 14:50:22 -05:00
let memory_item = self
2024-08-14 01:57:03 -04:00
. execute_expr (
2024-05-23 14:50:22 -05:00
& declaration . init ,
2024-09-16 15:10:33 -04:00
exec_state ,
2024-05-23 14:50:22 -05:00
& metadata ,
StatementKind ::Declaration { name : & var_name } ,
)
. await ? ;
2024-09-16 15:10:33 -04:00
exec_state . memory . add ( & var_name , memory_item , source_range ) ? ;
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-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 ,
SourceRange ( [ program . end , program . end ] ) ,
)
. await ? ;
2024-05-22 18:50:54 -05:00
}
2024-04-12 21:32:57 -07:00
2024-09-16 15:10:33 -04:00
Ok ( ( ) )
2024-04-12 21:32:57 -07:00
}
2024-03-23 15:45:55 -07:00
2024-08-14 01:57:03 -04:00
pub async fn execute_expr < ' a > (
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-09-16 15:10:33 -04: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 ? ,
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 ? ,
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-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
/// Execute the program, then get a PNG screenshot.
2024-09-18 17:04:04 -05:00
pub async fn execute_and_prepare_snapshot ( & self , program : & Program ) -> Result < TakeSnapshot > {
2024-06-06 16:01:41 -05:00
let _ = self . run ( program , None ) . await ? ;
// Zoom to fit.
self . engine
. send_modeling_cmd (
uuid ::Uuid ::new_v4 ( ) ,
crate ::executor ::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 ( ) ,
crate ::executor ::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 {
anyhow ::bail! ( " Unexpected response from engine: {:?} " , resp ) ;
} ;
2024-09-18 17:04:04 -05:00
Ok ( contents )
2024-06-06 16:01:41 -05:00
}
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 (
function_expression : & FunctionExpression ,
2024-08-12 16:53:24 -05:00
args : Vec < KclValue > ,
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.
fn_memory . add ( & param . identifier . name , arg . clone ( ) , ( & param . identifier ) . into ( ) ) ? ;
} else {
// Argument was not provided.
if param . optional {
// If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it.
2024-08-29 13:41:32 -05:00
let none = KclNone ::new ( param . identifier . start , param . identifier . end ) ;
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_memory . add (
& param . identifier . name ,
2024-08-12 16:53:24 -05:00
KclValue ::from ( & 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
( & 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-09-16 15:10:33 -04:00
pub ( crate ) async fn call_user_defined_function (
args : Vec < KclValue > ,
memory : & ProgramMemory ,
function_expression : & 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 ( function_expression , args , body_memory ) ? ;
// Execute the function body using the memory we just created.
let ( result , fn_memory ) = {
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_ )
}
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-02-11 15:08:54 -08:00
use crate ::ast ::types ::{ Identifier , Parameter } ;
2023-08-29 14:12:48 -07:00
2023-08-24 15:34:51 -07:00
pub async fn parse_execute ( code : & str ) -> Result < ProgramMemory > {
2024-04-15 17:18:32 -07:00
let tokens = crate ::token ::lexer ( code ) ? ;
2023-09-05 16:02:27 -07:00
let parser = crate ::parser ::Parser ::new ( tokens ) ;
let program = parser . ast ( ) ? ;
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-05-15 10:17:29 -07:00
is_mock : true ,
2024-03-13 12:56:46 -07:00
} ;
2024-09-16 15:10:33 -04:00
let exec_state = ctx . run ( & program , None ) . await ? ;
2023-08-24 15:34:51 -07:00
2024-09-16 15:10:33 -04:00
Ok ( exec_state . memory )
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.
fn mem_get_json ( memory : & ProgramMemory , name : & str ) -> serde_json ::Value {
memory
. get ( name , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
}
2023-08-24 15:34:51 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_assign_two_variables ( ) {
let ast = r #" const myVar = 5
const newVar = myVar + 1 " #;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 5 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " myVar " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
2023-08-24 15:34:51 -07:00
) ;
assert_eq! (
serde_json ::json! ( 6.0 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " newVar " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
2023-08-24 15:34:51 -07:00
) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_angled_line_that_intersects ( ) {
let ast_fn = | offset : & str | -> String {
format! (
2023-10-05 14:27:48 -07:00
r #" const part001 = startSketchOn('XY')
| > startProfileAt ( [ 0 , 0 ] , % )
2024-07-27 17:59:41 -07:00
| > lineTo ( [ 2 , 2 ] , % , $yo )
2023-08-24 15:34:51 -07:00
| > lineTo ( [ 3 , 1 ] , % )
| > angledLineThatIntersects ( { {
angle : 180 ,
2024-07-27 17:59:41 -07:00
intersectTag : yo ,
2023-08-24 15:34:51 -07:00
offset : { } ,
2024-07-27 17:59:41 -07:00
} } , % , $yo2 )
2024-07-27 22:56:46 -07:00
const intersect = segEndX ( yo2 ) " #,
2023-08-24 15:34:51 -07:00
offset
)
} ;
let memory = parse_execute ( & ast_fn ( " -1 " ) ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 1.0 + 2.0 f64 . sqrt ( ) ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " intersect " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
2023-08-24 15:34:51 -07:00
) ;
let memory = parse_execute ( & ast_fn ( " 0 " ) ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 1.0000000000000002 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " intersect " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
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 ( ) {
2023-09-13 11:42:09 -07:00
let ast = r #" fn box = (array) => {
2023-10-05 14:27:48 -07:00
let myBox = startSketchOn ( ' XY ' )
| > startProfileAt ( array [ 0 ] , % )
2023-09-13 09:23:24 -07:00
| > line ( [ 0 , array [ 1 ] ] , % )
2023-09-13 07:23:14 -07:00
| > line ( [ array [ 2 ] , 0 ] , % )
| > line ( [ 0 , - array [ 1 ] ] , % )
| > close ( % )
| > extrude ( array [ 3 ] , % )
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 ( ) ,
source_ranges : vec ! [ SourceRange ( [ 64 , 65 ] ) , SourceRange ( [ 97 , 106 ] ) ] ,
} ) ,
) ;
}
#[ 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 ( ) ,
source_ranges : vec ! [ SourceRange ( [ 80 , 81 ] ) ] ,
} ) ,
) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_function_with_parameter_redefined_outside ( ) {
let ast = r #"
fn myIdentity = ( x ) = > {
return x
}
const x = 33
const two = myIdentity ( 2 ) " #;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 2 ) ,
memory
. get ( " two " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
assert_eq! (
serde_json ::json! ( 33 ) ,
memory
. get ( " x " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_function_referencing_variable_in_parent_scope ( ) {
let ast = r #"
const x = 22
const y = 3
fn add = ( x ) = > {
return x + y
}
const answer = add ( 2 ) " #;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 5.0 ) ,
memory
. get ( " answer " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
assert_eq! (
serde_json ::json! ( 22 ) ,
memory
. get ( " x " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_function_redefining_variable_in_parent_scope ( ) {
let ast = r #"
const x = 1
fn foo = ( ) = > {
const x = 2
return x
}
const answer = foo ( ) " #;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 2 ) ,
memory
. get ( " answer " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
assert_eq! (
serde_json ::json! ( 1 ) ,
memory
. get ( " x " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_execute_pattern_transform_function_redefining_variable_in_parent_scope ( ) {
let ast = r #"
const scale = 100
fn transform = ( replicaId ) = > {
// Redefine same variable as in parent scope.
const scale = 2
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 , % )
}
// The 10 layers are replicas of each other, with a transform applied to each.
let shape = layer ( ) | > patternTransform ( 10 , transform , % ) " #;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
// TODO: Assert that scale 2 was used.
assert_eq! (
serde_json ::json! ( 100 ) ,
memory
. get ( " scale " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. unwrap ( )
) ;
}
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))"# ;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 5.0 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " myVar " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. 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"# ;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 7.4 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " myVar " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. 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"# ;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 1.0 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " myVar " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. 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"# ;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( std ::f64 ::consts ::TAU ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " myVar " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. 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"# ;
let memory = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! (
serde_json ::json! ( 7.4 ) ,
2024-07-22 19:43:40 -04:00
memory
. get ( " thing " , SourceRange ::default ( ) )
. unwrap ( )
. get_json_value ( )
. 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 )
" #;
let mem = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( serde_json ::json! ( false ) , mem_get_json ( & mem , " notTrue " ) ) ;
assert_eq! ( serde_json ::json! ( true ) , mem_get_json ( & mem , " notFalse " ) ) ;
assert_eq! ( serde_json ::json! ( true ) , mem_get_json ( & mem , " c " ) ) ;
assert_eq! ( serde_json ::json! ( false ) , mem_get_json ( & mem , " d " ) ) ;
}
#[ 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 {
message : " Cannot apply unary operator ! to non-boolean value: null " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ( [ 56 , 63 ] ) ] ,
} )
) ;
let code2 = " let notZero = !0 " ;
assert_eq! (
parse_execute ( code2 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
message : " Cannot apply unary operator ! to non-boolean value: 0 " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ( [ 14 , 16 ] ) ] ,
} )
) ;
let code3 = r #"
let notEmptyString = ! " "
" #;
assert_eq! (
parse_execute ( code3 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
message : " Cannot apply unary operator ! to non-boolean value: \" \" " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ( [ 22 , 25 ] ) ] ,
} )
) ;
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 {
message : " Cannot apply unary operator ! to non-boolean value: 1 " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ( [ 36 , 42 ] ) ] ,
} )
) ;
let code5 = "
let a = [ ]
let notArray = ! a " ;
assert_eq! (
parse_execute ( code5 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
message : " Cannot apply unary operator ! to non-boolean value: [] " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ( [ 27 , 29 ] ) ] ,
} )
) ;
let code6 = "
let x = { }
let notObject = ! x " ;
assert_eq! (
parse_execute ( code6 ) . await . unwrap_err ( ) . downcast ::< KclError > ( ) . unwrap ( ) ,
KclError ::Semantic ( KclErrorDetails {
message : " Cannot apply unary operator ! to non-boolean value: {} " . to_owned ( ) ,
source_ranges : vec ! [ SourceRange ( [ 28 , 30 ] ) ] ,
} )
) ;
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 ( )
. starts_with ( " Cannot apply unary operator ! to non-boolean value: { \" type \" : \" TagDeclarator \" , " ) ,
" Actual error: {:?} " ,
tag_declarator_err
) ;
let code9 = "
let myTagDeclarator = $myTag
let notTagIdentifier = ! myTag " ;
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 ( )
. starts_with ( " Cannot apply unary operator ! to non-boolean value: { \" type \" : \" TagIdentifier \" , " ) ,
" 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-08-14 02:38:37 -04:00
source_ranges : vec ! [ SourceRange ( [ 14 , 15 ] ) ] ,
} )
) ;
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-08-14 02:38:37 -04:00
source_ranges : vec ! [ SourceRange ( [ 54 , 56 ] ) ] ,
} )
) ;
// 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 ( ) ) ;
assert_eq! (
result . unwrap_err ( ) . to_string ( ) ,
r # "undefined value: KclErrorDetails { source_ranges: [SourceRange([10, 34])], message: "Result of user-defined function test is undefined" }"# . to_owned ( )
) ;
}
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 {
KclValue ::UserVal ( UserVal {
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
value : number . into ( ) ,
meta : Default ::default ( ) ,
} )
}
fn ident ( s : & 'static str ) -> Identifier {
Identifier {
start : 0 ,
end : 0 ,
name : s . to_owned ( ) ,
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 opt_param ( s : & 'static str ) -> Parameter {
Parameter {
identifier : ident ( s ) ,
2024-03-21 17:14:30 -07:00
type_ : 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
optional : 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 ,
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
optional : false ,
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 {
source_ranges : vec ! [ SourceRange ( [ 0 , 0 ] ) ] ,
message : " Expected 1 arguments, got 0 " . to_owned ( ) ,
} ) ) ,
) ,
(
" all params optional, none given, should be OK " ,
vec! [ opt_param ( " x " ) ] ,
vec! [ ] ,
2024-02-11 18:26:09 -08:00
Ok ( additional_program_memory ( & [ (
" x " . to_owned ( ) ,
2024-08-12 16:53:24 -05:00
KclValue ::from ( & KclNone ::default ( ) ) ,
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, too few given " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ SourceRange ( [ 0 , 0 ] ) ] ,
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-08-12 16:53:24 -05:00
( " y " . to_owned ( ) , KclValue ::from ( & KclNone ::default ( ) ) ) ,
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 {
source_ranges : vec ! [ SourceRange ( [ 0 , 0 ] ) ] ,
message : " Expected 1-2 arguments, got 3 " . to_owned ( ) ,
} ) ) ,
) ,
] {
// Run each test.
let func_expr = & FunctionExpression {
start : 0 ,
end : 0 ,
params ,
body : crate ::ast ::types ::Program {
start : 0 ,
end : 0 ,
body : Vec ::new ( ) ,
non_code_meta : Default ::default ( ) ,
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-03-21 17:14:30 -07:00
return_type : None ,
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
} ;
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-08-12 16:53:24 -05:00
let mem = KclValue ::ExtrudeGroups {
2024-02-11 15:08:54 -08:00
value : Default ::default ( ) ,
} ;
let json = serde_json ::to_string ( & mem ) . unwrap ( ) ;
assert_eq! ( json , r # "{"type":"ExtrudeGroups","value":[]}"# ) ;
}
2023-08-24 15:34:51 -07:00
}