Deduplicate executor code (#2494)
There are many places in the executor codebase which evaluate an AST expression and produce a KCL memory item. They could be deduplicated and put into one central location. Fixes <https://github.com/KittyCAD/modeling-app/issues/1931>.
This commit is contained in:
		@ -16,7 +16,9 @@ pub use crate::ast::types::{literal_value::LiteralValue, none::KclNone};
 | 
			
		||||
use crate::{
 | 
			
		||||
    docs::StdLibFn,
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::{BodyType, ExecutorContext, MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
 | 
			
		||||
    executor::{
 | 
			
		||||
        BodyType, ExecutorContext, MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, StatementKind, UserVal,
 | 
			
		||||
    },
 | 
			
		||||
    parser::PIPE_OPERATOR,
 | 
			
		||||
    std::{kcl_stdlib::KclStdLibFn, FunctionKind},
 | 
			
		||||
};
 | 
			
		||||
@ -1096,45 +1098,12 @@ impl CallExpression {
 | 
			
		||||
        let mut fn_args: Vec<MemoryItem> = Vec::with_capacity(self.arguments.len());
 | 
			
		||||
 | 
			
		||||
        for arg in &self.arguments {
 | 
			
		||||
            let result: MemoryItem = match arg {
 | 
			
		||||
                Value::None(none) => none.into(),
 | 
			
		||||
                Value::Literal(literal) => literal.into(),
 | 
			
		||||
                Value::Identifier(identifier) => {
 | 
			
		||||
                    let value = memory.get(&identifier.name, identifier.into())?;
 | 
			
		||||
                    value.clone()
 | 
			
		||||
                }
 | 
			
		||||
                Value::BinaryExpression(binary_expression) => {
 | 
			
		||||
                    binary_expression.get_result(memory, pipe_info, ctx).await?
 | 
			
		||||
                }
 | 
			
		||||
                Value::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, ctx).await?,
 | 
			
		||||
                Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).await?,
 | 
			
		||||
                Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, ctx).await?,
 | 
			
		||||
                Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, ctx).await?,
 | 
			
		||||
                Value::PipeExpression(pipe_expression) => {
 | 
			
		||||
                    return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                        message: format!("PipeExpression not implemented here: {:?}", pipe_expression),
 | 
			
		||||
                        source_ranges: vec![pipe_expression.into()],
 | 
			
		||||
                    }));
 | 
			
		||||
                }
 | 
			
		||||
                Value::PipeSubstitution(pipe_substitution) => pipe_info
 | 
			
		||||
                    .previous_results
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .ok_or_else(|| {
 | 
			
		||||
                        KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                            message: format!("PipeSubstitution index out of bounds: {:?}", pipe_info),
 | 
			
		||||
                            source_ranges: vec![pipe_substitution.into()],
 | 
			
		||||
                        })
 | 
			
		||||
                    })?
 | 
			
		||||
                    .clone(),
 | 
			
		||||
                Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
 | 
			
		||||
                Value::FunctionExpression(function_expression) => {
 | 
			
		||||
                    return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                        message: format!("FunctionExpression not implemented here: {:?}", function_expression),
 | 
			
		||||
                        source_ranges: vec![function_expression.into()],
 | 
			
		||||
                    }));
 | 
			
		||||
                }
 | 
			
		||||
            let metadata = Metadata {
 | 
			
		||||
                source_range: SourceRange([arg.start(), arg.end()]),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let result = ctx
 | 
			
		||||
                .arg_into_mem_item(arg, memory, pipe_info, &metadata, StatementKind::Expression)
 | 
			
		||||
                .await?;
 | 
			
		||||
            fn_args.push(result);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -2798,7 +2767,7 @@ async fn execute_pipe_body(
 | 
			
		||||
        _ => {
 | 
			
		||||
            // Return an error this should not happen.
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: format!("PipeExpression not implemented here: {:?}", first),
 | 
			
		||||
                message: format!("cannot start a PipeExpression with this value: {:?}", first),
 | 
			
		||||
                source_ranges: vec![first.into()],
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
@ -2819,7 +2788,7 @@ async fn execute_pipe_body(
 | 
			
		||||
            _ => {
 | 
			
		||||
                // Return an error this should not happen.
 | 
			
		||||
                return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                    message: format!("PipeExpression not implemented here: {:?}", expression),
 | 
			
		||||
                    message: format!("This cannot be in a PipeExpression: {:?}", expression),
 | 
			
		||||
                    source_ranges: vec![expression.into()],
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1135,35 +1135,13 @@ impl ExecutorContext {
 | 
			
		||||
                        let fn_name = call_expr.callee.name.to_string();
 | 
			
		||||
                        let mut args: Vec<MemoryItem> = Vec::new();
 | 
			
		||||
                        for arg in &call_expr.arguments {
 | 
			
		||||
                            match arg {
 | 
			
		||||
                                Value::Literal(literal) => args.push(literal.into()),
 | 
			
		||||
                                Value::Identifier(identifier) => {
 | 
			
		||||
                                    let memory_item = memory.get(&identifier.name, identifier.into())?;
 | 
			
		||||
                                    args.push(memory_item.clone());
 | 
			
		||||
                                }
 | 
			
		||||
                                Value::CallExpression(call_expr) => {
 | 
			
		||||
                                    let result = call_expr.execute(memory, &pipe_info, self).await?;
 | 
			
		||||
                                    args.push(result);
 | 
			
		||||
                                }
 | 
			
		||||
                                Value::BinaryExpression(binary_expression) => {
 | 
			
		||||
                                    let result = binary_expression.get_result(memory, &pipe_info, self).await?;
 | 
			
		||||
                                    args.push(result);
 | 
			
		||||
                                }
 | 
			
		||||
                                Value::UnaryExpression(unary_expression) => {
 | 
			
		||||
                                    let result = unary_expression.get_result(memory, &pipe_info, self).await?;
 | 
			
		||||
                                    args.push(result);
 | 
			
		||||
                                }
 | 
			
		||||
                                Value::ObjectExpression(object_expression) => {
 | 
			
		||||
                                    let result = object_expression.execute(memory, &pipe_info, self).await?;
 | 
			
		||||
                                    args.push(result);
 | 
			
		||||
                                }
 | 
			
		||||
                                Value::ArrayExpression(array_expression) => {
 | 
			
		||||
                                    let result = array_expression.execute(memory, &pipe_info, self).await?;
 | 
			
		||||
                                    args.push(result);
 | 
			
		||||
                                }
 | 
			
		||||
                                // We do nothing for the rest.
 | 
			
		||||
                                _ => (),
 | 
			
		||||
                            }
 | 
			
		||||
                            let metadata = Metadata {
 | 
			
		||||
                                source_range: SourceRange([arg.start(), arg.end()]),
 | 
			
		||||
                            };
 | 
			
		||||
                            let mem_item = self
 | 
			
		||||
                                .arg_into_mem_item(arg, memory, &pipe_info, &metadata, StatementKind::Expression)
 | 
			
		||||
                                .await?;
 | 
			
		||||
                            args.push(mem_item);
 | 
			
		||||
                        }
 | 
			
		||||
                        match self.stdlib.get_either(&call_expr.callee.name) {
 | 
			
		||||
                            FunctionKind::Core(func) => {
 | 
			
		||||
@ -1199,88 +1177,16 @@ impl ExecutorContext {
 | 
			
		||||
                        let source_range: SourceRange = declaration.init.clone().into();
 | 
			
		||||
                        let metadata = Metadata { source_range };
 | 
			
		||||
 | 
			
		||||
                        match &declaration.init {
 | 
			
		||||
                            Value::None(none) => {
 | 
			
		||||
                                memory.add(&var_name, none.into(), source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::Literal(literal) => {
 | 
			
		||||
                                memory.add(&var_name, literal.into(), source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::Identifier(identifier) => {
 | 
			
		||||
                                let value = memory.get(&identifier.name, identifier.into())?;
 | 
			
		||||
                                memory.add(&var_name, value.clone(), source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::BinaryExpression(binary_expression) => {
 | 
			
		||||
                                let result = binary_expression.get_result(memory, &pipe_info, self).await?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::FunctionExpression(function_expression) => {
 | 
			
		||||
                                let mem_func = force_memory_function(
 | 
			
		||||
                                    |args: Vec<MemoryItem>,
 | 
			
		||||
                                     memory: ProgramMemory,
 | 
			
		||||
                                     function_expression: Box<FunctionExpression>,
 | 
			
		||||
                                     _metadata: Vec<Metadata>,
 | 
			
		||||
                                     ctx: ExecutorContext| {
 | 
			
		||||
                                        Box::pin(async move {
 | 
			
		||||
                                            let mut fn_memory =
 | 
			
		||||
                                                assign_args_to_params(&function_expression, args, memory.clone())?;
 | 
			
		||||
 | 
			
		||||
                                            let result = ctx
 | 
			
		||||
                                                .inner_execute(
 | 
			
		||||
                                                    function_expression.body.clone(),
 | 
			
		||||
                                                    &mut fn_memory,
 | 
			
		||||
                                                    BodyType::Block,
 | 
			
		||||
                                                )
 | 
			
		||||
                                                .await?;
 | 
			
		||||
 | 
			
		||||
                                            Ok(result.return_)
 | 
			
		||||
                                        })
 | 
			
		||||
                                    },
 | 
			
		||||
                                );
 | 
			
		||||
                                memory.add(
 | 
			
		||||
                                    &var_name,
 | 
			
		||||
                                    MemoryItem::Function {
 | 
			
		||||
                                        expression: function_expression.clone(),
 | 
			
		||||
                                        meta: vec![metadata],
 | 
			
		||||
                                        func: Some(mem_func),
 | 
			
		||||
                                    },
 | 
			
		||||
                                    source_range,
 | 
			
		||||
                                )?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::CallExpression(call_expression) => {
 | 
			
		||||
                                let result = call_expression.execute(memory, &pipe_info, self).await?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::PipeExpression(pipe_expression) => {
 | 
			
		||||
                                let result = pipe_expression.get_result(memory, &pipe_info, self).await?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::PipeSubstitution(pipe_substitution) => {
 | 
			
		||||
                                return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                                    message: format!(
 | 
			
		||||
                                        "pipe substitution not implemented for declaration of variable {}",
 | 
			
		||||
                                        var_name
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    source_ranges: vec![pipe_substitution.into()],
 | 
			
		||||
                                }));
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::ArrayExpression(array_expression) => {
 | 
			
		||||
                                let result = array_expression.execute(memory, &pipe_info, self).await?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::ObjectExpression(object_expression) => {
 | 
			
		||||
                                let result = object_expression.execute(memory, &pipe_info, self).await?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::MemberExpression(member_expression) => {
 | 
			
		||||
                                let result = member_expression.get_result(memory)?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                            Value::UnaryExpression(unary_expression) => {
 | 
			
		||||
                                let result = unary_expression.get_result(memory, &pipe_info, self).await?;
 | 
			
		||||
                                memory.add(&var_name, result, source_range)?;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        let memory_item = self
 | 
			
		||||
                            .arg_into_mem_item(
 | 
			
		||||
                                &declaration.init,
 | 
			
		||||
                                memory,
 | 
			
		||||
                                &pipe_info,
 | 
			
		||||
                                &metadata,
 | 
			
		||||
                                StatementKind::Declaration { name: &var_name },
 | 
			
		||||
                            )
 | 
			
		||||
                            .await?;
 | 
			
		||||
                        memory.add(&var_name, memory_item, source_range)?;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
 | 
			
		||||
@ -1336,6 +1242,77 @@ impl ExecutorContext {
 | 
			
		||||
        Ok(memory.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn arg_into_mem_item<'a>(
 | 
			
		||||
        &self,
 | 
			
		||||
        init: &Value,
 | 
			
		||||
        memory: &mut ProgramMemory,
 | 
			
		||||
        pipe_info: &PipeInfo,
 | 
			
		||||
        metadata: &Metadata,
 | 
			
		||||
        statement_kind: StatementKind<'a>,
 | 
			
		||||
    ) -> Result<MemoryItem, KclError> {
 | 
			
		||||
        let item = match init {
 | 
			
		||||
            Value::None(none) => none.into(),
 | 
			
		||||
            Value::Literal(literal) => literal.into(),
 | 
			
		||||
            Value::Identifier(identifier) => {
 | 
			
		||||
                let value = memory.get(&identifier.name, identifier.into())?;
 | 
			
		||||
                value.clone()
 | 
			
		||||
            }
 | 
			
		||||
            Value::BinaryExpression(binary_expression) => binary_expression.get_result(memory, pipe_info, self).await?,
 | 
			
		||||
            Value::FunctionExpression(function_expression) => {
 | 
			
		||||
                let mem_func = force_memory_function(
 | 
			
		||||
                    |args: Vec<MemoryItem>,
 | 
			
		||||
                     memory: ProgramMemory,
 | 
			
		||||
                     function_expression: Box<FunctionExpression>,
 | 
			
		||||
                     _metadata: Vec<Metadata>,
 | 
			
		||||
                     ctx: ExecutorContext| {
 | 
			
		||||
                        Box::pin(async move {
 | 
			
		||||
                            let mut fn_memory = assign_args_to_params(&function_expression, args, memory.clone())?;
 | 
			
		||||
 | 
			
		||||
                            let result = ctx
 | 
			
		||||
                                .inner_execute(function_expression.body.clone(), &mut fn_memory, BodyType::Block)
 | 
			
		||||
                                .await?;
 | 
			
		||||
 | 
			
		||||
                            Ok(result.return_)
 | 
			
		||||
                        })
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                MemoryItem::Function {
 | 
			
		||||
                    expression: function_expression.clone(),
 | 
			
		||||
                    meta: vec![metadata.to_owned()],
 | 
			
		||||
                    func: Some(mem_func),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Value::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, self).await?,
 | 
			
		||||
            Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, self).await?,
 | 
			
		||||
            Value::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 pipe_info.previous_results.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()],
 | 
			
		||||
                        }));
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, self).await?,
 | 
			
		||||
            Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, self).await?,
 | 
			
		||||
            Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
 | 
			
		||||
            Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, self).await?,
 | 
			
		||||
        };
 | 
			
		||||
        Ok(item)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update the units for the executor.
 | 
			
		||||
    pub fn update_units(&mut self, units: crate::settings::types::UnitLength) {
 | 
			
		||||
        self.settings.units = units;
 | 
			
		||||
@ -1397,6 +1374,11 @@ fn assign_args_to_params(
 | 
			
		||||
    Ok(fn_memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum StatementKind<'a> {
 | 
			
		||||
    Declaration { name: &'a str },
 | 
			
		||||
    Expression,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								src/wasm-lib/tests/executor/inputs/pipe_as_arg.kcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/wasm-lib/tests/executor/inputs/pipe_as_arg.kcl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
fn cube = (length, center) => {
 | 
			
		||||
  let l = length/2
 | 
			
		||||
  let x = center[0]
 | 
			
		||||
  let y = center[1]
 | 
			
		||||
  let p0 = [-l + x, -l + y]
 | 
			
		||||
  let p1 = [-l + x,  l + y]
 | 
			
		||||
  let p2 = [ l + x,  l + y]
 | 
			
		||||
  let p3 = [ l + x, -l + y]
 | 
			
		||||
 | 
			
		||||
  return startSketchAt(p0)
 | 
			
		||||
  |> lineTo(p1, %)
 | 
			
		||||
  |> lineTo(p2, %)
 | 
			
		||||
  |> lineTo(p3, %)
 | 
			
		||||
  |> lineTo(p0, %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(length, %)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn double = (x) => { return x * 2}
 | 
			
		||||
fn width = () => { return 200 }
 | 
			
		||||
 | 
			
		||||
const myCube = cube(width() |> double(%), [0,0])
 | 
			
		||||
@ -128,6 +128,15 @@ async fn serial_test_lego() {
 | 
			
		||||
    twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
async fn serial_test_pipe_as_arg() {
 | 
			
		||||
    let code = include_str!("inputs/pipe_as_arg.kcl");
 | 
			
		||||
    let result = execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm)
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    twenty_twenty::assert_image("tests/executor/outputs/pipe_as_arg.png", &result, 0.999);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
async fn serial_test_pentagon_fillet_sugar() {
 | 
			
		||||
    let code = include_str!("inputs/pentagon_fillet_sugar.kcl");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/wasm-lib/tests/executor/outputs/pipe_as_arg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/wasm-lib/tests/executor/outputs/pipe_as_arg.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 170 KiB  | 
		Reference in New Issue
	
	Block a user