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-02-05 17:53:49 +13:00
use schemars ::JsonSchema ;
2024-12-03 16:39:51 +13:00
2024-10-16 14:33:03 -07:00
use crate ::{
2025-02-05 17:53:49 +13:00
engine ::ExecutionKind ,
2024-10-16 14:33:03 -07:00
errors ::{ KclError , KclErrorDetails } ,
2024-12-07 07:16:04 +13:00
execution ::{
2025-02-05 17:53:49 +13:00
annotations ,
cad_op ::{ OpArg , Operation } ,
state ::ModuleState ,
BodyType , ExecState , ExecutorContext , KclValue , MemoryFunction , Metadata , ModuleRepr , ProgramMemory ,
TagEngineInfo , TagIdentifier ,
2024-12-07 07:16:04 +13:00
} ,
2025-02-05 17:53:49 +13:00
fs ::FileSystem ,
2024-12-05 17:56:49 +13:00
parsing ::ast ::types ::{
2025-02-05 17:53:49 +13:00
ArrayExpression , ArrayRangeExpression , BinaryExpression , BinaryOperator , BinaryPart , BodyItem , CallExpression ,
CallExpressionKw , Expr , FunctionExpression , IfExpression , ImportPath , ImportSelector , ItemVisibility ,
LiteralIdentifier , LiteralValue , MemberExpression , MemberObject , Node , NodeRef , NonCodeValue , ObjectExpression ,
PipeExpression , TagDeclarator , UnaryExpression , UnaryOperator ,
2024-12-05 17:56:49 +13:00
} ,
2025-02-05 17:53:49 +13:00
source_range ::{ ModuleId , SourceRange } ,
2024-12-06 19:11:31 -06:00
std ::{
args ::{ Arg , KwArgs } ,
FunctionKind ,
} ,
2024-10-16 14:33:03 -07:00
} ;
2024-11-14 17:27:19 -06:00
2025-02-05 17:53:49 +13:00
enum StatementKind < ' a > {
Declaration { name : & ' a str } ,
Expression ,
}
impl ExecutorContext {
async fn handle_annotations (
& self ,
annotations : impl Iterator < Item = ( & NonCodeValue , SourceRange ) > ,
scope : annotations ::AnnotationScope ,
exec_state : & mut ExecState ,
) -> Result < ( ) , KclError > {
for ( annotation , source_range ) in annotations {
if annotation . annotation_name ( ) = = Some ( annotations ::SETTINGS ) {
if scope = = annotations ::AnnotationScope ::Module {
let old_units = exec_state . length_unit ( ) ;
exec_state
. mod_local
. settings
. update_from_annotation ( annotation , source_range ) ? ;
let new_units = exec_state . length_unit ( ) ;
if old_units ! = new_units {
self . engine . set_units ( new_units . into ( ) , source_range ) . await ? ;
}
} else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : " Settings can only be modified at the top level scope of a file " . to_owned ( ) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ;
}
}
// TODO warn on unknown annotations
}
Ok ( ( ) )
}
/// Execute an AST's program.
#[ async_recursion ]
pub ( super ) async fn exec_program < ' a > (
& ' a self ,
program : NodeRef < ' a , crate ::parsing ::ast ::types ::Program > ,
exec_state : & mut ExecState ,
body_type : BodyType ,
) -> Result < Option < KclValue > , KclError > {
self . handle_annotations (
program
. non_code_meta
. start_nodes
. iter ( )
. filter_map ( | n | n . annotation ( ) . map ( | result | ( result , n . as_source_range ( ) ) ) ) ,
annotations ::AnnotationScope ::Module ,
exec_state ,
)
. await ? ;
let mut last_expr = None ;
// Iterate over the body of the program.
for statement in & program . body {
match statement {
BodyItem ::ImportStatement ( import_stmt ) = > {
let source_range = SourceRange ::from ( import_stmt ) ;
let module_id = self . open_module ( & import_stmt . path , exec_state , source_range ) . await ? ;
match & import_stmt . selector {
ImportSelector ::List { items } = > {
let ( _ , module_memory , module_exports ) = self
. exec_module ( module_id , exec_state , ExecutionKind ::Isolated , source_range )
. await ? ;
for import_item in items {
// Extract the item from the module.
let item =
module_memory
. get ( & import_item . name . name , import_item . into ( ) )
. map_err ( | _err | {
KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " {} is not defined in module " , import_item . name . name ) ,
source_ranges : vec ! [ SourceRange ::from ( & import_item . name ) ] ,
} )
} ) ? ;
// Check that the item is allowed to be imported.
if ! module_exports . contains ( & import_item . name . name ) {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Cannot import \" {} \" from module because it is not exported. Add \" export \" before the definition to export it. " ,
import_item . name . name
) ,
source_ranges : vec ! [ SourceRange ::from ( & import_item . name ) ] ,
} ) ) ;
}
// Add the item to the current module.
exec_state . mut_memory ( ) . add (
import_item . identifier ( ) ,
item . clone ( ) ,
SourceRange ::from ( & import_item . name ) ,
) ? ;
if let ItemVisibility ::Export = import_stmt . visibility {
exec_state
. mod_local
. module_exports
. push ( import_item . identifier ( ) . to_owned ( ) ) ;
}
}
}
ImportSelector ::Glob ( _ ) = > {
let ( _ , module_memory , module_exports ) = self
. exec_module ( module_id , exec_state , ExecutionKind ::Isolated , source_range )
. await ? ;
for name in module_exports . iter ( ) {
let item = module_memory . get ( name , source_range ) . map_err ( | _err | {
KclError ::Internal ( KclErrorDetails {
message : format ! ( " {} is not defined in module (but was exported?) " , name ) ,
source_ranges : vec ! [ source_range ] ,
} )
} ) ? ;
exec_state . mut_memory ( ) . add ( name , item . clone ( ) , source_range ) ? ;
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 ( ) ] ,
} ;
exec_state . mut_memory ( ) . add ( & name , item , source_range ) ? ;
}
}
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 ,
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 } ;
let memory_item = self
. execute_expr (
& variable_declaration . declaration . init ,
exec_state ,
& metadata ,
StatementKind ::Declaration { name : & var_name } ,
)
. await ? ;
exec_state . mut_memory ( ) . add ( & var_name , memory_item , source_range ) ? ;
// Track exports.
if let ItemVisibility ::Export = variable_declaration . visibility {
exec_state . mod_local . module_exports . push ( var_name ) ;
}
last_expr = None ;
}
BodyItem ::ReturnStatement ( return_statement ) = > {
let metadata = Metadata ::from ( return_statement ) ;
let value = self
. execute_expr (
& return_statement . argument ,
exec_state ,
& metadata ,
StatementKind ::Expression ,
)
. await ? ;
exec_state . mut_memory ( ) . return_ = Some ( value ) ;
last_expr = None ;
}
}
}
if BodyType ::Root = = body_type {
// 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 )
}
async fn open_module (
& self ,
path : & ImportPath ,
exec_state : & mut ExecState ,
source_range : SourceRange ,
) -> Result < ModuleId , KclError > {
match path {
ImportPath ::Kcl { filename } = > {
let resolved_path = if let Some ( project_dir ) = & self . settings . project_directory {
project_dir . join ( filename )
} else {
std ::path ::PathBuf ::from ( filename )
} ;
if exec_state . mod_local . import_stack . contains ( & resolved_path ) {
return Err ( KclError ::ImportCycle ( KclErrorDetails {
message : format ! (
" circular import of modules is not allowed: {} -> {} " ,
exec_state
. mod_local
. import_stack
. iter ( )
. map ( | p | p . as_path ( ) . to_string_lossy ( ) )
. collect ::< Vec < _ > > ( )
. join ( " -> " ) ,
resolved_path . to_string_lossy ( )
) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ;
}
if let Some ( id ) = exec_state . global . path_to_source_id . get ( & resolved_path ) {
return Ok ( * id ) ;
}
let source = self . fs . read_to_string ( & resolved_path , source_range ) . await ? ;
let id = ModuleId ::from_usize ( exec_state . global . path_to_source_id . len ( ) ) ;
// TODO handle parsing errors properly
let parsed = crate ::parsing ::parse_str ( & source , id ) . parse_errs_as_err ( ) ? ;
let repr = ModuleRepr ::Kcl ( parsed ) ;
Ok ( exec_state . add_module ( id , resolved_path , repr ) )
}
ImportPath ::Foreign { path } = > {
let resolved_path = if let Some ( project_dir ) = & self . settings . project_directory {
project_dir . join ( path )
} else {
std ::path ::PathBuf ::from ( path )
} ;
if let Some ( id ) = exec_state . global . path_to_source_id . get ( & resolved_path ) {
return Ok ( * id ) ;
}
let geom = super ::import ::import_foreign ( & resolved_path , None , exec_state , self , source_range ) . await ? ;
let repr = ModuleRepr ::Foreign ( geom ) ;
let id = ModuleId ::from_usize ( exec_state . global . path_to_source_id . len ( ) ) ;
Ok ( exec_state . add_module ( id , resolved_path , repr ) )
}
i = > Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Unsupported import: `{i}` " ) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ,
}
}
async fn exec_module (
& self ,
module_id : ModuleId ,
exec_state : & mut ExecState ,
exec_kind : ExecutionKind ,
source_range : SourceRange ,
) -> Result < ( Option < KclValue > , ProgramMemory , Vec < String > ) , KclError > {
let old_units = exec_state . length_unit ( ) ;
// TODO It sucks that we have to clone the whole module AST here
let info = exec_state . global . module_infos [ & module_id ] . clone ( ) ;
match & info . repr {
ModuleRepr ::Root = > Err ( KclError ::ImportCycle ( KclErrorDetails {
message : format ! (
" circular import of modules is not allowed: {} -> {} " ,
exec_state
. mod_local
. import_stack
. iter ( )
. map ( | p | p . as_path ( ) . to_string_lossy ( ) )
. collect ::< Vec < _ > > ( )
. join ( " -> " ) ,
info . path . display ( )
) ,
source_ranges : vec ! [ source_range ] ,
} ) ) ,
ModuleRepr ::Kcl ( program ) = > {
let mut local_state = ModuleState {
import_stack : exec_state . mod_local . import_stack . clone ( ) ,
.. ModuleState ::new ( & self . settings )
} ;
local_state . import_stack . push ( info . path . clone ( ) ) ;
std ::mem ::swap ( & mut exec_state . mod_local , & mut local_state ) ;
let original_execution = self . engine . replace_execution_kind ( exec_kind ) ;
let result = self
. exec_program ( program , exec_state , crate ::execution ::BodyType ::Root )
. await ;
let new_units = exec_state . length_unit ( ) ;
std ::mem ::swap ( & mut exec_state . mod_local , & mut local_state ) ;
if new_units ! = old_units {
self . engine . set_units ( old_units . into ( ) , Default ::default ( ) ) . await ? ;
}
self . engine . replace_execution_kind ( original_execution ) ;
let result = result . map_err ( | err | {
if let KclError ::ImportCycle ( _ ) = err {
// It was an import cycle. Keep the original message.
err . override_source_ranges ( vec! [ source_range ] )
} else {
KclError ::Semantic ( KclErrorDetails {
message : format ! (
" Error loading imported file. Open it to view more details. {}: {} " ,
info . path . display ( ) ,
err . message ( )
) ,
source_ranges : vec ! [ source_range ] ,
} )
}
} ) ? ;
Ok ( ( result , local_state . memory , local_state . module_exports ) )
}
ModuleRepr ::Foreign ( geom ) = > {
let geom = super ::import ::send_to_engine ( geom . clone ( ) , self ) . await ? ;
Ok ( ( Some ( KclValue ::ImportedGeometry ( geom ) ) , ProgramMemory ::new ( ) , Vec ::new ( ) ) )
}
}
}
#[ async_recursion ]
async fn execute_expr < ' a : ' async_recursion > (
& self ,
init : & Expr ,
exec_state : & mut ExecState ,
metadata : & Metadata ,
statement_kind : StatementKind < ' a > ,
) -> Result < KclValue , KclError > {
let item = match init {
Expr ::None ( none ) = > KclValue ::from ( none ) ,
Expr ::Literal ( literal ) = > KclValue ::from ( literal ) ,
Expr ::TagDeclarator ( tag ) = > tag . execute ( exec_state ) . await ? ,
Expr ::Identifier ( identifier ) = > {
let value = exec_state . memory ( ) . get ( & identifier . name , identifier . into ( ) ) ? . clone ( ) ;
if let KclValue ::Module { value : module_id , meta } = value {
let ( result , _ , _ ) = self
. exec_module ( module_id , exec_state , ExecutionKind ::Normal , metadata . source_range )
. await ? ;
result . unwrap_or_else ( | | {
// The module didn't have a return value. Currently,
// the only way to have a return value is with the final
// statement being an expression statement.
//
// TODO: Make a warning when we support them in the
// execution phase.
let mut new_meta = vec! [ metadata . to_owned ( ) ] ;
new_meta . extend ( meta ) ;
KclValue ::KclNone {
value : Default ::default ( ) ,
meta : new_meta ,
}
} )
} else {
value
}
}
Expr ::BinaryExpression ( binary_expression ) = > binary_expression . get_result ( exec_state , self ) . await ? ,
Expr ::FunctionExpression ( function_expression ) = > {
// Cloning memory here is crucial for semantics so that we close
// over variables. Variables defined lexically later shouldn't
// be available to the function body.
KclValue ::Function {
expression : function_expression . clone ( ) ,
meta : vec ! [ metadata . to_owned ( ) ] ,
func : None ,
memory : Box ::new ( exec_state . memory ( ) . clone ( ) ) ,
}
}
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
. execute_expr ( & expr . expr , exec_state , metadata , statement_kind )
. await ? ;
exec_state
. mut_memory ( )
. add ( & expr . label . name , result . clone ( ) , init . into ( ) ) ? ;
// TODO this lets us use the label as a variable name, but not as a tag in most cases
result
}
} ;
Ok ( item )
}
}
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 {
BinaryPart ::Literal ( literal ) = > Ok ( literal . into ( ) ) ,
BinaryPart ::Identifier ( identifier ) = > {
2024-12-17 09:38:32 +13:00
let value = exec_state . memory ( ) . get ( & identifier . name , identifier . into ( ) ) ? ;
2024-10-16 14:33:03 -07:00
Ok ( value . clone ( ) )
}
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 ,
}
}
}
2024-10-30 16:52:17 -04:00
impl Node < MemberExpression > {
2024-10-16 14:33:03 -07:00
pub fn get_result_array ( & self , exec_state : & mut ExecState , index : usize ) -> Result < KclValue , KclError > {
let array = match & self . object {
MemberObject ::MemberExpression ( member_expr ) = > member_expr . get_result ( exec_state ) ? ,
MemberObject ::Identifier ( identifier ) = > {
2024-12-17 09:38:32 +13:00
let value = exec_state . memory ( ) . get ( & identifier . name , identifier . into ( ) ) ? ;
2024-10-16 14:33:03 -07:00
value . clone ( )
}
} ;
2024-11-14 17:27:19 -06:00
let KclValue ::Array { value : array , meta : _ } = array else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " MemberExpression array is not an array: {:?} " , array ) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) ) ;
} ;
2024-10-16 14:33:03 -07:00
2024-11-14 17:27:19 -06:00
if let Some ( value ) = array . get ( index ) {
Ok ( value . to_owned ( ) )
2024-10-16 14:33:03 -07:00
} else {
2024-11-14 17:27:19 -06:00
Err ( KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " index {} not found in array " , index ) ,
2024-10-16 14:33:03 -07:00
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
}
2024-10-17 16:29:27 -07:00
pub fn get_result ( & self , exec_state : & mut ExecState ) -> Result < KclValue , KclError > {
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 ) = > {
2024-12-17 09:38:32 +13:00
let value = exec_state . memory ( ) . 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.
2024-11-14 17:27:19 -06:00
match ( object , property ) {
( KclValue ::Object { value : map , meta : _ } , Property ::String ( property ) ) = > {
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 ( ) ] ,
} ) )
}
}
2024-11-14 17:27:19 -06:00
( KclValue ::Object { .. } , p ) = > {
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 ( ) ] ,
} ) )
}
2024-11-25 10:50:43 +13:00
( KclValue ::Array { value : arr , meta : _ } , Property ::UInt ( index ) ) = > {
2024-11-14 17:27:19 -06:00
let value_of_arr = arr . get ( index ) ;
2024-10-16 14:33:03 -07:00
if let Some ( value ) = value_of_arr {
2024-11-14 17:27:19 -06:00
Ok ( value . to_owned ( ) )
2024-10-16 14:33:03 -07:00
} else {
Err ( KclError ::UndefinedValue ( KclErrorDetails {
message : format ! ( " The array doesn't have any item at index {index} " ) ,
source_ranges : vec ! [ self . clone ( ) . into ( ) ] ,
} ) )
}
}
2024-11-14 17:27:19 -06:00
( KclValue ::Array { .. } , p ) = > {
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-01-22 09:42:09 +13:00
( KclValue ::Solid { value } , Property ::String ( prop ) ) if prop = = " sketch " = > Ok ( KclValue ::Sketch {
value : Box ::new ( value . sketch ) ,
2024-11-14 17:27:19 -06:00
} ) ,
( KclValue ::Sketch { value : sk } , Property ::String ( prop ) ) if prop = = " tags " = > Ok ( KclValue ::Object {
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 ( ) ,
} ) ,
2024-10-16 14:33:03 -07: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
}
}
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 } ) ;
}
2024-11-14 17:27:19 -06:00
let left = parse_number_as_f64 ( & left_value , self . left . clone ( ) . into ( ) ) ? ;
let right = parse_number_as_f64 ( & right_value , self . right . clone ( ) . into ( ) ) ? ;
let value = match self . operator {
BinaryOperator ::Add = > KclValue ::Number {
value : left + right ,
meta ,
} ,
BinaryOperator ::Sub = > KclValue ::Number {
value : left - right ,
meta ,
} ,
BinaryOperator ::Mul = > KclValue ::Number {
value : left * right ,
meta ,
} ,
BinaryOperator ::Div = > KclValue ::Number {
value : left / right ,
meta ,
} ,
BinaryOperator ::Mod = > KclValue ::Number {
value : left % right ,
meta ,
} ,
BinaryOperator ::Pow = > KclValue ::Number {
value : left . powf ( right ) ,
meta ,
} ,
BinaryOperator ::Neq = > KclValue ::Bool {
value : left ! = right ,
meta ,
} ,
BinaryOperator ::Gt = > KclValue ::Bool {
value : left > right ,
meta ,
} ,
BinaryOperator ::Gte = > KclValue ::Bool {
value : left > = right ,
meta ,
} ,
BinaryOperator ::Lt = > KclValue ::Bool {
value : left < right ,
meta ,
} ,
BinaryOperator ::Lte = > KclValue ::Bool {
value : left < = right ,
meta ,
} ,
BinaryOperator ::Eq = > KclValue ::Bool {
value : left = = right ,
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
}
}
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 ? ;
match value {
KclValue ::Number { value , meta : _ } = > {
let meta = vec! [ Metadata {
source_range : self . into ( ) ,
} ] ;
Ok ( KclValue ::Number { value : - value , meta } )
}
KclValue ::Int { value , meta : _ } = > {
let meta = vec! [ Metadata {
source_range : self . into ( ) ,
} ] ;
Ok ( KclValue ::Number {
value : ( - value ) as f64 ,
meta ,
} )
}
_ = > Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" You can only negate numbers, but this is a {} " ,
value . human_friendly_type ( )
) ,
source_ranges : vec ! [ self . into ( ) ] ,
} ) ) ,
}
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
. execute_expr ( first , exec_state , & meta , StatementKind ::Expression )
. 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
. execute_expr ( expression , exec_state , & metadata , StatementKind ::Expression )
. 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 > {
let fn_name = & self . callee . name ;
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.
let mut fn_args = HashMap ::with_capacity ( self . arguments . len ( ) ) ;
for arg_expr in & self . arguments {
let source_range = SourceRange ::from ( arg_expr . arg . clone ( ) ) ;
let metadata = Metadata { source_range } ;
let value = ctx
. execute_expr ( & arg_expr . arg , exec_state , & metadata , StatementKind ::Expression )
. 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
. execute_expr ( arg_expr , exec_state , & metadata , StatementKind ::Expression )
. 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 ( ) ,
2024-12-16 21:01:23 -06:00
exec_state . mod_local . pipe_value . clone ( ) . map ( Arg ::synthetic ) ,
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 ) = > {
2024-12-16 13:10:31 -05:00
let op = if func . feature_tree_operation ( ) {
let op_labeled_args = args
. kw_args
. labeled
. iter ( )
. map ( | ( k , v ) | ( k . clone ( ) , OpArg ::new ( v . source_range ) ) )
. collect ( ) ;
Some ( Operation ::StdLibCall {
std_lib_fn : ( & func ) . into ( ) ,
unlabeled_arg : args . kw_args . unlabeled . as_ref ( ) . map ( | arg | OpArg ::new ( arg . source_range ) ) ,
labeled_args : op_labeled_args ,
source_range : callsite ,
is_error : false ,
} )
} else {
None
} ;
2024-12-05 14:27:51 -06:00
// Attempt to call the function.
2024-12-16 13:10:31 -05:00
let result = {
// Don't early-return in this block.
let result = func . std_lib_fn ( ) ( exec_state , args ) . await ;
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.
2024-12-17 09:38:32 +13:00
exec_state . mod_local . operations . push ( op ) ;
2024-12-16 13:10:31 -05:00
}
result
} ;
let mut return_value = result ? ;
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
let source_range = SourceRange ::from ( self ) ;
// Clone the function so that we can use a mutable reference to
// exec_state.
2024-12-17 09:38:32 +13:00
let func = exec_state . memory ( ) . get ( fn_name , source_range ) ? . clone ( ) ;
let fn_dynamic_state = exec_state . mod_local . dynamic_state . merge ( exec_state . memory ( ) ) ;
2024-12-09 22:11:16 -06:00
2024-12-16 13:10:31 -05:00
// Track call operation.
let op_labeled_args = args
. kw_args
. labeled
. iter ( )
. map ( | ( k , v ) | ( k . clone ( ) , OpArg ::new ( v . source_range ) ) )
. collect ( ) ;
2024-12-17 09:38:32 +13:00
exec_state
. mod_local
. operations
. push ( Operation ::UserDefinedFunctionCall {
name : Some ( fn_name . clone ( ) ) ,
function_source_range : func . function_def_source_range ( ) . unwrap_or_default ( ) ,
unlabeled_arg : args . kw_args . unlabeled . as_ref ( ) . map ( | arg | OpArg ::new ( arg . source_range ) ) ,
labeled_args : op_labeled_args ,
source_range : callsite ,
} ) ;
2024-12-16 13:10:31 -05:00
2024-12-09 22:11:16 -06:00
let return_value = {
2024-12-17 09:38:32 +13:00
let previous_dynamic_state =
std ::mem ::replace ( & mut exec_state . mod_local . dynamic_state , fn_dynamic_state ) ;
2024-12-09 22:11:16 -06:00
let result = func
. call_fn_kw ( args , exec_state , ctx . clone ( ) , callsite )
. 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-12-17 09:38:32 +13:00
exec_state . mod_local . dynamic_state = previous_dynamic_state ;
2024-12-09 22:11:16 -06:00
result ?
} ;
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 {
message : format ! ( " Result of user-defined function {} is undefined " , fn_name ) ,
source_ranges ,
} )
} ) ? ;
2024-12-16 13:10:31 -05:00
// Track return operation.
2024-12-17 09:38:32 +13:00
exec_state
. mod_local
. operations
. push ( Operation ::UserDefinedFunctionReturn ) ;
2024-12-16 13:10:31 -05:00
2024-12-09 22:11:16 -06:00
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 > {
let fn_name = & self . callee . name ;
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
. 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 ) = > {
2024-12-16 13:10:31 -05:00
let op = if func . feature_tree_operation ( ) {
let op_labeled_args = func
. args ( false )
. iter ( )
. zip ( & fn_args )
. map ( | ( k , v ) | ( k . name . clone ( ) , OpArg ::new ( v . source_range ) ) )
. 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 ( ) ,
exec_state . mod_local . pipe_value . clone ( ) . map ( Arg ::synthetic ) ,
) ;
2024-12-16 13:10:31 -05:00
let result = {
// Don't early-return in this block.
let result = func . std_lib_fn ( ) ( exec_state , args ) . await ;
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.
2024-12-17 09:38:32 +13:00
exec_state . mod_local . operations . push ( op ) ;
2024-12-16 13:10:31 -05:00
}
result
} ;
let mut return_value = result ? ;
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.
2024-12-17 09:38:32 +13:00
let func = exec_state . memory ( ) . get ( fn_name , source_range ) ? . clone ( ) ;
let fn_dynamic_state = exec_state . mod_local . dynamic_state . merge ( exec_state . memory ( ) ) ;
2024-10-16 14:33:03 -07:00
2024-12-16 13:10:31 -05:00
// Track call operation.
2024-12-17 09:38:32 +13:00
exec_state
. mod_local
. operations
. push ( Operation ::UserDefinedFunctionCall {
name : Some ( fn_name . clone ( ) ) ,
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
2024-10-16 14:33:03 -07:00
let return_value = {
2024-12-17 09:38:32 +13:00
let previous_dynamic_state =
std ::mem ::replace ( & mut exec_state . mod_local . dynamic_state , fn_dynamic_state ) ;
2024-10-16 14:33:03 -07:00
let result = func . call_fn ( fn_args , exec_state , ctx . clone ( ) ) . await . map_err ( | e | {
// Add the call expression to the source ranges.
2024-12-06 13:57:31 +13:00
// TODO currently ignored by the frontend
2024-10-16 14:33:03 -07:00
e . add_source_ranges ( vec! [ source_range ] )
} ) ;
2024-12-17 09:38:32 +13:00
exec_state . mod_local . dynamic_state = previous_dynamic_state ;
2024-10-16 14:33:03 -07:00
result ?
} ;
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 {
message : format ! ( " Result of user-defined function {} is undefined " , fn_name ) ,
source_ranges ,
} )
} ) ? ;
2024-12-16 13:10:31 -05:00
// Track return operation.
2024-12-17 09:38:32 +13:00
exec_state
. mod_local
. operations
. push ( Operation ::UserDefinedFunctionReturn ) ;
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 {
KclValue ::Sketch { value : ref mut sketch } = > {
for ( _ , tag ) in sketch . tags . iter ( ) {
2024-12-17 09:38:32 +13:00
exec_state . mut_memory ( ) . update_tag ( & tag . value , tag . clone ( ) ) ? ;
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-01-22 09:42:09 +13:00
let mut t = if let Some ( t ) = value . sketch . tags . get ( & tag . name ) {
2024-12-05 14:27:51 -06:00
t . clone ( )
} else {
// It's probably a fillet or a chamfer.
// Initialize it.
TagIdentifier {
value : tag . name . clone ( ) ,
info : Some ( TagEngineInfo {
2025-01-22 09:42:09 +13:00
id : v . get_id ( ) ,
surface : Some ( v . clone ( ) ) ,
2024-12-05 14:27:51 -06:00
path : None ,
2025-01-22 09:42:09 +13:00
sketch : value . id ,
2024-12-05 14:27:51 -06:00
} ) ,
meta : vec ! [ Metadata {
source_range : tag . clone ( ) . into ( ) ,
} ] ,
}
} ;
let Some ( ref info ) = t . info else {
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! ( " Tag {} does not have path info " , tag . name ) ,
source_ranges : vec ! [ tag . into ( ) ] ,
} ) ) ;
} ;
let mut info = info . clone ( ) ;
2025-01-22 09:42:09 +13:00
info . surface = Some ( v . clone ( ) ) ;
info . sketch = value . id ;
2024-12-05 14:27:51 -06:00
t . info = Some ( info ) ;
2024-12-17 09:38:32 +13:00
exec_state . mut_memory ( ) . update_tag ( & tag . name , t . clone ( ) ) ? ;
2024-12-05 14:27:51 -06:00
// update the sketch tags.
2025-01-22 09:42:09 +13:00
value . sketch . tags . insert ( tag . name . clone ( ) , t ) ;
2024-12-05 14:27:51 -06:00
}
}
// Find the stale sketch in memory and update it.
2024-12-17 09:38:32 +13:00
let cur_env_index = exec_state . memory ( ) . current_env . index ( ) ;
if let Some ( current_env ) = exec_state . mut_memory ( ) . environments . get_mut ( cur_env_index ) {
2025-01-22 09:42:09 +13:00
current_env . update_sketch_tags ( & value . sketch ) ;
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 ( ) ,
info : None ,
meta : vec ! [ Metadata {
source_range : self . into ( ) ,
} ] ,
} ) ) ;
2024-12-17 09:38:32 +13:00
exec_state
. mut_memory ( )
. add ( & self . name , 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
. execute_expr ( element , exec_state , & metadata , StatementKind ::Expression )
. await ? ;
2024-11-14 17:27:19 -06:00
results . push ( value ) ;
2024-10-16 14:33:03 -07:00
}
2024-11-14 17:27:19 -06:00
Ok ( KclValue ::Array {
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
. 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
. 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 ( ) ,
} ] ;
Ok ( KclValue ::Array {
value : range
. into_iter ( )
. map ( | num | KclValue ::Int {
value : num ,
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
. execute_expr ( & property . value , exec_state , & metadata , StatementKind ::Expression )
. 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
}
}
2024-11-14 17:27:19 -06:00
pub fn parse_number_as_f64 ( v : & KclValue , source_range : SourceRange ) -> Result < f64 , KclError > {
if let KclValue ::Number { value : n , .. } = & v {
Ok ( * n )
} else if let KclValue ::Int { value : n , .. } = & v {
Ok ( * n as f64 )
2024-10-16 14:33:03 -07:00
} else {
2024-11-14 17:27:19 -06:00
let actual_type = v . human_friendly_type ( ) ;
let article = if actual_type . starts_with ( [ 'a' , 'e' , 'i' , 'o' , 'u' ] ) {
" an "
} else {
" a "
} ;
Err ( 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} " , ) ,
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
. execute_expr ( & self . cond , exec_state , & Metadata ::from ( self ) , StatementKind ::Expression )
. await ?
. get_bool ( ) ? ;
if cond {
2025-02-05 17:53:49 +13:00
let block_result = ctx . exec_program ( & 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 ) ,
StatementKind ::Expression ,
)
. await ?
. get_bool ( ) ? ;
if cond {
2025-02-05 17:53:49 +13:00
let block_result = ctx . exec_program ( & 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-05 17:53:49 +13:00
ctx . exec_program ( & 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 {
// Treat the property as a literal
Ok ( Property ::String ( name . to_string ( ) ) )
} else {
// Actually evaluate memory to compute the property.
2024-12-17 09:38:32 +13:00
let prop = exec_state . memory ( ) . 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
} ) )
}
}
LiteralValue ::String ( s ) = > Ok ( Property ::String ( s ) ) ,
_ = > Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges : vec ! [ sr ] ,
2024-11-25 10:50:43 +13:00
message : " Only strings or numbers (>= 0) can be properties/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 {
2024-11-14 17:27:19 -06:00
KclValue ::Int { value :num , meta : _ } = > {
let maybe_int : Result < usize , _ > = ( * num ) . try_into ( ) ;
if let Ok ( uint ) = maybe_int {
2024-11-25 10:50:43 +13:00
Ok ( Property ::UInt ( uint ) )
2024-11-14 17:27:19 -06:00
}
else {
make_err ( format! ( " ' {num} ' is negative, so you can't index an array with it " ) )
}
}
KclValue ::Number { value : num , meta :_ } = > {
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 > ,
mut fn_memory : ProgramMemory ,
) -> Result < ProgramMemory , KclError > {
let num_args = function_expression . number_of_args ( ) ;
let ( min_params , max_params ) = num_args . into_inner ( ) ;
let n = args . len ( ) ;
// Check if the user supplied too many arguments
// (we'll check for too few arguments below).
let err_wrong_number_args = KclError ::Semantic ( KclErrorDetails {
message : if min_params = = max_params {
format! ( " Expected {min_params} arguments, got {n} " )
} else {
format! ( " Expected {min_params} - {max_params} arguments, got {n} " )
} ,
source_ranges : vec ! [ function_expression . into ( ) ] ,
} ) ;
if n > max_params {
return Err ( err_wrong_number_args ) ;
}
// 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.
fn_memory . add ( & param . identifier . name , arg . value . clone ( ) , ( & param . identifier ) . into ( ) ) ? ;
} 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.
fn_memory . add (
& param . identifier . name ,
default_val . clone ( ) . into ( ) ,
( & param . identifier ) . into ( ) ,
) ? ;
} else {
// But if the corresponding parameter was required,
// then the user has called with too few arguments.
return Err ( err_wrong_number_args ) ;
}
}
}
Ok ( fn_memory )
}
fn assign_args_to_params_kw (
function_expression : NodeRef < '_ , FunctionExpression > ,
mut args : crate ::std ::args ::KwArgs ,
mut fn_memory : ProgramMemory ,
) -> Result < ProgramMemory , KclError > {
// Add the arguments to the memory. A new call frame should have already
// been created.
let source_ranges = vec! [ function_expression . into ( ) ] ;
for param in function_expression . params . iter ( ) {
if param . labeled {
let arg = args . labeled . get ( & param . identifier . name ) ;
let arg_val = match arg {
Some ( arg ) = > arg . value . clone ( ) ,
None = > match param . default_value {
Some ( ref default_val ) = > KclValue ::from ( default_val . clone ( ) ) ,
None = > {
return Err ( KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : format ! (
" This function requires a parameter {}, but you haven't passed it one. " ,
param . identifier . name
) ,
} ) ) ;
}
} ,
} ;
fn_memory . add ( & param . identifier . name , arg_val , ( & param . identifier ) . into ( ) ) ? ;
} else {
let Some ( unlabeled ) = args . unlabeled . take ( ) else {
let param_name = & param . identifier . name ;
return Err ( if args . labeled . contains_key ( param_name ) {
KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : format ! ( " The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:` " ) ,
} )
} else {
KclError ::Semantic ( KclErrorDetails {
source_ranges ,
message : " This function expects an unlabeled first parameter, but you haven't passed it one. "
. to_owned ( ) ,
} )
} ) ;
} ;
fn_memory . add (
& param . identifier . name ,
unlabeled . value . clone ( ) ,
( & param . identifier ) . into ( ) ,
) ? ;
}
}
Ok ( fn_memory )
}
pub ( crate ) async fn call_user_defined_function (
args : Vec < Arg > ,
memory : & ProgramMemory ,
function_expression : NodeRef < '_ , FunctionExpression > ,
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < Option < KclValue > , KclError > {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
let mut body_memory = memory . clone ( ) ;
let body_env = body_memory . new_env_for_call ( memory . current_env ) ;
body_memory . current_env = body_env ;
let fn_memory = assign_args_to_params ( function_expression , args , body_memory ) ? ;
// Execute the function body using the memory we just created.
let ( result , fn_memory ) = {
let previous_memory = std ::mem ::replace ( & mut exec_state . mod_local . memory , fn_memory ) ;
let result = ctx
. exec_program ( & function_expression . body , exec_state , BodyType ::Block )
. await ;
// Restore the previous memory.
let fn_memory = std ::mem ::replace ( & mut exec_state . mod_local . memory , previous_memory ) ;
( result , fn_memory )
} ;
result . map ( | _ | fn_memory . return_ )
}
pub ( crate ) async fn call_user_defined_function_kw (
args : crate ::std ::args ::KwArgs ,
memory : & ProgramMemory ,
function_expression : NodeRef < '_ , FunctionExpression > ,
exec_state : & mut ExecState ,
ctx : & ExecutorContext ,
) -> Result < Option < KclValue > , KclError > {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
let mut body_memory = memory . clone ( ) ;
let body_env = body_memory . new_env_for_call ( memory . current_env ) ;
body_memory . current_env = body_env ;
let fn_memory = assign_args_to_params_kw ( function_expression , args , body_memory ) ? ;
// Execute the function body using the memory we just created.
let ( result , fn_memory ) = {
let previous_memory = std ::mem ::replace ( & mut exec_state . mod_local . memory , fn_memory ) ;
let result = ctx
. exec_program ( & function_expression . body , exec_state , BodyType ::Block )
. await ;
// Restore the previous memory.
let fn_memory = std ::mem ::replace ( & mut exec_state . mod_local . memory , previous_memory ) ;
( result , fn_memory )
} ;
result . map ( | _ | fn_memory . return_ )
}
/// A function being used as a parameter into a stdlib function. This is a
/// closure, plus everything needed to execute it.
pub struct FunctionParam < ' a > {
pub inner : Option < & ' a MemoryFunction > ,
pub memory : ProgramMemory ,
pub fn_expr : crate ::parsing ::ast ::types ::BoxNode < FunctionExpression > ,
pub meta : Vec < Metadata > ,
pub ctx : ExecutorContext ,
}
2025-02-05 19:51:54 -05:00
impl FunctionParam < '_ > {
2025-02-05 17:53:49 +13:00
pub async fn call ( & self , exec_state : & mut ExecState , args : Vec < Arg > ) -> Result < Option < KclValue > , KclError > {
if let Some ( inner ) = self . inner {
inner (
args ,
self . memory . clone ( ) ,
self . fn_expr . clone ( ) ,
self . meta . clone ( ) ,
exec_state ,
self . ctx . clone ( ) ,
)
. await
} else {
call_user_defined_function ( args , & self . memory , self . fn_expr . as_ref ( ) , exec_state , & self . ctx ) . await
}
}
}
impl JsonSchema for FunctionParam < '_ > {
fn schema_name ( ) -> String {
" FunctionParam " . to_owned ( )
}
fn json_schema ( gen : & mut schemars ::gen ::SchemaGenerator ) -> schemars ::schema ::Schema {
// TODO: Actually generate a reasonable schema.
gen . subschema_for ::< ( ) > ( )
}
}
#[ cfg(test) ]
mod test {
use super ::* ;
2025-02-05 15:58:32 -08:00
use crate ::parsing ::ast ::types ::{ DefaultParamVal , Identifier , Parameter } ;
2025-02-05 17:53:49 +13:00
#[ test ]
fn test_assign_args_to_params ( ) {
// Set up a little framework for this test.
fn mem ( number : usize ) -> KclValue {
KclValue ::Int {
value : number as i64 ,
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 ,
}
}
fn additional_program_memory ( items : & [ ( String , KclValue ) ] ) -> ProgramMemory {
let mut program_memory = ProgramMemory ::new ( ) ;
for ( name , item ) in items {
program_memory
. add ( name . as_str ( ) , item . clone ( ) , SourceRange ::default ( ) )
. unwrap ( ) ;
}
program_memory
}
// Declare the test cases.
for ( test_name , params , args , expected ) in [
( " empty " , Vec ::new ( ) , Vec ::new ( ) , Ok ( ProgramMemory ::new ( ) ) ) ,
(
" all params required, and all given, should be OK " ,
vec! [ req_param ( " x " ) ] ,
vec! [ mem ( 1 ) ] ,
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 ,
body : Node {
inner : crate ::parsing ::ast ::types ::Program {
body : Vec ::new ( ) ,
non_code_meta : Default ::default ( ) ,
shebang : None ,
digest : None ,
} ,
start : 0 ,
end : 0 ,
module_id : ModuleId ::default ( ) ,
} ,
return_type : None ,
digest : None ,
} ) ;
let args = args . into_iter ( ) . map ( Arg ::synthetic ) . collect ( ) ;
let actual = assign_args_to_params ( func_expr , args , ProgramMemory ::new ( ) ) ;
assert_eq! (
actual , expected ,
" failed test '{test_name}': \n got {actual:?} \n but expected \n {expected:?} "
) ;
}
}
}