Add tracking of operations for the feature tree (#4746)
* Add operations tracking for the timeline * Change to only track certain stdlib functions as operations * Update gen files * Add operations to simulation snapshot tests * Add tracking of positional function calls * Fix generated field names to be camel case in TS * Fix generated TS field names to match and better docs * Fix order of ops with patternTransform * Fix sweep to be included * Add new expected test outputs * Add tracking for startSketchOn * Update ops output to include startSketchOn * Fix serde field name * Fix output field name * Add tracking of operations that fail * Add snapshots of operations even when there's a KCL execution error * Add ops output for error executions * Add operations output to executor error * Update op source ranges * Remove tracking of circle() and polygon() since they're not needed * Update output without circle and polygon * Fix to track patternCircular3d and patternLinear3d * Remove tracking for mirror2d * Update ops output * Fix to track the correct source range of function definitions --------- Co-authored-by: Frank Noirot <frank@zoo.dev>
This commit is contained in:
@ -19,6 +19,8 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::cad_op::{OpArg, Operation};
|
||||
|
||||
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
|
||||
|
||||
impl BinaryPart {
|
||||
@ -385,10 +387,45 @@ impl Node<CallExpressionKw> {
|
||||
);
|
||||
match ctx.stdlib.get_either(fn_name) {
|
||||
FunctionKind::Core(func) => {
|
||||
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
|
||||
};
|
||||
|
||||
// Attempt to call the function.
|
||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||
Ok(result)
|
||||
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.
|
||||
exec_state.operations.push(op);
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
let mut return_value = result?;
|
||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
||||
|
||||
Ok(return_value)
|
||||
}
|
||||
FunctionKind::UserDefined => {
|
||||
let source_range = SourceRange::from(self);
|
||||
@ -397,6 +434,21 @@ impl Node<CallExpressionKw> {
|
||||
let func = exec_state.memory.get(fn_name, source_range)?.clone();
|
||||
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
|
||||
|
||||
// Track call operation.
|
||||
let op_labeled_args = args
|
||||
.kw_args
|
||||
.labeled
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), OpArg::new(v.source_range)))
|
||||
.collect();
|
||||
exec_state.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,
|
||||
});
|
||||
|
||||
let return_value = {
|
||||
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||
let result = func
|
||||
@ -423,6 +475,9 @@ impl Node<CallExpressionKw> {
|
||||
})
|
||||
})?;
|
||||
|
||||
// Track return operation.
|
||||
exec_state.operations.push(Operation::UserDefinedFunctionReturn);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@ -433,6 +488,7 @@ impl Node<CallExpression> {
|
||||
#[async_recursion]
|
||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
let fn_name = &self.callee.name;
|
||||
let callsite = SourceRange::from(self);
|
||||
|
||||
let mut fn_args: Vec<Arg> = Vec::with_capacity(self.arguments.len());
|
||||
|
||||
@ -446,14 +502,50 @@ impl Node<CallExpression> {
|
||||
let arg = Arg::new(value, SourceRange::from(arg_expr));
|
||||
fn_args.push(arg);
|
||||
}
|
||||
let fn_args = fn_args; // remove mutability
|
||||
|
||||
match ctx.stdlib.get_either(fn_name) {
|
||||
FunctionKind::Core(func) => {
|
||||
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
|
||||
};
|
||||
|
||||
// Attempt to call the function.
|
||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||
Ok(result)
|
||||
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.
|
||||
exec_state.operations.push(op);
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
let mut return_value = result?;
|
||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
||||
|
||||
Ok(return_value)
|
||||
}
|
||||
FunctionKind::UserDefined => {
|
||||
let source_range = SourceRange::from(self);
|
||||
@ -462,6 +554,16 @@ impl Node<CallExpression> {
|
||||
let func = exec_state.memory.get(fn_name, source_range)?.clone();
|
||||
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
|
||||
|
||||
// Track call operation.
|
||||
exec_state.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,
|
||||
});
|
||||
|
||||
let return_value = {
|
||||
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
|
||||
@ -485,6 +587,9 @@ impl Node<CallExpression> {
|
||||
})
|
||||
})?;
|
||||
|
||||
// Track return operation.
|
||||
exec_state.operations.push(Operation::UserDefinedFunctionReturn);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user