Remove grackle (#2566)
This commit is contained in:
1520
src/wasm-lib/Cargo.lock
generated
1520
src/wasm-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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 |
@ -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,
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
@ -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>,
|
||||
}
|
||||
@ -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);
|
||||
@ -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,
|
||||
};
|
||||
@ -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)
|
||||
}
|
||||
@ -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
@ -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"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user