Remove grackle (#2566)

This commit is contained in:
Adam Chalmers
2024-06-04 16:28:32 -05:00
committed by GitHub
parent 29cdc66b34
commit f73556ba7b
17 changed files with 35 additions and 5657 deletions

1520
src/wasm-lib/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -56,17 +56,12 @@ debug = true
[workspace]
members = [
"derive-docs",
"grackle",
"kcl",
"kcl-macros",
]
[workspace.dependencies]
kittycad = { version = "0.3.3", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = "0.1.6"
kittycad-execution-plan-macros = "0.1.9"
kittycad-execution-plan-traits = "0.1.14"
kittycad-modeling-cmds = "0.2.24"
kittycad-modeling-session = "0.1.4"
[[test]]
@ -79,8 +74,5 @@ path = "tests/modify/main.rs"
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
#[patch."https://github.com/KittyCAD/modeling-api"]
#kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
#kittycad-execution-plan-macros = { path = "../../../modeling-api/execution-plan-macros" }
#kittycad-execution-plan-traits = { path = "../../../modeling-api/execution-plan-traits" }
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1,270 +0,0 @@
use std::collections::HashMap;
use kcl_lib::ast::types::{LiteralIdentifier, LiteralValue};
use kittycad_execution_plan::constants;
use kittycad_execution_plan_traits::Primitive;
use super::{native_functions, Address};
use crate::{CompileError, KclFunction};
/// KCL values which can be written to KCEP memory.
/// This is recursive. For example, the bound value might be an array, which itself contains bound values.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub enum EpBinding {
/// A KCL value which gets stored in a particular address in KCEP memory.
Single(Address),
/// A sequence of KCL values, indexed by their position in the sequence.
Sequence {
length_at: Address,
elements: Vec<EpBinding>,
},
/// A sequence of KCL values, indexed by their identifier.
Map {
length_at: Address,
properties: HashMap<String, EpBinding>,
},
/// Not associated with a KCEP address.
Constant(Primitive),
/// Not associated with a KCEP address.
Function(KclFunction),
/// SketchGroups have their own storage.
SketchGroup { index: usize },
}
impl From<KclFunction> for EpBinding {
fn from(f: KclFunction) -> Self {
Self::Function(f)
}
}
impl EpBinding {
/// Look up the given property of this binding.
pub fn property_of(&self, property: LiteralIdentifier) -> Result<&Self, CompileError> {
match property {
LiteralIdentifier::Identifier(_) => todo!("Support identifier properties"),
LiteralIdentifier::Literal(litval) => match litval.value {
// Arrays can be indexed by integers.
LiteralValue::IInteger(i) => match self {
EpBinding::Sequence { elements, length_at: _ } => {
let i = usize::try_from(i).map_err(|_| CompileError::InvalidIndex(i.to_string()))?;
elements
.get(i)
.ok_or(CompileError::IndexOutOfBounds { i, len: elements.len() })
}
EpBinding::Map { .. } => Err(CompileError::CannotIndex),
EpBinding::SketchGroup { .. } => Err(CompileError::CannotIndex),
EpBinding::Single(_) => Err(CompileError::CannotIndex),
EpBinding::Function(_) => Err(CompileError::CannotIndex),
EpBinding::Constant(_) => Err(CompileError::CannotIndex),
},
// Objects can be indexed by string properties.
LiteralValue::String(property) => match self {
EpBinding::Single(_) => Err(CompileError::NoProperties),
EpBinding::Function(_) => Err(CompileError::NoProperties),
EpBinding::Constant(_) => Err(CompileError::CannotIndex),
EpBinding::SketchGroup { .. } => Err(CompileError::NoProperties),
EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties),
EpBinding::Map {
properties,
length_at: _,
} => properties
.get(&property)
.ok_or(CompileError::UndefinedProperty { property }),
},
// It's never valid to index by a fractional number.
LiteralValue::Fractional(num) => Err(CompileError::InvalidIndex(num.to_string())),
LiteralValue::Bool(b) => Err(CompileError::InvalidIndex(b.to_string())),
},
}
}
}
/// A set of bindings in a particular scope.
/// Bindings are KCL values that get "compiled" into KCEP values, which are stored in KCEP memory
/// at a particular KCEP address.
/// Bindings are referenced by the name of their KCL identifier.
///
/// KCL has multiple scopes -- each function has a scope for its own local variables and parameters.
/// So when referencing a variable, it might be in this scope, or the parent scope. So, each environment
/// has to keep track of parent environments. The root environment has no parent, and is used for KCL globals
/// (e.g. the prelude of stdlib functions).
///
/// These are called "Environments" in the "Crafting Interpreters" book.
#[derive(Debug)]
pub struct BindingScope {
// KCL value which are stored in EP memory.
ep_bindings: HashMap<String, EpBinding>,
/// KCL functions. They do NOT get stored in EP memory.
parent: Option<Box<BindingScope>>,
}
impl BindingScope {
/// The parent scope for every program, before the user has defined anything.
/// Only includes some stdlib functions.
/// This is usually known as the "prelude" in other languages. It's the stdlib functions that
/// are already imported for you when you start coding.
pub fn prelude() -> Self {
Self {
// TODO: Actually put the stdlib prelude in here,
// things like `startSketchAt` and `line`.
ep_bindings: HashMap::from([
("E".into(), EpBinding::Constant(constants::E)),
("PI".into(), EpBinding::Constant(constants::PI)),
("id".into(), EpBinding::from(KclFunction::Id(native_functions::Id))),
("abs".into(), EpBinding::from(KclFunction::Abs(native_functions::Abs))),
(
"acos".into(),
EpBinding::from(KclFunction::Acos(native_functions::Acos)),
),
(
"asin".into(),
EpBinding::from(KclFunction::Asin(native_functions::Asin)),
),
(
"atan".into(),
EpBinding::from(KclFunction::Atan(native_functions::Atan)),
),
(
"ceil".into(),
EpBinding::from(KclFunction::Ceil(native_functions::Ceil)),
),
("cos".into(), EpBinding::from(KclFunction::Cos(native_functions::Cos))),
(
"floor".into(),
EpBinding::from(KclFunction::Floor(native_functions::Floor)),
),
("ln".into(), EpBinding::from(KclFunction::Ln(native_functions::Ln))),
(
"log10".into(),
EpBinding::from(KclFunction::Log10(native_functions::Log10)),
),
(
"log2".into(),
EpBinding::from(KclFunction::Log2(native_functions::Log2)),
),
("sin".into(), EpBinding::from(KclFunction::Sin(native_functions::Sin))),
(
"sqrt".into(),
EpBinding::from(KclFunction::Sqrt(native_functions::Sqrt)),
),
("tan".into(), EpBinding::from(KclFunction::Tan(native_functions::Tan))),
(
"toDegrees".into(),
EpBinding::from(KclFunction::ToDegrees(native_functions::ToDegrees)),
),
(
"toRadians".into(),
EpBinding::from(KclFunction::ToRadians(native_functions::ToRadians)),
),
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
("log".into(), EpBinding::from(KclFunction::Log(native_functions::Log))),
("max".into(), EpBinding::from(KclFunction::Max(native_functions::Max))),
("min".into(), EpBinding::from(KclFunction::Min(native_functions::Min))),
(
"startSketchAt".into(),
EpBinding::from(KclFunction::StartSketchAt(native_functions::sketch::StartSketchAt)),
),
(
"lineTo".into(),
EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)),
),
(
"line".into(),
EpBinding::from(KclFunction::Line(native_functions::sketch::Line)),
),
(
"xLineTo".into(),
EpBinding::from(KclFunction::XLineTo(native_functions::sketch::XLineTo)),
),
(
"xLine".into(),
EpBinding::from(KclFunction::XLine(native_functions::sketch::XLine)),
),
(
"yLineTo".into(),
EpBinding::from(KclFunction::YLineTo(native_functions::sketch::YLineTo)),
),
(
"yLine".into(),
EpBinding::from(KclFunction::YLine(native_functions::sketch::YLine)),
),
(
"tangentialArcTo".into(),
EpBinding::from(KclFunction::TangentialArcTo(native_functions::sketch::TangentialArcTo)),
),
(
"extrude".into(),
EpBinding::from(KclFunction::Extrude(native_functions::sketch::Extrude)),
),
(
"close".into(),
EpBinding::from(KclFunction::Close(native_functions::sketch::Close)),
),
]),
parent: None,
}
}
/// Add a new scope, e.g. for new function calls.
pub fn add_scope(&mut self) {
// Move all data from `self` into `this`.
let this_parent = self.parent.take();
let this_ep_bindings = self.ep_bindings.drain().collect();
let this = Self {
ep_bindings: this_ep_bindings,
parent: this_parent,
};
// Turn `self` into a new scope, with the old `self` as its parent.
self.parent = Some(Box::new(this));
}
//// Remove a scope, e.g. when exiting a function call.
pub fn remove_scope(&mut self) {
// The scope is finished, so erase all its local variables.
self.ep_bindings.clear();
// Pop the stack -- the parent scope is now the current scope.
let p = self.parent.take().expect("cannot remove the root scope");
self.parent = p.parent;
self.ep_bindings = p.ep_bindings;
}
/// Add a binding (e.g. defining a new variable)
pub fn bind(&mut self, identifier: String, binding: EpBinding) {
self.ep_bindings.insert(identifier, binding);
}
/// Look up a binding.
pub fn get(&self, identifier: &str) -> Option<&EpBinding> {
if let Some(b) = self.ep_bindings.get(identifier) {
// The name was found in this scope.
Some(b)
} else if let Some(ref parent) = self.parent {
// Check the next scope outwards.
parent.get(identifier)
} else {
// There's no outer scope, and it wasn't found, so there's nowhere else to look.
None
}
}
/// Look up a function bound to the given identifier.
pub fn get_fn(&self, identifier: &str) -> GetFnResult {
if let Some(x) = self.get(identifier) {
match x {
EpBinding::Function(f) => GetFnResult::Found(f),
_ => GetFnResult::NonCallable,
}
} else if let Some(ref parent) = self.parent {
parent.get_fn(identifier)
} else {
GetFnResult::NotFound
}
}
}
pub enum GetFnResult<'a> {
Found(&'a KclFunction),
NonCallable,
NotFound,
}

View File

@ -1,84 +0,0 @@
use kcl_lib::ast::types::RequiredParamAfterOptionalParam;
use kittycad_execution_plan::{ExecutionError, ExecutionFailed, Instruction};
use crate::String2;
#[derive(Debug, thiserror::Error, PartialEq, Clone)]
pub enum CompileError {
#[error("the name {name} was not defined")]
Undefined { name: String },
#[error("the function {fn_name} requires at least {required} arguments but you only supplied {actual}")]
NotEnoughArgs {
fn_name: String2,
required: usize,
actual: usize,
},
#[error("the function {fn_name} accepts at most {maximum} arguments but you supplied {actual}")]
TooManyArgs {
fn_name: String2,
maximum: usize,
actual: usize,
},
#[error("you tried to call {name} but it's not a function")]
NotCallable { name: String },
#[error("you're trying to use an operand that isn't compatible with the given arithmetic operator: {0}")]
InvalidOperand(&'static str),
#[error("you cannot use the value {0} as an index")]
InvalidIndex(String),
#[error("you tried to index into a value that isn't an array. Only arrays have numeric indices!")]
CannotIndex,
#[error("you tried to get the element {i} but that index is out of bounds. The array only has a length of {len}")]
IndexOutOfBounds { i: usize, len: usize },
#[error("you tried to access the property of a value that doesn't have any properties")]
NoProperties,
#[error("you tried to access a property of an array, but arrays don't have properties. They do have numeric indexes though, try using an index e.g. [0]")]
ArrayDoesNotHaveProperties,
#[error(
"you tried to read the '.{property}' of an object, but the object doesn't have any properties with that key"
)]
UndefinedProperty { property: String },
#[error("{0}")]
BadParamOrder(RequiredParamAfterOptionalParam),
#[error("A KCL function cannot have anything after its return value")]
MultipleReturns,
#[error("A KCL function must end with a return statement, but your function doesn't have one.")]
NoReturnStmt,
#[error("You used the %, which means \"substitute this argument for the value to the left in this |> pipeline\". But there is no such value, because you're not calling a pipeline.")]
NotInPipeline,
#[error("The function '{fn_name}' expects a parameter of type {expected} as argument number {arg_number} but you supplied {actual}")]
ArgWrongType {
fn_name: &'static str,
expected: &'static str,
actual: String,
arg_number: usize,
},
}
#[derive(Debug, thiserror::Error)]
#[allow(clippy::large_enum_variant)]
pub enum Error {
#[error("{0}")]
Compile(#[from] CompileError),
#[error("Failed on instruction {instruction_index}:\n{error}\n\nInstruction contents were {instruction:#?}")]
Execution {
error: ExecutionError,
instruction: Instruction,
instruction_index: usize,
},
}
impl From<ExecutionFailed> for Error {
fn from(
ExecutionFailed {
error,
instruction,
instruction_index,
}: ExecutionFailed,
) -> Self {
Self::Execution {
error,
instruction: instruction.expect("no instruction"),
instruction_index,
}
}
}

View File

@ -1,14 +0,0 @@
use kcl_lib::ast::{self, types::BinaryPart};
pub type SingleValue = kcl_lib::ast::types::Value;
pub fn into_single_value(value: ast::types::BinaryPart) -> SingleValue {
match value {
BinaryPart::Literal(e) => SingleValue::Literal(e),
BinaryPart::Identifier(e) => SingleValue::Identifier(e),
BinaryPart::BinaryExpression(e) => SingleValue::BinaryExpression(e),
BinaryPart::CallExpression(e) => SingleValue::CallExpression(e),
BinaryPart::UnaryExpression(e) => SingleValue::UnaryExpression(e),
BinaryPart::MemberExpression(e) => SingleValue::MemberExpression(e),
}
}

View File

@ -1,729 +0,0 @@
mod binding_scope;
mod error;
mod kcl_value_group;
mod native_functions;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use kcl_lib::{
ast,
ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program},
};
use kcl_value_group::into_single_value;
use kittycad_execution_plan::{
self as ep, instruction::SourceRange as KcvmSourceRange, Destination, Instruction, InstructionKind,
};
use kittycad_execution_plan_traits as ept;
use kittycad_execution_plan_traits::{Address, NumericPrimitive};
use kittycad_modeling_session::Session;
use crate::{
binding_scope::{BindingScope, EpBinding, GetFnResult},
error::{CompileError, Error},
kcl_value_group::SingleValue,
};
/// Execute a KCL program by compiling into an execution plan, then running that.
pub async fn execute(ast: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
let mut planner = Planner::new();
let (plan, _retval) = planner.build_plan(ast)?;
let mut mem = ep::Memory::default();
ep::execute(&mut mem, plan, session).await?;
Ok(mem)
}
/// Compiles KCL programs into Execution Plans.
#[derive(Debug)]
struct Planner {
/// Maps KCL identifiers to what they hold, and where in KCEP virtual memory they'll be written to.
binding_scope: BindingScope,
/// Next available KCVM virtual machine memory address.
next_addr: Address,
/// Next available KCVM sketch group index.
next_sketch_group: usize,
}
impl Planner {
pub fn new() -> Self {
Self {
binding_scope: BindingScope::prelude(),
next_addr: Address::ZERO,
next_sketch_group: 0,
}
}
/// If successful, return the KCEP instructions for executing the given program.
/// If the program is a function with a return, then it also returns the KCL function's return value.
fn build_plan(&mut self, program: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> {
program
.body
.into_iter()
.try_fold((Vec::new(), None), |(mut instructions, mut retval), item| {
if retval.is_some() {
return Err(CompileError::MultipleReturns);
}
let mut ctx = Context::default();
let instructions_for_this_node = match item {
BodyItem::ExpressionStatement(node) => {
self.plan_to_compute_single(&mut ctx, SingleValue::from(node.expression))?
.instructions
}
BodyItem::VariableDeclaration(node) => self.plan_to_bind(node)?,
BodyItem::ReturnStatement(node) => {
let EvalPlan { instructions, binding } =
self.plan_to_compute_single(&mut ctx, SingleValue::from(node.argument))?;
retval = Some(binding);
instructions
}
};
instructions.extend(instructions_for_this_node);
Ok((instructions, retval))
})
}
/// Emits instructions which, when run, compute a given KCL value and store it in memory.
/// Returns the instructions, and the destination address of the value.
fn plan_to_compute_single(&mut self, ctx: &mut Context, value: SingleValue) -> Result<EvalPlan, CompileError> {
match value {
SingleValue::None(KclNone { start, end }) => {
let address = self.next_addr.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::from_range(
InstructionKind::SetPrimitive {
address,
value: ept::Primitive::Nil,
},
KcvmSourceRange([start, end]),
)],
binding: EpBinding::Single(address),
})
}
SingleValue::FunctionExpression(expr) => {
let FunctionExpressionParts {
start: _,
end: _,
params_required,
params_optional,
body,
} = expr.into_parts().map_err(CompileError::BadParamOrder)?;
Ok(EvalPlan {
instructions: Vec::new(),
binding: EpBinding::from(KclFunction::UserDefined(UserDefinedFunction {
params_optional,
params_required,
body,
})),
})
}
SingleValue::Literal(expr) => {
let kcep_val = kcl_literal_to_kcep_literal(expr.value);
// KCEP primitives always have size of 1, because each address holds 1 primitive.
let size = 1;
let address = self.next_addr.offset_by(size);
Ok(EvalPlan {
instructions: vec![Instruction::from_range(
InstructionKind::SetPrimitive {
address,
value: kcep_val,
},
KcvmSourceRange([expr.start, expr.end]),
)],
binding: EpBinding::Single(address),
})
}
SingleValue::Identifier(expr) => {
// The KCL parser interprets bools as identifiers.
// Consider changing them to be KCL literals instead.
let b = if expr.name == "true" {
Some(true)
} else if expr.name == "false" {
Some(false)
} else {
None
};
if let Some(b) = b {
let address = self.next_addr.offset_by(1);
return Ok(EvalPlan {
instructions: vec![Instruction::from_range(
InstructionKind::SetPrimitive {
address,
value: ept::Primitive::Bool(b),
},
KcvmSourceRange([expr.start, expr.end]),
)],
binding: EpBinding::Single(address),
});
}
// This identifier is just duplicating a binding.
// So, don't emit any instructions, because the value has already been computed.
// Just return the address that it was stored at after being computed.
let previously_bound_to = self
.binding_scope
.get(&expr.name)
.ok_or(CompileError::Undefined { name: expr.name })?;
Ok(EvalPlan {
instructions: Vec::new(),
binding: previously_bound_to.clone(),
})
}
SingleValue::UnaryExpression(expr) => {
let operand = self.plan_to_compute_single(ctx, into_single_value(expr.argument))?;
let EpBinding::Single(binding) = operand.binding else {
return Err(CompileError::InvalidOperand(
"you tried to use a composite value (e.g. array or object) as the operand to some math",
));
};
let destination = self.next_addr.offset_by(1);
let mut plan = operand.instructions;
plan.push(Instruction::from_range(
InstructionKind::UnaryArithmetic {
arithmetic: ep::UnaryArithmetic {
operation: match expr.operator {
ast::types::UnaryOperator::Neg => ep::UnaryOperation::Neg,
ast::types::UnaryOperator::Not => ep::UnaryOperation::Not,
},
operand: ep::Operand::Reference(binding),
},
destination: Destination::Address(destination),
},
KcvmSourceRange([expr.start, expr.end]),
));
Ok(EvalPlan {
instructions: plan,
binding: EpBinding::Single(destination),
})
}
SingleValue::BinaryExpression(expr) => {
let l = self.plan_to_compute_single(ctx, into_single_value(expr.left))?;
let r = self.plan_to_compute_single(ctx, into_single_value(expr.right))?;
let EpBinding::Single(l_binding) = l.binding else {
return Err(CompileError::InvalidOperand(
"you tried to use a composite value (e.g. array or object) as the operand to some math",
));
};
let EpBinding::Single(r_binding) = r.binding else {
return Err(CompileError::InvalidOperand(
"you tried to use a composite value (e.g. array or object) as the operand to some math",
));
};
let destination = self.next_addr.offset_by(1);
let mut plan = Vec::with_capacity(l.instructions.len() + r.instructions.len() + 1);
plan.extend(l.instructions);
plan.extend(r.instructions);
plan.push(Instruction::from_range(
InstructionKind::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: match expr.operator {
ast::types::BinaryOperator::Add => ep::BinaryOperation::Add,
ast::types::BinaryOperator::Sub => ep::BinaryOperation::Sub,
ast::types::BinaryOperator::Mul => ep::BinaryOperation::Mul,
ast::types::BinaryOperator::Div => ep::BinaryOperation::Div,
ast::types::BinaryOperator::Mod => ep::BinaryOperation::Mod,
ast::types::BinaryOperator::Pow => ep::BinaryOperation::Pow,
},
operand0: ep::Operand::Reference(l_binding),
operand1: ep::Operand::Reference(r_binding),
},
destination: Destination::Address(destination),
},
KcvmSourceRange([expr.start, expr.end]),
));
Ok(EvalPlan {
instructions: plan,
binding: EpBinding::Single(destination),
})
}
SingleValue::CallExpression(expr) => {
// Make a plan to compute all the arguments to this call.
let (mut instructions, args) = expr.arguments.into_iter().try_fold(
(Vec::new(), Vec::new()),
|(mut acc_instrs, mut acc_args), argument| {
let EvalPlan {
instructions: new_instructions,
binding: arg,
} = self.plan_to_compute_single(ctx, SingleValue::from(argument))?;
acc_instrs.extend(new_instructions);
acc_args.push(arg);
Ok((acc_instrs, acc_args))
},
)?;
// Look up the function being called.
let callee = match self.binding_scope.get_fn(&expr.callee.name) {
GetFnResult::Found(f) => f,
GetFnResult::NonCallable => {
return Err(CompileError::NotCallable {
name: expr.callee.name.clone(),
});
}
GetFnResult::NotFound => {
return Err(CompileError::Undefined {
name: expr.callee.name.clone(),
})
}
};
// Emit instructions to call that function with the given arguments.
use native_functions::Callable;
let mut ctx = native_functions::Context {
next_address: &mut self.next_addr,
next_sketch_group: &mut self.next_sketch_group,
};
let EvalPlan {
instructions: eval_instrs,
binding,
} = match callee {
KclFunction::Id(f) => f.call(&mut ctx, args)?,
KclFunction::Abs(f) => f.call(&mut ctx, args)?,
KclFunction::Acos(f) => f.call(&mut ctx, args)?,
KclFunction::Asin(f) => f.call(&mut ctx, args)?,
KclFunction::Atan(f) => f.call(&mut ctx, args)?,
KclFunction::Ceil(f) => f.call(&mut ctx, args)?,
KclFunction::Cos(f) => f.call(&mut ctx, args)?,
KclFunction::Floor(f) => f.call(&mut ctx, args)?,
KclFunction::Ln(f) => f.call(&mut ctx, args)?,
KclFunction::Log10(f) => f.call(&mut ctx, args)?,
KclFunction::Log2(f) => f.call(&mut ctx, args)?,
KclFunction::Sin(f) => f.call(&mut ctx, args)?,
KclFunction::Sqrt(f) => f.call(&mut ctx, args)?,
KclFunction::Tan(f) => f.call(&mut ctx, args)?,
KclFunction::ToDegrees(f) => f.call(&mut ctx, args)?,
KclFunction::ToRadians(f) => f.call(&mut ctx, args)?,
KclFunction::Log(f) => f.call(&mut ctx, args)?,
KclFunction::Max(f) => f.call(&mut ctx, args)?,
KclFunction::Min(f) => f.call(&mut ctx, args)?,
KclFunction::StartSketchAt(f) => f.call(&mut ctx, args)?,
KclFunction::Extrude(f) => f.call(&mut ctx, args)?,
KclFunction::LineTo(f) => f.call(&mut ctx, args)?,
KclFunction::Line(f) => f.call(&mut ctx, args)?,
KclFunction::XLineTo(f) => f.call(&mut ctx, args)?,
KclFunction::XLine(f) => f.call(&mut ctx, args)?,
KclFunction::YLineTo(f) => f.call(&mut ctx, args)?,
KclFunction::YLine(f) => f.call(&mut ctx, args)?,
KclFunction::TangentialArcTo(f) => f.call(&mut ctx, args)?,
KclFunction::Add(f) => f.call(&mut ctx, args)?,
KclFunction::Close(f) => f.call(&mut ctx, args)?,
KclFunction::UserDefined(f) => {
let UserDefinedFunction {
params_optional,
params_required,
body: function_body,
} = f.clone();
let num_required_params = params_required.len();
self.binding_scope.add_scope();
// Bind the call's arguments to the names of the function's parameters.
let num_actual_params = args.len();
let mut arg_iter = args.into_iter();
let max_params = params_required.len() + params_optional.len();
if num_actual_params > max_params {
return Err(CompileError::TooManyArgs {
fn_name: "".into(),
maximum: max_params,
actual: num_actual_params,
});
}
// Bind required parameters
for param in params_required {
let arg = arg_iter.next().ok_or(CompileError::NotEnoughArgs {
fn_name: "".into(),
required: num_required_params,
actual: num_actual_params,
})?;
self.binding_scope.bind(param.identifier.name, arg);
}
// Bind optional parameters
for param in params_optional {
let Some(arg) = arg_iter.next() else {
break;
};
self.binding_scope.bind(param.identifier.name, arg);
}
let (instructions, retval) = self.build_plan(function_body)?;
let Some(retval) = retval else {
return Err(CompileError::NoReturnStmt);
};
self.binding_scope.remove_scope();
EvalPlan {
instructions,
binding: retval,
}
}
};
// Combine the "evaluate arguments" plan with the "call function" plan.
instructions.extend(eval_instrs);
Ok(EvalPlan { instructions, binding })
}
SingleValue::MemberExpression(mut expr) => {
let source_range = KcvmSourceRange([expr.start, expr.end]);
let parse = move || {
let mut stack = Vec::new();
loop {
stack.push((expr.property, expr.computed));
match expr.object {
ast::types::MemberObject::MemberExpression(subexpr) => {
expr = subexpr;
}
ast::types::MemberObject::Identifier(id) => return (stack, id),
}
}
};
let (mut properties, id) = parse();
let name = id.name;
let mut binding = self.binding_scope.get(&name).ok_or(CompileError::Undefined { name })?;
if properties.iter().any(|(_property, computed)| *computed) {
// There's a computed property, so the property/index can only be determined at runtime.
let mut instructions: Vec<Instruction> = Vec::new();
let starting_address = match binding {
EpBinding::Sequence { length_at, elements: _ } => *length_at,
EpBinding::Map {
length_at,
properties: _,
} => *length_at,
_ => return Err(CompileError::CannotIndex),
};
let mut structure_start = ep::Operand::Literal(starting_address.into());
properties.reverse();
for (property, _computed) in properties {
let source_range = KcvmSourceRange([property.start(), property.end()]);
// Where is the member stored?
let addr_of_member = match property {
// If it's some identifier, then look up where that identifier will be stored.
// That's the memory address the index/property should be in.
ast::types::LiteralIdentifier::Identifier(id) => {
let b = self
.binding_scope
.get(&id.name)
.ok_or(CompileError::Undefined { name: id.name })?;
match b {
EpBinding::Single(addr) => ep::Operand::Reference(*addr),
// TODO use a better error message here
other => return Err(CompileError::InvalidIndex(format!("{other:?}"))),
}
}
// If the index is a literal, then just use it.
ast::types::LiteralIdentifier::Literal(litval) => {
ep::Operand::Literal(kcl_literal_to_kcep_literal(litval.value))
}
};
// Find the address of the member, push to stack.
instructions.push(Instruction::from_range(
InstructionKind::AddrOfMember {
member: addr_of_member,
start: structure_start,
},
source_range,
));
// If there's another member after this one, its starting object is the
// address we just pushed to the stack.
structure_start = ep::Operand::StackPop;
}
// The final address is on the stack.
// Move it to addressable memory.
let final_prop_addr = self.next_addr.offset_by(1);
instructions.push(Instruction::from_range(
InstructionKind::CopyLen {
source_range: ep::Operand::StackPop,
destination_range: ep::Operand::Literal(final_prop_addr.into()),
},
source_range,
));
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(final_prop_addr),
})
} else {
// Compiler optimization:
// Because there are no computed properties, we can resolve the property chain
// at compile-time. Just jump to the right property at each step in the chain.
for (property, _) in properties {
binding = binding.property_of(property)?;
}
Ok(EvalPlan {
instructions: Vec::new(),
binding: binding.clone(),
})
}
}
SingleValue::PipeSubstitution(_expr) => {
if let Some(ref binding) = ctx.pipe_substitution {
Ok(EvalPlan {
instructions: Vec::new(),
binding: binding.clone(),
})
} else {
Err(CompileError::NotInPipeline)
}
}
SingleValue::PipeExpression(expr) => {
let mut bodies = expr.body.into_iter();
// Get the first expression (i.e. body) of the pipeline.
let first = bodies.next().expect("Pipe expression must have > 1 item");
let EvalPlan {
mut instructions,
binding: mut current_value,
} = self.plan_to_compute_single(ctx, SingleValue::from(first))?;
// Handle the remaining bodies.
for body in bodies {
let value = SingleValue::from(body);
// This body will probably contain a % (pipe substitution character).
// So it needs to know what the previous pipeline body's value is,
// to replace the % with that value.
ctx.pipe_substitution = Some(current_value.clone());
let EvalPlan {
instructions: instructions_for_this_body,
binding,
} = self.plan_to_compute_single(ctx, value)?;
instructions.extend(instructions_for_this_body);
current_value = binding;
}
// Before we return, clear the pipe substitution, because nothing outside this
// pipeline should be able to use it anymore.
ctx.pipe_substitution = None;
Ok(EvalPlan {
instructions,
binding: current_value,
})
}
SingleValue::ObjectExpression(expr) => {
let length_at = self.next_addr.offset_by(1);
let key_count = expr.properties.len();
// Compute elements
let (instructions_for_each_element, bindings, keys) = expr.properties.into_iter().try_fold(
(Vec::new(), HashMap::new(), Vec::with_capacity(key_count)),
|(mut acc_instrs, mut acc_properties, mut acc_keys), property| {
let key = property.key.name;
acc_keys.push(key.clone());
// Some elements will have their own length header (e.g. arrays).
// For all other elements, we'll need to add a length header.
let element_has_its_own_header = matches!(
SingleValue::from(property.value.clone()),
SingleValue::ArrayExpression(_) | SingleValue::ObjectExpression(_)
);
let element_needs_its_own_header = !element_has_its_own_header;
let length_at = element_needs_its_own_header.then(|| self.next_addr.offset_by(1));
let instrs_for_this_element = {
// If this element of the array is a single value, then binding it is
// straightforward -- you got a single binding, no need to change anything.
let EvalPlan { instructions, binding } =
self.plan_to_compute_single(ctx, SingleValue::from(property.value))?;
acc_properties.insert(key, binding);
instructions
};
// If we decided to add a length header for this element,
// this is where we actually add it.
if let Some(length_at) = length_at {
let length_of_this_element = (self.next_addr - length_at) - 1;
// Append element's length
acc_instrs.push(Instruction::from_range(
InstructionKind::SetPrimitive {
address: length_at,
value: length_of_this_element.into(),
},
KcvmSourceRange([expr.start, expr.end]),
));
}
// Append element's value
acc_instrs.extend(instrs_for_this_element);
Ok((acc_instrs, acc_properties, acc_keys))
},
)?;
// The array's overall instructions are:
// - Write a length header
// - Write everything to calculate its elements.
let mut instructions = vec![Instruction::from_range(
InstructionKind::SetPrimitive {
address: length_at,
value: ept::ObjectHeader {
properties: keys,
size: (self.next_addr - length_at) - 1,
}
.into(),
},
KcvmSourceRange([expr.start, expr.end]),
)];
instructions.extend(instructions_for_each_element);
let binding = EpBinding::Map {
length_at,
properties: bindings,
};
Ok(EvalPlan { instructions, binding })
}
SingleValue::ArrayExpression(expr) => {
let length_at = self.next_addr.offset_by(1);
let element_count = expr.elements.len();
// Compute elements
let (instructions_for_each_element, bindings) = expr.elements.into_iter().try_fold(
(Vec::new(), Vec::new()),
|(mut acc_instrs, mut acc_bindings), element| {
// Some elements will have their own length header (e.g. arrays).
// For all other elements, we'll need to add a length header.
let element_has_its_own_header = matches!(
SingleValue::from(element.clone()),
SingleValue::ArrayExpression(_) | SingleValue::ObjectExpression(_)
);
let element_needs_its_own_header = !element_has_its_own_header;
let length_at = element_needs_its_own_header.then(|| self.next_addr.offset_by(1));
let instrs_for_this_element = {
// If this element of the array is a single value, then binding it is
// straightforward -- you got a single binding, no need to change anything.
let EvalPlan { instructions, binding } =
self.plan_to_compute_single(ctx, SingleValue::from(element))?;
acc_bindings.push(binding);
instructions
};
// If we decided to add a length header for this element,
// this is where we actually add it.
if let Some(length_at) = length_at {
let length_of_this_element = (self.next_addr - length_at) - 1;
// Append element's length
acc_instrs.push(Instruction::from_range(
InstructionKind::SetPrimitive {
address: length_at,
value: length_of_this_element.into(),
},
KcvmSourceRange([expr.start, expr.end]),
));
}
// Append element's value
acc_instrs.extend(instrs_for_this_element);
Ok((acc_instrs, acc_bindings))
},
)?;
// The array's overall instructions are:
// - Write a length header
// - Write everything to calculate its elements.
let mut instructions = vec![Instruction::from_range(
InstructionKind::SetPrimitive {
address: length_at,
value: ept::ListHeader {
count: element_count,
size: (self.next_addr - length_at) - 1,
}
.into(),
},
KcvmSourceRange([expr.start, expr.end]),
)];
instructions.extend(instructions_for_each_element);
let binding = EpBinding::Sequence {
length_at,
elements: bindings,
};
Ok(EvalPlan { instructions, binding })
}
}
}
/// Emits instructions which, when run, compute a given KCL value and store it in memory.
/// Returns the instructions.
/// Also binds the value to a name.
fn plan_to_bind(
&mut self,
declarations: ast::types::VariableDeclaration,
) -> Result<Vec<Instruction>, CompileError> {
let mut ctx = Context::default();
declarations
.declarations
.into_iter()
.try_fold(Vec::new(), |mut acc, declaration| {
let EvalPlan { instructions, binding } =
self.plan_to_compute_single(&mut ctx, SingleValue::from(declaration.init))?;
self.binding_scope.bind(declaration.id.name, binding);
acc.extend(instructions);
Ok(acc)
})
}
}
/// Every KCL literal value is equivalent to an Execution Plan value, and therefore can be
/// bound to some KCL name and Execution Plan address.
fn kcl_literal_to_kcep_literal(expr: LiteralValue) -> ept::Primitive {
match expr {
LiteralValue::IInteger(x) => ept::Primitive::NumericValue(NumericPrimitive::Integer(x)),
LiteralValue::Fractional(x) => ept::Primitive::NumericValue(NumericPrimitive::Float(x)),
LiteralValue::String(x) => ept::Primitive::String(x),
LiteralValue::Bool(b) => ept::Primitive::Bool(b),
}
}
/// Instructions that can compute some value.
struct EvalPlan {
/// The instructions which will compute the value.
instructions: Vec<Instruction>,
/// Where the value will be stored.
binding: EpBinding,
}
/// Either an owned string, or a static string. Either way it can be read and moved around.
pub type String2 = std::borrow::Cow<'static, str>;
#[derive(Debug, Clone)]
struct UserDefinedFunction {
params_optional: Vec<ast::types::Parameter>,
params_required: Vec<ast::types::Parameter>,
body: ast::types::Program,
}
impl PartialEq for UserDefinedFunction {
fn eq(&self, other: &Self) -> bool {
self.params_optional == other.params_optional && self.params_required == other.params_required
}
}
impl Eq for UserDefinedFunction {}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
enum KclFunction {
Id(native_functions::Id),
Abs(native_functions::Abs),
Acos(native_functions::Acos),
Asin(native_functions::Asin),
Atan(native_functions::Atan),
Ceil(native_functions::Ceil),
Cos(native_functions::Cos),
Floor(native_functions::Floor),
Ln(native_functions::Ln),
Log10(native_functions::Log10),
Log2(native_functions::Log2),
Sin(native_functions::Sin),
Sqrt(native_functions::Sqrt),
Tan(native_functions::Tan),
ToDegrees(native_functions::ToDegrees),
ToRadians(native_functions::ToRadians),
StartSketchAt(native_functions::sketch::StartSketchAt),
LineTo(native_functions::sketch::LineTo),
Line(native_functions::sketch::Line),
XLineTo(native_functions::sketch::XLineTo),
XLine(native_functions::sketch::XLine),
YLineTo(native_functions::sketch::YLineTo),
YLine(native_functions::sketch::YLine),
TangentialArcTo(native_functions::sketch::TangentialArcTo),
Add(native_functions::Add),
Log(native_functions::Log),
Max(native_functions::Max),
Min(native_functions::Min),
UserDefined(UserDefinedFunction),
Extrude(native_functions::sketch::Extrude),
Close(native_functions::sketch::Close),
}
/// Context used when compiling KCL.
#[derive(Default, Debug)]
struct Context {
pipe_substitution: Option<EpBinding>,
}

View File

@ -1,165 +0,0 @@
//! Defines functions which are written in Rust, but called from KCL.
//! This includes some of the stdlib, e.g. `startSketchAt`.
//! But some other stdlib functions will be written in KCL.
use kittycad_execution_plan::{
BinaryArithmetic, BinaryOperation, Destination, Instruction, InstructionKind, Operand, UnaryArithmetic,
UnaryOperation,
};
use kittycad_execution_plan_traits::Address;
use crate::{CompileError, EpBinding, EvalPlan};
pub mod sketch;
pub trait Callable {
fn call(&self, ctx: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError>;
}
#[derive(Debug)]
pub struct Context<'a> {
pub next_address: &'a mut Address,
pub next_sketch_group: &'a mut usize,
}
impl<'a> Context<'a> {
pub fn assign_sketch_group(&mut self) -> usize {
let out = *self.next_sketch_group;
*self.next_sketch_group += 1;
out
}
}
/// Unary operator macro to quickly create new bindings.
macro_rules! define_unary {
() => {};
($h:ident$( $r:ident)*) => {
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct $h;
impl Callable for $h {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 {
return Err(CompileError::TooManyArgs {
fn_name: "$h".into(),
maximum: 1,
actual: args.len(),
});
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "$h".into(),
required: 1,
actual: args.len(),
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand("A single value binding is expected"));
};
let destination = ctx.next_address.offset_by(1);
let instructions = vec![
Instruction::from(InstructionKind::UnaryArithmetic {
arithmetic: UnaryArithmetic {
operation: UnaryOperation::$h,
operand: Operand::Reference(arg0)
},
destination: Destination::Address(destination)
})
];
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(destination),
})
}
}
define_unary!($($r)*);
};
}
define_unary!(Abs Acos Asin Atan Ceil Cos Floor Ln Log10 Log2 Sin Sqrt Tan ToDegrees ToRadians);
/// The identity function. Always returns its first input.
/// Implemented purely on the KCL side so it doesn't need to be in the
/// define_unary! macro above.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Id;
impl Callable for Id {
fn call(&self, _: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 {
return Err(CompileError::TooManyArgs {
fn_name: "id".into(),
maximum: 1,
actual: args.len(),
});
}
let arg = args
.first()
.ok_or(CompileError::NotEnoughArgs {
fn_name: "id".into(),
required: 1,
actual: 0,
})?
.clone();
Ok(EvalPlan {
instructions: Vec::new(),
binding: arg,
})
}
}
/// Binary operator macro to quickly create new bindings.
macro_rules! define_binary {
() => {};
($h:ident$( $r:ident)*) => {
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct $h;
impl Callable for $h {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let len = args.len();
if len > 2 {
return Err(CompileError::TooManyArgs {
fn_name: "$h".into(),
maximum: 2,
actual: len,
});
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "$h".into(),
required: 2,
actual: len,
};
const ERR: &str = "cannot use composite values (e.g. array) as arguments to $h";
let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand(ERR));
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args)? else {
return Err(CompileError::InvalidOperand(ERR));
};
let destination = ctx.next_address.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::from(InstructionKind::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::$h,
operand0: Operand::Reference(arg0),
operand1: Operand::Reference(arg1),
},
destination: Destination::Address(destination),
})],
binding: EpBinding::Single(destination),
})
}
}
define_binary!($($r)*);
};
}
define_binary!(Add Log Max Min);

View File

@ -1,8 +0,0 @@
//! Native functions for sketching on the plane.
pub mod helpers;
pub mod stdlib_functions;
pub use stdlib_functions::{
Close, Extrude, Line, LineTo, StartSketchAt, TangentialArcTo, XLine, XLineTo, YLine, YLineTo,
};

View File

@ -1,202 +0,0 @@
use kittycad_execution_plan::{api_request::ApiRequest, Destination, Instruction, InstructionKind};
use kittycad_execution_plan_traits::{Address, InMemory};
use kittycad_modeling_cmds::{id::ModelingCmdId, ModelingCmdEndpoint};
use crate::{binding_scope::EpBinding, error::CompileError};
/// Emit instructions for an API call with no parameters.
pub fn no_arg_api_call(instrs: &mut Vec<Instruction>, endpoint: ModelingCmdEndpoint, cmd_id: ModelingCmdId) {
instrs.push(Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint,
store_response: None,
arguments: vec![],
cmd_id,
})))
}
/// Emit instructions for an API call with the given parameters.
/// The API parameters are stored in the EP memory stack.
/// So, they have to be pushed onto the stack in the right order,
/// i.e. the reverse order in which the API call's Rust struct defines the fields.
pub fn stack_api_call<const N: usize>(
instrs: &mut Vec<Instruction>,
endpoint: ModelingCmdEndpoint,
store_response: Option<Address>,
cmd_id: ModelingCmdId,
data: [Vec<kittycad_execution_plan_traits::Primitive>; N],
) {
let arguments = vec![InMemory::StackPop; data.len()];
instrs.extend(data.map(|data| Instruction::from(InstructionKind::StackPush { data })));
instrs.push(Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint,
store_response,
arguments,
cmd_id,
})))
}
pub fn sg_binding(
b: EpBinding,
fn_name: &'static str,
expected: &'static str,
arg_number: usize,
) -> Result<usize, CompileError> {
match b {
EpBinding::SketchGroup { index } => Ok(index),
EpBinding::Single(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "single".to_owned(),
arg_number,
}),
EpBinding::Sequence { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "array".to_owned(),
arg_number,
}),
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "object".to_owned(),
arg_number,
}),
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "function".to_owned(),
arg_number,
}),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
}
}
pub fn single_binding(
b: EpBinding,
fn_name: &'static str,
expected: &'static str,
arg_number: usize,
) -> Result<Address, CompileError> {
match b {
EpBinding::Single(a) => Ok(a),
EpBinding::SketchGroup { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "SketchGroup".to_owned(),
arg_number,
}),
EpBinding::Sequence { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "array".to_owned(),
arg_number,
}),
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "object".to_owned(),
arg_number,
}),
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "function".to_owned(),
arg_number,
}),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
}
}
pub fn sequence_binding(
b: EpBinding,
fn_name: &'static str,
expected: &'static str,
arg_number: usize,
) -> Result<Vec<EpBinding>, CompileError> {
match b {
EpBinding::Sequence { elements, .. } => Ok(elements),
EpBinding::Single(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "single".to_owned(),
arg_number,
}),
EpBinding::SketchGroup { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "SketchGroup".to_owned(),
arg_number,
}),
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "object".to_owned(),
arg_number,
}),
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "function".to_owned(),
arg_number,
}),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
}
}
/// Extract a 2D point from an argument to a KCL Function.
pub fn arg_point2d(
arg: EpBinding,
fn_name: &'static str,
instructions: &mut Vec<Instruction>,
ctx: &mut crate::native_functions::Context<'_>,
arg_number: usize,
) -> Result<Address, CompileError> {
let expected = "2D point (array with length 2)";
let elements = sequence_binding(arg, fn_name, "an array of length 2", arg_number)?;
if elements.len() != 2 {
return Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: format!("array of length {}", elements.len()),
arg_number: 0,
});
}
// KCL stores points as an array.
// KC API stores them as Rust objects laid flat out in memory.
let start = ctx.next_address.offset_by(2);
let start_x = start;
let start_y = start + 1;
let start_z = start + 2;
instructions.extend([
Instruction::from(InstructionKind::Copy {
source: single_binding(elements[0].clone(), fn_name, "number", arg_number)?,
destination: Destination::Address(start_x),
length: 1,
}),
Instruction::from(InstructionKind::Copy {
source: single_binding(elements[1].clone(), fn_name, "number", arg_number)?,
destination: Destination::Address(start_y),
length: 1,
}),
Instruction::from(InstructionKind::SetPrimitive {
address: start_z,
value: 0.0.into(),
}),
]);
ctx.next_address.offset_by(1); // After we pushed 0.0 here, just above.
Ok(start)
}

View File

@ -1,853 +0,0 @@
use kittycad_execution_plan::{
api_request::ApiRequest,
sketch_types::{self, Axes, BasePath, Plane, SketchGroup},
BinaryArithmetic, BinaryOperation, Destination, Instruction, InstructionKind, Operand,
};
use kittycad_execution_plan_traits::{Address, InMemory, Primitive, Value};
use kittycad_modeling_cmds::{
shared::{Point3d, Point4d},
ModelingCmdEndpoint,
};
use uuid::Uuid;
use super::helpers::{arg_point2d, no_arg_api_call, sg_binding, single_binding, stack_api_call};
use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan};
#[derive(PartialEq)]
pub enum At {
RelativeXY,
AbsoluteXY,
RelativeX,
AbsoluteX,
RelativeY,
AbsoluteY,
}
impl At {
pub fn is_relative(&self) -> bool {
*self == At::RelativeX || *self == At::RelativeY || *self == At::RelativeXY
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Close;
impl Callable for Close {
fn call(
&self,
_ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
let fn_name = "close";
// Get all required params.
let mut args_iter = args.into_iter();
let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 1,
actual: 1,
});
};
// Check param type.
let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?;
let cmd_id = Uuid::new_v4().into();
instructions.extend([
// Push the path ID onto the stack.
Instruction::from(InstructionKind::SketchGroupCopyFrom {
destination: Destination::StackPush,
length: 1,
source: sg,
offset: SketchGroup::path_id_offset(),
}),
// Call the 'extrude' API request.
Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::ClosePath,
store_response: None,
arguments: vec![
// Target (path ID)
InMemory::StackPop,
],
cmd_id,
})),
]);
Ok(EvalPlan {
instructions,
binding: EpBinding::SketchGroup { index: sg },
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Extrude;
impl Callable for Extrude {
fn call(
&self,
_ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
let fn_name = "extrude";
// Get all required params.
let mut args_iter = args.into_iter();
let Some(height) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 0,
});
};
let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 1,
});
};
// Check param type.
let height = single_binding(height, fn_name, "numeric height", 0)?;
let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?;
let cmd_id = Uuid::new_v4().into();
instructions.extend([
// Push the `cap` bool onto the stack.
Instruction::from(InstructionKind::StackPush {
data: vec![true.into()],
}),
// Push the path ID onto the stack.
Instruction::from(InstructionKind::SketchGroupCopyFrom {
destination: Destination::StackPush,
length: 1,
source: sg,
offset: SketchGroup::path_id_offset(),
}),
// Call the 'extrude' API request.
Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::Extrude,
store_response: None,
arguments: vec![
// Target
InMemory::StackPop,
// Height
InMemory::Address(height),
// Cap
InMemory::StackPop,
],
cmd_id,
})),
]);
// TODO: make an ExtrudeGroup and store it.
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(Address::ZERO + 999),
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct LineTo;
impl Callable for LineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "lineTo", args, LineBareOptions { at: At::AbsoluteXY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Line;
impl Callable for Line {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "line", args, LineBareOptions { at: At::RelativeXY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct XLineTo;
impl Callable for XLineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "xLineTo", args, LineBareOptions { at: At::AbsoluteX })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct XLine;
impl Callable for XLine {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "xLine", args, LineBareOptions { at: At::RelativeX })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct YLineTo;
impl Callable for YLineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "yLineTo", args, LineBareOptions { at: At::AbsoluteY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct YLine;
impl Callable for YLine {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "yLine", args, LineBareOptions { at: At::RelativeY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
/// Exposes all the possible arguments the `line` modeling command can take.
/// Reduces code for the other line functions needed.
/// We do not expose this to the developer since it does not align with
/// the documentation (there is no "lineBare").
pub struct LineBare;
/// Used to configure the call to handle different line variants.
pub struct LineBareOptions {
/// Where to start coordinates at, ex: At::RelativeXY.
at: At,
}
impl LineBare {
fn call(
ctx: &mut crate::native_functions::Context<'_>,
fn_name: &'static str,
args: Vec<EpBinding>,
opts: LineBareOptions,
) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
let required = 2;
let mut args_iter = args.into_iter();
let Some(to) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required,
actual: args_iter.count(),
});
};
let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required,
actual: args_iter.count(),
});
};
let tag = match args_iter.next() {
Some(a) => a,
None => {
// Write an empty string and use that.
let empty_string_addr = ctx.next_address.offset_by(1);
instructions.push(Instruction::from(InstructionKind::SetPrimitive {
address: empty_string_addr,
value: String::new().into(),
}));
EpBinding::Single(empty_string_addr)
}
};
// Check the type of required params.
// We don't check `to` here because it can take on either a
// EpBinding::Sequence or EpBinding::Single.
let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?;
let tag = single_binding(tag, fn_name, "string tag", 2)?;
let id = Uuid::new_v4();
// Start of the path segment (which is a straight line).
let length_of_3d_point = Point3d::<f64>::default().into_parts().len();
let start_of_line = ctx.next_address.offset_by(1);
// Reserve space for the line's end, and the `relative: bool` field.
ctx.next_address.offset_by(length_of_3d_point + 1);
let new_sg_index = ctx.assign_sketch_group();
// Copy based on the options.
match opts {
LineBareOptions { at: At::AbsoluteXY, .. } | LineBareOptions { at: At::RelativeXY, .. } => {
// Push the `to` 2D point onto the stack.
let EpBinding::Sequence { elements, length_at: _ } = to.clone() else {
return Err(CompileError::InvalidOperand("Must pass a list of length 2"));
};
let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() else {
return Err(CompileError::InvalidOperand("Must pass a sequence here."));
};
instructions.extend([
// X
Instruction::from(InstructionKind::Copy {
source: el0,
length: 1,
destination: Destination::StackPush,
}),
// Y
Instruction::from(InstructionKind::Copy {
source: el1,
length: 1,
destination: Destination::StackExtend,
}),
// Z
Instruction::from(InstructionKind::StackExtend { data: vec![0.0.into()] }),
]);
}
LineBareOptions { at: At::AbsoluteX, .. } | LineBareOptions { at: At::RelativeX, .. } => {
let EpBinding::Single(addr) = to else {
return Err(CompileError::InvalidOperand("Must pass a single value here."));
};
instructions.extend([
Instruction::from(InstructionKind::Copy {
// X
source: addr,
length: 1,
destination: Destination::StackPush,
}),
Instruction::from(InstructionKind::StackExtend {
data: vec![Primitive::from(0.0)],
}), // Y
Instruction::from(InstructionKind::StackExtend {
data: vec![Primitive::from(0.0)],
}), // Z
]);
}
LineBareOptions { at: At::AbsoluteY, .. } | LineBareOptions { at: At::RelativeY, .. } => {
let EpBinding::Single(addr) = to else {
return Err(CompileError::InvalidOperand("Must pass a single value here."));
};
instructions.extend([
// X
Instruction::from(InstructionKind::StackPush {
data: vec![Primitive::from(0.0)],
}),
// Y
Instruction::from(InstructionKind::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
}),
// Z
Instruction::from(InstructionKind::StackExtend {
data: vec![Primitive::from(0.0)],
}),
]);
}
}
instructions.extend([
// Append the new path segment to memory.
// First comes its tag.
Instruction::from(InstructionKind::SetPrimitive {
address: start_of_line,
value: "Line".to_owned().into(),
}),
// Then its end
Instruction::from(InstructionKind::StackPop {
destination: Some(Destination::Address(start_of_line + 1)),
}),
// Then its `relative` field.
Instruction::from(InstructionKind::SetPrimitive {
address: start_of_line + 1 + length_of_3d_point,
value: opts.at.is_relative().into(),
}),
// Push the path ID onto the stack.
Instruction::from(InstructionKind::SketchGroupCopyFrom {
destination: Destination::StackPush,
length: 1,
source: sg,
offset: SketchGroup::path_id_offset(),
}),
// Send the ExtendPath request
Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::ExtendPath,
store_response: None,
arguments: vec![
// Path ID
InMemory::StackPop,
// Segment
InMemory::Address(start_of_line),
],
cmd_id: id.into(),
})),
// Push the new segment in SketchGroup format.
// Path tag.
Instruction::from(InstructionKind::StackPush {
data: vec![Primitive::from("ToPoint".to_owned())],
}),
// `BasePath::from` point.
// Place them in the secondary stack to prepare ToPoint structure.
Instruction::from(InstructionKind::SketchGroupGetLastPoint {
source: sg,
destination: Destination::StackExtend,
}),
]);
// Reserve space for the segment last point
let to_point_from = ctx.next_address.offset_by(2);
instructions.extend([
// Copy to the primary stack as well to be worked with.
Instruction::from(InstructionKind::SketchGroupGetLastPoint {
source: sg,
destination: Destination::Address(to_point_from),
}),
]);
// `BasePath::to` point.
// The copy here depends on the incoming `to` data.
// Sometimes it's a list, sometimes it's single datum.
// And the relative/not relative matters. When relative, we need to
// copy coords from `from` into the new `to` coord that don't change.
// At least everything else can be built up from these "primitives".
if let EpBinding::Sequence { elements, length_at: _ } = to.clone() {
if let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() {
match opts {
// ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 + y2 } }
LineBareOptions { at: At::RelativeXY, .. } => {
instructions.extend([
Instruction::from(InstructionKind::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 0),
operand1: Operand::Reference(el0),
},
destination: Destination::StackExtend,
}),
Instruction::from(InstructionKind::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 1),
operand1: Operand::Reference(el1),
},
destination: Destination::StackExtend,
}),
]);
}
// ToPoint { from: { x1, y1 }, to: { x2, y2 } }
LineBareOptions { at: At::AbsoluteXY, .. } => {
// Otherwise just directly copy the new points.
instructions.extend([
Instruction::from(InstructionKind::Copy {
source: el0,
length: 1,
destination: Destination::StackExtend,
}),
Instruction::from(InstructionKind::Copy {
source: el1,
length: 1,
destination: Destination::StackExtend,
}),
]);
}
_ => {
return Err(CompileError::InvalidOperand(
"A Sequence with At::...X or At::...Y is not valid here. Must be At::...XY.",
));
}
}
}
} else if let EpBinding::Single(addr) = to {
match opts {
// ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 } }
LineBareOptions { at: At::RelativeX } => {
instructions.extend([
Instruction::from(InstructionKind::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 0),
operand1: Operand::Reference(addr),
},
destination: Destination::StackExtend,
}),
Instruction::from(InstructionKind::Copy {
source: to_point_from + 1,
length: 1,
destination: Destination::StackExtend,
}),
]);
}
// ToPoint { from: { x1, y1 }, to: { x2, y1 } }
LineBareOptions { at: At::AbsoluteX } => {
instructions.extend([
Instruction::from(InstructionKind::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
}),
Instruction::from(InstructionKind::Copy {
source: to_point_from + 1,
length: 1,
destination: Destination::StackExtend,
}),
]);
}
// ToPoint { from: { x1, y1 }, to: { x1, y1 + y2 } }
LineBareOptions { at: At::RelativeY } => {
instructions.extend([
Instruction::from(InstructionKind::Copy {
source: to_point_from + 0,
length: 1,
destination: Destination::StackExtend,
}),
Instruction::from(InstructionKind::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 1),
operand1: Operand::Reference(addr),
},
destination: Destination::StackExtend,
}),
]);
}
// ToPoint { from: { x1, y1 }, to: { x1, y2 } }
LineBareOptions { at: At::AbsoluteY } => {
instructions.extend([
Instruction::from(InstructionKind::Copy {
source: to_point_from + 0,
length: 1,
destination: Destination::StackExtend,
}),
Instruction::from(InstructionKind::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
}),
]);
}
_ => {
return Err(CompileError::InvalidOperand(
"A Single binding with At::...XY is not valid here.",
));
}
}
} else {
return Err(CompileError::InvalidOperand(
"Must be a sequence or single value binding.",
));
}
instructions.extend([
// `BasePath::name` string.
Instruction::from(InstructionKind::Copy {
source: tag,
length: 1,
destination: Destination::StackExtend,
}),
// Update the SketchGroup with its new segment.
Instruction::from(InstructionKind::SketchGroupAddSegment {
destination: new_sg_index,
segment: InMemory::StackPop,
source: sg,
}),
]);
Ok(EvalPlan {
instructions,
binding: EpBinding::SketchGroup { index: new_sg_index },
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct StartSketchAt;
impl Callable for StartSketchAt {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
// First, before we send any API calls, let's validate the arguments to this function.
let mut args_iter = args.into_iter();
let Some(start) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: "startSketchAt".into(),
required: 1,
actual: 0,
});
};
let start_point = arg_point2d(start, "startSketchAt", &mut instructions, ctx, 0)?;
let tag = match args_iter.next() {
None => None,
Some(b) => Some(single_binding(b, "startSketchAt", "a single string", 1)?),
};
// Define some constants:
let axes = Axes {
x: Point3d { x: 1.0, y: 0.0, z: 0.0 },
y: Point3d { x: 0.0, y: 1.0, z: 0.0 },
z: Point3d { x: 0.0, y: 0.0, z: 1.0 },
};
let origin = Point3d::default();
// Now the function can start.
// First API call: make the plane.
let plane_id = Uuid::new_v4();
stack_api_call(
&mut instructions,
ModelingCmdEndpoint::MakePlane,
None,
plane_id.into(),
[
Some(true).into_parts(), // hide
vec![false.into()], // clobber
vec![60.0.into()], // size
axes.y.into_parts(),
axes.x.into_parts(),
origin.into_parts(),
],
);
// Next, enter sketch mode.
stack_api_call(
&mut instructions,
ModelingCmdEndpoint::EnableSketchMode,
None,
Uuid::new_v4().into(),
[
Some(axes.z).into_parts(),
vec![false.into()], // adjust camera
vec![false.into()], // animated
vec![false.into()], // ortho mode
vec![plane_id.into()], // entity id (plane in this case)
],
);
// Then start a path
let path_id = Uuid::new_v4();
no_arg_api_call(&mut instructions, ModelingCmdEndpoint::StartPath, path_id.into());
// Move the path pen to the given point.
instructions.push(Instruction::from(InstructionKind::StackPush {
data: vec![path_id.into()],
}));
instructions.push(Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::MovePathPen,
store_response: None,
arguments: vec![InMemory::StackPop, InMemory::Address(start_point)],
cmd_id: Uuid::new_v4().into(),
})));
// Starting a sketch creates a sketch group.
// Updating the sketch will update this sketch group later.
let sketch_group = SketchGroup {
id: path_id,
position: origin,
rotation: Point4d {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
},
// Below: Must copy the existing data (from the arguments to this KCL function)
// over these values after writing to memory.
path_first: BasePath {
from: Default::default(),
to: Default::default(),
name: Default::default(),
},
path_rest: Vec::new(),
on: sketch_types::SketchSurface::Plane(Plane {
id: plane_id,
value: sketch_types::PlaneType::XY,
origin,
axes,
}),
axes,
entity_id: Some(plane_id),
};
let sketch_group_index = ctx.assign_sketch_group();
instructions.extend([
Instruction::from(InstructionKind::SketchGroupSet {
sketch_group,
destination: sketch_group_index,
}),
// As mentioned above: Copy the existing data over the `path_first`.
Instruction::from(InstructionKind::SketchGroupSetBasePath {
source: sketch_group_index,
from: InMemory::Address(start_point),
to: InMemory::Address(start_point),
name: tag.map(InMemory::Address),
}),
]);
Ok(EvalPlan {
instructions,
binding: EpBinding::SketchGroup {
index: sketch_group_index,
},
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct TangentialArcTo;
impl Callable for TangentialArcTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
let fn_name = "tangential_arc_to";
// Get both required params.
let mut args_iter = args.into_iter();
let Some(to) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 0,
});
};
let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 1,
});
};
let tag = match args_iter.next() {
Some(a) => a,
None => {
// Write an empty string and use that.
let empty_string_addr = ctx.next_address.offset_by(1);
instructions.push(Instruction::from(InstructionKind::SetPrimitive {
address: empty_string_addr,
value: String::new().into(),
}));
EpBinding::Single(empty_string_addr)
}
};
// Check the type of required params.
let to = arg_point2d(to, fn_name, &mut instructions, ctx, 0)?;
let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?;
let tag = single_binding(tag, fn_name, "string tag", 2)?;
let id = Uuid::new_v4();
// Start of the path segment (which is a straight line).
let length_of_3d_point = Point3d::<f64>::default().into_parts().len();
let start_of_tangential_arc = ctx.next_address.offset_by(1);
// Reserve space for the line's end, and the `relative: bool` field.
ctx.next_address.offset_by(length_of_3d_point + 1);
let new_sg_index = ctx.assign_sketch_group();
instructions.extend([
// Push the `to` 2D point onto the stack.
Instruction::from(InstructionKind::Copy {
source: to,
length: 2,
destination: Destination::StackPush,
}),
// Make it a 3D point.
Instruction::from(InstructionKind::StackExtend { data: vec![0.0.into()] }),
// Append the new path segment to memory.
// First comes its tag.
Instruction::from(InstructionKind::SetPrimitive {
address: start_of_tangential_arc,
value: "TangentialArcTo".to_owned().into(),
}),
// Then its to
Instruction::from(InstructionKind::StackPop {
destination: Some(Destination::Address(start_of_tangential_arc + 1)),
}),
// Then its `angle_snap_increment` field.
Instruction::from(InstructionKind::SetPrimitive {
address: start_of_tangential_arc + 1 + length_of_3d_point,
value: Primitive::from("None".to_owned()),
}),
// Push the path ID onto the stack.
Instruction::from(InstructionKind::SketchGroupCopyFrom {
destination: Destination::StackPush,
length: 1,
source: sg,
offset: SketchGroup::path_id_offset(),
}),
// Send the ExtendPath request
Instruction::from(InstructionKind::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::ExtendPath,
store_response: None,
arguments: vec![
// Path ID
InMemory::StackPop,
// Segment
InMemory::Address(start_of_tangential_arc),
],
cmd_id: id.into(),
})),
// Push the new segment in SketchGroup format.
// Path tag.
Instruction::from(InstructionKind::StackPush {
data: vec![Primitive::from("ToPoint".to_owned())],
}),
// `BasePath::from` point.
Instruction::from(InstructionKind::SketchGroupGetLastPoint {
source: sg,
destination: Destination::StackExtend,
}),
// `BasePath::to` point.
Instruction::from(InstructionKind::Copy {
source: start_of_tangential_arc + 1,
length: 2,
destination: Destination::StackExtend,
}),
// `BasePath::name` string.
Instruction::from(InstructionKind::Copy {
source: tag,
length: 1,
destination: Destination::StackExtend,
}),
// Update the SketchGroup with its new segment.
Instruction::from(InstructionKind::SketchGroupAddSegment {
destination: new_sg_index,
segment: InMemory::StackPop,
source: sg,
}),
]);
Ok(EvalPlan {
instructions,
binding: EpBinding::SketchGroup { index: new_sg_index },
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,6 @@ futures = { version = "0.3.30" }
git_rev = "0.1.0"
gltf-json = "1.4.1"
kittycad = { workspace = true, features = ["clap"] }
kittycad-execution-plan-macros = { workspace = true }
kittycad-execution-plan-traits = { workspace = true }
lazy_static = "1.4.0"
mime_guess = "2.0.4"
parse-display = "0.9.0"

View File

@ -4,7 +4,6 @@ use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use async_recursion::async_recursion;
use kittycad_execution_plan_macros::ExecutionPlanValue;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -721,7 +720,7 @@ impl Point2d {
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema, ExecutionPlanValue)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point3d {
pub x: f64,

View File

@ -3,7 +3,6 @@
use anyhow::Result;
use derive_docs::stdlib;
use kittycad::types::{Angle, ModelingCmd, Point3D};
use kittycad_execution_plan_macros::ExecutionPlanValue;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -745,7 +744,7 @@ pub enum SketchData {
}
/// Data for a plane.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, ExecutionPlanValue)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum PlaneData {