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:
Jonathan Tran
2024-12-16 13:10:31 -05:00
committed by GitHub
parent efe8089b08
commit da301ba862
110 changed files with 6638 additions and 57 deletions

View File

@ -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)
}
}