KCL stdlib and circle function (#1029)
Allows stdlib functions to be written as KCL, not as Rust. Rust stdlib functions will hereafter be referred to as "core" not "std". Right now the only stdlib function I implemented is a circle function (it's a wrapper around the core arc function which sets the arc's start/end to 0 and 360 respectively). I know I want to change this function as soon as KCL has enums, which is my next task. So, I don't want users to start using this right away. To that end, I've named this function "unstable_stdlib_circle" not "circle". Once the function is ready to be stabilized, I can rename it to just "circle". Note that this PR modifies the existing "sketch and extrude a cylinder" KCL test so that instead of using a user-defined circle function, it uses the unstable_stdlib_circle function now. And the twenty-twenty tests pass, so we know my stdlib is working. https://github.com/KittyCAD/modeling-app/issues/922
This commit is contained in:
@ -6,10 +6,10 @@
|
||||
serial-integration = { max-threads = 4 }
|
||||
|
||||
[profile.default]
|
||||
slow-timeout = { period = "60s", terminate-after = 1 }
|
||||
slow-timeout = { period = "10s", terminate-after = 1 }
|
||||
|
||||
[profile.ci]
|
||||
slow-timeout = { period = "120s", terminate-after = 10 }
|
||||
slow-timeout = { period = "30s", terminate-after = 5 }
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "test(serial_test_)"
|
||||
@ -20,3 +20,7 @@ threads-required = 4
|
||||
filter = "test(serial_test_)"
|
||||
test-group = "serial-integration"
|
||||
threads-required = 4
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "test(parser::parser_impl::snapshot_tests)"
|
||||
slow-timeout = { period = "1s", terminate-after = 5 }
|
||||
|
@ -11,9 +11,11 @@ use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, R
|
||||
|
||||
pub use self::literal_value::LiteralValue;
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{ExecutorContext, MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
||||
executor::{BodyType, ExecutorContext, MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
||||
parser::PIPE_OPERATOR,
|
||||
std::{kcl_stdlib::KclStdLibFn, FunctionKind},
|
||||
};
|
||||
|
||||
mod literal_value;
|
||||
@ -960,8 +962,8 @@ impl CallExpression {
|
||||
fn_args.push(result);
|
||||
}
|
||||
|
||||
match ctx.stdlib.get(&self.callee.name) {
|
||||
Some(func) => {
|
||||
match ctx.stdlib.get_either(&self.callee.name) {
|
||||
FunctionKind::Core(func) => {
|
||||
// Attempt to call the function.
|
||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||
let result = func.std_lib_fn()(args).await?;
|
||||
@ -973,15 +975,54 @@ impl CallExpression {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
// Must be user-defined then
|
||||
None => {
|
||||
FunctionKind::Std(func) => {
|
||||
let function_expression = func.function();
|
||||
if fn_args.len() != function_expression.params.len() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected {} arguments, got {}",
|
||||
function_expression.params.len(),
|
||||
fn_args.len(),
|
||||
),
|
||||
source_ranges: vec![(function_expression).into()],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the arguments to the memory.
|
||||
let mut fn_memory = memory.clone();
|
||||
for (index, param) in function_expression.params.iter().enumerate() {
|
||||
fn_memory.add(¶m.name, fn_args.get(index).unwrap().clone(), param.into())?;
|
||||
}
|
||||
|
||||
// Call the stdlib function
|
||||
let p = func.function().clone().body;
|
||||
let results = crate::executor::execute(p, &mut fn_memory, BodyType::Block, ctx).await?;
|
||||
let out = results.return_;
|
||||
let result = out.ok_or_else(|| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Result of stdlib function {} is undefined", fn_name),
|
||||
source_ranges: vec![self.into()],
|
||||
})
|
||||
})?;
|
||||
let result = result.get_value()?;
|
||||
|
||||
if pipe_info.is_in_pipe {
|
||||
pipe_info.index += 1;
|
||||
pipe_info.previous_results.push(result);
|
||||
|
||||
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), ctx).await
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
FunctionKind::UserDefined => {
|
||||
let func = memory.get(&fn_name, self.into())?;
|
||||
let result = func
|
||||
.call_fn(fn_args, memory.clone(), ctx.clone())
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Result of function {} is undefined", fn_name),
|
||||
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||
source_ranges: vec![self.into()],
|
||||
})
|
||||
})?;
|
||||
@ -1056,10 +1097,15 @@ impl CallExpression {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Function {
|
||||
/// A stdlib function.
|
||||
/// A stdlib function written in Rust (aka core lib).
|
||||
StdLib {
|
||||
/// The function.
|
||||
func: Box<dyn crate::docs::StdLibFn>,
|
||||
func: Box<dyn StdLibFn>,
|
||||
},
|
||||
/// A stdlib function written in KCL.
|
||||
StdLibKcl {
|
||||
/// The function.
|
||||
func: Box<dyn KclStdLibFn>,
|
||||
},
|
||||
/// A function that is defined in memory.
|
||||
#[default]
|
||||
|
@ -3,6 +3,7 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_recursion::async_recursion;
|
||||
use kittycad::types::{Color, ModelingCmd, Point3D};
|
||||
use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
@ -13,7 +14,7 @@ use crate::{
|
||||
ast::types::{BodyItem, FunctionExpression, Value},
|
||||
engine::{EngineConnection, EngineManager},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
std::StdLib,
|
||||
std::{FunctionKind, StdLib},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
@ -781,6 +782,7 @@ pub struct ExecutorContext {
|
||||
}
|
||||
|
||||
/// Execute a AST's program.
|
||||
#[async_recursion(?Send)]
|
||||
pub async fn execute(
|
||||
program: crate::ast::types::Program,
|
||||
memory: &mut ProgramMemory,
|
||||
@ -828,27 +830,37 @@ pub async fn execute(
|
||||
}
|
||||
}
|
||||
let _show_fn = Box::new(crate::std::Show);
|
||||
if let Some(func) = ctx.stdlib.get(&call_expr.callee.name) {
|
||||
use crate::docs::StdLibFn;
|
||||
if func.name() == _show_fn.name() {
|
||||
if options != BodyType::Root {
|
||||
match ctx.stdlib.get_either(&call_expr.callee.name) {
|
||||
FunctionKind::Core(func) => {
|
||||
use crate::docs::StdLibFn;
|
||||
if func.name() == _show_fn.name() {
|
||||
if options != BodyType::Root {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot call show outside of a root".to_string(),
|
||||
source_ranges: vec![call_expr.into()],
|
||||
}));
|
||||
}
|
||||
|
||||
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||
}
|
||||
}
|
||||
FunctionKind::Std(func) => {
|
||||
let mut newmem = memory.clone();
|
||||
let result = execute(func.program().to_owned(), &mut newmem, BodyType::Block, ctx).await?;
|
||||
memory.return_ = result.return_;
|
||||
}
|
||||
FunctionKind::UserDefined => {
|
||||
if let Some(func) = memory.clone().root.get(&fn_name) {
|
||||
let result = func.call_fn(args.clone(), memory.clone(), ctx.clone()).await?;
|
||||
|
||||
memory.return_ = result;
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot call show outside of a root".to_string(),
|
||||
message: format!("No such name {} defined", fn_name),
|
||||
source_ranges: vec![call_expr.into()],
|
||||
}));
|
||||
}
|
||||
|
||||
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||
}
|
||||
} else if let Some(func) = memory.clone().root.get(&fn_name) {
|
||||
let result = func.call_fn(args.clone(), memory.clone(), ctx.clone()).await?;
|
||||
|
||||
memory.return_ = result;
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("No such name {} defined", fn_name),
|
||||
source_ranges: vec![call_expr.into()],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1388,7 +1388,7 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
let Value::PipeExpression(pipe) = val else {
|
||||
panic!("expected pipe");
|
||||
};
|
||||
let mut noncode = dbg!(pipe.non_code_meta);
|
||||
let mut noncode = pipe.non_code_meta;
|
||||
assert_eq!(noncode.non_code_nodes.len(), 1);
|
||||
let comment = noncode.non_code_nodes.remove(&0).unwrap().pop().unwrap();
|
||||
assert_eq!(
|
||||
@ -2575,8 +2575,7 @@ thing(false)
|
||||
let tokens = crate::token::lexer(test_program);
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let result = parser.ast();
|
||||
let e = result.unwrap_err();
|
||||
eprintln!("{e:?}")
|
||||
let _e = result.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -4,385 +4,69 @@ expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 330,
|
||||
"end": 90,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 254,
|
||||
"end": 74,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 3,
|
||||
"end": 254,
|
||||
"start": 6,
|
||||
"end": 74,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 3,
|
||||
"end": 9,
|
||||
"name": "circle"
|
||||
},
|
||||
"init": {
|
||||
"type": "FunctionExpression",
|
||||
"type": "FunctionExpression",
|
||||
"start": 12,
|
||||
"end": 254,
|
||||
"params": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 13,
|
||||
"end": 18,
|
||||
"name": "plane"
|
||||
},
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 20,
|
||||
"end": 26,
|
||||
"name": "center"
|
||||
},
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 28,
|
||||
"end": 34,
|
||||
"name": "radius"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"start": 39,
|
||||
"end": 254,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 43,
|
||||
"end": 240,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 49,
|
||||
"end": 240,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 49,
|
||||
"end": 51,
|
||||
"name": "sg"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 54,
|
||||
"end": 240,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 54,
|
||||
"end": 74,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 54,
|
||||
"end": 67,
|
||||
"name": "startSketchOn"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 68,
|
||||
"end": 73,
|
||||
"name": "plane"
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 82,
|
||||
"end": 132,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 82,
|
||||
"end": 96,
|
||||
"name": "startProfileAt"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 97,
|
||||
"end": 128,
|
||||
"elements": [
|
||||
{
|
||||
"type": "BinaryExpression",
|
||||
"type": "BinaryExpression",
|
||||
"start": 98,
|
||||
"end": 116,
|
||||
"operator": "+",
|
||||
"left": {
|
||||
"type": "MemberExpression",
|
||||
"type": "MemberExpression",
|
||||
"start": 98,
|
||||
"end": 107,
|
||||
"object": {
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 98,
|
||||
"end": 104,
|
||||
"name": "center"
|
||||
},
|
||||
"property": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 105,
|
||||
"end": 106,
|
||||
"value": 0,
|
||||
"raw": "0"
|
||||
},
|
||||
"computed": false
|
||||
},
|
||||
"right": {
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 110,
|
||||
"end": 116,
|
||||
"name": "radius"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "MemberExpression",
|
||||
"type": "MemberExpression",
|
||||
"start": 118,
|
||||
"end": 127,
|
||||
"object": {
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 118,
|
||||
"end": 124,
|
||||
"name": "center"
|
||||
},
|
||||
"property": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 125,
|
||||
"end": 126,
|
||||
"value": 1,
|
||||
"raw": "1"
|
||||
},
|
||||
"computed": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 130,
|
||||
"end": 131
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 140,
|
||||
"end": 224,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 140,
|
||||
"end": 143,
|
||||
"name": "arc"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 144,
|
||||
"end": 220,
|
||||
"properties": [
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 153,
|
||||
"end": 167,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 153,
|
||||
"end": 162,
|
||||
"name": "angle_end"
|
||||
},
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 164,
|
||||
"end": 167,
|
||||
"value": 360,
|
||||
"raw": "360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 176,
|
||||
"end": 190,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 176,
|
||||
"end": 187,
|
||||
"name": "angle_start"
|
||||
},
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 189,
|
||||
"end": 190,
|
||||
"value": 0,
|
||||
"raw": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 199,
|
||||
"end": 213,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 199,
|
||||
"end": 205,
|
||||
"name": "radius"
|
||||
},
|
||||
"value": {
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 207,
|
||||
"end": 213,
|
||||
"name": "radius"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 222,
|
||||
"end": 223
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 232,
|
||||
"end": 240,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 232,
|
||||
"end": 237,
|
||||
"name": "close"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 238,
|
||||
"end": 239
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"kind": "const"
|
||||
},
|
||||
{
|
||||
"type": "ReturnStatement",
|
||||
"type": "ReturnStatement",
|
||||
"start": 243,
|
||||
"end": 252,
|
||||
"argument": {
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 250,
|
||||
"end": 252,
|
||||
"name": "sg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"kind": "fn"
|
||||
},
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 256,
|
||||
"end": 314,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 262,
|
||||
"end": 314,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 262,
|
||||
"end": 270,
|
||||
"start": 6,
|
||||
"end": 14,
|
||||
"name": "cylinder"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 273,
|
||||
"end": 314,
|
||||
"start": 17,
|
||||
"end": 74,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 273,
|
||||
"end": 296,
|
||||
"start": 17,
|
||||
"end": 56,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 273,
|
||||
"end": 279,
|
||||
"name": "circle"
|
||||
"start": 17,
|
||||
"end": 39,
|
||||
"name": "unstable_stdlib_circle"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 280,
|
||||
"end": 284,
|
||||
"start": 40,
|
||||
"end": 44,
|
||||
"value": "XY",
|
||||
"raw": "'XY'"
|
||||
},
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 286,
|
||||
"end": 291,
|
||||
"start": 46,
|
||||
"end": 51,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 287,
|
||||
"end": 288,
|
||||
"start": 47,
|
||||
"end": 48,
|
||||
"value": 0,
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 289,
|
||||
"end": 290,
|
||||
"start": 49,
|
||||
"end": 50,
|
||||
"value": 0,
|
||||
"raw": "0"
|
||||
}
|
||||
@ -391,8 +75,8 @@ expression: actual
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 293,
|
||||
"end": 295,
|
||||
"start": 53,
|
||||
"end": 55,
|
||||
"value": 22,
|
||||
"raw": "22"
|
||||
}
|
||||
@ -402,28 +86,28 @@ expression: actual
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 300,
|
||||
"end": 314,
|
||||
"start": 60,
|
||||
"end": 74,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 300,
|
||||
"end": 307,
|
||||
"start": 60,
|
||||
"end": 67,
|
||||
"name": "extrude"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 308,
|
||||
"end": 310,
|
||||
"start": 68,
|
||||
"end": 70,
|
||||
"value": 14,
|
||||
"raw": "14"
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 312,
|
||||
"end": 313
|
||||
"start": 72,
|
||||
"end": 73
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
@ -441,25 +125,25 @@ expression: actual
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement",
|
||||
"start": 315,
|
||||
"end": 329,
|
||||
"start": 75,
|
||||
"end": 89,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 315,
|
||||
"end": 329,
|
||||
"start": 75,
|
||||
"end": 89,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 315,
|
||||
"end": 319,
|
||||
"start": 75,
|
||||
"end": 79,
|
||||
"name": "show"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 320,
|
||||
"end": 328,
|
||||
"start": 80,
|
||||
"end": 88,
|
||||
"name": "cylinder"
|
||||
}
|
||||
],
|
||||
@ -468,18 +152,7 @@ expression: actual
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"0": [
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 254,
|
||||
"end": 256,
|
||||
"value": {
|
||||
"type": "newLine"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nonCodeNodes": {},
|
||||
"start": []
|
||||
}
|
||||
}
|
||||
|
82
src/wasm-lib/kcl/src/std/kcl_stdlib.rs
Normal file
82
src/wasm-lib/kcl/src/std/kcl_stdlib.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
ast::types::{BodyItem, FunctionExpression, Program, Value},
|
||||
docs::{StdLibFn, StdLibFnData},
|
||||
token::lexer,
|
||||
};
|
||||
|
||||
pub trait KclStdLibFn: StdLibFn {
|
||||
fn kcl_clone_box(&self) -> Box<dyn KclStdLibFn>;
|
||||
fn function(&self) -> &FunctionExpression;
|
||||
fn program(&self) -> &Program;
|
||||
}
|
||||
|
||||
impl ts_rs::TS for dyn KclStdLibFn {
|
||||
const EXPORT_TO: Option<&'static str> = Some("bindings/StdLibFnData");
|
||||
|
||||
fn name() -> String {
|
||||
"StdLibFnData".to_string()
|
||||
}
|
||||
|
||||
fn dependencies() -> Vec<ts_rs::Dependency>
|
||||
where
|
||||
Self: 'static,
|
||||
{
|
||||
StdLibFnData::dependencies()
|
||||
}
|
||||
|
||||
fn transparent() -> bool {
|
||||
StdLibFnData::transparent()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn KclStdLibFn> {
|
||||
fn clone(&self) -> Box<dyn KclStdLibFn> {
|
||||
self.kcl_clone_box()
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for dyn KclStdLibFn {
|
||||
fn schema_name() -> String {
|
||||
"KclStdLibFn".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
gen.subschema_for::<StdLibFnData>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Box<dyn KclStdLibFn> {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let data = StdLibFnData::deserialize(deserializer)?;
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
let stdlib_fn = stdlib
|
||||
.get_kcl(&data.name)
|
||||
.ok_or_else(|| serde::de::Error::custom(format!("StdLibFn {} not found", data.name)))?;
|
||||
Ok(stdlib_fn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Box<dyn KclStdLibFn> {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.to_json().unwrap().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a KCL program. Expect it to have a single body item, which is a function.
|
||||
/// Return the program and its single function.
|
||||
/// Return None if those expectations aren't met.
|
||||
pub fn extract_function(source: &str) -> Option<(Program, Box<FunctionExpression>)> {
|
||||
let tokens = lexer(source);
|
||||
let src = crate::parser::Parser::new(tokens).ast().ok()?;
|
||||
assert_eq!(src.body.len(), 1);
|
||||
let BodyItem::ExpressionStatement(expr) = src.body.last()? else {
|
||||
panic!("expected expression statement");
|
||||
};
|
||||
let Value::FunctionExpression(function) = expr.expression.clone() else {
|
||||
panic!("expected function expr");
|
||||
};
|
||||
Some((src, function))
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
//! Functions implemented for language execution.
|
||||
|
||||
pub mod extrude;
|
||||
pub mod kcl_stdlib;
|
||||
pub mod math;
|
||||
pub mod segment;
|
||||
pub mod shapes;
|
||||
pub mod sketch;
|
||||
pub mod utils;
|
||||
|
||||
@ -16,6 +18,7 @@ use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use self::kcl_stdlib::KclStdLibFn;
|
||||
use crate::{
|
||||
ast::types::parse_json_number_as_f64,
|
||||
docs::StdLibFn,
|
||||
@ -92,12 +95,16 @@ pub fn name_in_stdlib(name: &str) -> bool {
|
||||
}
|
||||
|
||||
pub struct StdLib {
|
||||
pub fns: HashMap<String, Box<(dyn StdLibFn)>>,
|
||||
pub fns: HashMap<String, Box<dyn StdLibFn>>,
|
||||
pub kcl_fns: HashMap<String, Box<dyn KclStdLibFn>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for StdLib {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StdLib").field("fns.len()", &self.fns.len()).finish()
|
||||
f.debug_struct("StdLib")
|
||||
.field("fns.len()", &self.fns.len())
|
||||
.field("kcl_fns.len()", &self.kcl_fns.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,12 +116,36 @@ impl StdLib {
|
||||
.map(|internal_fn| (internal_fn.name(), internal_fn))
|
||||
.collect();
|
||||
|
||||
Self { fns }
|
||||
let kcl_internal_fns: [Box<dyn KclStdLibFn>; 1] = [Box::<shapes::Circle>::default()];
|
||||
let kcl_fns = kcl_internal_fns
|
||||
.into_iter()
|
||||
.map(|internal_fn| (internal_fn.name(), internal_fn))
|
||||
.collect();
|
||||
|
||||
Self { fns, kcl_fns }
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<Box<dyn StdLibFn>> {
|
||||
self.fns.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_kcl(&self, name: &str) -> Option<Box<dyn KclStdLibFn>> {
|
||||
self.kcl_fns.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_either(&self, name: &str) -> FunctionKind {
|
||||
if let Some(f) = self.get(name) {
|
||||
FunctionKind::Core(f)
|
||||
} else if let Some(f) = self.get_kcl(name) {
|
||||
FunctionKind::Std(f)
|
||||
} else {
|
||||
FunctionKind::UserDefined
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.fns.contains_key(key) || self.kcl_fns.contains_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StdLib {
|
||||
@ -123,6 +154,12 @@ impl Default for StdLib {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FunctionKind {
|
||||
Core(Box<dyn StdLibFn>),
|
||||
Std(Box<dyn KclStdLibFn>),
|
||||
UserDefined,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Args {
|
||||
pub args: Vec<MemoryItem>,
|
||||
|
102
src/wasm-lib/kcl/src/std/shapes.rs
Normal file
102
src/wasm-lib/kcl/src/std/shapes.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::kcl_stdlib::KclStdLibFn;
|
||||
use crate::{
|
||||
ast::types::{FunctionExpression, Program},
|
||||
docs::StdLibFn,
|
||||
};
|
||||
|
||||
pub const CIRCLE_FN: &str = r#"
|
||||
(plane, center, radius) => {
|
||||
const sg = startSketchOn(plane)
|
||||
|> startProfileAt([center[0] + radius, center[1]], %)
|
||||
|> arc({
|
||||
angle_end: 360,
|
||||
angle_start: 0,
|
||||
radius: radius
|
||||
}, %)
|
||||
|> close(%)
|
||||
return sg
|
||||
}
|
||||
"#;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
pub struct Circle {
|
||||
function: FunctionExpression,
|
||||
program: Program,
|
||||
}
|
||||
|
||||
impl Default for Circle {
|
||||
fn default() -> Self {
|
||||
// TODO in https://github.com/KittyCAD/modeling-app/issues/1018
|
||||
// Don't unwrap here, parse it at compile-time.
|
||||
let (src, function) = super::kcl_stdlib::extract_function(CIRCLE_FN).unwrap();
|
||||
Self {
|
||||
function: *function,
|
||||
program: src,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Circle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
"circle".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: Parse the KCL in a macro and generate these
|
||||
impl StdLibFn for Circle {
|
||||
fn name(&self) -> String {
|
||||
"unstable_stdlib_circle".to_owned()
|
||||
}
|
||||
|
||||
fn summary(&self) -> String {
|
||||
"Sketch a circle on the given plane".to_owned()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn tags(&self) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
|
||||
Vec::new() // TODO
|
||||
}
|
||||
|
||||
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn unpublished(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn deprecated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn StdLibFn> {
|
||||
Box::new(self.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl KclStdLibFn for Circle {
|
||||
fn function(&self) -> &FunctionExpression {
|
||||
&self.function
|
||||
}
|
||||
fn program(&self) -> &Program {
|
||||
&self.program
|
||||
}
|
||||
|
||||
fn kcl_clone_box(&self) -> Box<dyn KclStdLibFn> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
@ -1,14 +1,2 @@
|
||||
fn circle = (plane, center, radius) => {
|
||||
const sg = startSketchOn(plane)
|
||||
|> startProfileAt([center[0] + radius, center[1]], %)
|
||||
|> arc({
|
||||
angle_end: 360,
|
||||
angle_start: 0,
|
||||
radius: radius
|
||||
}, %)
|
||||
|> close(%)
|
||||
return sg
|
||||
}
|
||||
|
||||
const cylinder = circle('XY', [0,0], 22) |> extrude(14, %)
|
||||
const cylinder = unstable_stdlib_circle('XY', [0,0], 22) |> extrude(14, %)
|
||||
show(cylinder)
|
||||
|
Reference in New Issue
Block a user