2024-11-14 17:27:19 -06:00
use std ::collections ::HashMap ;
2024-12-03 16:39:51 +13:00
use async_recursion ::async_recursion ;
2024-10-16 14:33:03 -07:00
use crate ::{
2025-06-26 17:02:54 -05:00
CompilationError , NodePath ,
2024-10-16 14:33:03 -07:00
errors ::{ KclError , KclErrorDetails } ,
2024-12-07 07:16:04 +13:00
execution ::{
2025-06-26 17:02:54 -05:00
BodyType , EnvironmentRef , ExecState , ExecutorContext , KclValue , Metadata , ModelingCmdMeta , ModuleArtifactState ,
Operation , PlaneType , StatementKind , TagIdentifier , annotations ,
2025-06-12 12:38:12 -04:00
cad_op ::OpKclValue ,
2025-05-19 16:50:15 +12:00
fn_call ::Args ,
2025-04-26 21:21:26 -07:00
kcl_value ::{ FunctionSource , TypeDef } ,
2025-02-12 10:22:56 +13:00
memory ,
2025-02-05 17:53:49 +13:00
state ::ModuleState ,
2025-04-26 21:21:26 -07:00
types ::{ NumericType , PrimitiveType , RuntimeType } ,
2024-12-07 07:16:04 +13:00
} ,
2025-05-12 01:30:33 -04:00
fmt ,
2025-02-11 13:52:46 +13:00
modules ::{ ModuleId , ModulePath , ModuleRepr } ,
2025-06-25 20:36:57 +12:00
parsing ::{
ast ::types ::{
Annotation , ArrayExpression , ArrayRangeExpression , AscribedExpression , BinaryExpression , BinaryOperator ,
BinaryPart , BodyItem , Expr , IfExpression , ImportPath , ImportSelector , ItemVisibility , LiteralIdentifier ,
LiteralValue , MemberExpression , Name , Node , NodeRef , ObjectExpression , PipeExpression , Program ,
TagDeclarator , Type , UnaryExpression , UnaryOperator ,
} ,
token ::NumericSuffix ,
2024-12-05 17:56:49 +13:00
} ,
2025-02-11 13:52:46 +13:00
source_range ::SourceRange ,
2025-05-19 16:50:15 +12:00
std ::args ::TyF64 ,
2024-10-16 14:33:03 -07:00
} ;
2024-11-14 17:27:19 -06:00
2025-02-20 19:33:21 +13:00
impl < ' a > StatementKind < ' a > {
fn expect_name ( & self ) -> & ' a str {
match self {
StatementKind ::Declaration { name } = > name ,
StatementKind ::Expression = > unreachable! ( ) ,
}
}
}
2025-02-05 17:53:49 +13:00
impl ExecutorContext {
2025-02-20 19:33:21 +13:00
/// Returns true if importing the prelude should be skipped.
2025-02-05 17:53:49 +13:00
async fn handle_annotations (
& self ,
2025-02-13 16:17:09 +13:00
annotations : impl Iterator < Item = & Node < Annotation > > ,
2025-02-21 09:30:44 +13:00
body_type : BodyType ,
2025-02-05 17:53:49 +13:00
exec_state : & mut ExecState ,
2025-02-11 11:26:14 +13:00
) -> Result < bool , KclError > {
let mut no_prelude = false ;
2025-02-13 16:17:09 +13:00
for annotation in annotations {
if annotation . name ( ) = = Some ( annotations ::SETTINGS ) {
2025-02-25 16:10:06 +13:00
if matches! ( body_type , BodyType ::Root ) {
2025-03-26 01:59:43 -04:00
if exec_state . mod_local . settings . update_from_annotation ( annotation ) ? {
exec_state . mod_local . explicit_length_units = true ;
}
2025-02-05 17:53:49 +13:00
} else {
2025-02-21 09:30:44 +13:00
exec_state . err ( CompilationError ::err (
annotation . as_source_range ( ) ,
" Settings can only be modified at the top level scope of a file " ,
) ) ;
2025-02-05 17:53:49 +13:00
}
2025-02-21 09:30:44 +13:00
} else if annotation . name ( ) = = Some ( annotations ::NO_PRELUDE ) {
2025-02-25 16:10:06 +13:00
if matches! ( body_type , BodyType ::Root ) {
2025-02-11 11:26:14 +13:00
no_prelude = true ;
} else {
2025-02-21 09:30:44 +13:00
exec_state . err ( CompilationError ::err (
annotation . as_source_range ( ) ,
2025-02-25 16:10:06 +13:00
" The standard library can only be skipped at the top level scope of a file " ,
2025-02-21 09:30:44 +13:00
) ) ;
2025-02-11 11:26:14 +13:00
}
2025-02-21 09:30:44 +13:00
} else {
exec_state . warn ( CompilationError ::err (
annotation . as_source_range ( ) ,
" Unknown annotation " ,
) ) ;
2025-02-11 11:26:14 +13:00
}
2025-02-05 17:53:49 +13:00
}
2025-02-11 11:26:14 +13:00
Ok ( no_prelude )
2025-02-05 17:53:49 +13:00
}
2025-02-25 16:10:06 +13:00
pub ( super ) async fn exec_module_body (
& self ,
program : & Node < Program > ,
exec_state : & mut ExecState ,
preserve_mem : bool ,
2025-03-15 10:08:39 -07:00
module_id : ModuleId ,
2025-02-27 15:46:41 +13:00
path : & ModulePath ,
2025-06-16 13:55:24 -04:00
) -> Result <
( Option < KclValue > , EnvironmentRef , Vec < String > , ModuleArtifactState ) ,
( KclError , Option < ModuleArtifactState > ) ,
> {
2025-04-16 11:52:14 -07:00
crate ::log ::log ( format! ( " enter module {path} {} " , exec_state . stack ( ) ) ) ;
2025-03-05 12:03:32 +13:00
2025-05-20 20:47:32 -04:00
let mut local_state = ModuleState ::new ( path . clone ( ) , exec_state . stack ( ) . memory . clone ( ) , Some ( module_id ) ) ;
2025-03-05 12:03:32 +13:00
if ! preserve_mem {
std ::mem ::swap ( & mut exec_state . mod_local , & mut local_state ) ;
}
2025-02-25 16:10:06 +13:00
let no_prelude = self
. handle_annotations ( program . inner_attrs . iter ( ) , crate ::execution ::BodyType ::Root , exec_state )
2025-06-16 13:55:24 -04:00
. await
. map_err ( | err | ( err , None ) ) ? ;
2025-02-25 16:10:06 +13:00
if ! preserve_mem {
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . push_new_root_env ( ! no_prelude ) ;
2025-02-25 16:10:06 +13:00
}
let result = self
. exec_block ( program , exec_state , crate ::execution ::BodyType ::Root )
. await ;
let env_ref = if preserve_mem {
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_and_preserve_env ( )
2025-02-25 16:10:06 +13:00
} else {
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_env ( )
2025-02-25 16:10:06 +13:00
} ;
2025-06-10 21:30:48 -04:00
let module_artifacts = if ! preserve_mem {
2025-03-05 12:03:32 +13:00
std ::mem ::swap ( & mut exec_state . mod_local , & mut local_state ) ;
2025-06-10 21:30:48 -04:00
local_state . artifacts
} else {
2025-06-16 13:55:24 -04:00
std ::mem ::take ( & mut exec_state . mod_local . artifacts )
2025-06-10 21:30:48 -04:00
} ;
2025-02-27 15:46:41 +13:00
crate ::log ::log ( format! ( " leave {path} " ) ) ;
2025-06-16 13:55:24 -04:00
result
. map_err ( | err | ( err , Some ( module_artifacts . clone ( ) ) ) )
. map ( | result | ( result , env_ref , local_state . module_exports , module_artifacts ) )
2025-02-25 16:10:06 +13:00
}
2025-02-05 17:53:49 +13:00
/// Execute an AST's program.
#[ async_recursion ]
2025-02-25 16:10:06 +13:00
pub ( super ) async fn exec_block < ' a > (
2025-02-05 17:53:49 +13:00
& ' a self ,
2025-04-16 11:52:14 -07:00
program : NodeRef < ' a , Program > ,
2025-02-05 17:53:49 +13:00
exec_state : & mut ExecState ,
body_type : BodyType ,
) -> Result < Option < KclValue > , KclError > {
let mut last_expr = None ;
// Iterate over the body of the program.
2025-02-13 16:17:09 +13:00
for statement in & program . body {
2025-02-05 17:53:49 +13:00
match statement {
BodyItem ::ImportStatement ( import_stmt ) = > {
2025-02-25 16:10:06 +13:00
if ! matches! ( body_type , BodyType ::Root ) {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Imports are only supported at the top-level of a file. " . to_owned ( ) ,
vec! [ import_stmt . into ( ) ] ,
) ) ) ;
2025-02-11 11:26:14 +13:00
}
2025-02-05 17:53:49 +13:00
let source_range = SourceRange ::from ( import_stmt ) ;
2025-02-13 16:17:09 +13:00
let attrs = & import_stmt . outer_attrs ;
2025-05-20 20:47:32 -04:00
let module_path = ModulePath ::from_import_path (
& import_stmt . path ,
& self . settings . project_directory ,
& exec_state . mod_local . path ,
) ? ;
2025-02-13 06:24:27 +13:00
let module_id = self
2025-05-20 20:47:32 -04:00
. open_module ( & import_stmt . path , attrs , & module_path , exec_state , source_range )
2025-02-13 06:24:27 +13:00
. await ? ;
2025-02-05 17:53:49 +13:00
match & import_stmt . selector {
ImportSelector ::List { items } = > {
2025-04-16 11:52:14 -07:00
let ( env_ref , module_exports ) =
self . exec_module_for_items ( module_id , exec_state , source_range ) . await ? ;
2025-02-05 17:53:49 +13:00
for import_item in items {
// Extract the item from the module.
2025-04-22 11:00:53 +12:00
let mem = & exec_state . stack ( ) . memory ;
let mut value = mem
2025-03-05 12:03:32 +13:00
. get_from ( & import_item . name . name , env_ref , import_item . into ( ) , 0 )
2025-04-22 11:00:53 +12:00
. cloned ( ) ;
let ty_name = format! ( " {} {} " , memory ::TYPE_PREFIX , import_item . name . name ) ;
let mut ty = mem . get_from ( & ty_name , env_ref , import_item . into ( ) , 0 ) . cloned ( ) ;
2025-05-28 11:25:27 +12:00
let mod_name = format! ( " {} {} " , memory ::MODULE_PREFIX , import_item . name . name ) ;
let mut mod_value = mem . get_from ( & mod_name , env_ref , import_item . into ( ) , 0 ) . cloned ( ) ;
2025-04-22 11:00:53 +12:00
2025-05-28 11:25:27 +12:00
if value . is_err ( ) & & ty . is_err ( ) & & mod_value . is_err ( ) {
2025-06-02 17:25:55 -05:00
return Err ( KclError ::new_undefined_value (
KclErrorDetails ::new (
format! ( " {} is not defined in module " , import_item . name . name ) ,
vec! [ SourceRange ::from ( & import_item . name ) ] ,
) ,
None ,
) ) ;
2025-04-22 11:00:53 +12:00
}
// Check that the item is allowed to be imported (in at least one namespace).
if value . is_ok ( ) & & ! module_exports . contains ( & import_item . name . name ) {
2025-06-02 13:30:57 -05:00
value = Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-02-05 17:53:49 +13:00
" Cannot import \" {} \" from module because it is not exported. Add \" export \" before the definition to export it. " ,
import_item . name . name
) ,
2025-05-19 14:13:10 -04:00
vec! [ SourceRange ::from ( & import_item . name ) ] ,
) ) ) ;
2025-02-05 17:53:49 +13:00
}
2025-04-22 11:00:53 +12:00
if ty . is_ok ( ) & & ! module_exports . contains ( & ty_name ) {
2025-06-26 17:02:54 -05:00
ty = Err ( KclError ::new_semantic ( KclErrorDetails ::new (
format! (
" Cannot import \" {} \" from module because it is not exported. Add \" export \" before the definition to export it. " ,
import_item . name . name
) ,
vec! [ SourceRange ::from ( & import_item . name ) ] ,
) ) ) ;
2025-05-28 11:25:27 +12:00
}
if mod_value . is_ok ( ) & & ! module_exports . contains ( & mod_name ) {
2025-06-26 17:02:54 -05:00
mod_value = Err ( KclError ::new_semantic ( KclErrorDetails ::new (
format! (
" Cannot import \" {} \" from module because it is not exported. Add \" export \" before the definition to export it. " ,
import_item . name . name
) ,
vec! [ SourceRange ::from ( & import_item . name ) ] ,
) ) ) ;
2025-04-22 11:00:53 +12:00
}
2025-05-28 11:25:27 +12:00
if value . is_err ( ) & & ty . is_err ( ) & & mod_value . is_err ( ) {
2025-04-22 11:00:53 +12:00
return value . map ( Option ::Some ) ;
}
2025-02-05 17:53:49 +13:00
// Add the item to the current module.
2025-04-22 11:00:53 +12:00
if let Ok ( value ) = value {
exec_state . mut_stack ( ) . add (
import_item . identifier ( ) . to_owned ( ) ,
value ,
SourceRange ::from ( & import_item . name ) ,
) ? ;
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state
. mod_local
. module_exports
. push ( import_item . identifier ( ) . to_owned ( ) ) ;
}
}
2025-02-05 17:53:49 +13:00
2025-04-22 11:00:53 +12:00
if let Ok ( ty ) = ty {
let ty_name = format! ( " {} {} " , memory ::TYPE_PREFIX , import_item . identifier ( ) ) ;
exec_state . mut_stack ( ) . add (
ty_name . clone ( ) ,
ty ,
SourceRange ::from ( & import_item . name ) ,
) ? ;
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state . mod_local . module_exports . push ( ty_name ) ;
}
2025-02-05 17:53:49 +13:00
}
2025-05-28 11:25:27 +12:00
if let Ok ( mod_value ) = mod_value {
let mod_name = format! ( " {} {} " , memory ::MODULE_PREFIX , import_item . identifier ( ) ) ;
exec_state . mut_stack ( ) . add (
mod_name . clone ( ) ,
mod_value ,
SourceRange ::from ( & import_item . name ) ,
) ? ;
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state . mod_local . module_exports . push ( mod_name ) ;
}
}
2025-02-05 17:53:49 +13:00
}
}
ImportSelector ::Glob ( _ ) = > {
2025-04-16 11:52:14 -07:00
let ( env_ref , module_exports ) =
self . exec_module_for_items ( module_id , exec_state , source_range ) . await ? ;
2025-02-05 17:53:49 +13:00
for name in module_exports . iter ( ) {
2025-02-12 10:22:56 +13:00
let item = exec_state
2025-03-05 12:03:32 +13:00
. stack ( )
. memory
. get_from ( name , env_ref , source_range , 0 )
2025-02-12 10:22:56 +13:00
. map_err ( | _err | {
2025-06-02 13:30:57 -05:00
KclError ::new_internal ( KclErrorDetails ::new (
2025-06-26 17:02:54 -05:00
format! ( " {name} is not defined in module (but was exported?) " ) ,
2025-05-19 14:13:10 -04:00
vec! [ source_range ] ,
) )
2025-02-12 10:22:56 +13:00
} ) ?
. clone ( ) ;
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . add ( name . to_owned ( ) , item , source_range ) ? ;
2025-02-05 17:53:49 +13:00
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state . mod_local . module_exports . push ( name . clone ( ) ) ;
}
}
}
ImportSelector ::None { .. } = > {
let name = import_stmt . module_name ( ) . unwrap ( ) ;
let item = KclValue ::Module {
value : module_id ,
meta : vec ! [ source_range . into ( ) ] ,
} ;
2025-05-28 11:25:27 +12:00
exec_state . mut_stack ( ) . add (
format! ( " {} {} " , memory ::MODULE_PREFIX , name ) ,
item ,
source_range ,
) ? ;
2025-02-05 17:53:49 +13:00
}
}
last_expr = None ;
}
BodyItem ::ExpressionStatement ( expression_statement ) = > {
let metadata = Metadata ::from ( expression_statement ) ;
last_expr = Some (
self . execute_expr (
& expression_statement . expression ,
exec_state ,
& metadata ,
2025-02-20 19:33:21 +13:00
& [ ] ,
2025-02-05 17:53:49 +13:00
StatementKind ::Expression ,
)
. await ? ,
) ;
}
BodyItem ::VariableDeclaration ( variable_declaration ) = > {
let var_name = variable_declaration . declaration . id . name . to_string ( ) ;
let source_range = SourceRange ::from ( & variable_declaration . declaration . init ) ;
let metadata = Metadata { source_range } ;
2025-02-20 19:33:21 +13:00
let annotations = & variable_declaration . outer_attrs ;
2025-02-11 11:26:14 +13:00
2025-06-03 11:46:28 -05:00
// During the evaluation of the variable's RHS, set context that this is all happening inside a variable
2025-06-02 17:25:55 -05:00
// declaration, for the given name. This helps improve user-facing error messages.
2025-06-03 11:46:28 -05:00
let lhs = variable_declaration . inner . name ( ) . to_owned ( ) ;
2025-06-05 08:34:16 -05:00
let prev_being_declared = exec_state . mod_local . being_declared . take ( ) ;
2025-06-03 11:46:28 -05:00
exec_state . mod_local . being_declared = Some ( lhs ) ;
2025-06-02 17:25:55 -05:00
let rhs_result = self
2025-02-05 17:53:49 +13:00
. execute_expr (
& variable_declaration . declaration . init ,
exec_state ,
& metadata ,
2025-02-20 19:33:21 +13:00
annotations ,
2025-02-05 17:53:49 +13:00
StatementKind ::Declaration { name : & var_name } ,
)
2025-06-02 17:25:55 -05:00
. await ;
// Declaration over, so unset this context.
2025-06-04 23:48:15 -05:00
exec_state . mod_local . being_declared = prev_being_declared ;
2025-06-02 17:25:55 -05:00
let rhs = rhs_result ? ;
2025-02-12 10:22:56 +13:00
exec_state
2025-03-05 12:03:32 +13:00
. mut_stack ( )
2025-06-02 17:25:55 -05:00
. add ( var_name . clone ( ) , rhs . clone ( ) , source_range ) ? ;
2025-02-05 17:53:49 +13:00
2025-06-12 12:38:12 -04:00
if rhs . show_variable_in_feature_tree ( ) {
exec_state . push_op ( Operation ::VariableDeclaration {
name : var_name . clone ( ) ,
value : OpKclValue ::from ( & rhs ) ,
visibility : variable_declaration . visibility ,
node_path : NodePath ::placeholder ( ) ,
source_range ,
} ) ;
}
2025-02-05 17:53:49 +13:00
// Track exports.
if let ItemVisibility ::Export = variable_declaration . visibility {
2025-05-20 12:43:11 -04:00
if matches! ( body_type , BodyType ::Root ) {
exec_state . mod_local . module_exports . push ( var_name ) ;
} else {
exec_state . err ( CompilationError ::err (
variable_declaration . as_source_range ( ) ,
" Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level. " ,
) ) ;
}
2025-02-05 17:53:49 +13:00
}
2025-04-25 17:55:54 -04:00
// Variable declaration can be the return value of a module.
2025-06-02 17:25:55 -05:00
last_expr = matches! ( body_type , BodyType ::Root ) . then_some ( rhs ) ;
2025-02-05 17:53:49 +13:00
}
2025-03-08 03:53:34 +13:00
BodyItem ::TypeDeclaration ( ty ) = > {
let metadata = Metadata ::from ( & * * ty ) ;
let impl_kind = annotations ::get_impl ( & ty . outer_attrs , metadata . source_range ) ? . unwrap_or_default ( ) ;
match impl_kind {
annotations ::Impl ::Rust = > {
2025-05-20 20:47:32 -04:00
let std_path = match & exec_state . mod_local . path {
ModulePath ::Std { value } = > value ,
ModulePath ::Local { .. } | ModulePath ::Main = > {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" User-defined types are not yet supported. " . to_owned ( ) ,
vec! [ metadata . source_range ] ,
) ) ) ;
2025-03-08 03:53:34 +13:00
}
} ;
2025-03-21 10:56:55 +13:00
let ( t , props ) = crate ::std ::std_ty ( std_path , & ty . name . name ) ;
2025-03-08 03:53:34 +13:00
let value = KclValue ::Type {
2025-03-21 10:56:55 +13:00
value : TypeDef ::RustRepr ( t , props ) ,
2025-03-08 03:53:34 +13:00
meta : vec ! [ metadata ] ,
} ;
2025-04-22 11:00:53 +12:00
let name_in_mem = format! ( " {} {} " , memory ::TYPE_PREFIX , ty . name . name ) ;
2025-03-08 03:53:34 +13:00
exec_state
. mut_stack ( )
2025-04-22 11:00:53 +12:00
. add ( name_in_mem . clone ( ) , value , metadata . source_range )
2025-03-08 03:53:34 +13:00
. map_err ( | _ | {
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Redefinition of type {} . " , ty . name . name ) ,
vec! [ metadata . source_range ] ,
) )
2025-03-08 03:53:34 +13:00
} ) ? ;
2025-04-22 11:00:53 +12:00
if let ItemVisibility ::Export = ty . visibility {
exec_state . mod_local . module_exports . push ( name_in_mem ) ;
}
2025-03-08 03:53:34 +13:00
}
// Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
annotations ::Impl ::Primitive = > { }
2025-03-21 10:56:55 +13:00
annotations ::Impl ::Kcl = > match & ty . alias {
Some ( alias ) = > {
let value = KclValue ::Type {
value : TypeDef ::Alias (
RuntimeType ::from_parsed (
alias . inner . clone ( ) ,
exec_state ,
metadata . source_range ,
)
2025-06-02 13:30:57 -05:00
. map_err ( | e | KclError ::new_semantic ( e . into ( ) ) ) ? ,
2025-03-21 10:56:55 +13:00
) ,
meta : vec ! [ metadata ] ,
} ;
2025-04-22 11:00:53 +12:00
let name_in_mem = format! ( " {} {} " , memory ::TYPE_PREFIX , ty . name . name ) ;
2025-03-21 10:56:55 +13:00
exec_state
. mut_stack ( )
2025-04-22 11:00:53 +12:00
. add ( name_in_mem . clone ( ) , value , metadata . source_range )
2025-03-21 10:56:55 +13:00
. map_err ( | _ | {
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Redefinition of type {} . " , ty . name . name ) ,
vec! [ metadata . source_range ] ,
) )
2025-03-21 10:56:55 +13:00
} ) ? ;
2025-04-22 11:00:53 +12:00
if let ItemVisibility ::Export = ty . visibility {
exec_state . mod_local . module_exports . push ( name_in_mem ) ;
}
2025-03-21 10:56:55 +13:00
}
None = > {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" User-defined types are not yet supported. " . to_owned ( ) ,
vec! [ metadata . source_range ] ,
2025-06-26 17:02:54 -05:00
) ) ) ;
2025-03-21 10:56:55 +13:00
}
} ,
2025-03-08 03:53:34 +13:00
}
last_expr = None ;
}
2025-02-05 17:53:49 +13:00
BodyItem ::ReturnStatement ( return_statement ) = > {
let metadata = Metadata ::from ( return_statement ) ;
2025-02-12 10:22:56 +13:00
2025-02-25 16:10:06 +13:00
if matches! ( body_type , BodyType ::Root ) {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Cannot return from outside a function. " . to_owned ( ) ,
vec! [ metadata . source_range ] ,
) ) ) ;
2025-02-12 10:22:56 +13:00
}
2025-02-05 17:53:49 +13:00
let value = self
. execute_expr (
& return_statement . argument ,
exec_state ,
& metadata ,
2025-02-20 19:33:21 +13:00
& [ ] ,
2025-02-05 17:53:49 +13:00
StatementKind ::Expression ,
)
. await ? ;
2025-02-12 10:22:56 +13:00
exec_state
2025-03-05 12:03:32 +13:00
. mut_stack ( )
2025-02-12 10:22:56 +13:00
. add ( memory ::RETURN_NAME . to_owned ( ) , value , metadata . source_range )
. map_err ( | _ | {
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Multiple returns from a single function. " . to_owned ( ) ,
vec! [ metadata . source_range ] ,
) )
2025-02-12 10:22:56 +13:00
} ) ? ;
2025-02-05 17:53:49 +13:00
last_expr = None ;
}
}
}
2025-02-25 16:10:06 +13:00
if matches! ( body_type , BodyType ::Root ) {
2025-02-05 17:53:49 +13:00
// Flush the batch queue.
2025-06-10 21:30:48 -04:00
exec_state
2025-02-05 17:53:49 +13:00
. flush_batch (
2025-06-10 21:30:48 -04:00
ModelingCmdMeta ::new ( self , SourceRange ::new ( program . end , program . end , program . module_id ) ) ,
2025-02-05 17:53:49 +13:00
// 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 ,
)
. await ? ;
}
Ok ( last_expr )
}
2025-04-16 11:52:14 -07:00
pub async fn open_module (
2025-02-05 17:53:49 +13:00
& self ,
path : & ImportPath ,
2025-02-13 16:17:09 +13:00
attrs : & [ Node < Annotation > ] ,
2025-05-20 20:47:32 -04:00
resolved_path : & ModulePath ,
2025-02-05 17:53:49 +13:00
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < ModuleId , KclError > {
match path {
2025-02-11 11:26:14 +13:00
ImportPath ::Kcl { .. } = > {
2025-05-20 20:47:32 -04:00
exec_state . global . mod_loader . cycle_check ( resolved_path , source_range ) ? ;
2025-02-05 17:53:49 +13:00
2025-05-20 20:47:32 -04:00
if let Some ( id ) = exec_state . id_for_module ( resolved_path ) {
2025-02-11 11:26:14 +13:00
return Ok ( id ) ;
2025-02-05 17:53:49 +13:00
}
2025-02-11 11:26:14 +13:00
let id = exec_state . next_module_id ( ) ;
2025-02-25 11:51:54 -06:00
// Add file path string to global state even if it fails to import
exec_state . add_path_to_source_id ( resolved_path . clone ( ) , id ) ;
2025-02-11 11:26:14 +13:00
let source = resolved_path . source ( & self . fs , source_range ) . await ? ;
2025-02-26 19:29:59 -08:00
exec_state . add_id_to_source ( id , source . clone ( ) ) ;
2025-02-05 17:53:49 +13:00
// TODO handle parsing errors properly
2025-02-26 19:29:59 -08:00
let parsed = crate ::parsing ::parse_str ( & source . source , id ) . parse_errs_as_err ( ) ? ;
2025-05-20 20:47:32 -04:00
exec_state . add_module ( id , resolved_path . clone ( ) , ModuleRepr ::Kcl ( parsed , None ) ) ;
2025-02-12 10:22:56 +13:00
2025-02-11 11:26:14 +13:00
Ok ( id )
2025-02-05 17:53:49 +13:00
}
2025-02-11 11:26:14 +13:00
ImportPath ::Foreign { .. } = > {
2025-05-20 20:47:32 -04:00
if let Some ( id ) = exec_state . id_for_module ( resolved_path ) {
2025-02-11 11:26:14 +13:00
return Ok ( id ) ;
}
2025-02-05 17:53:49 +13:00
2025-02-11 11:26:14 +13:00
let id = exec_state . next_module_id ( ) ;
2025-02-13 06:24:27 +13:00
let path = resolved_path . expect_path ( ) ;
2025-02-25 11:51:54 -06:00
// Add file path string to global state even if it fails to import
exec_state . add_path_to_source_id ( resolved_path . clone ( ) , id ) ;
2025-02-13 16:17:09 +13:00
let format = super ::import ::format_from_annotations ( attrs , path , source_range ) ? ;
2025-02-13 06:24:27 +13:00
let geom = super ::import ::import_foreign ( path , format , exec_state , self , source_range ) . await ? ;
2025-05-20 20:47:32 -04:00
exec_state . add_module ( id , resolved_path . clone ( ) , ModuleRepr ::Foreign ( geom , None ) ) ;
2025-02-11 11:26:14 +13:00
Ok ( id )
}
ImportPath ::Std { .. } = > {
2025-05-20 20:47:32 -04:00
if let Some ( id ) = exec_state . id_for_module ( resolved_path ) {
2025-02-11 11:26:14 +13:00
return Ok ( id ) ;
2025-02-05 17:53:49 +13:00
}
2025-02-11 11:26:14 +13:00
let id = exec_state . next_module_id ( ) ;
2025-02-25 11:51:54 -06:00
// Add file path string to global state even if it fails to import
exec_state . add_path_to_source_id ( resolved_path . clone ( ) , id ) ;
2025-02-11 11:26:14 +13:00
let source = resolved_path . source ( & self . fs , source_range ) . await ? ;
2025-02-26 19:29:59 -08:00
exec_state . add_id_to_source ( id , source . clone ( ) ) ;
let parsed = crate ::parsing ::parse_str ( & source . source , id )
. parse_errs_as_err ( )
. unwrap ( ) ;
2025-05-20 20:47:32 -04:00
exec_state . add_module ( id , resolved_path . clone ( ) , ModuleRepr ::Kcl ( parsed , None ) ) ;
2025-02-11 11:26:14 +13:00
Ok ( id )
2025-02-05 17:53:49 +13:00
}
}
}
2025-02-25 16:10:06 +13:00
pub ( super ) async fn exec_module_for_items (
2025-02-05 17:53:49 +13:00
& self ,
module_id : ModuleId ,
exec_state : & mut ExecState ,
source_range : SourceRange ,
2025-02-12 10:22:56 +13:00
) -> Result < ( EnvironmentRef , Vec < String > ) , KclError > {
let path = exec_state . global . module_infos [ & module_id ] . path . clone ( ) ;
let mut repr = exec_state . global . module_infos [ & module_id ] . take_repr ( ) ;
// DON'T EARLY RETURN! We need to restore the module repr
let result = match & mut repr {
ModuleRepr ::Root = > Err ( exec_state . circular_import_error ( & path , source_range ) ) ,
2025-06-10 21:30:48 -04:00
ModuleRepr ::Kcl ( _ , Some ( ( _ , env_ref , items , _ ) ) ) = > Ok ( ( * env_ref , items . clone ( ) ) ) ,
2025-02-12 10:22:56 +13:00
ModuleRepr ::Kcl ( program , cache ) = > self
2025-04-16 11:52:14 -07:00
. exec_module_from_ast ( program , module_id , & path , exec_state , source_range , false )
2025-02-12 10:22:56 +13:00
. await
2025-06-10 21:30:48 -04:00
. map ( | ( val , er , items , module_artifacts ) | {
* cache = Some ( ( val , er , items . clone ( ) , module_artifacts . clone ( ) ) ) ;
2025-02-12 10:22:56 +13:00
( er , items )
} ) ,
2025-06-02 13:30:57 -05:00
ModuleRepr ::Foreign ( geom , _ ) = > Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Cannot import items from foreign modules " . to_owned ( ) ,
vec! [ geom . source_range ] ,
) ) ) ,
2025-04-22 11:00:53 +12:00
ModuleRepr ::Dummy = > unreachable! ( " Looking up {} , but it is still being interpreted " , path ) ,
2025-02-12 10:22:56 +13:00
} ;
2025-02-05 17:53:49 +13:00
2025-02-12 10:22:56 +13:00
exec_state . global . module_infos [ & module_id ] . restore_repr ( repr ) ;
result
}
2025-02-05 17:53:49 +13:00
2025-02-12 10:22:56 +13:00
async fn exec_module_for_result (
& self ,
module_id : ModuleId ,
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < Option < KclValue > , KclError > {
let path = exec_state . global . module_infos [ & module_id ] . path . clone ( ) ;
2025-03-24 20:58:55 +13:00
let mut repr = exec_state . global . module_infos [ & module_id ] . take_repr ( ) ;
2025-02-12 10:22:56 +13:00
// DON'T EARLY RETURN! We need to restore the module repr
2025-03-24 20:58:55 +13:00
let result = match & mut repr {
2025-02-12 10:22:56 +13:00
ModuleRepr ::Root = > Err ( exec_state . circular_import_error ( & path , source_range ) ) ,
2025-06-10 21:30:48 -04:00
ModuleRepr ::Kcl ( _ , Some ( ( val , _ , _ , _ ) ) ) = > Ok ( val . clone ( ) ) ,
2025-03-24 20:58:55 +13:00
ModuleRepr ::Kcl ( program , cached_items ) = > {
let result = self
2025-04-16 11:52:14 -07:00
. exec_module_from_ast ( program , module_id , & path , exec_state , source_range , false )
2025-03-24 20:58:55 +13:00
. await ;
match result {
2025-06-10 21:30:48 -04:00
Ok ( ( val , env , items , module_artifacts ) ) = > {
* cached_items = Some ( ( val . clone ( ) , env , items , module_artifacts ) ) ;
2025-03-24 20:58:55 +13:00
Ok ( val )
}
Err ( e ) = > Err ( e ) ,
}
}
2025-06-10 21:30:48 -04:00
ModuleRepr ::Foreign ( _ , Some ( ( imported , _ ) ) ) = > Ok ( imported . clone ( ) ) ,
2025-04-24 20:25:02 -07:00
ModuleRepr ::Foreign ( geom , cached ) = > {
2025-06-10 21:30:48 -04:00
let result = super ::import ::send_to_engine ( geom . clone ( ) , exec_state , self )
2025-04-24 20:25:02 -07:00
. await
. map ( | geom | Some ( KclValue ::ImportedGeometry ( geom ) ) ) ;
match result {
Ok ( val ) = > {
2025-06-10 21:30:48 -04:00
* cached = Some ( ( val . clone ( ) , exec_state . mod_local . artifacts . clone ( ) ) ) ;
2025-04-24 20:25:02 -07:00
Ok ( val )
}
Err ( e ) = > Err ( e ) ,
}
}
2025-02-12 10:22:56 +13:00
ModuleRepr ::Dummy = > unreachable! ( ) ,
} ;
2025-02-05 17:53:49 +13:00
2025-02-12 10:22:56 +13:00
exec_state . global . module_infos [ & module_id ] . restore_repr ( repr ) ;
2025-04-03 22:10:39 -04:00
2025-02-12 10:22:56 +13:00
result
}
2025-04-16 11:52:14 -07:00
pub async fn exec_module_from_ast (
2025-02-12 10:22:56 +13:00
& self ,
program : & Node < Program > ,
2025-03-15 10:08:39 -07:00
module_id : ModuleId ,
2025-02-12 10:22:56 +13:00
path : & ModulePath ,
exec_state : & mut ExecState ,
source_range : SourceRange ,
2025-04-16 11:52:14 -07:00
preserve_mem : bool ,
2025-06-10 21:30:48 -04:00
) -> Result < ( Option < KclValue > , EnvironmentRef , Vec < String > , ModuleArtifactState ) , KclError > {
2025-02-12 10:22:56 +13:00
exec_state . global . mod_loader . enter_module ( path ) ;
2025-03-15 10:08:39 -07:00
let result = self
2025-04-16 11:52:14 -07:00
. exec_module_body ( program , exec_state , preserve_mem , module_id , path )
2025-03-15 10:08:39 -07:00
. await ;
2025-02-12 10:22:56 +13:00
exec_state . global . mod_loader . leave_module ( path ) ;
2025-06-16 13:55:24 -04:00
// TODO: ModuleArtifactState is getting dropped here when there's an
// error. Should we propagate it for non-root modules?
result . map_err ( | ( err , _ ) | {
2025-06-02 13:30:57 -05:00
if let KclError ::ImportCycle { .. } = err {
2025-02-27 15:46:41 +13:00
// It was an import cycle. Keep the original message.
err . override_source_ranges ( vec! [ source_range ] )
} else {
2025-04-22 11:00:53 +12:00
// TODO would be great to have line/column for the underlying error here
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-04-22 11:00:53 +12:00
" Error loading imported file ({path}). Open it to view more details. \n {} " ,
2025-02-27 15:46:41 +13:00
err . message ( )
) ,
2025-05-19 14:13:10 -04:00
vec! [ source_range ] ,
) )
2025-02-27 15:46:41 +13:00
}
} )
2025-02-05 17:53:49 +13:00
}
#[ async_recursion ]
2025-05-19 16:50:15 +12:00
pub ( super ) async fn execute_expr < ' a : ' async_recursion > (
2025-02-05 17:53:49 +13:00
& self ,
init : & Expr ,
exec_state : & mut ExecState ,
metadata : & Metadata ,
2025-02-20 19:33:21 +13:00
annotations : & [ Node < Annotation > ] ,
2025-02-05 17:53:49 +13:00
statement_kind : StatementKind < ' a > ,
) -> Result < KclValue , KclError > {
let item = match init {
Expr ::None ( none ) = > KclValue ::from ( none ) ,
2025-03-26 01:59:43 -04:00
Expr ::Literal ( literal ) = > KclValue ::from_literal ( ( * * literal ) . clone ( ) , exec_state ) ,
2025-02-05 17:53:49 +13:00
Expr ::TagDeclarator ( tag ) = > tag . execute ( exec_state ) . await ? ,
2025-03-24 20:58:55 +13:00
Expr ::Name ( name ) = > {
2025-06-03 11:46:28 -05:00
let being_declared = exec_state . mod_local . being_declared . clone ( ) ;
let value = name
. get_result ( exec_state , self )
. await
. map_err ( | e | var_in_own_ref_err ( e , & being_declared ) ) ?
. clone ( ) ;
2025-02-05 17:53:49 +13:00
if let KclValue ::Module { value : module_id , meta } = value {
2025-04-26 21:21:26 -07:00
self . exec_module_for_result (
module_id ,
exec_state ,
metadata . source_range
) . await ?
2025-02-12 10:22:56 +13:00
. unwrap_or_else ( | | {
2025-03-12 14:05:18 -04:00
exec_state . warn ( CompilationError ::err (
metadata . source_range ,
" Imported module has no return value. The last statement of the module must be an expression, usually the Solid. " ,
) ) ;
2025-02-12 10:22:56 +13:00
let mut new_meta = vec! [ metadata . to_owned ( ) ] ;
new_meta . extend ( meta ) ;
KclValue ::KclNone {
value : Default ::default ( ) ,
meta : new_meta ,
}
} )
2025-02-05 17:53:49 +13:00
} else {
value
}
}
Expr ::BinaryExpression ( binary_expression ) = > binary_expression . get_result ( exec_state , self ) . await ? ,
2025-02-20 19:33:21 +13:00
Expr ::FunctionExpression ( function_expression ) = > {
2025-03-08 03:53:34 +13:00
let rust_impl = annotations ::get_impl ( annotations , metadata . source_range ) ?
. map ( | s | s = = annotations ::Impl ::Rust )
. unwrap_or ( false ) ;
2025-02-20 19:33:21 +13:00
if rust_impl {
2025-05-20 20:47:32 -04:00
if let ModulePath ::Std { value : std_path } = & exec_state . mod_local . path {
2025-02-22 20:16:29 +13:00
let ( func , props ) = crate ::std ::std_fn ( std_path , statement_kind . expect_name ( ) ) ;
2025-02-20 19:33:21 +13:00
KclValue ::Function {
2025-03-24 21:55:24 +13:00
value : FunctionSource ::Std {
func ,
props ,
ast : function_expression . clone ( ) ,
} ,
2025-02-20 19:33:21 +13:00
meta : vec ! [ metadata . to_owned ( ) ] ,
}
} else {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Rust implementation of functions is restricted to the standard library " . to_owned ( ) ,
vec! [ metadata . source_range ] ,
) ) ) ;
2025-02-20 19:33:21 +13:00
}
} else {
2025-02-22 20:16:29 +13:00
// Snapshotting memory here is crucial for semantics so that we close
2025-03-24 21:55:24 +13:00
// over variables. Variables defined lexically later shouldn't
2025-02-20 19:33:21 +13:00
// be available to the function body.
KclValue ::Function {
2025-02-22 20:16:29 +13:00
value : FunctionSource ::User {
ast : function_expression . clone ( ) ,
2025-03-08 04:04:57 +13:00
settings : exec_state . mod_local . settings . clone ( ) ,
2025-03-05 12:03:32 +13:00
memory : exec_state . mut_stack ( ) . snapshot ( ) ,
2025-02-22 20:16:29 +13:00
} ,
2025-02-20 19:33:21 +13:00
meta : vec ! [ metadata . to_owned ( ) ] ,
}
}
}
2025-02-05 17:53:49 +13:00
Expr ::CallExpressionKw ( call_expression ) = > call_expression . execute ( exec_state , self ) . await ? ,
Expr ::PipeExpression ( pipe_expression ) = > pipe_expression . get_result ( exec_state , self ) . await ? ,
Expr ::PipeSubstitution ( pipe_substitution ) = > match statement_kind {
StatementKind ::Declaration { name } = > {
let message = format! (
" you cannot declare variable {name} as %, because % can only be used in function calls "
) ;
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-02-05 17:53:49 +13:00
message ,
2025-05-19 14:13:10 -04:00
vec! [ pipe_substitution . into ( ) ] ,
) ) ) ;
2025-02-05 17:53:49 +13:00
}
StatementKind ::Expression = > match exec_state . mod_local . pipe_value . clone ( ) {
Some ( x ) = > x ,
None = > {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" cannot use % outside a pipe expression " . to_owned ( ) ,
vec! [ pipe_substitution . into ( ) ] ,
) ) ) ;
2025-02-05 17:53:49 +13:00
}
} ,
} ,
Expr ::ArrayExpression ( array_expression ) = > array_expression . execute ( exec_state , self ) . await ? ,
Expr ::ArrayRangeExpression ( range_expression ) = > range_expression . execute ( exec_state , self ) . await ? ,
Expr ::ObjectExpression ( object_expression ) = > object_expression . execute ( exec_state , self ) . await ? ,
2025-06-05 08:23:48 -05:00
Expr ::MemberExpression ( member_expression ) = > member_expression . get_result ( exec_state , self ) . await ? ,
2025-02-05 17:53:49 +13:00
Expr ::UnaryExpression ( unary_expression ) = > unary_expression . get_result ( exec_state , self ) . await ? ,
Expr ::IfExpression ( expr ) = > expr . get_result ( exec_state , self ) . await ? ,
Expr ::LabelledExpression ( expr ) = > {
let result = self
2025-02-20 19:33:21 +13:00
. execute_expr ( & expr . expr , exec_state , metadata , & [ ] , statement_kind )
2025-02-05 17:53:49 +13:00
. await ? ;
exec_state
2025-03-05 12:03:32 +13:00
. mut_stack ( )
2025-02-12 10:22:56 +13:00
. add ( expr . label . name . clone ( ) , result . clone ( ) , init . into ( ) ) ? ;
2025-02-05 17:53:49 +13:00
// TODO this lets us use the label as a variable name, but not as a tag in most cases
result
}
2025-05-12 14:07:57 +12:00
Expr ::AscribedExpression ( expr ) = > expr . get_result ( exec_state , self ) . await ? ,
2025-02-05 17:53:49 +13:00
} ;
Ok ( item )
}
}
2025-06-03 11:46:28 -05:00
/// If the error is about an undefined name, and that name matches the name being defined,
/// make the error message more specific.
fn var_in_own_ref_err ( e : KclError , being_declared : & Option < String > ) -> KclError {
let KclError ::UndefinedValue { name , mut details } = e else {
return e ;
} ;
// TODO after June 26th: replace this with a let-chain,
// which will be available in Rust 1.88
// https://rust-lang.github.io/rfcs/2497-if-let-chains.html
2025-06-26 17:02:54 -05:00
if let ( Some ( name0 ) , Some ( name1 ) ) = ( & being_declared , & name )
& & name0 = = name1
{
details . message = format! (
" You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead. "
) ;
2025-06-03 11:46:28 -05:00
}
KclError ::UndefinedValue { details , name }
}
2025-05-12 14:07:57 +12:00
impl Node < AscribedExpression > {
#[ async_recursion ]
pub async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
let metadata = Metadata {
source_range : SourceRange ::from ( self ) ,
} ;
let result = ctx
. execute_expr ( & self . expr , exec_state , & metadata , & [ ] , StatementKind ::Expression )
. await ? ;
apply_ascription ( & result , & self . ty , exec_state , self . into ( ) )
}
}
2025-04-07 16:13:15 +12:00
fn apply_ascription (
2025-03-21 10:56:55 +13:00
value : & KclValue ,
ty : & Node < Type > ,
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < KclValue , KclError > {
2025-03-17 17:57:26 +13:00
let ty = RuntimeType ::from_parsed ( ty . inner . clone ( ) , exec_state , value . into ( ) )
2025-06-02 13:30:57 -05:00
. map_err ( | e | KclError ::new_semantic ( e . into ( ) ) ) ? ;
2025-02-27 15:58:58 +13:00
2025-06-13 02:20:04 +12:00
if matches! ( & ty , & RuntimeType ::Primitive ( PrimitiveType ::Number ( .. ) ) ) {
exec_state . clear_units_warnings ( & source_range ) ;
}
2025-05-21 17:22:30 -04:00
value . coerce ( & ty , false , exec_state ) . map_err ( | _ | {
2025-05-16 10:58:21 +12:00
let suggestion = if ty = = RuntimeType ::length ( ) {
" , you might try coercing to a fully specified numeric type such as `number(mm)` "
} else if ty = = RuntimeType ::angle ( ) {
" , you might try coercing to a fully specified numeric type such as `number(deg)` "
} else {
" "
} ;
2025-06-13 02:20:04 +12:00
let ty_str = if let Some ( ty ) = value . principal_type ( ) {
format! ( " (with type ` {ty} `) " )
} else {
String ::new ( )
} ;
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-06-13 02:20:04 +12:00
" could not coerce {} {ty_str}to type `{ty}`{suggestion} " ,
2025-05-16 10:58:21 +12:00
value . human_friendly_type ( )
) ,
2025-05-19 14:13:10 -04:00
vec! [ source_range ] ,
) )
2025-03-21 10:56:55 +13:00
} )
2025-02-27 15:58:58 +13:00
}
2024-10-16 14:33:03 -07:00
impl BinaryPart {
#[ async_recursion ]
pub async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
match self {
2025-03-26 01:59:43 -04:00
BinaryPart ::Literal ( literal ) = > Ok ( KclValue ::from_literal ( ( * * literal ) . clone ( ) , exec_state ) ) ,
2025-03-24 20:58:55 +13:00
BinaryPart ::Name ( name ) = > name . get_result ( exec_state , ctx ) . await . cloned ( ) ,
2024-10-16 14:33:03 -07:00
BinaryPart ::BinaryExpression ( binary_expression ) = > binary_expression . get_result ( exec_state , ctx ) . await ,
2024-12-02 15:23:18 -06:00
BinaryPart ::CallExpressionKw ( call_expression ) = > call_expression . execute ( exec_state , ctx ) . await ,
2024-10-16 14:33:03 -07:00
BinaryPart ::UnaryExpression ( unary_expression ) = > unary_expression . get_result ( exec_state , ctx ) . await ,
2025-06-05 08:23:48 -05:00
BinaryPart ::MemberExpression ( member_expression ) = > member_expression . get_result ( exec_state , ctx ) . await ,
2025-07-01 19:33:36 -04:00
BinaryPart ::ArrayExpression ( e ) = > e . execute ( exec_state , ctx ) . await ,
BinaryPart ::ArrayRangeExpression ( e ) = > e . execute ( exec_state , ctx ) . await ,
BinaryPart ::ObjectExpression ( e ) = > e . execute ( exec_state , ctx ) . await ,
2024-10-16 14:33:03 -07:00
BinaryPart ::IfExpression ( e ) = > e . get_result ( exec_state , ctx ) . await ,
2025-05-12 14:07:57 +12:00
BinaryPart ::AscribedExpression ( e ) = > e . get_result ( exec_state , ctx ) . await ,
2024-10-16 14:33:03 -07:00
}
}
}
2025-03-24 20:58:55 +13:00
impl Node < Name > {
2025-05-19 16:50:15 +12:00
pub ( super ) async fn get_result < ' a > (
2025-03-24 20:58:55 +13:00
& self ,
exec_state : & ' a mut ExecState ,
ctx : & ExecutorContext ,
2025-06-04 23:48:15 -05:00
) -> Result < & ' a KclValue , KclError > {
let being_declared = exec_state . mod_local . being_declared . clone ( ) ;
self . get_result_inner ( exec_state , ctx )
. await
. map_err ( | e | var_in_own_ref_err ( e , & being_declared ) )
}
async fn get_result_inner < ' a > (
& self ,
exec_state : & ' a mut ExecState ,
ctx : & ExecutorContext ,
2025-03-24 20:58:55 +13:00
) -> Result < & ' a KclValue , KclError > {
if self . abs_path {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Absolute paths (names beginning with `::` are not yet supported) " . to_owned ( ) ,
self . as_source_ranges ( ) ,
) ) ) ;
2025-03-24 20:58:55 +13:00
}
2025-05-28 11:25:27 +12:00
let mod_name = format! ( " {} {} " , memory ::MODULE_PREFIX , self . name . name ) ;
2025-03-24 20:58:55 +13:00
if self . path . is_empty ( ) {
2025-05-28 11:25:27 +12:00
let item_value = exec_state . stack ( ) . get ( & self . name . name , self . into ( ) ) ;
if item_value . is_ok ( ) {
return item_value ;
}
return exec_state . stack ( ) . get ( & mod_name , self . into ( ) ) ;
2025-03-24 20:58:55 +13:00
}
let mut mem_spec : Option < ( EnvironmentRef , Vec < String > ) > = None ;
for p in & self . path {
let value = match mem_spec {
Some ( ( env , exports ) ) = > {
if ! exports . contains ( & p . name ) {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Item {} not found in module's exported items " , p . name ) ,
p . as_source_ranges ( ) ,
) ) ) ;
2025-03-24 20:58:55 +13:00
}
exec_state
. stack ( )
. memory
. get_from ( & p . name , env , p . as_source_range ( ) , 0 ) ?
}
2025-05-28 11:25:27 +12:00
None = > exec_state
. stack ( )
. get ( & format! ( " {} {} " , memory ::MODULE_PREFIX , p . name ) , self . into ( ) ) ? ,
2025-03-24 20:58:55 +13:00
} ;
let KclValue ::Module { value : module_id , .. } = value else {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-03-24 20:58:55 +13:00
" Identifier in path must refer to a module, found {} " ,
value . human_friendly_type ( )
) ,
2025-05-19 14:13:10 -04:00
p . as_source_ranges ( ) ,
) ) ) ;
2025-03-24 20:58:55 +13:00
} ;
mem_spec = Some (
2025-04-16 11:52:14 -07:00
ctx . exec_module_for_items ( * module_id , exec_state , p . as_source_range ( ) )
2025-03-24 20:58:55 +13:00
. await ? ,
) ;
}
let ( env , exports ) = mem_spec . unwrap ( ) ;
2025-05-28 11:25:27 +12:00
let item_exported = exports . contains ( & self . name . name ) ;
let item_value = exec_state
. stack ( )
. memory
. get_from ( & self . name . name , env , self . name . as_source_range ( ) , 0 ) ;
// Item is defined and exported.
if item_exported & & item_value . is_ok ( ) {
return item_value ;
2025-03-24 20:58:55 +13:00
}
2025-05-28 11:25:27 +12:00
let mod_exported = exports . contains ( & mod_name ) ;
let mod_value = exec_state
2025-03-24 20:58:55 +13:00
. stack ( )
. memory
2025-05-28 11:25:27 +12:00
. get_from ( & mod_name , env , self . name . as_source_range ( ) , 0 ) ;
// Module is defined and exported.
if mod_exported & & mod_value . is_ok ( ) {
return mod_value ;
}
// Neither item or module is defined.
if item_value . is_err ( ) & & mod_value . is_err ( ) {
return item_value ;
}
// Either item or module is defined, but not exported.
debug_assert! ( ( item_value . is_ok ( ) & & ! item_exported ) | | ( mod_value . is_ok ( ) & & ! mod_exported ) ) ;
2025-06-02 13:30:57 -05:00
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-28 11:25:27 +12:00
format! ( " Item {} not found in module's exported items " , self . name . name ) ,
self . name . as_source_ranges ( ) ,
) ) )
2025-03-24 20:58:55 +13:00
}
}
2024-10-30 16:52:17 -04:00
impl Node < MemberExpression > {
2025-06-05 08:23:48 -05:00
async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
2024-10-17 16:29:27 -07:00
let property = Property ::try_from ( self . computed , self . property . clone ( ) , exec_state , self . into ( ) ) ? ;
2025-06-05 08:23:48 -05:00
let meta = Metadata {
source_range : SourceRange ::from ( self ) ,
2024-10-16 14:33:03 -07:00
} ;
2025-06-05 08:23:48 -05:00
let object = ctx
. execute_expr ( & self . object , exec_state , & meta , & [ ] , StatementKind ::Expression )
. await ? ;
2024-10-16 14:33:03 -07:00
// Check the property and object match -- e.g. ints for arrays, strs for objects.
2025-04-28 12:08:47 -04:00
match ( object , property , self . computed ) {
KCL: Getter for axes of planes (#7662)
## Goal
Currently, there's no way in KCL to get fields of a plane, e.g. the underlying X axis, Y axis or origin.
This would be useful for geometry calculations in KCL. It would help KCL users write transformations between planes for rotating geometry.
For example, this enables
```kcl
export fn crossProduct(@vectors) {
a = vectors[0]
b = vectors[1]
x = a[1] * b[2] - (a[2] * b[1])
y = a[2] * b[0] - (a[0] * b[2])
z = a[0] * b[1] - (a[1] * b[0])
return [x, y, z]
}
export fn normalOf(@plane) {
return crossProduct([plane.xAxis, plane.yAxis])
}
```
## Implementation
My goal was just to enable a simple getter for planes, like `myPlane.xAxis` and yAxis and origins. That's nearly what happened, except I discovered that there's two ways to represent a plane: either `KclValue::Plane` or `KclValue::Object` with the right fields.
No matter which format your plane is represented as, it should behave consistently when you get its properties. Those properties should be returned as `[number; 3]` because that is how KCL represents points.
Unfortunately we actually require planes-as-objects to be defined with axes like `myPlane = { xAxis = { x = 1, y = 0, z = 0 }, ...}`, but that's a mistake in my opinion. So if you do use that representation of a plane, it should still return a [number; 3]. This required some futzing around so that we let you access object fields .x and .y as [0] and [1], which is weird, but whatever, I think it's good.
This PR is tested via planestuff.kcl which has a Rust unit test.
Part of the hole efforts, see https://github.com/KittyCAD/modeling-app/discussions/7543
2025-07-02 11:24:26 -05:00
( KclValue ::Plane { value : plane } , Property ::String ( property ) , false ) = > match property . as_str ( ) {
" yAxis " = > {
let ( p , u ) = plane . info . y_axis . as_3_dims ( ) ;
Ok ( KclValue ::array_from_point3d (
p ,
NumericType ::Known ( crate ::exec ::UnitType ::Length ( u ) ) ,
vec! [ meta ] ,
) )
}
" xAxis " = > {
let ( p , u ) = plane . info . x_axis . as_3_dims ( ) ;
Ok ( KclValue ::array_from_point3d (
p ,
NumericType ::Known ( crate ::exec ::UnitType ::Length ( u ) ) ,
vec! [ meta ] ,
) )
}
" origin " = > {
let ( p , u ) = plane . info . origin . as_3_dims ( ) ;
Ok ( KclValue ::array_from_point3d (
p ,
NumericType ::Known ( crate ::exec ::UnitType ::Length ( u ) ) ,
vec! [ meta ] ,
) )
}
other = > Err ( KclError ::new_undefined_value (
KclErrorDetails ::new (
format! ( " Property ' {other} ' not found in plane " ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ,
None ,
) ) ,
} ,
2025-04-28 12:08:47 -04:00
( KclValue ::Object { value : map , meta : _ } , Property ::String ( property ) , false ) = > {
2024-10-16 14:33:03 -07:00
if let Some ( value ) = map . get ( & property ) {
2024-11-14 17:27:19 -06:00
Ok ( value . to_owned ( ) )
2024-10-16 14:33:03 -07:00
} else {
2025-06-02 17:25:55 -05:00
Err ( KclError ::new_undefined_value (
KclErrorDetails ::new (
format! ( " Property ' {property} ' not found in object " ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ,
None ,
) )
2024-10-16 14:33:03 -07:00
}
}
2025-05-19 14:13:10 -04:00
( KclValue ::Object { .. } , Property ::String ( property ) , true ) = > {
2025-06-02 13:30:57 -05:00
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Cannot index object with string; use dot notation instead, e.g. `obj. {property} ` " ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ) )
}
KCL: Getter for axes of planes (#7662)
## Goal
Currently, there's no way in KCL to get fields of a plane, e.g. the underlying X axis, Y axis or origin.
This would be useful for geometry calculations in KCL. It would help KCL users write transformations between planes for rotating geometry.
For example, this enables
```kcl
export fn crossProduct(@vectors) {
a = vectors[0]
b = vectors[1]
x = a[1] * b[2] - (a[2] * b[1])
y = a[2] * b[0] - (a[0] * b[2])
z = a[0] * b[1] - (a[1] * b[0])
return [x, y, z]
}
export fn normalOf(@plane) {
return crossProduct([plane.xAxis, plane.yAxis])
}
```
## Implementation
My goal was just to enable a simple getter for planes, like `myPlane.xAxis` and yAxis and origins. That's nearly what happened, except I discovered that there's two ways to represent a plane: either `KclValue::Plane` or `KclValue::Object` with the right fields.
No matter which format your plane is represented as, it should behave consistently when you get its properties. Those properties should be returned as `[number; 3]` because that is how KCL represents points.
Unfortunately we actually require planes-as-objects to be defined with axes like `myPlane = { xAxis = { x = 1, y = 0, z = 0 }, ...}`, but that's a mistake in my opinion. So if you do use that representation of a plane, it should still return a [number; 3]. This required some futzing around so that we let you access object fields .x and .y as [0] and [1], which is weird, but whatever, I think it's good.
This PR is tested via planestuff.kcl which has a Rust unit test.
Part of the hole efforts, see https://github.com/KittyCAD/modeling-app/discussions/7543
2025-07-02 11:24:26 -05:00
( KclValue ::Object { value : map , .. } , p @ Property ::UInt ( i ) , _ ) = > {
if i = = 0
& & let Some ( value ) = map . get ( " x " )
{
return Ok ( value . to_owned ( ) ) ;
}
if i = = 1
& & let Some ( value ) = map . get ( " y " )
{
return Ok ( value . to_owned ( ) ) ;
}
if i = = 2
& & let Some ( value ) = map . get ( " z " )
{
return Ok ( value . to_owned ( ) ) ;
}
2024-11-14 17:27:19 -06:00
let t = p . type_name ( ) ;
let article = article_for ( t ) ;
2025-06-02 13:30:57 -05:00
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Only strings can be used as the property of an object, but you're using {article} {t} " , ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ) )
2024-11-14 17:27:19 -06:00
}
2025-05-11 23:57:31 -04:00
( KclValue ::HomArray { value : arr , .. } , Property ::UInt ( index ) , _ ) = > {
2024-11-14 17:27:19 -06:00
let value_of_arr = arr . get ( index ) ;
2024-10-16 14:33:03 -07:00
if let Some ( value ) = value_of_arr {
2024-11-14 17:27:19 -06:00
Ok ( value . to_owned ( ) )
2024-10-16 14:33:03 -07:00
} else {
2025-06-02 17:25:55 -05:00
Err ( KclError ::new_undefined_value (
KclErrorDetails ::new (
format! ( " The array doesn't have any item at index {index} " ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ,
None ,
) )
2024-10-16 14:33:03 -07:00
}
}
2025-05-28 16:29:23 +12:00
// Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
// This is kind of a silly property, but it's possible it occurs in generic code or something.
( obj , Property ::UInt ( 0 ) , _ ) = > Ok ( obj ) ,
2025-05-11 23:57:31 -04:00
( KclValue ::HomArray { .. } , p , _ ) = > {
2024-11-14 17:27:19 -06:00
let t = p . type_name ( ) ;
let article = article_for ( t ) ;
2025-06-02 13:30:57 -05:00
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Only integers >= 0 can be used as the index of an array, but you're using {article} {t} " , ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ) )
2024-11-14 17:27:19 -06:00
}
2025-04-28 12:08:47 -04:00
( KclValue ::Solid { value } , Property ::String ( prop ) , false ) if prop = = " sketch " = > Ok ( KclValue ::Sketch {
2025-01-22 09:42:09 +13:00
value : Box ::new ( value . sketch ) ,
2024-11-14 17:27:19 -06:00
} ) ,
2025-07-01 14:37:01 -04:00
( geometry @ KclValue ::Solid { .. } , Property ::String ( prop ) , false ) if prop = = " tags " = > {
// This is a common mistake.
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
format! (
" Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`. " ,
geometry . human_friendly_type ( )
) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ) )
}
2025-04-28 12:08:47 -04:00
( KclValue ::Sketch { value : sk } , Property ::String ( prop ) , false ) if prop = = " tags " = > Ok ( KclValue ::Object {
2024-11-14 17:27:19 -06:00
meta : vec ! [ Metadata {
source_range : SourceRange ::from ( self . clone ( ) ) ,
} ] ,
value : sk
. tags
. iter ( )
. map ( | ( k , tag ) | ( k . to_owned ( ) , KclValue ::TagIdentifier ( Box ::new ( tag . to_owned ( ) ) ) ) )
. collect ( ) ,
} ) ,
2025-07-01 14:37:01 -04:00
( geometry @ ( KclValue ::Sketch { .. } | KclValue ::Solid { .. } ) , Property ::String ( property ) , false ) = > {
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
format! ( " Property ` {property} ` not found on {} " , geometry . human_friendly_type ( ) ) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ) )
}
2025-06-13 02:20:04 +12:00
( being_indexed , _ , _ ) = > Err ( KclError ::new_semantic ( KclErrorDetails ::new (
format! (
" Only arrays can be indexed, but you're trying to index {} " ,
being_indexed . human_friendly_type ( )
) ,
vec! [ self . clone ( ) . into ( ) ] ,
) ) ) ,
2024-10-16 14:33:03 -07:00
}
}
}
2024-10-30 16:52:17 -04:00
impl Node < BinaryExpression > {
2024-10-16 14:33:03 -07:00
#[ async_recursion ]
pub async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
2024-11-14 17:27:19 -06:00
let left_value = self . left . get_result ( exec_state , ctx ) . await ? ;
let right_value = self . right . get_result ( exec_state , ctx ) . await ? ;
let mut meta = left_value . metadata ( ) ;
meta . extend ( right_value . metadata ( ) ) ;
2024-10-16 14:33:03 -07:00
// First check if we are doing string concatenation.
if self . operator = = BinaryOperator ::Add {
2024-11-14 17:27:19 -06:00
if let ( KclValue ::String { value : left , meta : _ } , KclValue ::String { value : right , meta : _ } ) =
( & left_value , & right_value )
{
return Ok ( KclValue ::String {
2025-06-26 17:02:54 -05:00
value : format ! ( " {left}{right} " ) ,
2024-11-14 17:27:19 -06:00
meta ,
} ) ;
2024-10-16 14:33:03 -07:00
}
}
2025-04-02 19:13:03 -07:00
// Then check if we have solids.
if self . operator = = BinaryOperator ::Add | | self . operator = = BinaryOperator ::Or {
if let ( KclValue ::Solid { value : left } , KclValue ::Solid { value : right } ) = ( & left_value , & right_value ) {
2025-05-11 17:43:12 +12:00
let args = Args ::new ( Default ::default ( ) , self . into ( ) , ctx . clone ( ) , None ) ;
2025-04-10 18:30:57 -07:00
let result = crate ::std ::csg ::inner_union (
vec! [ * left . clone ( ) , * right . clone ( ) ] ,
Default ::default ( ) ,
exec_state ,
args ,
)
. await ? ;
2025-04-02 19:13:03 -07:00
return Ok ( result . into ( ) ) ;
}
} else if self . operator = = BinaryOperator ::Sub {
// Check if we have solids.
if let ( KclValue ::Solid { value : left } , KclValue ::Solid { value : right } ) = ( & left_value , & right_value ) {
2025-05-11 17:43:12 +12:00
let args = Args ::new ( Default ::default ( ) , self . into ( ) , ctx . clone ( ) , None ) ;
2025-04-10 18:30:57 -07:00
let result = crate ::std ::csg ::inner_subtract (
vec! [ * left . clone ( ) ] ,
vec! [ * right . clone ( ) ] ,
Default ::default ( ) ,
exec_state ,
args ,
)
. await ? ;
2025-04-02 19:13:03 -07:00
return Ok ( result . into ( ) ) ;
}
} else if self . operator = = BinaryOperator ::And {
// Check if we have solids.
if let ( KclValue ::Solid { value : left } , KclValue ::Solid { value : right } ) = ( & left_value , & right_value ) {
2025-05-11 17:43:12 +12:00
let args = Args ::new ( Default ::default ( ) , self . into ( ) , ctx . clone ( ) , None ) ;
2025-04-10 18:30:57 -07:00
let result = crate ::std ::csg ::inner_intersect (
vec! [ * left . clone ( ) , * right . clone ( ) ] ,
Default ::default ( ) ,
exec_state ,
args ,
)
. await ? ;
2025-04-02 19:13:03 -07:00
return Ok ( result . into ( ) ) ;
}
}
2024-12-16 17:33:08 -05:00
// Check if we are doing logical operations on booleans.
if self . operator = = BinaryOperator ::Or | | self . operator = = BinaryOperator ::And {
let KclValue ::Bool {
value : left_value ,
meta : _ ,
} = left_value
else {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2024-12-16 17:33:08 -05:00
" Cannot apply logical operator to non-boolean value: {} " ,
left_value . human_friendly_type ( )
) ,
2025-05-19 14:13:10 -04:00
vec! [ self . left . clone ( ) . into ( ) ] ,
) ) ) ;
2024-12-16 17:33:08 -05:00
} ;
let KclValue ::Bool {
value : right_value ,
meta : _ ,
} = right_value
else {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2024-12-16 17:33:08 -05:00
" Cannot apply logical operator to non-boolean value: {} " ,
right_value . human_friendly_type ( )
) ,
2025-05-19 14:13:10 -04:00
vec! [ self . right . clone ( ) . into ( ) ] ,
) ) ) ;
2024-12-16 17:33:08 -05:00
} ;
let raw_value = match self . operator {
BinaryOperator ::Or = > left_value | | right_value ,
BinaryOperator ::And = > left_value & & right_value ,
_ = > unreachable! ( ) ,
} ;
return Ok ( KclValue ::Bool { value : raw_value , meta } ) ;
}
2025-04-07 16:13:15 +12:00
let left = number_as_f64 ( & left_value , self . left . clone ( ) . into ( ) ) ? ;
let right = number_as_f64 ( & right_value , self . right . clone ( ) . into ( ) ) ? ;
2024-11-14 17:27:19 -06:00
let value = match self . operator {
2025-04-07 16:13:15 +12:00
BinaryOperator ::Add = > {
2025-04-29 12:24:18 +12:00
let ( l , r , ty ) = NumericType ::combine_eq_coerce ( left , right ) ;
2025-04-07 16:13:15 +12:00
self . warn_on_unknown ( & ty , " Adding " , exec_state ) ;
KclValue ::Number { value : l + r , meta , ty }
}
BinaryOperator ::Sub = > {
2025-04-29 12:24:18 +12:00
let ( l , r , ty ) = NumericType ::combine_eq_coerce ( left , right ) ;
2025-04-07 16:13:15 +12:00
self . warn_on_unknown ( & ty , " Subtracting " , exec_state ) ;
KclValue ::Number { value : l - r , meta , ty }
}
BinaryOperator ::Mul = > {
let ( l , r , ty ) = NumericType ::combine_mul ( left , right ) ;
self . warn_on_unknown ( & ty , " Multiplying " , exec_state ) ;
KclValue ::Number { value : l * r , meta , ty }
}
BinaryOperator ::Div = > {
let ( l , r , ty ) = NumericType ::combine_div ( left , right ) ;
self . warn_on_unknown ( & ty , " Dividing " , exec_state ) ;
KclValue ::Number { value : l / r , meta , ty }
}
BinaryOperator ::Mod = > {
2025-06-12 08:44:55 +12:00
let ( l , r , ty ) = NumericType ::combine_mod ( left , right ) ;
2025-04-07 16:13:15 +12:00
self . warn_on_unknown ( & ty , " Modulo of " , exec_state ) ;
KclValue ::Number { value : l % r , meta , ty }
}
2024-11-14 17:27:19 -06:00
BinaryOperator ::Pow = > KclValue ::Number {
2025-04-07 16:13:15 +12:00
value : left . n . powf ( right . n ) ,
2024-11-14 17:27:19 -06:00
meta ,
2025-04-23 10:58:35 +12:00
ty : exec_state . current_default_units ( ) ,
2024-11-14 17:27:19 -06:00
} ,
2025-04-07 16:13:15 +12:00
BinaryOperator ::Neq = > {
let ( l , r , ty ) = NumericType ::combine_eq ( left , right ) ;
self . warn_on_unknown ( & ty , " Comparing " , exec_state ) ;
KclValue ::Bool { value : l ! = r , meta }
}
BinaryOperator ::Gt = > {
let ( l , r , ty ) = NumericType ::combine_eq ( left , right ) ;
self . warn_on_unknown ( & ty , " Comparing " , exec_state ) ;
KclValue ::Bool { value : l > r , meta }
}
BinaryOperator ::Gte = > {
let ( l , r , ty ) = NumericType ::combine_eq ( left , right ) ;
self . warn_on_unknown ( & ty , " Comparing " , exec_state ) ;
KclValue ::Bool { value : l > = r , meta }
}
BinaryOperator ::Lt = > {
let ( l , r , ty ) = NumericType ::combine_eq ( left , right ) ;
self . warn_on_unknown ( & ty , " Comparing " , exec_state ) ;
KclValue ::Bool { value : l < r , meta }
}
BinaryOperator ::Lte = > {
let ( l , r , ty ) = NumericType ::combine_eq ( left , right ) ;
self . warn_on_unknown ( & ty , " Comparing " , exec_state ) ;
KclValue ::Bool { value : l < = r , meta }
}
BinaryOperator ::Eq = > {
let ( l , r , ty ) = NumericType ::combine_eq ( left , right ) ;
self . warn_on_unknown ( & ty , " Comparing " , exec_state ) ;
KclValue ::Bool { value : l = = r , meta }
}
2024-12-16 17:33:08 -05:00
BinaryOperator ::And | BinaryOperator ::Or = > unreachable! ( ) ,
2024-10-16 14:33:03 -07:00
} ;
2024-11-14 17:27:19 -06:00
Ok ( value )
2024-10-16 14:33:03 -07:00
}
2025-04-07 16:13:15 +12:00
fn warn_on_unknown ( & self , ty : & NumericType , verb : & str , exec_state : & mut ExecState ) {
2025-04-23 10:58:35 +12:00
if ty = = & NumericType ::Unknown {
2025-06-13 02:20:04 +12:00
let sr = self . as_source_range ( ) ;
exec_state . clear_units_warnings ( & sr ) ;
let mut err = CompilationError ::err (
sr ,
2025-06-26 17:02:54 -05:00
format! (
" {verb} numbers which have unknown or incompatible units. \n You can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`. "
) ,
2025-06-13 02:20:04 +12:00
) ;
err . tag = crate ::errors ::Tag ::UnknownNumericUnits ;
exec_state . warn ( err ) ;
2025-04-07 16:13:15 +12:00
}
}
2024-10-16 14:33:03 -07:00
}
2024-10-30 16:52:17 -04:00
impl Node < UnaryExpression > {
2024-10-16 14:33:03 -07:00
pub async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
if self . operator = = UnaryOperator ::Not {
2024-11-14 17:27:19 -06:00
let value = self . argument . get_result ( exec_state , ctx ) . await ? ;
let KclValue ::Bool {
value : bool_value ,
meta : _ ,
} = value
else {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2024-11-14 17:27:19 -06:00
" Cannot apply unary operator ! to non-boolean value: {} " ,
value . human_friendly_type ( )
) ,
2025-05-19 14:13:10 -04:00
vec! [ self . into ( ) ] ,
) ) ) ;
2024-10-16 14:33:03 -07:00
} ;
2024-11-14 17:27:19 -06:00
let meta = vec! [ Metadata {
source_range : self . into ( ) ,
} ] ;
let negated = KclValue ::Bool {
value : ! bool_value ,
meta ,
} ;
return Ok ( negated ) ;
2024-10-16 14:33:03 -07:00
}
2024-11-14 17:27:19 -06:00
let value = & self . argument . get_result ( exec_state , ctx ) . await ? ;
2025-04-03 22:44:52 +13:00
let err = | | {
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-04-03 22:44:52 +13:00
" You can only negate numbers, planes, or lines, but this is a {} " ,
value . human_friendly_type ( )
) ,
2025-05-19 14:13:10 -04:00
vec! [ self . into ( ) ] ,
) )
2025-04-03 22:44:52 +13:00
} ;
2024-11-14 17:27:19 -06:00
match value {
2025-02-14 13:03:23 +13:00
KclValue ::Number { value , ty , .. } = > {
2024-11-14 17:27:19 -06:00
let meta = vec! [ Metadata {
source_range : self . into ( ) ,
} ] ;
2025-02-14 13:03:23 +13:00
Ok ( KclValue ::Number {
value : - value ,
meta ,
2025-07-01 12:42:12 -05:00
ty : * ty ,
2025-02-14 13:03:23 +13:00
} )
2024-11-14 17:27:19 -06:00
}
2025-02-27 15:58:58 +13:00
KclValue ::Plane { value } = > {
let mut plane = value . clone ( ) ;
2025-04-29 19:11:02 -07:00
if plane . info . x_axis . x ! = 0.0 {
plane . info . x_axis . x * = - 1.0 ;
2025-04-24 22:01:27 +12:00
}
2025-04-29 19:11:02 -07:00
if plane . info . x_axis . y ! = 0.0 {
plane . info . x_axis . y * = - 1.0 ;
2025-04-24 22:01:27 +12:00
}
2025-04-29 19:11:02 -07:00
if plane . info . x_axis . z ! = 0.0 {
plane . info . x_axis . z * = - 1.0 ;
2025-04-24 22:01:27 +12:00
}
2025-02-27 15:58:58 +13:00
plane . value = PlaneType ::Uninit ;
plane . id = exec_state . next_uuid ( ) ;
Ok ( KclValue ::Plane { value : plane } )
}
2025-04-03 22:44:52 +13:00
KclValue ::Object { value : values , meta } = > {
// Special-case for negating line-like objects.
let Some ( direction ) = values . get ( " direction " ) else {
return Err ( err ( ) ) ;
} ;
let direction = match direction {
2025-05-11 23:57:31 -04:00
KclValue ::Tuple { value : values , meta } = > {
2025-04-03 22:44:52 +13:00
let values = values
. iter ( )
. map ( | v | match v {
KclValue ::Number { value , ty , meta } = > Ok ( KclValue ::Number {
value : * value * - 1.0 ,
2025-07-01 12:42:12 -05:00
ty : * ty ,
2025-04-03 22:44:52 +13:00
meta : meta . clone ( ) ,
} ) ,
_ = > Err ( err ( ) ) ,
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
2025-05-11 23:57:31 -04:00
KclValue ::Tuple {
2025-04-03 22:44:52 +13:00
value : values ,
meta : meta . clone ( ) ,
}
}
KclValue ::HomArray {
value : values ,
ty : ty @ RuntimeType ::Primitive ( PrimitiveType ::Number ( _ ) ) ,
} = > {
let values = values
. iter ( )
. map ( | v | match v {
KclValue ::Number { value , ty , meta } = > Ok ( KclValue ::Number {
value : * value * - 1.0 ,
2025-07-01 12:42:12 -05:00
ty : * ty ,
2025-04-03 22:44:52 +13:00
meta : meta . clone ( ) ,
} ) ,
_ = > Err ( err ( ) ) ,
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
KclValue ::HomArray {
value : values ,
ty : ty . clone ( ) ,
}
}
_ = > return Err ( err ( ) ) ,
} ;
let mut value = values . clone ( ) ;
value . insert ( " direction " . to_owned ( ) , direction ) ;
Ok ( KclValue ::Object {
value ,
meta : meta . clone ( ) ,
} )
}
_ = > Err ( err ( ) ) ,
2024-11-14 17:27:19 -06:00
}
2024-10-16 14:33:03 -07:00
}
}
pub ( crate ) async fn execute_pipe_body (
exec_state : & mut ExecState ,
body : & [ Expr ] ,
source_range : SourceRange ,
ctx : & ExecutorContext ,
) -> Result < KclValue , KclError > {
let Some ( ( first , body ) ) = body . split_first ( ) else {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Pipe expressions cannot be empty " . to_owned ( ) ,
vec! [ source_range ] ,
) ) ) ;
2024-10-16 14:33:03 -07:00
} ;
// Evaluate the first element in the pipeline.
// They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
// they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
// of its own.
let meta = Metadata {
2024-11-07 11:23:41 -05:00
source_range : SourceRange ::from ( first ) ,
2024-10-16 14:33:03 -07:00
} ;
let output = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( first , exec_state , & meta , & [ ] , StatementKind ::Expression )
2024-10-16 14:33:03 -07:00
. await ? ;
// Now that we've evaluated the first child expression in the pipeline, following child expressions
// should use the previous child expression for %.
// This means there's no more need for the previous pipe_value from the parent AST node above this one.
2025-06-03 16:32:24 -05:00
let previous_pipe_value = exec_state . mod_local . pipe_value . replace ( output ) ;
2024-10-16 14:33:03 -07:00
// Evaluate remaining elements.
let result = inner_execute_pipe_body ( exec_state , body , ctx ) . await ;
// Restore the previous pipe value.
2024-12-17 09:38:32 +13:00
exec_state . mod_local . pipe_value = previous_pipe_value ;
2024-10-16 14:33:03 -07:00
result
}
/// Execute the tail of a pipe expression. exec_state.pipe_value must be set by
/// the caller.
#[ async_recursion ]
async fn inner_execute_pipe_body (
exec_state : & mut ExecState ,
body : & [ Expr ] ,
ctx : & ExecutorContext ,
) -> Result < KclValue , KclError > {
for expression in body {
2024-12-11 21:26:42 +13:00
if let Expr ::TagDeclarator ( _ ) = expression {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-06-26 17:02:54 -05:00
format! ( " This cannot be in a PipeExpression: {expression:?} " ) ,
2025-05-19 14:13:10 -04:00
vec! [ expression . into ( ) ] ,
) ) ) ;
2024-12-11 21:26:42 +13:00
}
2024-10-16 14:33:03 -07:00
let metadata = Metadata {
2024-11-07 11:23:41 -05:00
source_range : SourceRange ::from ( expression ) ,
2024-10-16 14:33:03 -07:00
} ;
let output = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( expression , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-10-16 14:33:03 -07:00
. await ? ;
2024-12-17 09:38:32 +13:00
exec_state . mod_local . pipe_value = Some ( output ) ;
2024-10-16 14:33:03 -07:00
}
// Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
2024-12-17 09:38:32 +13:00
let final_output = exec_state . mod_local . pipe_value . take ( ) . unwrap ( ) ;
2024-10-16 14:33:03 -07:00
Ok ( final_output )
}
2024-12-13 16:39:40 -05:00
impl Node < TagDeclarator > {
pub async fn execute ( & self , exec_state : & mut ExecState ) -> Result < KclValue , KclError > {
let memory_item = KclValue ::TagIdentifier ( Box ::new ( TagIdentifier {
value : self . name . clone ( ) ,
2025-03-17 12:28:51 +13:00
info : Vec ::new ( ) ,
2024-12-13 16:39:40 -05:00
meta : vec ! [ Metadata {
source_range : self . into ( ) ,
} ] ,
} ) ) ;
2024-12-17 09:38:32 +13:00
exec_state
2025-03-05 12:03:32 +13:00
. mut_stack ( )
2025-02-12 10:22:56 +13:00
. add ( self . name . clone ( ) , memory_item . clone ( ) , self . into ( ) ) ? ;
2024-12-13 16:39:40 -05:00
Ok ( self . into ( ) )
}
}
2024-10-30 16:52:17 -04:00
impl Node < ArrayExpression > {
2024-10-16 14:33:03 -07:00
#[ async_recursion ]
pub async fn execute ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
let mut results = Vec ::with_capacity ( self . elements . len ( ) ) ;
for element in & self . elements {
let metadata = Metadata ::from ( element ) ;
// TODO: Carry statement kind here so that we know if we're
// inside a variable declaration.
let value = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( element , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-10-16 14:33:03 -07:00
. await ? ;
2024-11-14 17:27:19 -06:00
results . push ( value ) ;
2024-10-16 14:33:03 -07:00
}
2025-05-11 23:57:31 -04:00
Ok ( KclValue ::HomArray {
2024-11-14 17:27:19 -06:00
value : results ,
2025-05-11 23:57:31 -04:00
ty : RuntimeType ::Primitive ( PrimitiveType ::Any ) ,
2024-11-14 17:27:19 -06:00
} )
2024-10-16 14:33:03 -07:00
}
}
2024-10-30 16:52:17 -04:00
impl Node < ArrayRangeExpression > {
2024-10-16 14:33:03 -07:00
#[ async_recursion ]
pub async fn execute ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let metadata = Metadata ::from ( & self . start_element ) ;
2025-05-12 01:30:33 -04:00
let start_val = ctx
2025-02-20 19:33:21 +13:00
. execute_expr (
& self . start_element ,
exec_state ,
& metadata ,
& [ ] ,
StatementKind ::Expression ,
)
2024-11-14 17:27:19 -06:00
. await ? ;
2025-05-19 14:13:10 -04:00
let ( start , start_ty ) = start_val
. as_int_with_ty ( )
2025-06-02 13:30:57 -05:00
. ok_or ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Expected int but found {} " , start_val . human_friendly_type ( ) ) ,
vec! [ self . into ( ) ] ,
) ) ) ? ;
2024-10-30 16:52:17 -04:00
let metadata = Metadata ::from ( & self . end_element ) ;
2025-05-12 01:30:33 -04:00
let end_val = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( & self . end_element , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-11-14 17:27:19 -06:00
. await ? ;
2025-06-02 13:30:57 -05:00
let ( end , end_ty ) = end_val
. as_int_with_ty ( )
. ok_or ( KclError ::new_semantic ( KclErrorDetails ::new (
format! ( " Expected int but found {} " , end_val . human_friendly_type ( ) ) ,
vec! [ self . into ( ) ] ,
) ) ) ? ;
2024-10-16 14:33:03 -07:00
2025-05-12 01:30:33 -04:00
if start_ty ! = end_ty {
let start = start_val . as_ty_f64 ( ) . unwrap_or ( TyF64 { n : 0.0 , ty : start_ty } ) ;
let start = fmt ::human_display_number ( start . n , start . ty ) ;
let end = end_val . as_ty_f64 ( ) . unwrap_or ( TyF64 { n : 0.0 , ty : end_ty } ) ;
let end = fmt ::human_display_number ( end . n , end . ty ) ;
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Range start and end must be of the same type, but found {start} and {end} " ) ,
vec! [ self . into ( ) ] ,
) ) ) ;
2025-05-12 01:30:33 -04:00
}
2024-10-16 14:33:03 -07:00
if end < start {
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Range start is greater than range end: {start} .. {end} " ) ,
vec! [ self . into ( ) ] ,
) ) ) ;
2024-10-16 14:33:03 -07:00
}
let range : Vec < _ > = if self . end_inclusive {
2024-11-14 17:27:19 -06:00
( start ..= end ) . collect ( )
2024-10-16 14:33:03 -07:00
} else {
2024-11-14 17:27:19 -06:00
( start .. end ) . collect ( )
2024-10-16 14:33:03 -07:00
} ;
2024-11-14 17:27:19 -06:00
let meta = vec! [ Metadata {
source_range : self . into ( ) ,
} ] ;
2025-05-11 23:57:31 -04:00
Ok ( KclValue ::HomArray {
2024-11-14 17:27:19 -06:00
value : range
. into_iter ( )
2025-02-14 08:28:00 +13:00
. map ( | num | KclValue ::Number {
value : num as f64 ,
2025-07-01 12:42:12 -05:00
ty : start_ty ,
2024-11-14 17:27:19 -06:00
meta : meta . clone ( ) ,
} )
. collect ( ) ,
2025-05-12 01:30:33 -04:00
ty : RuntimeType ::Primitive ( PrimitiveType ::Number ( start_ty ) ) ,
2024-11-14 17:27:19 -06:00
} )
2024-10-16 14:33:03 -07:00
}
}
2024-10-30 16:52:17 -04:00
impl Node < ObjectExpression > {
2024-10-16 14:33:03 -07:00
#[ async_recursion ]
pub async fn execute ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
2024-11-14 17:27:19 -06:00
let mut object = HashMap ::with_capacity ( self . properties . len ( ) ) ;
2024-10-16 14:33:03 -07:00
for property in & self . properties {
let metadata = Metadata ::from ( & property . value ) ;
let result = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( & property . value , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-10-16 14:33:03 -07:00
. await ? ;
2024-11-14 17:27:19 -06:00
object . insert ( property . key . name . clone ( ) , result ) ;
2024-10-16 14:33:03 -07:00
}
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Object {
value : object ,
2024-10-16 14:33:03 -07:00
meta : vec ! [ Metadata {
source_range : self . into ( ) ,
} ] ,
2024-11-14 17:27:19 -06:00
} )
2024-10-16 14:33:03 -07:00
}
}
2025-05-14 10:04:51 -04:00
fn article_for < S : AsRef < str > > ( s : S ) -> & 'static str {
// '[' is included since it's an array.
if s . as_ref ( ) . starts_with ( [ 'a' , 'e' , 'i' , 'o' , 'u' , '[' ] ) {
2024-11-14 17:27:19 -06:00
" an "
2024-10-16 14:33:03 -07:00
} else {
2024-11-14 17:27:19 -06:00
" a "
2024-10-16 14:33:03 -07:00
}
}
2025-04-07 16:13:15 +12:00
fn number_as_f64 ( v : & KclValue , source_range : SourceRange ) -> Result < TyF64 , KclError > {
v . as_ty_f64 ( ) . ok_or_else ( | | {
2024-11-14 17:27:19 -06:00
let actual_type = v . human_friendly_type ( ) ;
2025-06-02 13:30:57 -05:00
KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Expected a number, but found {actual_type} " , ) ,
vec! [ source_range ] ,
) )
2025-04-07 16:13:15 +12:00
} )
2024-10-16 14:33:03 -07:00
}
2024-10-30 16:52:17 -04:00
impl Node < IfExpression > {
2024-10-17 12:30:44 -07:00
#[ async_recursion ]
pub async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
// Check the `if` branch.
let cond = ctx
2025-02-20 19:33:21 +13:00
. execute_expr (
& self . cond ,
exec_state ,
& Metadata ::from ( self ) ,
& [ ] ,
StatementKind ::Expression ,
)
2024-10-17 12:30:44 -07:00
. await ?
. get_bool ( ) ? ;
if cond {
2025-02-25 16:10:06 +13:00
let block_result = ctx . exec_block ( & self . then_val , exec_state , BodyType ::Block ) . await ? ;
2024-10-17 12:30:44 -07:00
// Block must end in an expression, so this has to be Some.
// Enforced by the parser.
// See https://github.com/KittyCAD/modeling-app/issues/4015
return Ok ( block_result . unwrap ( ) ) ;
}
// Check any `else if` branches.
for else_if in & self . else_ifs {
let cond = ctx
. execute_expr (
& else_if . cond ,
exec_state ,
& Metadata ::from ( self ) ,
2025-02-20 19:33:21 +13:00
& [ ] ,
2024-10-17 12:30:44 -07:00
StatementKind ::Expression ,
)
. await ?
. get_bool ( ) ? ;
if cond {
2025-02-25 16:10:06 +13:00
let block_result = ctx . exec_block ( & else_if . then_val , exec_state , BodyType ::Block ) . await ? ;
2024-10-17 12:30:44 -07:00
// Block must end in an expression, so this has to be Some.
// Enforced by the parser.
// See https://github.com/KittyCAD/modeling-app/issues/4015
return Ok ( block_result . unwrap ( ) ) ;
}
}
// Run the final `else` branch.
2025-02-25 16:10:06 +13:00
ctx . exec_block ( & self . final_else , exec_state , BodyType ::Block )
2024-10-17 12:30:44 -07:00
. await
. map ( | expr | expr . unwrap ( ) )
}
}
2024-10-17 16:29:27 -07:00
#[ derive(Debug) ]
enum Property {
2024-11-25 10:50:43 +13:00
UInt ( usize ) ,
2024-10-17 16:29:27 -07:00
String ( String ) ,
}
impl Property {
fn try_from (
computed : bool ,
value : LiteralIdentifier ,
exec_state : & ExecState ,
sr : SourceRange ,
) -> Result < Self , KclError > {
let property_sr = vec! [ sr ] ;
let property_src : SourceRange = value . clone ( ) . into ( ) ;
match value {
LiteralIdentifier ::Identifier ( identifier ) = > {
2024-10-30 16:52:17 -04:00
let name = & identifier . name ;
2024-10-17 16:29:27 -07:00
if ! computed {
2025-04-28 12:08:47 -04:00
// This is dot syntax. Treat the property as a literal.
2024-10-17 16:29:27 -07:00
Ok ( Property ::String ( name . to_string ( ) ) )
} else {
2025-04-28 12:08:47 -04:00
// This is bracket syntax. Actually evaluate memory to
// compute the property.
2025-03-05 12:03:32 +13:00
let prop = exec_state . stack ( ) . get ( name , property_src ) ? ;
2024-11-14 17:27:19 -06:00
jvalue_to_prop ( prop , property_sr , name )
2024-10-17 16:29:27 -07:00
}
}
LiteralIdentifier ::Literal ( literal ) = > {
let value = literal . value . clone ( ) ;
match value {
2025-06-25 20:36:57 +12:00
n @ LiteralValue ::Number { value , suffix } = > {
if ! matches! ( suffix , NumericSuffix ::None | NumericSuffix ::Count ) {
return Err ( KclError ::new_semantic ( KclErrorDetails ::new (
format! ( " {n} is not a valid index, indices must be non-dimensional numbers " ) ,
property_sr ,
) ) ) ;
}
2025-01-22 08:29:30 +13:00
if let Some ( x ) = crate ::try_f64_to_usize ( value ) {
2024-11-25 10:50:43 +13:00
Ok ( Property ::UInt ( x ) )
2024-10-17 16:29:27 -07:00
} else {
2025-06-02 13:30:57 -05:00
Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-06-25 20:36:57 +12:00
format! ( " {n} is not a valid index, indices must be whole numbers >= 0 " ) ,
2025-05-19 14:13:10 -04:00
property_sr ,
) ) )
2024-10-17 16:29:27 -07:00
}
}
2025-06-02 13:30:57 -05:00
_ = > Err ( KclError ::new_semantic ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
" Only numbers (>= 0) can be indexes " . to_owned ( ) ,
vec! [ sr ] ,
) ) ) ,
2024-10-17 16:29:27 -07:00
}
}
}
}
}
2024-11-14 17:27:19 -06:00
fn jvalue_to_prop ( value : & KclValue , property_sr : Vec < SourceRange > , name : & str ) -> Result < Property , KclError > {
2025-06-02 13:30:57 -05:00
let make_err =
| message : String | Err ::< Property , _ > ( KclError ::new_semantic ( KclErrorDetails ::new ( message , property_sr ) ) ) ;
2024-10-17 16:29:27 -07:00
match value {
2025-06-26 17:02:54 -05:00
n @ KclValue ::Number { value : num , ty , .. } = > {
if ! matches! (
ty ,
NumericType ::Known ( crate ::exec ::UnitType ::Count ) | NumericType ::Default { .. } | NumericType ::Any
) {
return make_err ( format! (
" arrays can only be indexed by non-dimensioned numbers, found {} " ,
n . human_friendly_type ( )
) ) ;
2025-06-25 20:36:57 +12:00
}
2024-11-14 17:27:19 -06:00
let num = * num ;
if num < 0.0 {
2025-06-25 20:36:57 +12:00
return make_err ( format! ( " ' {num} ' is negative, so you can't index an array with it " ) ) ;
2024-11-14 17:27:19 -06:00
}
2025-01-16 14:00:32 +13:00
let nearest_int = crate ::try_f64_to_usize ( num ) ;
if let Some ( nearest_int ) = nearest_int {
Ok ( Property ::UInt ( nearest_int ) )
2024-10-17 16:29:27 -07:00
} else {
2025-06-26 17:02:54 -05:00
make_err ( format! (
" '{num}' is not an integer, so you can't index an array with it "
) )
2024-10-17 16:29:27 -07:00
}
}
2025-06-26 17:02:54 -05:00
KclValue ::String { value : x , meta : _ } = > Ok ( Property ::String ( x . to_owned ( ) ) ) ,
_ = > make_err ( format! (
" {name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array "
) ) ,
2024-10-17 16:29:27 -07:00
}
}
2025-01-16 14:00:32 +13:00
2024-10-17 16:29:27 -07:00
impl Property {
fn type_name ( & self ) -> & 'static str {
match self {
2024-11-25 10:50:43 +13:00
Property ::UInt ( _ ) = > " number " ,
2024-10-17 16:29:27 -07:00
Property ::String ( _ ) = > " string " ,
}
}
}
2024-12-07 07:16:04 +13:00
impl Node < PipeExpression > {
#[ async_recursion ]
pub async fn get_result ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
execute_pipe_body ( exec_state , & self . body , self . into ( ) , ctx ) . await
}
}
2025-02-05 17:53:49 +13:00
#[ cfg(test) ]
mod test {
2025-03-15 10:08:39 -07:00
use std ::sync ::Arc ;
2025-04-16 11:52:14 -07:00
use tokio ::io ::AsyncWriteExt ;
2025-02-19 00:11:11 -05:00
use super ::* ;
2025-02-12 10:22:56 +13:00
use crate ::{
2025-05-11 23:57:31 -04:00
ExecutorSettings , UnitLen ,
2025-06-26 17:02:54 -05:00
exec ::UnitType ,
execution ::{ ContextType , parse_execute } ,
2025-02-12 10:22:56 +13:00
} ;
2025-02-27 15:58:58 +13:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn ascription ( ) {
let program = r #"
a = 42 : number
b = a : number
p = {
origin = { x = 0 , y = 0 , z = 0 } ,
xAxis = { x = 1 , y = 0 , z = 0 } ,
yAxis = { x = 0 , y = 1 , z = 0 } ,
zAxis = { x = 0 , y = 0 , z = 1 }
} : Plane
2025-05-11 23:57:31 -04:00
arr1 = [ 42 ] : [ number ( cm ) ]
2025-02-27 15:58:58 +13:00
" #;
let result = parse_execute ( program ) . await . unwrap ( ) ;
2025-03-08 08:00:55 +13:00
let mem = result . exec_state . stack ( ) ;
2025-02-27 15:58:58 +13:00
assert! ( matches! (
2025-03-08 08:00:55 +13:00
mem . memory
. get_from ( " p " , result . mem_env , SourceRange ::default ( ) , 0 )
. unwrap ( ) ,
2025-02-27 15:58:58 +13:00
KclValue ::Plane { .. }
) ) ;
2025-05-11 23:57:31 -04:00
let arr1 = mem
. memory
. get_from ( " arr1 " , result . mem_env , SourceRange ::default ( ) , 0 )
. unwrap ( ) ;
if let KclValue ::HomArray { value , ty } = arr1 {
2025-06-26 17:02:54 -05:00
assert_eq! ( value . len ( ) , 1 , " Expected Vec with specific length: found {value:?} " ) ;
2025-05-11 23:57:31 -04:00
assert_eq! ( * ty , RuntimeType ::known_length ( UnitLen ::Cm ) ) ;
// Compare, ignoring meta.
if let KclValue ::Number { value , ty , .. } = & value [ 0 ] {
2025-05-21 17:22:30 -04:00
// It should not convert units.
assert_eq! ( * value , 42.0 ) ;
2025-05-11 23:57:31 -04:00
assert_eq! ( * ty , NumericType ::Known ( UnitType ::Length ( UnitLen ::Cm ) ) ) ;
} else {
panic! ( " Expected a number; found {:?} " , value [ 0 ] ) ;
}
} else {
panic! ( " Expected HomArray; found {arr1:?} " ) ;
}
2025-02-27 15:58:58 +13:00
let program = r #"
a = 42 : string
" #;
let result = parse_execute ( program ) . await ;
2025-05-14 10:04:51 -04:00
let err = result . unwrap_err ( ) ;
assert! (
err . to_string ( )
2025-06-13 02:20:04 +12:00
. contains ( " could not coerce a number (with type `number`) to type `string` " ) ,
2025-05-14 10:04:51 -04:00
" Expected error but found {err:?} "
) ;
2025-02-27 15:58:58 +13:00
let program = r #"
a = 42 : Plane
" #;
let result = parse_execute ( program ) . await ;
2025-05-14 10:04:51 -04:00
let err = result . unwrap_err ( ) ;
assert! (
err . to_string ( )
2025-06-13 02:20:04 +12:00
. contains ( " could not coerce a number (with type `number`) to type `Plane` " ) ,
2025-05-14 10:04:51 -04:00
" Expected error but found {err:?} "
) ;
2025-05-11 23:57:31 -04:00
let program = r #"
arr = [ 0 ] : [ string ]
" #;
let result = parse_execute ( program ) . await ;
let err = result . unwrap_err ( ) ;
assert! (
2025-05-20 20:50:24 -04:00
err . to_string ( ) . contains (
2025-06-13 02:20:04 +12:00
" could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]` "
2025-05-20 20:50:24 -04:00
) ,
2025-05-11 23:57:31 -04:00
" Expected error but found {err:?} "
) ;
let program = r #"
mixedArr = [ 0 , " a " ] : [ number ( mm ) ]
" #;
let result = parse_execute ( program ) . await ;
let err = result . unwrap_err ( ) ;
assert! (
2025-06-13 02:20:04 +12:00
err . to_string ( ) . contains (
" could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]` "
) ,
2025-05-11 23:57:31 -04:00
" Expected error but found {err:?} "
) ;
2025-02-27 15:58:58 +13:00
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn neg_plane ( ) {
let program = r #"
p = {
origin = { x = 0 , y = 0 , z = 0 } ,
xAxis = { x = 1 , y = 0 , z = 0 } ,
yAxis = { x = 0 , y = 1 , z = 0 } ,
} : Plane
p2 = - p
" #;
let result = parse_execute ( program ) . await . unwrap ( ) ;
2025-03-08 08:00:55 +13:00
let mem = result . exec_state . stack ( ) ;
match mem
. memory
. get_from ( " p2 " , result . mem_env , SourceRange ::default ( ) , 0 )
. unwrap ( )
{
2025-04-24 22:01:27 +12:00
KclValue ::Plane { value } = > {
2025-04-29 19:11:02 -07:00
assert_eq! ( value . info . x_axis . x , - 1.0 ) ;
assert_eq! ( value . info . x_axis . y , 0.0 ) ;
assert_eq! ( value . info . x_axis . z , 0.0 ) ;
2025-04-24 22:01:27 +12:00
}
2025-02-27 15:58:58 +13:00
_ = > unreachable! ( ) ,
}
}
2025-02-12 10:22:56 +13:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn multiple_returns ( ) {
let program = r #" fn foo() {
return 0
return 42
}
a = foo ( )
" #;
let result = parse_execute ( program ) . await ;
assert! ( result . unwrap_err ( ) . to_string ( ) . contains ( " return " ) ) ;
}
2025-04-07 18:02:46 +12:00
2025-04-16 11:52:14 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn load_all_modules ( ) {
// program a.kcl
2025-05-07 12:48:23 -05:00
let program_a_kcl = r #"
2025-04-16 11:52:14 -07:00
export a = 1
" #;
// program b.kcl
2025-05-07 12:48:23 -05:00
let program_b_kcl = r #"
2025-04-16 11:52:14 -07:00
import a from ' a . kcl '
export b = a + 1
" #;
// program c.kcl
2025-05-07 12:48:23 -05:00
let program_c_kcl = r #"
2025-04-16 11:52:14 -07:00
import a from ' a . kcl '
export c = a + 2
" #;
// program main.kcl
let main_kcl = r #"
import b from ' b . kcl '
import c from ' c . kcl '
d = b + c
" #;
let main = crate ::parsing ::parse_str ( main_kcl , ModuleId ::default ( ) )
. parse_errs_as_err ( )
. unwrap ( ) ;
2025-04-25 17:19:04 -04:00
let tmpdir = tempfile ::TempDir ::with_prefix ( " zma_kcl_load_all_modules " ) . unwrap ( ) ;
2025-04-16 11:52:14 -07:00
tokio ::fs ::File ::create ( tmpdir . path ( ) . join ( " main.kcl " ) )
. await
. unwrap ( )
. write_all ( main_kcl . as_bytes ( ) )
. await
. unwrap ( ) ;
tokio ::fs ::File ::create ( tmpdir . path ( ) . join ( " a.kcl " ) )
. await
. unwrap ( )
2025-05-07 12:48:23 -05:00
. write_all ( program_a_kcl . as_bytes ( ) )
2025-04-16 11:52:14 -07:00
. await
. unwrap ( ) ;
tokio ::fs ::File ::create ( tmpdir . path ( ) . join ( " b.kcl " ) )
. await
. unwrap ( )
2025-05-07 12:48:23 -05:00
. write_all ( program_b_kcl . as_bytes ( ) )
2025-04-16 11:52:14 -07:00
. await
. unwrap ( ) ;
tokio ::fs ::File ::create ( tmpdir . path ( ) . join ( " c.kcl " ) )
. await
. unwrap ( )
2025-05-07 12:48:23 -05:00
. write_all ( program_c_kcl . as_bytes ( ) )
2025-04-16 11:52:14 -07:00
. await
. unwrap ( ) ;
let exec_ctxt = ExecutorContext {
engine : Arc ::new ( Box ::new (
crate ::engine ::conn_mock ::EngineConnection ::new ( )
. await
. map_err ( | err | {
2025-06-02 13:30:57 -05:00
KclError ::new_internal ( KclErrorDetails ::new (
2025-06-26 17:02:54 -05:00
format! ( " Failed to create mock engine connection: {err} " ) ,
2025-05-19 14:13:10 -04:00
vec! [ SourceRange ::default ( ) ] ,
) )
2025-04-16 11:52:14 -07:00
} )
. unwrap ( ) ,
) ) ,
fs : Arc ::new ( crate ::fs ::FileManager ::new ( ) ) ,
settings : ExecutorSettings {
2025-05-06 20:04:34 -07:00
project_directory : Some ( crate ::TypedPath ( tmpdir . path ( ) . into ( ) ) ) ,
2025-04-16 11:52:14 -07:00
.. Default ::default ( )
} ,
context_type : ContextType ::Mock ,
} ;
let mut exec_state = ExecState ::new ( & exec_ctxt ) ;
exec_ctxt
2025-05-02 10:41:14 -07:00
. run (
2025-04-16 11:52:14 -07:00
& crate ::Program {
ast : main . clone ( ) ,
original_file_contents : " " . to_owned ( ) ,
} ,
& mut exec_state ,
)
. await
. unwrap ( ) ;
}
2025-04-07 18:02:46 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn user_coercion ( ) {
let program = r #" fn foo(x: Axis2d) {
return 0
}
foo ( x = { direction = [ 0 , 0 ] , origin = [ 0 , 0 ] } )
" #;
parse_execute ( program ) . await . unwrap ( ) ;
let program = r #" fn foo(x: Axis3d) {
return 0
}
foo ( x = { direction = [ 0 , 0 ] , origin = [ 0 , 0 ] } )
2025-04-22 11:00:53 +12:00
" #;
parse_execute ( program ) . await . unwrap_err ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn coerce_return ( ) {
let program = r #" fn foo(): number(mm) {
return 42
}
a = foo ( )
" #;
parse_execute ( program ) . await . unwrap ( ) ;
let program = r #" fn foo(): number(mm) {
return { bar : 42 }
}
a = foo ( )
2025-04-07 18:02:46 +12:00
" #;
parse_execute ( program ) . await . unwrap_err ( ) ;
}
2025-05-09 14:28:04 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_sensible_error_when_missing_equals_in_kwarg ( ) {
2025-05-15 07:42:48 +12:00
for ( i , call ) in [ " f(x=1,3,0) " , " f(x=1,3,z) " , " f(x=1,0,z=1) " , " f(x=1, 3 + 4, z) " ]
2025-05-09 14:28:04 +12:00
. into_iter ( )
. enumerate ( )
{
let program = format! (
" fn foo() {{ return 0 }}
z = 0
fn f ( x , y , z ) { { return 0 } }
{ call } "
) ;
let err = parse_execute ( & program ) . await . unwrap_err ( ) ;
let msg = err . message ( ) ;
assert! (
msg . contains ( " This argument needs a label, but it doesn't have one " ) ,
" failed test {i}: {msg} "
) ;
assert! ( msg . contains ( " `y` " ) , " failed test {i}, missing `y`: {msg} " ) ;
if i = = 0 {
assert! ( msg . contains ( " `z` " ) , " failed test {i}, missing `z`: {msg} " ) ;
}
}
}
2025-05-11 17:43:12 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn default_param_for_unlabeled ( ) {
2025-05-15 07:42:48 +12:00
// Tests that the input param for myExtrude is taken from the pipeline value and same-name
// keyword args.
2025-05-11 17:43:12 +12:00
let ast = r #" fn myExtrude(@sk, length) {
2025-05-15 07:42:48 +12:00
return extrude ( sk , length )
2025-05-11 17:43:12 +12:00
}
sketch001 = startSketchOn ( XY )
| > circle ( center = [ 0 , 0 ] , radius = 93.75 )
| > myExtrude ( length = 40 )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2025-05-12 14:07:57 +12:00
2025-05-15 07:42:48 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn dont_use_unlabelled_as_input ( ) {
// `length` should be used as the `length` argument to extrude, not the unlabelled input
let ast = r #" length = 10
startSketchOn ( XY )
| > circle ( center = [ 0 , 0 ] , radius = 93.75 )
| > extrude ( length )
" #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2025-05-12 14:07:57 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn ascription_in_binop ( ) {
let ast = r # "foo = tan(0): number(rad) - 4deg"# ;
parse_execute ( ast ) . await . unwrap ( ) ;
}
2025-05-12 16:47:01 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn neg_sqrt ( ) {
let ast = r # "bad = sqrt(-2)"# ;
let e = parse_execute ( ast ) . await . unwrap_err ( ) ;
// Make sure we get a useful error message and not an engine error.
assert! ( e . message ( ) . contains ( " sqrt " ) , " Error message: '{}' " , e . message ( ) ) ;
}
2025-05-16 10:58:21 +12:00
2025-05-28 16:29:23 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn non_array_fns ( ) {
let ast = r #" push(1, item = 2)
pop ( 1 )
map ( 1 , f = fn ( @ x ) { return x + 1 } )
reduce ( 1 , f = fn ( @ x , accum ) { return accum + x } , initial = 0 ) " #;
parse_execute ( ast ) . await . unwrap ( ) ;
}
#[ tokio::test(flavor = " multi_thread " ) ]
async fn non_array_indexing ( ) {
let good = r #" a = 42
good = a [ 0 ]
" #;
let result = parse_execute ( good ) . await . unwrap ( ) ;
let mem = result . exec_state . stack ( ) ;
let num = mem
. memory
. get_from ( " good " , result . mem_env , SourceRange ::default ( ) , 0 )
. unwrap ( )
. as_ty_f64 ( )
. unwrap ( ) ;
assert_eq! ( num . n , 42.0 ) ;
let bad = r #" a = 42
bad = a [ 1 ]
" #;
parse_execute ( bad ) . await . unwrap_err ( ) ;
}
2025-05-16 10:58:21 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn coerce_unknown_to_length ( ) {
let ast = r #" x = 2mm * 2mm
y = x : number ( Length ) " #;
let e = parse_execute ( ast ) . await . unwrap_err ( ) ;
assert! (
e . message ( ) . contains ( " could not coerce " ) ,
" Error message: '{}' " ,
e . message ( )
) ;
let ast = r #" x = 2mm
y = x : number ( Length ) " #;
let result = parse_execute ( ast ) . await . unwrap ( ) ;
let mem = result . exec_state . stack ( ) ;
let num = mem
. memory
. get_from ( " y " , result . mem_env , SourceRange ::default ( ) , 0 )
. unwrap ( )
. as_ty_f64 ( )
. unwrap ( ) ;
assert_eq! ( num . n , 2.0 ) ;
assert_eq! ( num . ty , NumericType ::mm ( ) ) ;
}
2025-06-13 02:20:04 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn one_warning_unknown ( ) {
let ast = r #"
// Should warn once
a = PI * 2
// Should warn once
b = ( PI * 2 ) / 3
// Should not warn
c = ( ( PI * 2 ) / 3 ) : number ( deg )
" #;
let result = parse_execute ( ast ) . await . unwrap ( ) ;
assert_eq! ( result . exec_state . errors ( ) . len ( ) , 2 ) ;
}
2025-06-25 20:36:57 +12:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn non_count_indexing ( ) {
let ast = r #" x = [0, 0]
y = x [ 1 mm ]
" #;
parse_execute ( ast ) . await . unwrap_err ( ) ;
let ast = r #" x = [0, 0]
y = 1 deg
z = x [ y ]
" #;
parse_execute ( ast ) . await . unwrap_err ( ) ;
let ast = r #" x = [0, 0]
y = x [ 0 mm + 1 ]
" #;
parse_execute ( ast ) . await . unwrap_err ( ) ;
}
KCL: Getter for axes of planes (#7662)
## Goal
Currently, there's no way in KCL to get fields of a plane, e.g. the underlying X axis, Y axis or origin.
This would be useful for geometry calculations in KCL. It would help KCL users write transformations between planes for rotating geometry.
For example, this enables
```kcl
export fn crossProduct(@vectors) {
a = vectors[0]
b = vectors[1]
x = a[1] * b[2] - (a[2] * b[1])
y = a[2] * b[0] - (a[0] * b[2])
z = a[0] * b[1] - (a[1] * b[0])
return [x, y, z]
}
export fn normalOf(@plane) {
return crossProduct([plane.xAxis, plane.yAxis])
}
```
## Implementation
My goal was just to enable a simple getter for planes, like `myPlane.xAxis` and yAxis and origins. That's nearly what happened, except I discovered that there's two ways to represent a plane: either `KclValue::Plane` or `KclValue::Object` with the right fields.
No matter which format your plane is represented as, it should behave consistently when you get its properties. Those properties should be returned as `[number; 3]` because that is how KCL represents points.
Unfortunately we actually require planes-as-objects to be defined with axes like `myPlane = { xAxis = { x = 1, y = 0, z = 0 }, ...}`, but that's a mistake in my opinion. So if you do use that representation of a plane, it should still return a [number; 3]. This required some futzing around so that we let you access object fields .x and .y as [0] and [1], which is weird, but whatever, I think it's good.
This PR is tested via planestuff.kcl which has a Rust unit test.
Part of the hole efforts, see https://github.com/KittyCAD/modeling-app/discussions/7543
2025-07-02 11:24:26 -05:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn getting_property_of_plane ( ) {
// let ast = include_str!("../../tests/inputs/planestuff.kcl");
let ast = std ::fs ::read_to_string ( " tests/inputs/planestuff.kcl " ) . unwrap ( ) ;
parse_execute ( & ast ) . await . unwrap ( ) ;
}
2025-02-05 17:53:49 +13:00
}