Compare commits

..

3 Commits

Author SHA1 Message Date
8c4543b333 void 2024-04-08 18:08:55 -05:00
ce36f09064 Merge branch 'main' into achalmers/update-h2 2024-01-22 10:45:59 +11:00
bc7e1f4e19 Rust: Update h2 2024-01-22 10:29:06 +11:00
9 changed files with 92 additions and 682 deletions

View File

@ -136,7 +136,7 @@
"prettier": "^2.8.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.3.6",
"vite": "^4.5.2",
"vite": "^4.5.1",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.1",
"wait-on": "^7.2.0",

View File

@ -1943,7 +1943,7 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan"
version = "0.1.0"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#935256e4a7080ea130b09b578e16820dc96e78e4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9200b9540fa5ae99b692db276c625223116f467f"
dependencies = [
"bytes",
"insta",
@ -1972,7 +1972,7 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan-macros"
version = "0.1.2"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#935256e4a7080ea130b09b578e16820dc96e78e4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9200b9540fa5ae99b692db276c625223116f467f"
dependencies = [
"proc-macro2",
"quote",
@ -1992,8 +1992,8 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.1.12"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#935256e4a7080ea130b09b578e16820dc96e78e4"
version = "0.1.11"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9200b9540fa5ae99b692db276c625223116f467f"
dependencies = [
"anyhow",
"chrono",
@ -2020,7 +2020,7 @@ dependencies = [
[[package]]
name = "kittycad-modeling-session"
version = "0.1.0"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#935256e4a7080ea130b09b578e16820dc96e78e4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9200b9540fa5ae99b692db276c625223116f467f"
dependencies = [
"futures",
"kittycad",

View File

@ -2,10 +2,11 @@ use kcl_lib::ast::types::LiteralIdentifier;
use kcl_lib::ast::types::LiteralValue;
use crate::CompileError;
use crate::KclFunction;
use super::native_functions;
use super::Address;
use super::KclFunction;
use super::String2;
use std::collections::HashMap;
@ -20,14 +21,6 @@ pub enum EpBinding {
Sequence(Vec<EpBinding>),
/// A sequence of KCL values, indexed by their identifier.
Map(HashMap<String, EpBinding>),
/// Not associated with a KCEP address.
Function(KclFunction),
}
impl From<KclFunction> for EpBinding {
fn from(f: KclFunction) -> Self {
Self::Function(f)
}
}
impl EpBinding {
@ -38,18 +31,16 @@ impl EpBinding {
LiteralIdentifier::Literal(litval) => match litval.value {
// Arrays can be indexed by integers.
LiteralValue::IInteger(i) => match self {
EpBinding::Single(_) => Err(CompileError::CannotIndex),
EpBinding::Sequence(seq) => {
let i = usize::try_from(i).map_err(|_| CompileError::InvalidIndex(i.to_string()))?;
seq.get(i).ok_or(CompileError::IndexOutOfBounds { i, len: seq.len() })
}
EpBinding::Map(_) => Err(CompileError::CannotIndex),
EpBinding::Single(_) => Err(CompileError::CannotIndex),
EpBinding::Function(_) => 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::Sequence(_) => Err(CompileError::ArrayDoesNotHaveProperties),
EpBinding::Map(map) => map.get(&property).ok_or(CompileError::UndefinedProperty { property }),
},
@ -76,6 +67,7 @@ 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.
function_bindings: HashMap<String2, Box<dyn KclFunction>>,
parent: Option<Box<BindingScope>>,
}
@ -88,39 +80,29 @@ impl BindingScope {
Self {
// TODO: Actually put the stdlib prelude in here,
// things like `startSketchAt` and `line`.
ep_bindings: HashMap::from([
("id".into(), EpBinding::from(KclFunction::Id(native_functions::Id))),
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
(
"startSketchAt".into(),
EpBinding::from(KclFunction::StartSketchAt(native_functions::StartSketchAt)),
),
function_bindings: HashMap::from([
("id".into(), Box::new(native_functions::Id) as _),
("add".into(), Box::new(native_functions::Add) as _),
]),
ep_bindings: Default::default(),
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));
#[allow(dead_code)] // TODO: when we implement function expressions.
pub fn add_scope(self) -> Self {
Self {
function_bindings: Default::default(),
ep_bindings: Default::default(),
parent: Some(Box::new(self)),
}
}
//// 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;
#[allow(dead_code)] // TODO: when we implement function expressions.
pub fn remove_scope(self) -> Self {
*self.parent.unwrap()
}
/// Add a binding (e.g. defining a new variable)
@ -144,11 +126,10 @@ impl BindingScope {
/// 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,
}
if let Some(f) = self.function_bindings.get(identifier) {
GetFnResult::Found(f.as_ref())
} else if self.get(identifier).is_some() {
GetFnResult::NonCallable
} else if let Some(ref parent) = self.parent {
parent.get_fn(identifier)
} else {
@ -158,7 +139,7 @@ impl BindingScope {
}
pub enum GetFnResult<'a> {
Found(&'a KclFunction),
Found(&'a dyn KclFunction),
NonCallable,
NotFound,
}

View File

@ -18,7 +18,6 @@ pub enum SingleValue {
UnaryExpression(Box<ast::types::UnaryExpression>),
KclNoneExpression(ast::types::KclNone),
MemberExpression(Box<ast::types::MemberExpression>),
FunctionExpression(Box<ast::types::FunctionExpression>),
}
impl From<ast::types::BinaryPart> for KclValueGroup {
@ -60,8 +59,7 @@ impl From<ast::types::Value> for KclValueGroup {
ast::types::Value::ArrayExpression(e) => Self::ArrayExpression(e),
ast::types::Value::ObjectExpression(e) => Self::ObjectExpression(e),
ast::types::Value::MemberExpression(e) => Self::Single(SingleValue::MemberExpression(e)),
ast::types::Value::FunctionExpression(e) => Self::Single(SingleValue::FunctionExpression(e)),
ast::types::Value::PipeSubstitution(_) => todo!(),
ast::types::Value::PipeSubstitution(_) | ast::types::Value::FunctionExpression(_) => todo!(),
}
}
}
@ -78,7 +76,6 @@ impl From<KclValueGroup> for ast::types::Value {
SingleValue::UnaryExpression(e) => ast::types::Value::UnaryExpression(e),
SingleValue::KclNoneExpression(e) => ast::types::Value::None(e),
SingleValue::MemberExpression(e) => ast::types::Value::MemberExpression(e),
SingleValue::FunctionExpression(e) => ast::types::Value::FunctionExpression(e),
},
KclValueGroup::ArrayExpression(e) => ast::types::Value::ArrayExpression(e),
KclValueGroup::ObjectExpression(e) => ast::types::Value::ObjectExpression(e),

View File

@ -8,7 +8,7 @@ use std::collections::HashMap;
use kcl_lib::{
ast,
ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program, RequiredParamAfterOptionalParam},
ast::types::{BodyItem, KclNone, LiteralValue, Program},
};
use kittycad_execution_plan as ep;
use kittycad_execution_plan::{Address, ExecutionError, Instruction};
@ -22,7 +22,7 @@ use self::kcl_value_group::{KclValueGroup, SingleValue};
/// Execute a KCL program by compiling into an execution plan, then running that.
pub async fn execute(ast: Program, session: Session) -> Result<(), Error> {
let mut planner = Planner::new();
let (plan, _retval) = planner.build_plan(ast)?;
let plan = planner.build_plan(ast)?;
let mut mem = kittycad_execution_plan::Memory::default();
kittycad_execution_plan::execute(&mut mem, plan, session).await?;
Ok(())
@ -44,36 +44,20 @@ impl Planner {
}
}
/// 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 instructions_for_this_node = match item {
BodyItem::ExpressionStatement(node) => match KclValueGroup::from(node.expression) {
KclValueGroup::Single(value) => self.plan_to_compute_single(value)?.instructions,
KclValueGroup::ArrayExpression(_) => todo!(),
KclValueGroup::ObjectExpression(_) => todo!(),
},
BodyItem::VariableDeclaration(node) => self.plan_to_bind(node)?,
BodyItem::ReturnStatement(node) => match KclValueGroup::from(node.argument) {
KclValueGroup::Single(value) => {
let EvalPlan { instructions, binding } = self.plan_to_compute_single(value)?;
retval = Some(binding);
instructions
}
KclValueGroup::ArrayExpression(_) => todo!(),
KclValueGroup::ObjectExpression(_) => todo!(),
},
};
instructions.extend(instructions_for_this_node);
Ok((instructions, retval))
})
fn build_plan(&mut self, program: Program) -> PlanRes {
program.body.into_iter().try_fold(Vec::new(), |mut instructions, item| {
let instructions_for_this_node = match item {
BodyItem::ExpressionStatement(node) => match KclValueGroup::from(node.expression) {
KclValueGroup::Single(value) => self.plan_to_compute_single(value)?.instructions,
KclValueGroup::ArrayExpression(_) => todo!(),
KclValueGroup::ObjectExpression(_) => todo!(),
},
BodyItem::VariableDeclaration(node) => self.plan_to_bind(node)?,
BodyItem::ReturnStatement(_) => todo!(),
};
instructions.extend(instructions_for_this_node);
Ok(instructions)
})
}
/// Emits instructions which, when run, compute a given KCL value and store it in memory.
@ -90,23 +74,6 @@ impl Planner {
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.
@ -133,30 +100,6 @@ impl Planner {
binding: previously_bound_to.clone(),
})
}
SingleValue::UnaryExpression(expr) => {
let operand = self.plan_to_compute_single(SingleValue::from(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::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,
});
Ok(EvalPlan {
instructions: plan,
binding: EpBinding::Single(destination),
})
}
SingleValue::BinaryExpression(expr) => {
let l = self.plan_to_compute_single(SingleValue::from(expr.left))?;
let r = self.plan_to_compute_single(SingleValue::from(expr.right))?;
@ -174,13 +117,13 @@ impl Planner {
let mut plan = Vec::with_capacity(l.instructions.len() + r.instructions.len() + 1);
plan.extend(l.instructions);
plan.extend(r.instructions);
plan.push(Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
plan.push(Instruction::Arithmetic {
arithmetic: ep::Arithmetic {
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::Add => ep::Operation::Add,
ast::types::BinaryOperator::Sub => ep::Operation::Sub,
ast::types::BinaryOperator::Mul => ep::Operation::Mul,
ast::types::BinaryOperator::Div => ep::Operation::Div,
ast::types::BinaryOperator::Mod => {
todo!("execution plan instruction set doesn't support Mod yet")
}
@ -199,7 +142,6 @@ impl Planner {
})
}
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| {
@ -216,7 +158,6 @@ impl Planner {
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 => {
@ -231,67 +172,10 @@ impl Planner {
}
};
// Emit instructions to call that function with the given arguments.
use native_functions::Callable;
let EvalPlan {
instructions: eval_instrs,
binding,
} = match callee {
KclFunction::Id(f) => f.call(&mut self.next_addr, args)?,
KclFunction::StartSketchAt(f) => f.call(&mut self.next_addr, args)?,
KclFunction::Add(f) => f.call(&mut self.next_addr, 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.
} = callee.call(&mut self.next_addr, args)?;
instructions.extend(eval_instrs);
Ok(EvalPlan { instructions, binding })
}
@ -324,6 +208,7 @@ impl Planner {
})
}
SingleValue::PipeExpression(_) => todo!(),
SingleValue::UnaryExpression(_) => todo!(),
}
}
@ -502,12 +387,6 @@ pub enum CompileError {
"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,
}
#[derive(Debug, thiserror::Error)]
@ -518,6 +397,8 @@ pub enum Error {
Execution(#[from] ExecutionError),
}
type PlanRes = Result<Vec<Instruction>, CompileError>;
/// 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 {
@ -536,29 +417,9 @@ struct EvalPlan {
binding: EpBinding,
}
trait KclFunction: std::fmt::Debug {
fn call(&self, next_addr: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError>;
}
/// 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),
StartSketchAt(native_functions::StartSketchAt),
Add(native_functions::Add),
UserDefined(UserDefinedFunction),
}

View File

@ -3,21 +3,16 @@
//! But some other stdlib functions will be written in KCL.
use kcl_lib::std::sketch::PlaneData;
use kittycad_execution_plan::{Address, BinaryArithmetic, Instruction};
use kittycad_execution_plan::{Address, Arithmetic, Instruction};
use kittycad_execution_plan_traits::Value;
use crate::{CompileError, EpBinding, EvalPlan};
use crate::{CompileError, EpBinding, EvalPlan, KclFunction};
/// The identity function. Always returns its first input.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[derive(Debug)]
pub struct Id;
pub trait Callable {
fn call(&self, next_addr: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError>;
}
impl Callable for Id {
impl KclFunction for Id {
fn call(&self, _: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 {
return Err(CompileError::TooManyArgs {
@ -41,11 +36,10 @@ impl Callable for Id {
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[derive(Debug)]
pub struct StartSketchAt;
impl Callable for StartSketchAt {
impl KclFunction for StartSketchAt {
fn call(&self, next_addr: &mut Address, _args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
// Store the plane.
@ -70,11 +64,10 @@ impl Callable for StartSketchAt {
}
/// A test function that adds two numbers.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[derive(Debug)]
pub struct Add;
impl Callable for Add {
impl KclFunction for Add {
fn call(&self, next_address: &mut Address, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let len = args.len();
if len > 2 {
@ -98,9 +91,9 @@ impl Callable for Add {
};
let destination = next_address.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: kittycad_execution_plan::BinaryOperation::Add,
instructions: vec![Instruction::Arithmetic {
arithmetic: Arithmetic {
operation: kittycad_execution_plan::Operation::Add,
operand0: kittycad_execution_plan::Operand::Reference(arg0),
operand1: kittycad_execution_plan::Operand::Reference(arg1),
},

View File

@ -1,4 +1,3 @@
use ep::UnaryArithmetic;
use pretty_assertions::assert_eq;
use super::*;
@ -8,7 +7,7 @@ fn must_plan(program: &str) -> (Vec<Instruction>, BindingScope) {
let parser = kcl_lib::parser::Parser::new(tokens);
let ast = parser.ast().unwrap();
let mut p = Planner::new();
let (instrs, _) = p.build_plan(ast).unwrap();
let instrs = p.build_plan(ast).unwrap();
(instrs, p.binding_scope)
}
@ -110,17 +109,6 @@ fn bind_arrays_with_objects_elements() {
);
}
#[test]
fn statement_after_return() {
let program = "fn f = () => {
return 1
let x = 2
}
f()";
let err = should_not_compile(program);
assert_eq!(err, CompileError::MultipleReturns);
}
#[test]
fn name_not_found() {
// Users can't assign `y` to anything because `y` is undefined.
@ -158,9 +146,9 @@ fn use_native_function_add() {
address: Address::ZERO.offset(1),
value: 2i64.into()
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
Instruction::Arithmetic {
arithmetic: ep::Arithmetic {
operation: ep::Operation::Add,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO.offset(1))
},
@ -264,27 +252,6 @@ fn member_expressions_array() {
}
}
#[test]
fn compile_flipped_sign() {
let program = "let x = 3
let y = -x";
let (plan, _scope) = must_plan(program);
let expected = vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 3i64.into(),
},
Instruction::UnaryArithmetic {
arithmetic: UnaryArithmetic {
operation: ep::UnaryOperation::Neg,
operand: ep::Operand::Reference(Address::ZERO),
},
destination: Address::ZERO + 1,
},
];
assert_eq!(plan, expected);
}
#[test]
fn add_literals() {
let program = "let x = 1 + 2";
@ -300,9 +267,9 @@ fn add_literals() {
address: Address::ZERO.offset(1),
value: 2i64.into()
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
Instruction::Arithmetic {
arithmetic: ep::Arithmetic {
operation: ep::Operation::Add,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO.offset(1)),
},
@ -332,9 +299,9 @@ fn add_vars() {
address: addr1,
value: 2i64.into(),
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
Instruction::Arithmetic {
arithmetic: ep::Arithmetic {
operation: ep::Operation::Add,
operand0: ep::Operand::Reference(addr0),
operand1: ep::Operand::Reference(addr1),
},
@ -373,18 +340,18 @@ fn composite_binary_exprs() {
value: 3i64.into(),
},
// Adds 1 + 2
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
Instruction::Arithmetic {
arithmetic: ep::Arithmetic {
operation: ep::Operation::Add,
operand0: ep::Operand::Reference(addr0),
operand1: ep::Operand::Reference(addr1),
},
destination: addr3,
},
// Adds `x` + 3, where `x` is (1 + 2)
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Add,
Instruction::Arithmetic {
arithmetic: ep::Arithmetic {
operation: ep::Operation::Add,
operand0: ep::Operand::Reference(addr3),
operand1: ep::Operand::Reference(addr2),
},
@ -394,348 +361,6 @@ fn composite_binary_exprs() {
);
}
#[test]
fn use_kcl_functions_zero_params() {
let (plan, scope) = must_plan(
"fn triple = () => { return 123 }
let x = triple()",
);
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 123i64.into()
}]
);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &Address::ZERO);
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}");
}
}
}
#[test]
fn use_kcl_functions_with_optional_params() {
for (i, program) in ["fn triple = (x, y?) => { return x*3 }
let x = triple(1, 888)"]
.into_iter()
.enumerate()
{
let (plan, scope) = must_plan(program);
let destination = Address::ZERO + 3;
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 888i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 2,
value: 3i64.into(),
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Mul,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO + 2)
},
destination,
}
],
"failed test {i}"
);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &destination, "failed test {i}");
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test {i}");
}
}
}
}
#[test]
fn use_kcl_functions_with_too_many_params() {
let program = "fn triple = (x, y?) => { return x*3 }
let x = triple(1, 2, 3)";
let err = should_not_compile(program);
assert!(matches!(
err,
CompileError::TooManyArgs {
maximum: 2,
actual: 3,
..
}
))
}
#[test]
fn use_kcl_function_as_return_value() {
let program = "fn twotwotwo = () => {
return () => { return 222 }
}
let f = twotwotwo()
let x = f()";
let (plan, scope) = must_plan(program);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &Address::ZERO);
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test");
}
}
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 222i64.into()
}]
)
}
#[test]
fn define_recursive_function() {
let program = "fn add_infinitely = (i) => {
return add_infinitely(i+1)
}";
let (plan, _scope) = must_plan(program);
assert_eq!(plan, Vec::new())
}
#[test]
fn use_kcl_function_as_param() {
let program = "fn wrapper = (f) => {
return f()
}
fn twotwotwo = () => {
return 222
}
let x = wrapper(twotwotwo)";
let (plan, scope) = must_plan(program);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &Address::ZERO);
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test");
}
}
assert_eq!(
plan,
vec![Instruction::SetPrimitive {
address: Address::ZERO,
value: 222i64.into()
}]
)
}
fn use_kcl_function_y_combinator() {
let program = "
// TRUE := λx.λy.x
fn _TRUE = (x) => {
return (y) => { return x }
}
// FALSE := λx.λy.y
fn _FALSE = (x) => {
return (y) => { return y }
}
// constant false (no matter what is applied, the falsey value is returned)
fn cFalse = (x) => {
return _FALSE
}
// ISZERO := λn.n (λx.FALSE) TRUE
fn is_zero = (n) => {
let fa = n(cFalse)
return fa(_TRUE)
}
// IFTHENELSE := λp.λa.λb.p a b
fn ifthenelse = (p) => {
return (a) => {
return (b) => {
let fa = p(a)
return fa(b)
}
}
}
// SUCC := λn.λf.λx.f (n f x)
// Inserts another (f x) in the church numeral chain
fn succ = (n) => {
return (f) => {
return (x) => {
let fa = n(f)
let fb = fa(x)
return f(fb)
}
}
}
// PLUS := λm.λn.m SUCC n
fn plus = (m) => {
return (n) => {
let fa = m(succ)
return fa(n)
}
}
// 0 := λf.λx.x
fn _0 = (f) => {
return (x) => { return x }
}
fn cZero = (x) => {
return _0
}
// 1 := λf.λx.f x
fn _1 = (f) => {
return (x) => { return f(x) }
}
let _2 = succ(_1)
let _3 = succ(_2)
let _4 = succ(_3)
let _5 = succ(_4)
let _6 = succ(_5)
// ...
// PRED := λn.n (λg.λk.ISZERO (g 1) k (PLUS (g k) 1)) (λv.0) 0
fn pred = (n) => {
fn f1 = (g) => {
return (k) => {
let fa = is_zero(g(_1))
let fb = fa(k)
let fc1 = plus(g(k))
let fc2 = fc1(_1)
let fc = fb(fc2)
return fc
}
}
let f2 = n(f1)
let f3 = f2(cZero)
let f4 = f3(_0)
return f4
}
// MUL := λm.λn.m (PLUS n) 0
fn mul = (m) => {
return (n) => {
let fa = m(plus(n))
let fb = fa(_0)
return fb
}
}
// G := λr. λn.(1, if n = 0; else n × (r (n1)))
fn G = (r) => {
return (n) => {
let fa = ifthenelse(n)
let fb = fa(_1)
let fc1 = mul(n)
let fc2 = fc1(r(pred(n)))
let fc = fb(fc2)
return fc
}
}
// Y := λg.(λx.g (x x)) (λx.g (x x))
fn Y = (g) => {
fn f1 = (x) => { return g(x(x)) }
let f2 = g(f1)
let f3 = f2(f1)
return f3
}
fn fact = (n) => {
let fa = Y(G)
return fa(n)
}
// x should be _6
let x = fact(_3)
";
let (plan, scope) = must_plan(program);
// Somehow check the result is the same as _6 definition
}
#[test]
fn use_kcl_functions_with_params() {
for (i, program) in [
"fn triple = (x) => { return x*3 }
let x = triple(1)",
"fn triple = (x,y?) => { return x*3 }
let x = triple(1)",
]
.into_iter()
.enumerate()
{
let (plan, scope) = must_plan(program);
let destination = Address::ZERO + 2;
assert_eq!(
plan,
vec![
Instruction::SetPrimitive {
address: Address::ZERO,
value: 1i64.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 3i64.into(),
},
Instruction::BinaryArithmetic {
arithmetic: ep::BinaryArithmetic {
operation: ep::BinaryOperation::Mul,
operand0: ep::Operand::Reference(Address::ZERO),
operand1: ep::Operand::Reference(Address::ZERO.offset(1))
},
destination,
}
],
"failed test {i}"
);
match scope.get("x").unwrap() {
EpBinding::Single(addr) => {
assert_eq!(addr, &destination, "failed test {i}");
}
other => {
panic!("expected 'x' bound to an address but it was bound to {other:?}, so failed test {i}");
}
}
}
}
#[test]
fn define_kcl_functions() {
let (plan, scope) = must_plan("fn triple = (x) => { return x * 3 }");
assert!(plan.is_empty());
match scope.get("triple").unwrap() {
EpBinding::Function(KclFunction::UserDefined(expr)) => {
assert!(expr.params_optional.is_empty());
assert_eq!(expr.params_required.len(), 1);
}
other => {
panic!("expected 'triple' bound to a user-defined KCL function but it was bound to {other:?}");
}
}
}
#[test]
fn aliases_dont_affect_plans() {
let (plan1, _) = must_plan(

View File

@ -2631,7 +2631,7 @@ async fn execute_pipe_body(
}
/// Parameter of a KCL function.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema, Bake)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
#[serde(tag = "type")]
@ -2655,23 +2655,6 @@ pub struct FunctionExpression {
impl_value_meta!(FunctionExpression);
pub struct FunctionExpressionParts {
pub start: usize,
pub end: usize,
pub params_required: Vec<Parameter>,
pub params_optional: Vec<Parameter>,
pub body: Program,
}
#[derive(Debug, PartialEq, Clone)]
pub struct RequiredParamAfterOptionalParam(pub Parameter);
impl std::fmt::Display for RequiredParamAfterOptionalParam {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "KCL functions must declare any optional parameters after all the required parameters. But your required parameter {} is _after_ an optional parameter. You must move it to before the optional parameters instead.", self.0.identifier.name)
}
}
impl FunctionExpression {
/// Function expressions don't really apply.
pub fn get_constraint_level(&self) -> ConstraintLevel {
@ -2680,36 +2663,6 @@ impl FunctionExpression {
}
}
pub fn into_parts(self) -> Result<FunctionExpressionParts, RequiredParamAfterOptionalParam> {
let Self {
start,
end,
params,
body,
} = self;
let mut params_required = Vec::with_capacity(params.len());
let mut params_optional = Vec::with_capacity(params.len());
for param in params {
if param.optional {
params_optional.push(param);
} else {
if !params_optional.is_empty() {
return Err(RequiredParamAfterOptionalParam(param));
}
params_required.push(param);
}
}
params_required.shrink_to_fit();
params_optional.shrink_to_fit();
Ok(FunctionExpressionParts {
start,
end,
params_required,
params_optional,
body,
})
}
/// Required parameters must be declared before optional parameters.
/// This gets all the required parameters.
pub fn required_params(&self) -> &[Parameter] {

View File

@ -8188,10 +8188,10 @@ vite-tsconfig-paths@^4.2.1:
globrex "^0.1.2"
tsconfck "^2.1.0"
"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82"
integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==
"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.1.tgz#3370986e1ed5dbabbf35a6c2e1fb1e18555b968a"
integrity sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.27"