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:
Adam Chalmers
2024-05-23 14:50:22 -05:00
committed by GitHub
parent 8e9286a747
commit 51868f892b
5 changed files with 134 additions and 152 deletions

View File

@ -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()],
}));
}

View File

@ -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;

View 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])

View File

@ -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");

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB