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 ;
2025-03-24 21:55:24 +13:00
use indexmap ::IndexMap ;
2024-12-03 16:39:51 +13:00
2024-10-16 14:33:03 -07:00
use crate ::{
errors ::{ KclError , KclErrorDetails } ,
2024-12-07 07:16:04 +13:00
execution ::{
2025-02-05 17:53:49 +13:00
annotations ,
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 } ,
2025-03-17 17:57:26 +13:00
BodyType , EnvironmentRef , ExecState , ExecutorContext , KclValue , Metadata , PlaneType , TagEngineInfo ,
TagIdentifier ,
2024-12-07 07:16:04 +13:00
} ,
2025-02-11 13:52:46 +13:00
modules ::{ ModuleId , ModulePath , ModuleRepr } ,
2024-12-05 17:56:49 +13:00
parsing ::ast ::types ::{
2025-02-13 16:17:09 +13:00
Annotation , ArrayExpression , ArrayRangeExpression , BinaryExpression , BinaryOperator , BinaryPart , BodyItem ,
2025-04-26 21:21:26 -07:00
CallExpression , CallExpressionKw , Expr , FunctionExpression , IfExpression , ImportPath , ImportSelector ,
2025-03-24 20:58:55 +13:00
ItemVisibility , LiteralIdentifier , LiteralValue , MemberExpression , MemberObject , Name , Node , NodeRef ,
2025-02-27 15:58:58 +13:00
ObjectExpression , PipeExpression , Program , TagDeclarator , Type , UnaryExpression , UnaryOperator ,
2024-12-05 17:56:49 +13:00
} ,
2025-02-11 13:52:46 +13:00
source_range ::SourceRange ,
2024-12-06 19:11:31 -06:00
std ::{
2025-04-07 16:13:15 +12:00
args ::{ Arg , KwArgs , TyF64 } ,
2024-12-06 19:11:31 -06:00
FunctionKind ,
} ,
2025-02-21 09:30:44 +13:00
CompilationError ,
2024-10-16 14:33:03 -07:00
} ;
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
use crate ::{
execution ::cad_op ::{ Group , OpArg , OpKclValue , Operation } ,
parsing ::ast ::types ::BoxNode ,
} ;
2024-11-14 17:27:19 -06:00
2025-02-05 17:53:49 +13:00
enum StatementKind < ' a > {
Declaration { name : & ' a str } ,
Expression ,
}
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 ,
) -> Result < ( Option < KclValue > , EnvironmentRef , Vec < String > ) , KclError > {
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-03-31 10:56:03 -04:00
let mut local_state = ModuleState ::new ( path . std_path ( ) , 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 )
. await ? ;
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-03-05 12:03:32 +13:00
if ! preserve_mem {
std ::mem ::swap ( & mut exec_state . mod_local , & mut local_state ) ;
}
2025-02-27 15:46:41 +13:00
crate ::log ::log ( format! ( " leave {path} " ) ) ;
result . map ( | result | ( result , env_ref , local_state . module_exports ) )
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-02-11 11:26:14 +13:00
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " Imports are only supported at the top-level of a file. " . to_owned ( ) ,
source_ranges : vec ! [ import_stmt . into ( ) ] ,
} ) ) ;
}
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-02-13 06:24:27 +13:00
let module_id = self
2025-02-13 16:17:09 +13:00
. open_module ( & import_stmt . path , attrs , 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 ( ) ;
if value . is_err ( ) & & ty . is_err ( ) {
return Err ( KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " {} is not defined in module " , import_item . name . name ) ,
source_ranges : vec ! [ SourceRange ::from ( & import_item . name ) ] ,
} ) ) ;
}
// Check that the item is allowed to be imported (in at least one namespace).
if value . is_ok ( ) & & ! module_exports . contains ( & import_item . name . name ) {
value = Err ( KclError ::Semantic ( KclErrorDetails {
2025-02-05 17:53:49 +13:00
message : format ! (
" Cannot import \" {} \" from module because it is not exported. Add \" export \" before the definition to export it. " ,
import_item . name . name
) ,
source_ranges : vec ! [ SourceRange ::from ( & import_item . name ) ] ,
} ) ) ;
}
2025-04-22 11:00:53 +12:00
if ty . is_ok ( ) & & ! module_exports . contains ( & ty_name ) {
ty = Err ( KclError ::Semantic ( KclErrorDetails {
message : String ::new ( ) ,
source_ranges : vec ! [ ] ,
} ) ) ;
}
if value . is_err ( ) & & ty . is_err ( ) {
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 ( ) ) ;
// Add the item to the current module.
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
}
}
}
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 | {
KclError ::Internal ( KclErrorDetails {
message : format ! ( " {} is not defined in module (but was exported?) " , name ) ,
source_ranges : vec ! [ source_range ] ,
} )
} ) ?
. 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-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . add ( 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-04-25 17:55:54 -04:00
let value = 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 } ,
)
. await ? ;
2025-02-12 10:22:56 +13:00
exec_state
2025-03-05 12:03:32 +13:00
. mut_stack ( )
2025-04-25 17:55:54 -04:00
. add ( var_name . clone ( ) , value . clone ( ) , source_range ) ? ;
2025-02-05 17:53:49 +13:00
// Track exports.
if let ItemVisibility ::Export = variable_declaration . visibility {
exec_state . mod_local . module_exports . push ( var_name ) ;
}
2025-04-25 17:55:54 -04:00
// Variable declaration can be the return value of a module.
last_expr = matches! ( body_type , BodyType ::Root ) . then_some ( value ) ;
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-03-26 01:59:43 -04:00
let std_path = match & exec_state . mod_local . std_path {
2025-03-08 03:53:34 +13:00
Some ( p ) = > p ,
None = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " User-defined types are not yet supported. " . to_owned ( ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} ) ) ;
}
} ;
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 ( | _ | {
KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Redefinition of type {}. " , ty . name . name ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} )
} ) ? ;
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 ,
)
. map_err ( | e | KclError ::Semantic ( e . into ( ) ) ) ? ,
) ,
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 ( | _ | {
KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Redefinition of type {}. " , ty . name . name ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} )
} ) ? ;
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 = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " User-defined types are not yet supported. " . to_owned ( ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} ) )
}
} ,
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-02-12 10:22:56 +13:00
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " Cannot return from outside a function. " . to_owned ( ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} ) ) ;
}
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 ( | _ | {
KclError ::Semantic ( KclErrorDetails {
message : " Multiple returns from a single function. " . to_owned ( ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} )
} ) ? ;
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.
self . engine
. flush_batch (
// True here tells the engine to flush all the end commands as well like fillets
// and chamfers where the engine would otherwise eat the ID of the segments.
true ,
SourceRange ::new ( program . end , program . end , program . module_id ) ,
)
. 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-02-05 17:53:49 +13:00
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < ModuleId , KclError > {
2025-02-11 11:26:14 +13:00
let resolved_path = ModulePath ::from_import_path ( path , & self . settings . project_directory ) ;
2025-04-16 11:52:14 -07:00
2025-02-05 17:53:49 +13:00
match path {
2025-02-11 11:26:14 +13:00
ImportPath ::Kcl { .. } = > {
exec_state . global . mod_loader . cycle_check ( & resolved_path , source_range ) ? ;
2025-02-05 17:53:49 +13:00
2025-02-11 11:26:14 +13:00
if let Some ( id ) = exec_state . id_for_module ( & resolved_path ) {
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-02-12 10:22:56 +13:00
exec_state . add_module ( id , resolved_path , ModuleRepr ::Kcl ( parsed , None ) ) ;
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 { .. } = > {
if let Some ( id ) = exec_state . id_for_module ( & resolved_path ) {
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-04-24 20:25:02 -07:00
exec_state . add_module ( id , resolved_path , ModuleRepr ::Foreign ( geom , None ) ) ;
2025-02-11 11:26:14 +13:00
Ok ( id )
}
ImportPath ::Std { .. } = > {
if let Some ( id ) = exec_state . id_for_module ( & resolved_path ) {
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-02-12 10:22:56 +13:00
exec_state . add_module ( id , resolved_path , 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-04-16 11:52:14 -07: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-04-16 11:52:14 -07:00
. map ( | ( val , er , items ) | {
* cache = Some ( ( val , er , items . clone ( ) ) ) ;
2025-02-12 10:22:56 +13:00
( er , items )
} ) ,
2025-04-24 20:25:02 -07:00
ModuleRepr ::Foreign ( geom , _ ) = > Err ( KclError ::Semantic ( KclErrorDetails {
2025-02-12 10:22:56 +13:00
message : " Cannot import items from foreign modules " . to_owned ( ) ,
source_ranges : vec ! [ geom . source_range ] ,
2025-02-05 17:53:49 +13:00
} ) ) ,
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 ,
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ] module_name : & BoxNode < Name > ,
2025-02-12 10:22:56 +13:00
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < Option < KclValue > , KclError > {
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-03 22:10:39 -04:00
exec_state . global . operations . push ( Operation ::GroupBegin {
group : Group ::ModuleInstance {
name : module_name . to_string ( ) ,
module_id ,
} ,
source_range ,
} ) ;
2025-02-12 10:22:56 +13:00
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-04-16 11:52:14 -07: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 {
Ok ( ( val , env , items ) ) = > {
2025-04-16 11:52:14 -07:00
* cached_items = Some ( ( val . clone ( ) , env , items ) ) ;
2025-03-24 20:58:55 +13:00
Ok ( val )
}
Err ( e ) = > Err ( e ) ,
}
}
2025-04-24 20:25:02 -07:00
ModuleRepr ::Foreign ( _ , Some ( imported ) ) = > Ok ( Some ( imported . clone ( ) ) ) ,
ModuleRepr ::Foreign ( geom , cached ) = > {
let result = super ::import ::send_to_engine ( geom . clone ( ) , self )
. await
. map ( | geom | Some ( KclValue ::ImportedGeometry ( geom ) ) ) ;
match result {
Ok ( val ) = > {
* cached = val . clone ( ) ;
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-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-03 22:10:39 -04:00
exec_state . global . operations . push ( Operation ::GroupEnd ) ;
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-02-12 10:22:56 +13:00
) -> Result < ( Option < KclValue > , EnvironmentRef , Vec < String > ) , KclError > {
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-02-27 15:46:41 +13:00
result . map_err ( | err | {
if let KclError ::ImportCycle ( _ ) = err {
// It was an import cycle. Keep the original message.
err . override_source_ranges ( vec! [ source_range ] )
} else {
2025-04-22 11:00:53 +12:00
// TODO would be great to have line/column for the underlying error here
2025-02-27 15:46:41 +13:00
KclError ::Semantic ( KclErrorDetails {
message : 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 ( )
) ,
source_ranges : vec ! [ source_range ] ,
} )
}
} )
2025-02-05 17:53:49 +13:00
}
#[ async_recursion ]
async fn execute_expr < ' a : ' async_recursion > (
& 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 ) = > {
let value = name . get_result ( exec_state , self ) . await ? . 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 ,
#[ cfg(feature = " artifact-graph " ) ] name ,
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-03-26 01:59:43 -04:00
if let Some ( std_path ) = & exec_state . mod_local . std_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 {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " Rust implementation of functions is restricted to the standard library "
. to_owned ( ) ,
source_ranges : vec ! [ metadata . source_range ] ,
} ) ) ;
}
} 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 ::CallExpression ( call_expression ) = > call_expression . execute ( exec_state , self ) . await ? ,
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 "
) ;
return Err ( KclError ::Semantic ( KclErrorDetails {
message ,
source_ranges : vec ! [ pipe_substitution . into ( ) ] ,
} ) ) ;
}
StatementKind ::Expression = > match exec_state . mod_local . pipe_value . clone ( ) {
Some ( x ) = > x ,
None = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " cannot use % outside a pipe expression " . to_owned ( ) ,
source_ranges : vec ! [ pipe_substitution . into ( ) ] ,
} ) ) ;
}
} ,
} ,
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 ? ,
Expr ::MemberExpression ( member_expression ) = > member_expression . get_result ( exec_state ) ? ,
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-02-27 15:58:58 +13:00
Expr ::AscribedExpression ( expr ) = > {
let result = self
. execute_expr ( & expr . expr , exec_state , metadata , & [ ] , statement_kind )
. await ? ;
2025-04-07 16:13:15 +12:00
apply_ascription ( & result , & expr . ty , exec_state , expr . into ( ) ) ?
2025-02-27 15:58:58 +13:00
}
2025-02-05 17:53:49 +13:00
} ;
Ok ( item )
}
}
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-03-21 10:56:55 +13:00
. map_err ( | e | KclError ::Semantic ( e . into ( ) ) ) ? ;
2025-02-27 15:58:58 +13:00
2025-04-14 05:58:19 -04:00
if let KclValue ::Number { value , meta , .. } = value {
2025-04-07 16:13:15 +12:00
// If the number has unknown units but the user is explicitly specifying them, treat the value as having had it's units erased,
// rather than forcing the user to explicitly erase them.
KclValue ::Number {
ty : NumericType ::Any ,
value : * value ,
meta : meta . clone ( ) ,
}
. coerce ( & ty , exec_state )
} else {
value . coerce ( & ty , exec_state )
}
. map_err ( | _ | {
2025-03-21 10:56:55 +13:00
KclError ::Semantic ( KclErrorDetails {
message : format ! ( " could not coerce {} value to type {} " , value . human_friendly_type ( ) , ty ) ,
source_ranges : vec ! [ source_range ] ,
} )
} )
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 ,
BinaryPart ::CallExpression ( call_expression ) = > call_expression . execute ( 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 ,
BinaryPart ::MemberExpression ( member_expression ) = > member_expression . get_result ( exec_state ) ,
BinaryPart ::IfExpression ( e ) = > e . get_result ( exec_state , ctx ) . await ,
}
}
}
2025-03-24 20:58:55 +13:00
impl Node < Name > {
async fn get_result < ' a > (
& self ,
exec_state : & ' a mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < & ' a KclValue , KclError > {
if self . abs_path {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " Absolute paths (names beginning with `::` are not yet supported) " . to_owned ( ) ,
source_ranges : self . as_source_ranges ( ) ,
} ) ) ;
}
if self . path . is_empty ( ) {
return exec_state . stack ( ) . get ( & self . name . name , self . into ( ) ) ;
}
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 ) {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Item {} not found in module's exported items " , p . name ) ,
source_ranges : p . as_source_ranges ( ) ,
} ) ) ;
}
exec_state
. stack ( )
. memory
. get_from ( & p . name , env , p . as_source_range ( ) , 0 ) ?
}
None = > exec_state . stack ( ) . get ( & p . name , self . into ( ) ) ? ,
} ;
let KclValue ::Module { value : module_id , .. } = value else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Identifier in path must refer to a module, found {} " ,
value . human_friendly_type ( )
) ,
source_ranges : p . as_source_ranges ( ) ,
} ) ) ;
} ;
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 ( ) ;
if ! exports . contains ( & self . name . name ) {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Item {} not found in module's exported items " , self . name . name ) ,
source_ranges : self . name . as_source_ranges ( ) ,
} ) ) ;
}
exec_state
. stack ( )
. memory
. get_from ( & self . name . name , env , self . name . as_source_range ( ) , 0 )
}
}
2024-10-30 16:52:17 -04:00
impl Node < MemberExpression > {
2025-03-17 12:28:51 +13:00
fn get_result ( & self , exec_state : & mut ExecState ) -> 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 ( ) ) ? ;
2024-10-16 14:33:03 -07:00
let object = match & self . object {
// TODO: Don't use recursion here, use a loop.
MemberObject ::MemberExpression ( member_expr ) = > member_expr . get_result ( exec_state ) ? ,
MemberObject ::Identifier ( identifier ) = > {
2025-03-05 12:03:32 +13:00
let value = exec_state . stack ( ) . get ( & identifier . name , identifier . into ( ) ) ? ;
2024-10-16 14:33:03 -07:00
value . clone ( )
}
} ;
// 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 ) {
( 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 {
Err ( KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " Property '{property}' not found in object " ) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
}
2025-04-28 12:08:47 -04:00
( KclValue ::Object { .. } , Property ::String ( property ) , true ) = > Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Cannot index object with string; use dot notation instead, e.g. `obj.{property}` " ) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) ) ,
( KclValue ::Object { .. } , p , _ ) = > {
2024-11-14 17:27:19 -06:00
let t = p . type_name ( ) ;
let article = article_for ( t ) ;
Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Only strings can be used as the property of an object, but you're using {article} {t} " ,
) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
2025-04-23 10:58:35 +12:00
(
KclValue ::MixedArray { value : arr , .. } | KclValue ::HomArray { value : arr , .. } ,
Property ::UInt ( index ) ,
2025-04-28 12:08:47 -04:00
_ ,
2025-04-23 10:58:35 +12:00
) = > {
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 {
Err ( KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " The array doesn't have any item at index {index} " ) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
}
2025-04-28 12:08:47 -04:00
( KclValue ::MixedArray { .. } | KclValue ::HomArray { .. } , p , _ ) = > {
2024-11-14 17:27:19 -06:00
let t = p . type_name ( ) ;
let article = article_for ( t ) ;
Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Only integers >= 0 can be used as the index of an array, but you're using {article} {t} " ,
) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
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-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-04-28 12:08:47 -04:00
( being_indexed , _ , _ ) = > {
2024-11-14 17:27:19 -06:00
let t = being_indexed . human_friendly_type ( ) ;
let article = article_for ( t ) ;
2024-10-16 14:33:03 -07:00
Err ( KclError ::Semantic ( KclErrorDetails {
2024-11-14 17:27:19 -06:00
message : format ! (
" Only arrays and objects can be indexed, but you're trying to index {article} {t} "
) ,
2024-10-16 14:33:03 -07:00
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
}
}
}
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 {
value : format ! ( " {}{} " , left , right ) ,
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 ) {
let args = crate ::std ::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 ) {
let args = crate ::std ::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 ) {
let args = crate ::std ::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 {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Cannot apply logical operator to non-boolean value: {} " ,
left_value . human_friendly_type ( )
) ,
source_ranges : vec ! [ self . left . clone ( ) . into ( ) ] ,
} ) ) ;
} ;
let KclValue ::Bool {
value : right_value ,
meta : _ ,
} = right_value
else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Cannot apply logical operator to non-boolean value: {} " ,
right_value . human_friendly_type ( )
) ,
source_ranges : vec ! [ self . right . clone ( ) . into ( ) ] ,
} ) ) ;
} ;
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 = > {
let ( l , r , ty ) = NumericType ::combine_div ( left , right ) ;
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-04-07 16:13:15 +12:00
// TODO suggest how to fix this
exec_state . warn ( CompilationError ::err (
self . as_source_range ( ) ,
format! ( " {} numbers which have unknown or incompatible units. " , verb ) ,
) ) ;
}
}
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 {
2024-10-16 14:33:03 -07:00
return Err ( KclError ::Semantic ( KclErrorDetails {
2024-11-14 17:27:19 -06:00
message : format ! (
" Cannot apply unary operator ! to non-boolean value: {} " ,
value . human_friendly_type ( )
) ,
2024-10-16 14:33:03 -07:00
source_ranges : vec ! [ self . into ( ) ] ,
} ) ) ;
} ;
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 = | | {
KclError ::Semantic ( KclErrorDetails {
message : format ! (
" You can only negate numbers, planes, or lines, but this is a {} " ,
value . human_friendly_type ( )
) ,
source_ranges : vec ! [ self . into ( ) ] ,
} )
} ;
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 ,
ty : ty . clone ( ) ,
} )
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 {
KclValue ::MixedArray { value : values , meta } = > {
let values = values
. iter ( )
. map ( | v | match v {
KclValue ::Number { value , ty , meta } = > Ok ( KclValue ::Number {
value : * value * - 1.0 ,
ty : ty . clone ( ) ,
meta : meta . clone ( ) ,
} ) ,
_ = > Err ( err ( ) ) ,
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
KclValue ::MixedArray {
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 ,
ty : ty . clone ( ) ,
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 {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " Pipe expressions cannot be empty " . to_owned ( ) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ;
} ;
// 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.
2024-12-17 09:38:32 +13:00
let previous_pipe_value = std ::mem ::replace ( & mut exec_state . mod_local . pipe_value , Some ( 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 {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " This cannot be in a PipeExpression: {:?} " , expression ) ,
source_ranges : vec ! [ expression . into ( ) ] ,
} ) ) ;
}
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-02 15:23:18 -06:00
impl Node < CallExpressionKw > {
2024-12-05 14:27:51 -06:00
#[ async_recursion ]
pub async fn execute ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
2025-03-24 20:58:55 +13:00
let fn_name = & self . callee ;
2024-12-09 22:11:16 -06:00
let callsite : SourceRange = self . into ( ) ;
2024-12-05 14:27:51 -06:00
// Build a hashmap from argument labels to the final evaluated values.
2025-03-24 21:55:24 +13:00
let mut fn_args = IndexMap ::with_capacity ( self . arguments . len ( ) ) ;
2024-12-05 14:27:51 -06:00
for arg_expr in & self . arguments {
let source_range = SourceRange ::from ( arg_expr . arg . clone ( ) ) ;
let metadata = Metadata { source_range } ;
let value = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( & arg_expr . arg , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-12-05 14:27:51 -06:00
. await ? ;
fn_args . insert ( arg_expr . label . name . clone ( ) , Arg ::new ( value , source_range ) ) ;
}
let fn_args = fn_args ; // remove mutability
// Evaluate the unlabeled first param, if any exists.
let unlabeled = if let Some ( ref arg_expr ) = self . unlabeled {
let source_range = SourceRange ::from ( arg_expr . clone ( ) ) ;
let metadata = Metadata { source_range } ;
let value = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( arg_expr , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-12-05 14:27:51 -06:00
. await ? ;
Some ( Arg ::new ( value , source_range ) )
} else {
None
} ;
2024-12-06 19:11:31 -06:00
let args = crate ::std ::Args ::new_kw (
KwArgs {
unlabeled ,
labeled : fn_args ,
} ,
self . into ( ) ,
ctx . clone ( ) ,
2025-03-12 14:05:18 -04:00
exec_state . mod_local . pipe_value . clone ( ) . map ( | v | Arg ::new ( v , callsite ) ) ,
2024-12-06 19:11:31 -06:00
) ;
2024-12-05 14:27:51 -06:00
match ctx . stdlib . get_either ( fn_name ) {
FunctionKind ::Core ( func ) = > {
2025-02-22 20:16:29 +13:00
if func . deprecated ( ) {
exec_state . warn ( CompilationError ::err (
self . callee . as_source_range ( ) ,
format! ( " ` {fn_name} ` is deprecated, see the docs for a recommended replacement " ) ,
) ) ;
}
2025-03-24 20:58:55 +13:00
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-16 11:52:14 -07:00
let op = if func . feature_tree_operation ( ) {
2024-12-16 13:10:31 -05:00
let op_labeled_args = args
. kw_args
. labeled
. iter ( )
2025-02-21 10:24:12 -05:00
. map ( | ( k , arg ) | ( k . clone ( ) , OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ) )
2024-12-16 13:10:31 -05:00
. collect ( ) ;
Some ( Operation ::StdLibCall {
std_lib_fn : ( & func ) . into ( ) ,
2025-02-21 10:24:12 -05:00
unlabeled_arg : args
. unlabeled_kw_arg_unconverted ( )
. map ( | arg | OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ) ,
2024-12-16 13:10:31 -05:00
labeled_args : op_labeled_args ,
source_range : callsite ,
is_error : false ,
} )
} else {
None
} ;
2025-03-24 21:55:24 +13:00
let formals = func . args ( false ) ;
for ( label , arg ) in & args . kw_args . labeled {
match formals . iter ( ) . find ( | p | & p . name = = label ) {
Some ( p ) = > {
if ! p . label_required {
exec_state . err ( CompilationError ::err (
arg . source_range ,
format! (
" The function `{fn_name}` expects an unlabeled first parameter (`{label}`), but it is labelled in the call "
) ,
) ) ;
}
}
None = > {
exec_state . err ( CompilationError ::err (
arg . source_range ,
format! ( " ` {label} ` is not an argument of ` {fn_name} ` " ) ,
) ) ;
}
}
}
2024-12-05 14:27:51 -06:00
// Attempt to call the function.
2025-02-12 10:22:56 +13:00
let mut return_value = {
2024-12-16 13:10:31 -05:00
// Don't early-return in this block.
2025-03-24 21:55:24 +13:00
exec_state . mut_stack ( ) . push_new_env_for_rust_call ( ) ;
2024-12-16 13:10:31 -05:00
let result = func . std_lib_fn ( ) ( exec_state , args ) . await ;
2025-03-24 21:55:24 +13:00
exec_state . mut_stack ( ) . pop_env ( ) ;
2024-12-16 13:10:31 -05:00
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2024-12-16 13:10:31 -05:00
if let Some ( mut op ) = op {
op . set_std_lib_call_is_error ( result . is_err ( ) ) ;
// Track call operation. We do this after the call
// since things like patternTransform may call user code
// before running, and we will likely want to use the
// return value. The call takes ownership of the args,
// so we need to build the op before the call.
2025-02-27 15:46:41 +13:00
exec_state . global . operations . push ( op ) ;
2024-12-16 13:10:31 -05:00
}
2025-04-26 21:21:26 -07:00
2024-12-16 13:10:31 -05:00
result
2025-02-12 10:22:56 +13:00
} ? ;
2024-12-16 13:10:31 -05:00
update_memory_for_tags_of_geometry ( & mut return_value , exec_state ) ? ;
Ok ( return_value )
2024-12-05 14:27:51 -06:00
}
FunctionKind ::UserDefined = > {
2024-12-09 22:11:16 -06:00
// Clone the function so that we can use a mutable reference to
// exec_state.
2025-03-24 20:58:55 +13:00
let func = fn_name . get_result ( exec_state , ctx ) . await ? . clone ( ) ;
2024-12-09 22:11:16 -06:00
2025-03-24 21:55:24 +13:00
let Some ( fn_src ) = func . as_fn ( ) else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " cannot call this because it isn't a function " . to_string ( ) ,
source_ranges : vec ! [ callsite ] ,
} ) ) ;
} ;
2025-04-03 22:44:52 +13:00
let return_value = fn_src
. call_kw ( Some ( fn_name . to_string ( ) ) , exec_state , ctx , args , callsite )
. await
. map_err ( | e | {
// Add the call expression to the source ranges.
// TODO currently ignored by the frontend
e . add_source_ranges ( vec! [ callsite ] )
} ) ? ;
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-16 11:52:14 -07:00
if matches! ( fn_src , FunctionSource ::User { .. } ) {
2025-04-03 22:44:52 +13:00
// Track return operation.
2025-04-03 22:10:39 -04:00
exec_state . global . operations . push ( Operation ::GroupEnd ) ;
2025-04-03 22:44:52 +13:00
}
2024-12-09 22:11:16 -06:00
let result = return_value . ok_or_else ( move | | {
2025-03-24 21:55:24 +13:00
let mut source_ranges : Vec < SourceRange > = vec! [ callsite ] ;
2024-12-09 22:11:16 -06:00
// We want to send the source range of the original function.
if let KclValue ::Function { meta , .. } = func {
source_ranges = meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ;
} ;
KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " Result of user-defined function {} is undefined " , fn_name ) ,
source_ranges ,
} )
} ) ? ;
Ok ( result )
2024-12-05 14:27:51 -06:00
}
}
2024-12-02 15:23:18 -06:00
}
}
2024-10-30 16:52:17 -04:00
impl Node < CallExpression > {
2024-10-16 14:33:03 -07:00
#[ async_recursion ]
pub async fn execute ( & self , exec_state : & mut ExecState , ctx : & ExecutorContext ) -> Result < KclValue , KclError > {
2025-03-24 20:58:55 +13:00
let fn_name = & self . callee ;
2024-12-16 13:10:31 -05:00
let callsite = SourceRange ::from ( self ) ;
2024-10-16 14:33:03 -07:00
2024-11-21 13:10:03 -05:00
let mut fn_args : Vec < Arg > = Vec ::with_capacity ( self . arguments . len ( ) ) ;
2024-10-16 14:33:03 -07:00
2024-11-21 13:10:03 -05:00
for arg_expr in & self . arguments {
2024-10-16 14:33:03 -07:00
let metadata = Metadata {
2024-11-21 13:10:03 -05:00
source_range : SourceRange ::from ( arg_expr ) ,
2024-10-16 14:33:03 -07:00
} ;
2024-11-21 13:10:03 -05:00
let value = ctx
2025-02-20 19:33:21 +13:00
. execute_expr ( arg_expr , exec_state , & metadata , & [ ] , StatementKind ::Expression )
2024-10-16 14:33:03 -07:00
. await ? ;
2024-11-21 13:10:03 -05:00
let arg = Arg ::new ( value , SourceRange ::from ( arg_expr ) ) ;
fn_args . push ( arg ) ;
2024-10-16 14:33:03 -07:00
}
2024-12-16 13:10:31 -05:00
let fn_args = fn_args ; // remove mutability
2024-10-16 14:33:03 -07:00
2024-12-05 14:27:51 -06:00
match ctx . stdlib . get_either ( fn_name ) {
2024-10-16 14:33:03 -07:00
FunctionKind ::Core ( func ) = > {
2025-02-22 20:16:29 +13:00
if func . deprecated ( ) {
exec_state . warn ( CompilationError ::err (
self . callee . as_source_range ( ) ,
format! ( " ` {fn_name} ` is deprecated, see the docs for a recommended replacement " ) ,
) ) ;
}
2025-03-20 15:59:16 +13:00
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-16 11:52:14 -07:00
let op = if func . feature_tree_operation ( ) {
2024-12-16 13:10:31 -05:00
let op_labeled_args = func
. args ( false )
. iter ( )
. zip ( & fn_args )
2025-02-21 10:24:12 -05:00
. map ( | ( k , arg ) | {
(
k . name . clone ( ) ,
OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ,
)
} )
2024-12-16 13:10:31 -05:00
. collect ( ) ;
Some ( Operation ::StdLibCall {
std_lib_fn : ( & func ) . into ( ) ,
unlabeled_arg : None ,
labeled_args : op_labeled_args ,
source_range : callsite ,
is_error : false ,
} )
} else {
None
} ;
2024-10-16 14:33:03 -07:00
// Attempt to call the function.
2024-12-16 21:01:23 -06:00
let args = crate ::std ::Args ::new (
fn_args ,
self . into ( ) ,
ctx . clone ( ) ,
2025-03-12 14:05:18 -04:00
exec_state . mod_local . pipe_value . clone ( ) . map ( | v | Arg ::new ( v , callsite ) ) ,
2024-12-16 21:01:23 -06:00
) ;
2025-02-12 10:22:56 +13:00
let mut return_value = {
2024-12-16 13:10:31 -05:00
// Don't early-return in this block.
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . push_new_env_for_rust_call ( ) ;
2024-12-16 13:10:31 -05:00
let result = func . std_lib_fn ( ) ( exec_state , args ) . await ;
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_env ( ) ;
2024-12-16 13:10:31 -05:00
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2024-12-16 13:10:31 -05:00
if let Some ( mut op ) = op {
op . set_std_lib_call_is_error ( result . is_err ( ) ) ;
// Track call operation. We do this after the call
// since things like patternTransform may call user code
// before running, and we will likely want to use the
// return value. The call takes ownership of the args,
// so we need to build the op before the call.
2025-02-27 15:46:41 +13:00
exec_state . global . operations . push ( op ) ;
2024-12-16 13:10:31 -05:00
}
result
2025-02-12 10:22:56 +13:00
} ? ;
2024-12-16 13:10:31 -05:00
update_memory_for_tags_of_geometry ( & mut return_value , exec_state ) ? ;
Ok ( return_value )
2024-10-16 14:33:03 -07:00
}
FunctionKind ::UserDefined = > {
let source_range = SourceRange ::from ( self ) ;
// Clone the function so that we can use a mutable reference to
// exec_state.
2025-03-24 20:58:55 +13:00
let func = fn_name . get_result ( exec_state , ctx ) . await ? . clone ( ) ;
2024-10-16 14:33:03 -07:00
2025-04-16 11:52:14 -07:00
// Track call operation.
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-16 11:52:14 -07:00
exec_state . global . operations . push ( Operation ::GroupBegin {
group : Group ::FunctionCall {
name : Some ( fn_name . to_string ( ) ) ,
function_source_range : func . function_def_source_range ( ) . unwrap_or_default ( ) ,
unlabeled_arg : None ,
// TODO: Add the arguments for legacy positional parameters.
labeled_args : Default ::default ( ) ,
} ,
source_range : callsite ,
} ) ;
2024-12-16 13:10:31 -05:00
2025-03-24 21:55:24 +13:00
let Some ( fn_src ) = func . as_fn ( ) else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " cannot call this because it isn't a function " . to_string ( ) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ;
} ;
2025-04-03 22:44:52 +13:00
let return_value = fn_src
. call ( Some ( fn_name . to_string ( ) ) , exec_state , ctx , fn_args , source_range )
. await
. map_err ( | e | {
// Add the call expression to the source ranges.
// TODO currently ignored by the frontend
e . add_source_ranges ( vec! [ source_range ] )
} ) ? ;
2024-10-16 14:33:03 -07:00
let result = return_value . ok_or_else ( move | | {
let mut source_ranges : Vec < SourceRange > = vec! [ source_range ] ;
// We want to send the source range of the original function.
if let KclValue ::Function { meta , .. } = func {
source_ranges = meta . iter ( ) . map ( | m | m . source_range ) . collect ( ) ;
} ;
KclError ::UndefinedValue ( KclErrorDetails {
2025-02-22 20:16:29 +13:00
message : format ! ( " Result of function {} is undefined " , fn_name ) ,
2024-10-16 14:33:03 -07:00
source_ranges ,
} )
} ) ? ;
2025-04-16 11:52:14 -07:00
// Track return operation.
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-16 11:52:14 -07:00
exec_state . global . operations . push ( Operation ::GroupEnd ) ;
2024-12-16 13:10:31 -05:00
2024-10-16 14:33:03 -07:00
Ok ( result )
}
}
}
}
2024-12-13 16:39:40 -05:00
fn update_memory_for_tags_of_geometry ( result : & mut KclValue , exec_state : & mut ExecState ) -> Result < ( ) , KclError > {
2024-12-05 14:27:51 -06:00
// If the return result is a sketch or solid, we want to update the
// memory for the tags of the group.
// TODO: This could probably be done in a better way, but as of now this was my only idea
// and it works.
match result {
2025-03-17 12:28:51 +13:00
KclValue ::Sketch { value } = > {
for ( name , tag ) in value . tags . iter ( ) {
if exec_state . stack ( ) . cur_frame_contains ( name ) {
exec_state . mut_stack ( ) . update ( name , | v , _ | {
v . as_mut_tag ( ) . unwrap ( ) . merge_info ( tag ) ;
} ) ;
} else {
exec_state
. mut_stack ( )
. add (
name . to_owned ( ) ,
KclValue ::TagIdentifier ( Box ::new ( tag . clone ( ) ) ) ,
SourceRange ::default ( ) ,
)
. unwrap ( ) ;
}
2024-12-05 14:27:51 -06:00
}
}
2025-01-22 09:42:09 +13:00
KclValue ::Solid { ref mut value } = > {
for v in & value . value {
if let Some ( tag ) = v . get_tag ( ) {
2024-12-05 14:27:51 -06:00
// Get the past tag and update it.
2025-02-12 10:22:56 +13:00
let tag_id = if let Some ( t ) = value . sketch . tags . get ( & tag . name ) {
let mut t = t . clone ( ) ;
2025-03-17 12:28:51 +13:00
let Some ( info ) = t . get_cur_info ( ) else {
2025-02-12 10:22:56 +13:00
return Err ( KclError ::Internal ( KclErrorDetails {
message : format ! ( " Tag {} does not have path info " , tag . name ) ,
source_ranges : vec ! [ tag . into ( ) ] ,
} ) ) ;
} ;
let mut info = info . clone ( ) ;
info . surface = Some ( v . clone ( ) ) ;
info . sketch = value . id ;
2025-03-17 12:28:51 +13:00
t . info . push ( ( exec_state . stack ( ) . current_epoch ( ) , info ) ) ;
2025-02-12 10:22:56 +13:00
t
2024-12-05 14:27:51 -06:00
} else {
// It's probably a fillet or a chamfer.
// Initialize it.
TagIdentifier {
value : tag . name . clone ( ) ,
2025-03-17 12:28:51 +13:00
info : vec ! [ (
exec_state . stack ( ) . current_epoch ( ) ,
TagEngineInfo {
id : v . get_id ( ) ,
surface : Some ( v . clone ( ) ) ,
path : None ,
sketch : value . id ,
} ,
) ] ,
2024-12-05 14:27:51 -06:00
meta : vec ! [ Metadata {
source_range : tag . clone ( ) . into ( ) ,
} ] ,
}
} ;
// update the sketch tags.
2025-03-17 12:28:51 +13:00
value . sketch . merge_tags ( Some ( & tag_id ) . into_iter ( ) ) ;
if exec_state . stack ( ) . cur_frame_contains ( & tag . name ) {
exec_state . mut_stack ( ) . update ( & tag . name , | v , _ | {
v . as_mut_tag ( ) . unwrap ( ) . merge_info ( & tag_id ) ;
} ) ;
} else {
exec_state
. mut_stack ( )
. add (
tag . name . clone ( ) ,
KclValue ::TagIdentifier ( Box ::new ( tag_id ) ) ,
SourceRange ::default ( ) ,
)
. unwrap ( ) ;
}
2024-12-05 14:27:51 -06:00
}
}
// Find the stale sketch in memory and update it.
2025-02-12 10:22:56 +13:00
if ! value . sketch . tags . is_empty ( ) {
2025-03-17 12:28:51 +13:00
let sketches_to_update : Vec < _ > = exec_state
2025-03-05 12:03:32 +13:00
. stack ( )
2025-03-17 12:28:51 +13:00
. find_keys_in_current_env ( | v | match v {
2025-04-26 21:21:26 -07:00
KclValue ::Sketch { value : sk } = > sk . original_id = = value . sketch . original_id ,
2025-02-12 10:22:56 +13:00
_ = > false ,
} )
2025-03-17 12:28:51 +13:00
. cloned ( )
2025-02-12 10:22:56 +13:00
. collect ( ) ;
2025-03-17 12:28:51 +13:00
for k in sketches_to_update {
exec_state . mut_stack ( ) . update ( & k , | v , _ | {
let sketch = v . as_mut_sketch ( ) . unwrap ( ) ;
sketch . merge_tags ( value . sketch . tags . values ( ) ) ;
} ) ;
}
2024-12-05 14:27:51 -06:00
}
}
2025-03-17 17:57:26 +13:00
KclValue ::MixedArray { value , .. } | KclValue ::HomArray { value , .. } = > {
for v in value {
update_memory_for_tags_of_geometry ( v , exec_state ) ? ;
}
}
2024-12-05 14:27:51 -06:00
_ = > { }
}
Ok ( ( ) )
}
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-03-08 04:04:57 +13:00
Ok ( KclValue ::MixedArray {
2024-11-14 17:27:19 -06:00
value : results ,
meta : vec ! [ self . into ( ) ] ,
} )
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 ) ;
2024-10-16 14:33:03 -07:00
let start = 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 ? ;
let start = start . as_int ( ) . ok_or ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ self . into ( ) ] ,
message : format ! ( " Expected int but found {} " , start . human_friendly_type ( ) ) ,
} ) ) ? ;
2024-10-30 16:52:17 -04:00
let metadata = Metadata ::from ( & self . end_element ) ;
2024-10-16 14:33:03 -07:00
let end = 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 ? ;
let end = end . as_int ( ) . ok_or ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ self . into ( ) ] ,
message : format ! ( " Expected int but found {} " , end . human_friendly_type ( ) ) ,
} ) ) ? ;
2024-10-16 14:33:03 -07:00
if end < start {
return Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ self . into ( ) ] ,
message : format ! ( " Range start is greater than range end: {start} .. {end} " ) ,
} ) ) ;
}
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-03-08 04:04:57 +13:00
Ok ( KclValue ::MixedArray {
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-02-20 10:12:37 +13:00
ty : NumericType ::count ( ) ,
2024-11-14 17:27:19 -06:00
meta : meta . clone ( ) ,
} )
. collect ( ) ,
meta ,
} )
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
}
}
2024-11-14 17:27:19 -06:00
fn article_for ( s : & str ) -> & 'static str {
if s . starts_with ( [ 'a' , 'e' , 'i' , 'o' , 'u' ] ) {
" 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-04-07 16:13:15 +12:00
let article = article_for ( actual_type ) ;
KclError ::Semantic ( KclErrorDetails {
2024-10-16 14:33:03 -07:00
source_ranges : vec ! [ source_range ] ,
2024-11-14 17:27:19 -06:00
message : format ! ( " Expected a number, but found {article} {actual_type} " , ) ,
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-01-22 08:29:30 +13:00
LiteralValue ::Number { value , .. } = > {
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 {
Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : property_sr ,
2025-01-22 08:29:30 +13:00
message : format ! ( " {value} is not a valid index, indices must be whole numbers >= 0 " ) ,
2024-10-17 16:29:27 -07:00
} ) )
}
}
_ = > Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ sr ] ,
2025-04-28 12:08:47 -04:00
message : " Only numbers (>= 0) can be indexes " . to_owned ( ) ,
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 > {
2024-10-17 16:29:27 -07:00
let make_err = | message : String | {
Err ::< Property , _ > ( KclError ::Semantic ( KclErrorDetails {
source_ranges : property_sr ,
message ,
} ) )
} ;
match value {
2025-02-14 13:03:23 +13:00
KclValue ::Number { value : num , .. } = > {
2024-11-14 17:27:19 -06:00
let num = * num ;
if num < 0.0 {
return make_err ( format! ( " ' {num} ' is negative, so you can't index an array with it " ) )
}
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 {
2024-11-14 17:27:19 -06: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
}
}
2024-11-14 17:27:19 -06:00
KclValue ::String { value : x , meta :_ } = > Ok ( Property ::String ( x . to_owned ( ) ) ) ,
2024-10-17 16:29:27 -07:00
_ = > {
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 " ) )
}
}
}
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
/// For each argument given,
/// assign it to a parameter of the function, in the given block of function memory.
/// Returns Err if too few/too many arguments were given for the function.
fn assign_args_to_params (
function_expression : NodeRef < '_ , FunctionExpression > ,
args : Vec < Arg > ,
2025-02-14 13:03:23 +13:00
exec_state : & mut ExecState ,
2025-02-12 10:22:56 +13:00
) -> Result < ( ) , KclError > {
2025-02-05 17:53:49 +13:00
let num_args = function_expression . number_of_args ( ) ;
let ( min_params , max_params ) = num_args . into_inner ( ) ;
let n = args . len ( ) ;
// Check if the user supplied too many arguments
// (we'll check for too few arguments below).
let err_wrong_number_args = KclError ::Semantic ( KclErrorDetails {
message : if min_params = = max_params {
format! ( " Expected {min_params} arguments, got {n} " )
} else {
format! ( " Expected {min_params} - {max_params} arguments, got {n} " )
} ,
source_ranges : vec ! [ function_expression . into ( ) ] ,
} ) ;
if n > max_params {
return Err ( err_wrong_number_args ) ;
}
// Add the arguments to the memory. A new call frame should have already
// been created.
for ( index , param ) in function_expression . params . iter ( ) . enumerate ( ) {
if let Some ( arg ) = args . get ( index ) {
// Argument was provided.
2025-04-23 10:58:35 +12:00
if let Some ( ty ) = & param . type_ {
let value = arg
. value
. coerce (
& RuntimeType ::from_parsed ( ty . inner . clone ( ) , exec_state , arg . source_range ) . unwrap ( ) ,
exec_state ,
)
. map_err ( | e | {
let mut message = format! (
" Argument requires a value with type `{}`, but found {} " ,
ty . inner ,
arg . value . human_friendly_type ( ) ,
) ;
if let Some ( ty ) = e . explicit_coercion {
// TODO if we have access to the AST for the argument we could choose which example to suggest.
message = format! ( " {message} \n \n You may need to add information about the type of the argument, for example: \n using a numeric suffix: `42 {ty} ` \n or using type ascription: `foo(): number( {ty} )` " ) ;
}
KclError ::Semantic ( KclErrorDetails {
message ,
source_ranges : vec ! [ arg . source_range ] ,
} )
} ) ? ;
exec_state
. mut_stack ( )
. add ( param . identifier . name . clone ( ) , value , ( & param . identifier ) . into ( ) ) ? ;
} else {
exec_state . mut_stack ( ) . add (
param . identifier . name . clone ( ) ,
arg . value . clone ( ) ,
( & param . identifier ) . into ( ) ,
) ? ;
}
2025-02-05 17:53:49 +13:00
} else {
// Argument was not provided.
if let Some ( ref default_val ) = param . default_value {
// If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it.
2025-03-26 01:59:43 -04:00
let value = KclValue ::from_default_param ( default_val . clone ( ) , exec_state ) ;
exec_state
. mut_stack ( )
. add ( param . identifier . name . clone ( ) , value , ( & param . identifier ) . into ( ) ) ? ;
2025-02-05 17:53:49 +13:00
} else {
// But if the corresponding parameter was required,
// then the user has called with too few arguments.
return Err ( err_wrong_number_args ) ;
}
}
}
2025-02-12 10:22:56 +13:00
Ok ( ( ) )
2025-02-05 17:53:49 +13:00
}
2025-04-07 18:02:46 +12:00
fn type_check_params_kw (
fn_name : Option < & str > ,
2025-02-05 17:53:49 +13:00
function_expression : NodeRef < '_ , FunctionExpression > ,
2025-04-07 18:02:46 +12:00
args : & mut crate ::std ::args ::KwArgs ,
2025-02-14 13:03:23 +13:00
exec_state : & mut ExecState ,
2025-02-12 10:22:56 +13:00
) -> Result < ( ) , KclError > {
2025-04-07 18:02:46 +12:00
for ( label , arg ) in & mut args . labeled {
2025-03-24 21:55:24 +13:00
match function_expression . params . iter ( ) . find ( | p | & p . identifier . name = = label ) {
Some ( p ) = > {
if ! p . labeled {
exec_state . err ( CompilationError ::err (
arg . source_range ,
format! (
2025-04-07 18:02:46 +12:00
" {} expects an unlabeled first parameter (`{label}`), but it is labelled in the call " ,
fn_name
. map ( | n | format! ( " The function ` {} ` " , n ) )
. unwrap_or_else ( | | " This function " . to_owned ( ) ) ,
2025-03-24 21:55:24 +13:00
) ,
) ) ;
}
2025-04-07 18:02:46 +12:00
if let Some ( ty ) = & p . type_ {
arg . value = arg
. value
. coerce (
& RuntimeType ::from_parsed ( ty . inner . clone ( ) , exec_state , arg . source_range ) . unwrap ( ) ,
exec_state ,
)
. map_err ( | e | {
let mut message = format! (
" {label} requires a value with type `{}`, but found {} " ,
ty . inner ,
arg . value . human_friendly_type ( ) ,
) ;
if let Some ( ty ) = e . explicit_coercion {
// TODO if we have access to the AST for the argument we could choose which example to suggest.
message = format! ( " {message} \n \n You may need to add information about the type of the argument, for example: \n using a numeric suffix: `42 {ty} ` \n or using type ascription: `foo(): number( {ty} )` " ) ;
}
KclError ::Semantic ( KclErrorDetails {
message ,
source_ranges : vec ! [ arg . source_range ] ,
} )
} ) ? ;
}
2025-03-24 21:55:24 +13:00
}
None = > {
exec_state . err ( CompilationError ::err (
arg . source_range ,
2025-04-07 18:02:46 +12:00
format! (
" `{label}` is not an argument of {} " ,
fn_name
. map ( | n | format! ( " ` {} ` " , n ) )
. unwrap_or_else ( | | " this function " . to_owned ( ) ) ,
) ,
2025-03-24 21:55:24 +13:00
) ) ;
}
}
}
2025-04-07 18:02:46 +12:00
if let Some ( arg ) = & mut args . unlabeled {
if let Some ( p ) = function_expression . params . iter ( ) . find ( | p | ! p . labeled ) {
if let Some ( ty ) = & p . type_ {
arg . value = arg
. value
. coerce (
& RuntimeType ::from_parsed ( ty . inner . clone ( ) , exec_state , arg . source_range ) . unwrap ( ) ,
exec_state ,
)
. map_err ( | _ | {
KclError ::Semantic ( KclErrorDetails {
message : format ! (
" The input argument of {} requires a value with type `{}`, but found {} " ,
fn_name
. map ( | n | format! ( " ` {} ` " , n ) )
. unwrap_or_else ( | | " this function " . to_owned ( ) ) ,
ty . inner ,
arg . value . human_friendly_type ( )
) ,
source_ranges : vec ! [ arg . source_range ] ,
} )
} ) ? ;
}
}
}
Ok ( ( ) )
}
fn assign_args_to_params_kw (
fn_name : Option < & str > ,
function_expression : NodeRef < '_ , FunctionExpression > ,
mut args : crate ::std ::args ::KwArgs ,
exec_state : & mut ExecState ,
) -> Result < ( ) , KclError > {
type_check_params_kw ( fn_name , function_expression , & mut args , exec_state ) ? ;
2025-02-05 17:53:49 +13:00
// Add the arguments to the memory. A new call frame should have already
// been created.
let source_ranges = vec! [ function_expression . into ( ) ] ;
2025-02-14 13:03:23 +13:00
2025-02-05 17:53:49 +13:00
for param in function_expression . params . iter ( ) {
if param . labeled {
let arg = args . labeled . get ( & param . identifier . name ) ;
let arg_val = match arg {
Some ( arg ) = > arg . value . clone ( ) ,
None = > match param . default_value {
2025-03-26 01:59:43 -04:00
Some ( ref default_val ) = > KclValue ::from_default_param ( default_val . clone ( ) , exec_state ) ,
2025-02-05 17:53:49 +13:00
None = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : format ! (
" This function requires a parameter {}, but you haven't passed it one. " ,
param . identifier . name
) ,
} ) ) ;
}
} ,
} ;
2025-03-26 01:59:43 -04:00
exec_state
. mut_stack ( )
. add ( param . identifier . name . clone ( ) , arg_val , ( & param . identifier ) . into ( ) ) ? ;
2025-02-05 17:53:49 +13:00
} else {
let Some ( unlabeled ) = args . unlabeled . take ( ) else {
let param_name = & param . identifier . name ;
return Err ( if args . labeled . contains_key ( param_name ) {
KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : format ! ( " The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:` " ) ,
} )
} else {
KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : " This function expects an unlabeled first parameter, but you haven't passed it one. "
. to_owned ( ) ,
} )
} ) ;
} ;
2025-03-26 01:59:43 -04:00
exec_state . mut_stack ( ) . add (
2025-02-12 10:22:56 +13:00
param . identifier . name . clone ( ) ,
2025-02-05 17:53:49 +13:00
unlabeled . value . clone ( ) ,
( & param . identifier ) . into ( ) ,
) ? ;
}
}
2025-03-24 21:55:24 +13:00
2025-02-12 10:22:56 +13:00
Ok ( ( ) )
2025-02-05 17:53:49 +13:00
}
2025-04-22 11:00:53 +12:00
fn coerce_result_type (
result : Result < Option < KclValue > , KclError > ,
function_expression : NodeRef < '_ , FunctionExpression > ,
exec_state : & mut ExecState ,
) -> Result < Option < KclValue > , KclError > {
if let Ok ( Some ( val ) ) = result {
if let Some ( ret_ty ) = & function_expression . return_type {
let ty = RuntimeType ::from_parsed ( ret_ty . inner . clone ( ) , exec_state , ret_ty . as_source_range ( ) )
. map_err ( | e | KclError ::Semantic ( e . into ( ) ) ) ? ;
let val = val . coerce ( & ty , exec_state ) . map_err ( | _ | {
KclError ::Semantic ( KclErrorDetails {
message : format ! (
" This function requires its result to be of type `{}`, but found {} " ,
ty . human_friendly_type ( ) ,
val . human_friendly_type ( ) ,
) ,
source_ranges : ret_ty . as_source_ranges ( ) ,
} )
} ) ? ;
Ok ( Some ( val ) )
} else {
Ok ( Some ( val ) )
}
} else {
result
}
}
2025-03-24 21:55:24 +13:00
async fn call_user_defined_function (
2025-02-05 17:53:49 +13:00
args : Vec < Arg > ,
2025-02-12 10:22:56 +13:00
memory : EnvironmentRef ,
2025-02-05 17:53:49 +13:00
function_expression : NodeRef < '_ , FunctionExpression > ,
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < Option < KclValue > , KclError > {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . push_new_env_for_call ( memory ) ;
2025-02-14 13:03:23 +13:00
if let Err ( e ) = assign_args_to_params ( function_expression , args , exec_state ) {
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_env ( ) ;
2025-02-12 10:22:56 +13:00
return Err ( e ) ;
}
2025-02-05 17:53:49 +13:00
// Execute the function body using the memory we just created.
2025-02-12 10:22:56 +13:00
let result = ctx
2025-02-25 16:10:06 +13:00
. exec_block ( & function_expression . body , exec_state , BodyType ::Block )
2025-02-12 10:22:56 +13:00
. await ;
2025-04-22 11:00:53 +12:00
let mut result = result . map ( | _ | {
2025-02-12 10:22:56 +13:00
exec_state
2025-03-05 12:03:32 +13:00
. stack ( )
2025-02-12 10:22:56 +13:00
. get ( memory ::RETURN_NAME , function_expression . as_source_range ( ) )
. ok ( )
. cloned ( )
} ) ;
2025-04-22 11:00:53 +12:00
result = coerce_result_type ( result , function_expression , exec_state ) ;
2025-02-12 10:22:56 +13:00
// Restore the previous memory.
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_env ( ) ;
2025-02-05 17:53:49 +13:00
2025-02-12 10:22:56 +13:00
result
2025-02-05 17:53:49 +13:00
}
2025-03-24 21:55:24 +13:00
async fn call_user_defined_function_kw (
2025-04-07 18:02:46 +12:00
fn_name : Option < & str > ,
2025-02-05 17:53:49 +13:00
args : crate ::std ::args ::KwArgs ,
2025-02-12 10:22:56 +13:00
memory : EnvironmentRef ,
2025-02-05 17:53:49 +13:00
function_expression : NodeRef < '_ , FunctionExpression > ,
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < Option < KclValue > , KclError > {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . push_new_env_for_call ( memory ) ;
2025-04-07 18:02:46 +12:00
if let Err ( e ) = assign_args_to_params_kw ( fn_name , function_expression , args , exec_state ) {
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_env ( ) ;
2025-02-12 10:22:56 +13:00
return Err ( e ) ;
}
2025-02-05 17:53:49 +13:00
// Execute the function body using the memory we just created.
2025-02-12 10:22:56 +13:00
let result = ctx
2025-02-25 16:10:06 +13:00
. exec_block ( & function_expression . body , exec_state , BodyType ::Block )
2025-02-12 10:22:56 +13:00
. await ;
2025-04-22 11:00:53 +12:00
let mut result = result . map ( | _ | {
2025-02-12 10:22:56 +13:00
exec_state
2025-03-05 12:03:32 +13:00
. stack ( )
2025-02-12 10:22:56 +13:00
. get ( memory ::RETURN_NAME , function_expression . as_source_range ( ) )
. ok ( )
. cloned ( )
} ) ;
2025-04-22 11:00:53 +12:00
result = coerce_result_type ( result , function_expression , exec_state ) ;
2025-02-12 10:22:56 +13:00
// Restore the previous memory.
2025-03-05 12:03:32 +13:00
exec_state . mut_stack ( ) . pop_env ( ) ;
2025-02-05 17:53:49 +13:00
2025-02-12 10:22:56 +13:00
result
2025-02-05 17:53:49 +13:00
}
2025-02-22 20:16:29 +13:00
impl FunctionSource {
2025-02-20 19:33:21 +13:00
pub async fn call (
& self ,
2025-04-03 22:44:52 +13:00
fn_name : Option < String > ,
2025-02-20 19:33:21 +13:00
exec_state : & mut ExecState ,
2025-02-22 20:16:29 +13:00
ctx : & ExecutorContext ,
2025-03-24 21:55:24 +13:00
mut args : Vec < Arg > ,
callsite : SourceRange ,
2025-02-20 19:33:21 +13:00
) -> Result < Option < KclValue > , KclError > {
2025-02-22 20:16:29 +13:00
match self {
2025-03-24 21:55:24 +13:00
FunctionSource ::Std { props , .. } = > {
if args . len ( ) < = 1 {
let args = crate ::std ::Args ::new_kw (
KwArgs {
unlabeled : args . pop ( ) ,
labeled : IndexMap ::new ( ) ,
} ,
callsite ,
ctx . clone ( ) ,
exec_state . mod_local . pipe_value . clone ( ) . map ( | v | Arg ::new ( v , callsite ) ) ,
) ;
2025-04-03 22:44:52 +13:00
self . call_kw ( fn_name , exec_state , ctx , args , callsite ) . await
2025-03-24 21:55:24 +13:00
} else {
Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " {} requires its arguments to be labelled " , props . name ) ,
source_ranges : vec ! [ callsite ] ,
} ) )
}
}
FunctionSource ::User { ast , memory , .. } = > {
call_user_defined_function ( args , * memory , ast , exec_state , ctx ) . await
}
FunctionSource ::None = > unreachable! ( ) ,
}
}
pub async fn call_kw (
& self ,
2025-04-03 22:44:52 +13:00
fn_name : Option < String > ,
2025-03-24 21:55:24 +13:00
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
mut args : crate ::std ::Args ,
callsite : SourceRange ,
) -> Result < Option < KclValue > , KclError > {
match self {
FunctionSource ::Std { func , ast , props } = > {
2025-02-22 20:16:29 +13:00
if props . deprecated {
exec_state . warn ( CompilationError ::err (
2025-03-24 21:55:24 +13:00
callsite ,
2025-02-22 20:16:29 +13:00
format! (
" `{}` is deprecated, see the docs for a recommended replacement " ,
props . name
) ,
) ) ;
}
2025-02-05 17:53:49 +13:00
2025-04-07 18:02:46 +12:00
type_check_params_kw ( Some ( & props . name ) , ast , & mut args . kw_args , exec_state ) ? ;
2025-03-24 21:55:24 +13:00
if let Some ( arg ) = & mut args . kw_args . unlabeled {
if let Some ( p ) = ast . params . iter ( ) . find ( | p | ! p . labeled ) {
if let Some ( ty ) = & p . type_ {
arg . value = arg
. value
. coerce (
& RuntimeType ::from_parsed ( ty . inner . clone ( ) , exec_state , arg . source_range ) . unwrap ( ) ,
exec_state ,
)
2025-04-07 16:13:15 +12:00
. map_err ( | _ | {
2025-03-24 21:55:24 +13:00
KclError ::Semantic ( KclErrorDetails {
message : format ! (
" The input argument of {} requires a value with type `{}`, but found {} " ,
props . name ,
ty . inner ,
2025-04-07 16:13:15 +12:00
arg . value . human_friendly_type ( ) ,
2025-03-24 21:55:24 +13:00
) ,
source_ranges : vec ! [ callsite ] ,
} )
} ) ? ;
}
}
}
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-16 11:52:14 -07:00
let op = if props . include_in_feature_tree {
2025-04-03 22:44:52 +13:00
let op_labeled_args = args
. kw_args
. labeled
. iter ( )
. map ( | ( k , arg ) | ( k . clone ( ) , OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ) )
. collect ( ) ;
Some ( Operation ::KclStdLibCall {
name : fn_name . unwrap_or_default ( ) ,
unlabeled_arg : args
. unlabeled_kw_arg_unconverted ( )
. map ( | arg | OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ) ,
labeled_args : op_labeled_args ,
source_range : callsite ,
is_error : false ,
} )
} else {
None
} ;
2025-03-24 21:55:24 +13:00
// Attempt to call the function.
exec_state . mut_stack ( ) . push_new_env_for_rust_call ( ) ;
let mut result = {
// Don't early-return in this block.
let result = func ( exec_state , args ) . await ;
exec_state . mut_stack ( ) . pop_env ( ) ;
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
2025-04-03 22:44:52 +13:00
if let Some ( mut op ) = op {
op . set_std_lib_call_is_error ( result . is_err ( ) ) ;
// Track call operation. We do this after the call
// since things like patternTransform may call user code
// before running, and we will likely want to use the
// return value. The call takes ownership of the args,
// so we need to build the op before the call.
exec_state . global . operations . push ( op ) ;
}
2025-03-24 21:55:24 +13:00
result
} ? ;
update_memory_for_tags_of_geometry ( & mut result , exec_state ) ? ;
Ok ( Some ( result ) )
2025-02-22 20:16:29 +13:00
}
2025-03-08 04:04:57 +13:00
FunctionSource ::User { ast , memory , .. } = > {
2025-04-16 11:52:14 -07:00
// Track call operation.
2025-04-26 21:21:26 -07:00
#[ cfg(feature = " artifact-graph " ) ]
{
let op_labeled_args = args
. kw_args
. labeled
. iter ( )
. map ( | ( k , arg ) | ( k . clone ( ) , OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ) )
. collect ( ) ;
exec_state . global . operations . push ( Operation ::GroupBegin {
group : Group ::FunctionCall {
name : fn_name . clone ( ) ,
function_source_range : ast . as_source_range ( ) ,
unlabeled_arg : args
. kw_args
. unlabeled
. as_ref ( )
. map ( | arg | OpArg ::new ( OpKclValue ::from ( & arg . value ) , arg . source_range ) ) ,
labeled_args : op_labeled_args ,
} ,
source_range : callsite ,
} ) ;
}
2025-04-03 22:44:52 +13:00
2025-04-07 18:02:46 +12:00
call_user_defined_function_kw ( fn_name . as_deref ( ) , args . kw_args , * memory , ast , exec_state , ctx ) . await
2025-02-22 20:16:29 +13:00
}
FunctionSource ::None = > unreachable! ( ) ,
}
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-03-15 10:08:39 -07:00
execution ::{ memory ::Stack , parse_execute , ContextType } ,
2025-02-12 10:22:56 +13:00
parsing ::ast ::types ::{ DefaultParamVal , Identifier , Parameter } ,
2025-04-16 11:52:14 -07:00
ExecutorSettings ,
2025-02-12 10:22:56 +13:00
} ;
2025-03-15 10:08:39 -07:00
#[ tokio::test(flavor = " multi_thread " ) ]
async fn test_assign_args_to_params ( ) {
2025-02-05 17:53:49 +13:00
// Set up a little framework for this test.
fn mem ( number : usize ) -> KclValue {
2025-02-14 08:28:00 +13:00
KclValue ::Number {
value : number as f64 ,
2025-02-14 13:03:23 +13:00
ty : NumericType ::count ( ) ,
2025-02-05 17:53:49 +13:00
meta : Default ::default ( ) ,
}
}
fn ident ( s : & 'static str ) -> Node < Identifier > {
Node ::no_src ( Identifier {
name : s . to_owned ( ) ,
digest : None ,
} )
}
fn opt_param ( s : & 'static str ) -> Parameter {
Parameter {
identifier : ident ( s ) ,
type_ : None ,
default_value : Some ( DefaultParamVal ::none ( ) ) ,
labeled : true ,
digest : None ,
}
}
fn req_param ( s : & 'static str ) -> Parameter {
Parameter {
identifier : ident ( s ) ,
type_ : None ,
default_value : None ,
labeled : true ,
digest : None ,
}
}
2025-03-05 12:03:32 +13:00
fn additional_program_memory ( items : & [ ( String , KclValue ) ] ) -> Stack {
let mut program_memory = Stack ::new_for_tests ( ) ;
2025-02-05 17:53:49 +13:00
for ( name , item ) in items {
program_memory
2025-02-12 10:22:56 +13:00
. add ( name . clone ( ) , item . clone ( ) , SourceRange ::default ( ) )
2025-02-05 17:53:49 +13:00
. unwrap ( ) ;
}
program_memory
}
// Declare the test cases.
for ( test_name , params , args , expected ) in [
2025-02-25 16:10:06 +13:00
( " empty " , Vec ::new ( ) , Vec ::new ( ) , Ok ( additional_program_memory ( & [ ] ) ) ) ,
2025-02-05 17:53:49 +13:00
(
" all params required, and all given, should be OK " ,
vec! [ req_param ( " x " ) ] ,
vec! [ mem ( 1 ) ] ,
Ok ( additional_program_memory ( & [ ( " x " . to_owned ( ) , mem ( 1 ) ) ] ) ) ,
) ,
(
" all params required, none given, should error " ,
vec! [ req_param ( " x " ) ] ,
vec! [ ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
message : " Expected 1 arguments, got 0 " . to_owned ( ) ,
} ) ) ,
) ,
(
" all params optional, none given, should be OK " ,
vec! [ opt_param ( " x " ) ] ,
vec! [ ] ,
Ok ( additional_program_memory ( & [ ( " x " . to_owned ( ) , KclValue ::none ( ) ) ] ) ) ,
) ,
(
" mixed params, too few given " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
message : " Expected 1-2 arguments, got 0 " . to_owned ( ) ,
} ) ) ,
) ,
(
" mixed params, minimum given, should be OK " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ mem ( 1 ) ] ,
Ok ( additional_program_memory ( & [
( " x " . to_owned ( ) , mem ( 1 ) ) ,
( " y " . to_owned ( ) , KclValue ::none ( ) ) ,
] ) ) ,
) ,
(
" mixed params, maximum given, should be OK " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ mem ( 1 ) , mem ( 2 ) ] ,
Ok ( additional_program_memory ( & [
( " x " . to_owned ( ) , mem ( 1 ) ) ,
( " y " . to_owned ( ) , mem ( 2 ) ) ,
] ) ) ,
) ,
(
" mixed params, too many given " ,
vec! [ req_param ( " x " ) , opt_param ( " y " ) ] ,
vec! [ mem ( 1 ) , mem ( 2 ) , mem ( 3 ) ] ,
Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
message : " Expected 1-2 arguments, got 3 " . to_owned ( ) ,
} ) ) ,
) ,
] {
// Run each test.
let func_expr = & Node ::no_src ( FunctionExpression {
params ,
2025-04-16 11:52:14 -07:00
body : Program ::empty ( ) ,
2025-02-05 17:53:49 +13:00
return_type : None ,
digest : None ,
} ) ;
let args = args . into_iter ( ) . map ( Arg ::synthetic ) . collect ( ) ;
2025-03-15 10:08:39 -07:00
let exec_ctxt = ExecutorContext {
engine : Arc ::new ( Box ::new (
crate ::engine ::conn_mock ::EngineConnection ::new ( ) . await . unwrap ( ) ,
) ) ,
fs : Arc ::new ( crate ::fs ::FileManager ::new ( ) ) ,
stdlib : Arc ::new ( crate ::std ::StdLib ::new ( ) ) ,
settings : Default ::default ( ) ,
context_type : ContextType ::Mock ,
} ;
let mut exec_state = ExecState ::new ( & exec_ctxt ) ;
2025-03-05 12:03:32 +13:00
exec_state . mod_local . stack = Stack ::new_for_tests ( ) ;
let actual = assign_args_to_params ( func_expr , args , & mut exec_state ) . map ( | _ | exec_state . mod_local . stack ) ;
2025-02-05 17:53:49 +13:00
assert_eq! (
actual , expected ,
" failed test '{test_name}': \n got {actual:?} \n but expected \n {expected:?} "
) ;
}
}
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
" #;
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 { .. }
) ) ;
let program = r #"
a = 42 : string
" #;
let result = parse_execute ( program ) . await ;
assert! ( result
. unwrap_err ( )
. to_string ( )
. contains ( " could not coerce number value to type string " ) ) ;
let program = r #"
a = 42 : Plane
" #;
let result = parse_execute ( program ) . await ;
assert! ( result
. unwrap_err ( )
. to_string ( )
. contains ( " could not coerce number value to type Plane " ) ) ;
}
#[ 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
let programa_kcl = r #"
export a = 1
" #;
// program b.kcl
let programb_kcl = r #"
import a from ' a . kcl '
export b = a + 1
" #;
// program c.kcl
let programc_kcl = r #"
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 ( )
. write_all ( programa_kcl . as_bytes ( ) )
. await
. unwrap ( ) ;
tokio ::fs ::File ::create ( tmpdir . path ( ) . join ( " b.kcl " ) )
. await
. unwrap ( )
. write_all ( programb_kcl . as_bytes ( ) )
. await
. unwrap ( ) ;
tokio ::fs ::File ::create ( tmpdir . path ( ) . join ( " c.kcl " ) )
. await
. unwrap ( )
. write_all ( programc_kcl . as_bytes ( ) )
. await
. unwrap ( ) ;
let exec_ctxt = ExecutorContext {
engine : Arc ::new ( Box ::new (
crate ::engine ::conn_mock ::EngineConnection ::new ( )
. await
. map_err ( | err | {
KclError ::Internal ( crate ::errors ::KclErrorDetails {
message : format ! ( " Failed to create mock engine connection: {} " , err ) ,
source_ranges : vec ! [ SourceRange ::default ( ) ] ,
} )
} )
. unwrap ( ) ,
) ) ,
fs : Arc ::new ( crate ::fs ::FileManager ::new ( ) ) ,
settings : ExecutorSettings {
project_directory : Some ( tmpdir . path ( ) . into ( ) ) ,
.. Default ::default ( )
} ,
stdlib : Arc ::new ( crate ::std ::StdLib ::new ( ) ) ,
context_type : ContextType ::Mock ,
} ;
let mut exec_state = ExecState ::new ( & exec_ctxt ) ;
exec_ctxt
. run_concurrent (
& crate ::Program {
ast : main . clone ( ) ,
original_file_contents : " " . to_owned ( ) ,
} ,
& mut exec_state ,
false ,
)
. 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-02-05 17:53:49 +13:00
}