Docs macros (#318)

* initial port

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* start of macro

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more macros

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* new

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix clippy

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* start of generated docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates for objects

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixiups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* descriptions

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* descriptions

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* remove vecs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix clippy

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2023-08-25 13:41:04 -07:00
committed by GitHub
parent 33eb6126d4
commit 0f3f0b3b68
16 changed files with 23209 additions and 306 deletions

17021
docs/kcl.json Normal file

File diff suppressed because it is too large Load Diff

3048
docs/kcl.md Normal file

File diff suppressed because it is too large Load Diff

1432
src/wasm-lib/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ crate-type = ["cdylib"]
anyhow = "1.0.75"
backtrace = "0.3"
bincode = "1.3.3"
derive-docs = { path = "derive-docs" }
futures = { version = "0.3.28", optional = true }
gloo-file = { version = "0.3.0", optional = true }
gloo-utils = "0.2.0"
@ -19,7 +20,9 @@ httparse = { version = "1.8.0", optional = true }
js-sys = { version = "0.3.64", optional = true }
kittycad = { version = "0.2.15", default-features = false, features = ["js"] }
lazy_static = "1.4.0"
parse-display = "0.8.2"
regex = "1.7.1"
schemars = { version = "0.8", features = ["url", "uuid1"] }
serde = {version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
thiserror = "1.0.47"
@ -35,6 +38,7 @@ panic = "abort"
debug = true
[dev-dependencies]
expectorate = "1.0.7"
pretty_assertions = "1.4.0"
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
@ -42,3 +46,8 @@ tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
default = ["web"]
web = ["dep:gloo-file", "dep:js-sys"]
noweb = ["dep:futures", "dep:httparse", "dep:tokio", "dep:tokio-tungstenite"]
[workspace]
members = [
"derive-docs"
]

View File

@ -0,0 +1,23 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
convert_case = "0.6.0"
proc-macro2 = "1"
quote = "1"
serde = { version = "1.0.186", features = ["derive"] }
serde_tokenstream = "0.2"
syn = { version = "2.0.29", features = ["full"] }
[dev-dependencies]
expectorate = "1.0.7"
openapitor = "0.0.5"
pretty_assertions = "1.4.0"

View File

@ -0,0 +1,548 @@
// Copyright 2023 Oxide Computer Company
//! This package defines macro attributes associated with HTTP handlers. These
//! attributes are used both to define an HTTP API and to generate an OpenAPI
//! Spec (OAS) v3 document that describes the API.
// Clippy's style advice is definitely valuable, but not worth the trouble for
// automated enforcement.
#![allow(clippy::style)]
use convert_case::Casing;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use serde::Deserialize;
use serde_tokenstream::{from_tokenstream, Error};
use syn::{
parse::{Parse, ParseStream},
Attribute, Signature, Visibility,
};
#[derive(Deserialize, Debug)]
struct StdlibMetadata {
/// The name of the function in the API.
name: String,
/// Tags for the function.
#[serde(default)]
tags: Vec<String>,
/// Whether the function is unpublished.
/// Then docs will not be generated.
#[serde(default)]
unpublished: bool,
/// Whether the function is deprecated.
/// Then specific docs detailing that this is deprecated will be generated.
#[serde(default)]
deprecated: bool,
}
#[proc_macro_attribute]
pub fn stdlib(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
do_output(do_stdlib(attr.into(), item.into()))
}
fn do_stdlib(
attr: proc_macro2::TokenStream,
item: proc_macro2::TokenStream,
) -> Result<(proc_macro2::TokenStream, Vec<Error>), Error> {
let metadata = from_tokenstream(&attr)?;
do_stdlib_inner(metadata, attr, item)
}
fn do_output(
res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>,
) -> proc_macro::TokenStream {
match res {
Err(err) => err.to_compile_error().into(),
Ok((stdlib_docs, errors)) => {
let compiler_errors = errors.iter().map(|err| err.to_compile_error());
let output = quote! {
#stdlib_docs
#( #compiler_errors )*
};
output.into()
}
}
}
fn do_stdlib_inner(
metadata: StdlibMetadata,
_attr: proc_macro2::TokenStream,
item: proc_macro2::TokenStream,
) -> Result<(proc_macro2::TokenStream, Vec<Error>), Error> {
let ast: ItemFnForSignature = syn::parse2(item.clone())?;
let mut errors = Vec::new();
if ast.sig.constness.is_some() {
errors.push(Error::new_spanned(
&ast.sig.constness,
"stdlib functions may not be const functions",
));
}
if ast.sig.asyncness.is_some() {
errors.push(Error::new_spanned(
&ast.sig.fn_token,
"stdlib functions must not be async",
));
}
if ast.sig.unsafety.is_some() {
errors.push(Error::new_spanned(
&ast.sig.unsafety,
"stdlib functions may not be unsafe",
));
}
if ast.sig.abi.is_some() {
errors.push(Error::new_spanned(
&ast.sig.abi,
"stdlib functions may not use an alternate ABI",
));
}
if !ast.sig.generics.params.is_empty() {
errors.push(Error::new_spanned(
&ast.sig.generics,
"generics are not permitted for stdlib functions",
));
}
if ast.sig.variadic.is_some() {
errors.push(Error::new_spanned(&ast.sig.variadic, "no language C here"));
}
let name = metadata.name;
let name_ident = format_ident!("{}", name.to_case(convert_case::Case::UpperCamel));
let name_str = name.to_string();
let fn_name = &ast.sig.ident;
let fn_name_str = fn_name.to_string().replace("inner_", "");
let fn_name_ident = format_ident!("{}", fn_name_str);
let _visibility = &ast.vis;
let (summary_text, description_text) = extract_doc_from_attrs(&ast.attrs);
let comment_text = {
let mut buf = String::new();
buf.push_str("Std lib function: ");
buf.push_str(&name_str);
if let Some(s) = &summary_text {
buf.push_str("\n");
buf.push_str(&s);
}
if let Some(s) = &description_text {
buf.push_str("\n");
buf.push_str(&s);
}
buf
};
let description_doc_comment = quote! {
#[doc = #comment_text]
};
let summary = if let Some(summary) = summary_text {
quote! { #summary }
} else {
quote! { "" }
};
let description = if let Some(description) = description_text {
quote! { #description }
} else {
quote! { "" }
};
let tags = metadata
.tags
.iter()
.map(|tag| {
quote! { #tag.to_string() }
})
.collect::<Vec<_>>();
let deprecated = if metadata.deprecated {
quote! { true }
} else {
quote! { false }
};
let unpublished = if metadata.unpublished {
quote! { true }
} else {
quote! { false }
};
let docs_crate = get_crate(None);
// When the user attaches this proc macro to a function with the wrong type
// signature, the resulting errors can be deeply inscrutable. To attempt to
// make failures easier to understand, we inject code that asserts the types
// of the various parameters. We do this by calling dummy functions that
// require a type that satisfies SharedExtractor or ExclusiveExtractor.
let mut arg_types = Vec::new();
for arg in ast.sig.inputs.iter() {
// Get the name of the argument.
let arg_name = match arg {
syn::FnArg::Receiver(pat) => {
let span = pat.self_token.span.unwrap();
span.source_text().unwrap().to_string()
}
syn::FnArg::Typed(pat) => match &*pat.pat {
syn::Pat::Ident(ident) => ident.ident.to_string(),
_ => {
errors.push(Error::new_spanned(
&pat.pat,
"stdlib functions may not use destructuring patterns",
));
continue;
}
},
};
let ty = match arg {
syn::FnArg::Receiver(pat) => pat.ty.as_ref().into_token_stream(),
syn::FnArg::Typed(pat) => pat.ty.as_ref().into_token_stream(),
};
let ty_string = ty
.to_string()
.replace('&', "")
.replace("mut", "")
.replace(' ', "");
let ty_string = ty_string.trim().to_string();
let ty_ident = if ty_string.starts_with("Vec<") {
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
let ty_ident = format_ident!("{}", ty_string);
quote! {
Vec<#ty_ident>
}
} else {
let ty_ident = format_ident!("{}", ty_string);
quote! {
#ty_ident
}
};
let ty_string = clean_type(&ty_string);
if ty_string != "Args" {
let schema = if ty_ident.to_string().starts_with("Vec < ") {
quote! {
<#ty_ident>::json_schema(&mut generator)
}
} else {
quote! {
#ty_ident::json_schema(&mut generator)
}
};
arg_types.push(quote! {
#docs_crate::StdLibFnArg {
name: #arg_name.to_string(),
type_: #ty_string.to_string(),
schema: #schema,
required: true,
}
});
}
}
let ret_ty = ast.sig.output.clone();
let ret_ty_string = ret_ty
.into_token_stream()
.to_string()
.replace("-> ", "")
.replace("Result < ", "")
.replace(", KclError >", "");
let ret_ty_string = ret_ty_string.trim().to_string();
let ret_ty_ident = format_ident!("{}", ret_ty_string);
let ret_ty_string = clean_type(&ret_ty_string);
let return_type = quote! {
#docs_crate::StdLibFnArg {
name: "".to_string(),
type_: #ret_ty_string.to_string(),
schema: #ret_ty_ident::json_schema(&mut generator),
required: true,
}
};
// For reasons that are not well understood unused constants that use the
// (default) call_site() Span do not trigger the dead_code lint. Because
// defining but not using an endpoint is likely a programming error, we
// want to be sure to have the compiler flag this. We force this by using
// the span from the name of the function to which this macro was applied.
let span = ast.sig.ident.span();
let const_struct = quote_spanned! {span=>
pub(crate) const #name_ident: #name_ident = #name_ident {};
};
// The final TokenStream returned will have a few components that reference
// `#name_ident`, the name of the function to which this macro was applied...
let stream = quote! {
// ... a struct type called `#name_ident` that has no members
#[allow(non_camel_case_types, missing_docs)]
#description_doc_comment
pub(crate) struct #name_ident {}
// ... a constant of type `#name` whose identifier is also #name_ident
#[allow(non_upper_case_globals, missing_docs)]
#description_doc_comment
#const_struct
impl #docs_crate::StdLibFn for #name_ident
{
fn name(&self) -> String {
#name_str.to_string()
}
fn summary(&self) -> String {
#summary.to_string()
}
fn description(&self) -> String {
#description.to_string()
}
fn tags(&self) -> Vec<String> {
vec![#(#tags),*]
}
fn args(&self) -> Vec<#docs_crate::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![#(#arg_types),*]
}
fn return_value(&self) -> #docs_crate::StdLibFnArg {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
#return_type
}
fn unpublished(&self) -> bool {
#unpublished
}
fn deprecated(&self) -> bool {
#deprecated
}
fn std_lib_fn(&self) -> crate::std::StdFn {
#fn_name_ident
}
}
#item
};
// Prepend the usage message if any errors were detected.
if !errors.is_empty() {
errors.insert(0, Error::new_spanned(&ast.sig, ""));
}
Ok((stream, errors))
}
#[allow(dead_code)]
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
quote!(#(#compile_errors)*)
}
fn get_crate(var: Option<String>) -> proc_macro2::TokenStream {
if let Some(s) = var {
if let Ok(ts) = syn::parse_str(s.as_str()) {
return ts;
}
}
quote!(crate::docs)
}
fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> (Option<String>, Option<String>) {
let doc = syn::Ident::new("doc", proc_macro2::Span::call_site());
let mut lines = attrs.iter().flat_map(|attr| {
if let syn::Meta::NameValue(nv) = &attr.meta {
if nv.path.is_ident(&doc) {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) = &nv.value
{
return normalize_comment_string(s.value());
}
}
}
Vec::new()
});
// Skip initial blank lines; they make for excessively terse summaries.
let summary = loop {
match lines.next() {
Some(s) if s.is_empty() => (),
next => break next,
}
};
// Skip initial blank description lines.
let first = loop {
match lines.next() {
Some(s) if s.is_empty() => (),
next => break next,
}
};
match (summary, first) {
(None, _) => (None, None),
(summary, None) => (summary, None),
(Some(summary), Some(first)) => (
Some(summary),
Some(
lines
.fold(first, |acc, comment| {
if acc.ends_with('-') || acc.ends_with('\n') || acc.is_empty() {
// Continuation lines and newlines.
format!("{}{}", acc, comment)
} else if comment.is_empty() {
// Handle fully blank comments as newlines we keep.
format!("{}\n", acc)
} else {
// Default to space-separating comment fragments.
format!("{} {}", acc, comment)
}
})
.trim_end()
.to_string(),
),
),
}
}
fn normalize_comment_string(s: String) -> Vec<String> {
s.split('\n')
.enumerate()
.map(|(idx, s)| {
// Rust-style comments are intrinsically single-line. We don't want
// to trim away formatting such as an initial '*'.
if idx == 0 {
s.trim_start().trim_end()
} else {
let trimmed = s.trim_start().trim_end();
trimmed
.strip_prefix("* ")
.unwrap_or_else(|| trimmed.strip_prefix('*').unwrap_or(trimmed))
}
})
.map(ToString::to_string)
.collect()
}
/// Represent an item without concern for its body which may (or may not)
/// contain syntax errors.
struct ItemFnForSignature {
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub sig: Signature,
pub _block: proc_macro2::TokenStream,
}
impl Parse for ItemFnForSignature {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let vis: Visibility = input.parse()?;
let sig: Signature = input.parse()?;
let block = input.parse()?;
Ok(ItemFnForSignature {
attrs,
vis,
sig,
_block: block,
})
}
}
fn clean_type(t: &str) -> String {
let mut t = t.to_string();
// Turn vecs into arrays.
if t.starts_with("Vec<") {
t = t.replace("Vec<", "[").replace('>', "]");
}
if t == "f64" {
return "number".to_string();
} else if t == "str" {
return "string".to_string();
} else {
return t.replace("f64", "number").to_string();
}
}
#[cfg(test)]
mod tests {
use quote::quote;
use super::*;
#[test]
fn test_stdlib_line_to() {
let (item, errors) = do_stdlib(
quote! {
name = "lineTo",
},
quote! {
fn inner_line_to(
data: LineToData,
sketch_group: SketchGroup,
args: &Args,
) -> Result<SketchGroup, KclError> {
Ok(())
}
},
)
.unwrap();
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/lineTo.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
);
}
#[test]
fn test_stdlib_min() {
let (item, errors) = do_stdlib(
quote! {
name = "min",
},
quote! {
fn inner_min(
/// The args to do shit to.
args: Vec<f64>
) -> f64 {
let mut min = std::f64::MAX;
for arg in args.iter() {
if *arg < min {
min = *arg;
}
}
min
}
},
)
.unwrap();
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/min.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
);
}
}

View File

@ -0,0 +1,76 @@
#[allow(non_camel_case_types, missing_docs)]
#[doc = "Std lib function: lineTo"]
pub(crate) struct LineTo {}
#[allow(non_upper_case_globals, missing_docs)]
#[doc = "Std lib function: lineTo"]
pub(crate) const LineTo: LineTo = LineTo {};
impl crate::docs::StdLibFn for LineTo {
fn name(&self) -> String {
"lineTo".to_string()
}
fn summary(&self) -> String {
"".to_string()
}
fn description(&self) -> String {
"".to_string()
}
fn tags(&self) -> Vec<String> {
vec![]
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![
crate::docs::StdLibFnArg {
name: "data".to_string(),
type_: "LineToData".to_string(),
schema: LineToData::json_schema(&mut generator),
required: true,
},
crate::docs::StdLibFnArg {
name: "sketch_group".to_string(),
type_: "SketchGroup".to_string(),
schema: SketchGroup::json_schema(&mut generator),
required: true,
},
]
}
fn return_value(&self) -> crate::docs::StdLibFnArg {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
crate::docs::StdLibFnArg {
name: "".to_string(),
type_: "SketchGroup".to_string(),
schema: SketchGroup::json_schema(&mut generator),
required: true,
}
}
fn unpublished(&self) -> bool {
false
}
fn deprecated(&self) -> bool {
false
}
fn std_lib_fn(&self) -> crate::std::StdFn {
line_to
}
}
fn inner_line_to(
data: LineToData,
sketch_group: SketchGroup,
args: &Args,
) -> Result<SketchGroup, KclError> {
Ok(())
}

View File

@ -0,0 +1,71 @@
#[allow(non_camel_case_types, missing_docs)]
#[doc = "Std lib function: min"]
pub(crate) struct Min {}
#[allow(non_upper_case_globals, missing_docs)]
#[doc = "Std lib function: min"]
pub(crate) const Min: Min = Min {};
impl crate::docs::StdLibFn for Min {
fn name(&self) -> String {
"min".to_string()
}
fn summary(&self) -> String {
"".to_string()
}
fn description(&self) -> String {
"".to_string()
}
fn tags(&self) -> Vec<String> {
vec![]
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "[number]".to_string(),
schema: <Vec<f64>>::json_schema(&mut generator),
required: true,
}]
}
fn return_value(&self) -> crate::docs::StdLibFnArg {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
crate::docs::StdLibFnArg {
name: "".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
}
}
fn unpublished(&self) -> bool {
false
}
fn deprecated(&self) -> bool {
false
}
fn std_lib_fn(&self) -> crate::std::StdFn {
min
}
}
fn inner_min(#[doc = r" The args to do shit to."] args: Vec<f64>) -> f64 {
let mut min = std::f64::MAX;
for arg in args.iter() {
if *arg < min {
min = *arg;
}
}
min
}

View File

@ -2,6 +2,7 @@
use std::collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Map;
@ -11,7 +12,7 @@ use crate::{
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange},
};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct Program {
@ -21,7 +22,7 @@ pub struct Program {
pub non_code_meta: NoneCodeMeta,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum BodyItem {
@ -30,7 +31,7 @@ pub enum BodyItem {
ReturnStatement(ReturnStatement),
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum Value {
@ -131,7 +132,7 @@ impl From<&Value> for crate::executor::SourceRange {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum BinaryPart {
@ -178,6 +179,7 @@ impl BinaryPart {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
pipe_info.is_in_pipe = false;
@ -188,10 +190,10 @@ impl BinaryPart {
Ok(value.clone())
}
BinaryPart::BinaryExpression(binary_expression) => {
binary_expression.get_result(memory, pipe_info, engine)
binary_expression.get_result(memory, pipe_info, stdlib, engine)
}
BinaryPart::CallExpression(call_expression) => {
call_expression.execute(memory, pipe_info, engine)
call_expression.execute(memory, pipe_info, stdlib, engine)
}
BinaryPart::UnaryExpression(unary_expression) => {
// Return an error this should not happen.
@ -207,7 +209,7 @@ impl BinaryPart {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct NoneCodeNode {
@ -216,7 +218,7 @@ pub struct NoneCodeNode {
pub value: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct NoneCodeMeta {
@ -250,7 +252,7 @@ impl<'de> Deserialize<'de> for NoneCodeMeta {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ExpressionStatement {
@ -261,7 +263,7 @@ pub struct ExpressionStatement {
impl_value_meta!(ExpressionStatement);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct CallExpression {
@ -279,6 +281,7 @@ impl CallExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
let fn_name = self.callee.name.clone();
@ -293,20 +296,20 @@ impl CallExpression {
value.clone()
}
Value::BinaryExpression(binary_expression) => {
binary_expression.get_result(memory, pipe_info, engine)?
binary_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = false;
call_expression.execute(memory, pipe_info, engine)?
call_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::UnaryExpression(unary_expression) => {
unary_expression.get_result(memory, pipe_info, engine)?
unary_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::ObjectExpression(object_expression) => {
object_expression.execute(memory, pipe_info, engine)?
object_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::ArrayExpression(array_expression) => {
array_expression.execute(memory, pipe_info, engine)?
array_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::PipeExpression(pipe_expression) => {
return Err(KclError::Semantic(KclErrorDetails {
@ -353,7 +356,7 @@ impl CallExpression {
fn_args.push(result);
}
if let Some(func) = crate::std::INTERNAL_FNS.get(&fn_name) {
if let Some(func) = stdlib.fns.get(&fn_name) {
// Attempt to call the function.
let mut args = crate::std::Args::new(fn_args, self.into(), engine);
let result = func(&mut args)?;
@ -365,6 +368,7 @@ impl CallExpression {
&pipe_info.body.clone(),
pipe_info,
self.into(),
stdlib,
engine,
)
} else {
@ -391,6 +395,7 @@ impl CallExpression {
&pipe_info.body.clone(),
pipe_info,
self.into(),
stdlib,
engine,
)
} else {
@ -400,7 +405,7 @@ impl CallExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct VariableDeclaration {
@ -412,7 +417,7 @@ pub struct VariableDeclaration {
impl_value_meta!(VariableDeclaration);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct VariableDeclarator {
@ -424,7 +429,7 @@ pub struct VariableDeclarator {
impl_value_meta!(VariableDeclarator);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct Literal {
@ -458,7 +463,7 @@ impl From<&Box<Literal>> for MemoryItem {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct Identifier {
@ -469,7 +474,7 @@ pub struct Identifier {
impl_value_meta!(Identifier);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct PipeSubstitution {
@ -479,7 +484,7 @@ pub struct PipeSubstitution {
impl_value_meta!(PipeSubstitution);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ArrayExpression {
@ -495,6 +500,7 @@ impl ArrayExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
let mut results = Vec::with_capacity(self.elements.len());
@ -507,23 +513,23 @@ impl ArrayExpression {
value.clone()
}
Value::BinaryExpression(binary_expression) => {
binary_expression.get_result(memory, pipe_info, engine)?
binary_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = false;
call_expression.execute(memory, pipe_info, engine)?
call_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::UnaryExpression(unary_expression) => {
unary_expression.get_result(memory, pipe_info, engine)?
unary_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::ObjectExpression(object_expression) => {
object_expression.execute(memory, pipe_info, engine)?
object_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::ArrayExpression(array_expression) => {
array_expression.execute(memory, pipe_info, engine)?
array_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::PipeExpression(pipe_expression) => {
pipe_expression.get_result(memory, pipe_info, engine)?
pipe_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::PipeSubstitution(pipe_substitution) => {
return Err(KclError::Semantic(KclErrorDetails {
@ -567,7 +573,7 @@ impl ArrayExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ObjectExpression {
@ -581,6 +587,7 @@ impl ObjectExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
let mut object = Map::new();
@ -592,23 +599,23 @@ impl ObjectExpression {
value.clone()
}
Value::BinaryExpression(binary_expression) => {
binary_expression.get_result(memory, pipe_info, engine)?
binary_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = false;
call_expression.execute(memory, pipe_info, engine)?
call_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::UnaryExpression(unary_expression) => {
unary_expression.get_result(memory, pipe_info, engine)?
unary_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::ObjectExpression(object_expression) => {
object_expression.execute(memory, pipe_info, engine)?
object_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::ArrayExpression(array_expression) => {
array_expression.execute(memory, pipe_info, engine)?
array_expression.execute(memory, pipe_info, stdlib, engine)?
}
Value::PipeExpression(pipe_expression) => {
pipe_expression.get_result(memory, pipe_info, engine)?
pipe_expression.get_result(memory, pipe_info, stdlib, engine)?
}
Value::PipeSubstitution(pipe_substitution) => {
return Err(KclError::Semantic(KclErrorDetails {
@ -653,7 +660,7 @@ impl ObjectExpression {
impl_value_meta!(ObjectExpression);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ObjectProperty {
@ -665,7 +672,7 @@ pub struct ObjectProperty {
impl_value_meta!(ObjectProperty);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum MemberObject {
@ -673,7 +680,7 @@ pub enum MemberObject {
Identifier(Box<Identifier>),
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum LiteralIdentifier {
@ -681,7 +688,7 @@ pub enum LiteralIdentifier {
Literal(Box<Literal>),
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct MemberExpression {
@ -747,7 +754,7 @@ impl MemberExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct ObjectKeyInfo {
pub key: LiteralIdentifier,
@ -755,7 +762,7 @@ pub struct ObjectKeyInfo {
pub computed: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct BinaryExpression {
@ -774,17 +781,18 @@ impl BinaryExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
pipe_info.is_in_pipe = false;
let left_json_value = self
.left
.get_result(memory, pipe_info, engine)?
.get_result(memory, pipe_info, stdlib, engine)?
.get_json_value()?;
let right_json_value = self
.right
.get_result(memory, pipe_info, engine)?
.get_result(memory, pipe_info, stdlib, engine)?
.get_json_value()?;
// First check if we are doing string concatenation.
@ -856,7 +864,7 @@ pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct UnaryExpression {
@ -873,6 +881,7 @@ impl UnaryExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
pipe_info.is_in_pipe = false;
@ -880,7 +889,7 @@ impl UnaryExpression {
let num = parse_json_number_as_f64(
&self
.argument
.get_result(memory, pipe_info, engine)?
.get_result(memory, pipe_info, stdlib, engine)?
.get_json_value()?,
self.into(),
)?;
@ -893,7 +902,7 @@ impl UnaryExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", tag = "type")]
pub struct PipeExpression {
@ -910,12 +919,13 @@ impl PipeExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
// Reset the previous results.
pipe_info.previous_results = vec![];
pipe_info.index = 0;
execute_pipe_body(memory, &self.body, pipe_info, self.into(), engine)
execute_pipe_body(memory, &self.body, pipe_info, self.into(), stdlib, engine)
}
}
@ -924,6 +934,7 @@ fn execute_pipe_body(
body: &[Value],
pipe_info: &mut PipeInfo,
source_range: SourceRange,
stdlib: &crate::std::StdLib,
engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> {
if pipe_info.index == body.len() {
@ -949,15 +960,15 @@ fn execute_pipe_body(
match expression {
Value::BinaryExpression(binary_expression) => {
let result = binary_expression.get_result(memory, pipe_info, engine)?;
let result = binary_expression.get_result(memory, pipe_info, stdlib, engine)?;
pipe_info.previous_results.push(result);
pipe_info.index += 1;
execute_pipe_body(memory, body, pipe_info, source_range, engine)
execute_pipe_body(memory, body, pipe_info, source_range, stdlib, engine)
}
Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = true;
pipe_info.body = body.to_vec();
call_expression.execute(memory, pipe_info, engine)
call_expression.execute(memory, pipe_info, stdlib, engine)
}
_ => {
// Return an error this should not happen.
@ -969,7 +980,7 @@ fn execute_pipe_body(
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct FunctionExpression {
@ -982,7 +993,7 @@ pub struct FunctionExpression {
impl_value_meta!(FunctionExpression);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ReturnStatement {

195
src/wasm-lib/src/docs.rs Normal file
View File

@ -0,0 +1,195 @@
//! Functions for generating docs for our stdlib functions.
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::std::Primitive;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct StdLibFnData {
/// The name of the function.
pub name: String,
/// The summary of the function.
pub summary: String,
/// The description of the function.
pub description: String,
/// The tags of the function.
pub tags: Vec<String>,
/// The args of the function.
pub args: Vec<StdLibFnArg>,
/// The return value of the function.
pub return_value: StdLibFnArg,
/// If the function is unpublished.
pub unpublished: bool,
/// If the function is deprecated.
pub deprecated: bool,
}
/// This struct defines a single argument to a stdlib function.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct StdLibFnArg {
/// The name of the argument.
pub name: String,
/// The type of the argument.
pub type_: String,
/// The schema of the argument.
pub schema: schemars::schema::Schema,
/// If the argument is required.
pub required: bool,
}
impl StdLibFnArg {
#[allow(dead_code)]
pub fn get_type_string(&self) -> Result<(String, bool)> {
get_type_string_from_schema(&self.schema)
}
#[allow(dead_code)]
pub fn description(&self) -> Option<String> {
get_description_string_from_schema(&self.schema)
}
}
/// This trait defines functions called upon stdlib functions to generate
/// documentation for them.
pub trait StdLibFn {
/// The name of the function.
fn name(&self) -> String;
/// The summary of the function.
fn summary(&self) -> String;
/// The description of the function.
fn description(&self) -> String;
/// The tags of the function.
fn tags(&self) -> Vec<String>;
/// The args of the function.
fn args(&self) -> Vec<StdLibFnArg>;
/// The return value of the function.
fn return_value(&self) -> StdLibFnArg;
/// If the function is unpublished.
fn unpublished(&self) -> bool;
/// If the function is deprecated.
fn deprecated(&self) -> bool;
/// The function itself.
fn std_lib_fn(&self) -> crate::std::StdFn;
/// Return a JSON struct representing the function.
fn to_json(&self) -> Result<StdLibFnData> {
Ok(StdLibFnData {
name: self.name(),
summary: self.summary(),
description: self.description(),
tags: self.tags(),
args: self.args(),
return_value: self.return_value(),
unpublished: self.unpublished(),
deprecated: self.deprecated(),
})
}
}
fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Option<String> {
if let schemars::schema::Schema::Object(o) = schema {
if let Some(metadata) = &o.metadata {
if let Some(description) = &metadata.description {
return Some(description.to_string());
}
}
}
None
}
fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(String, bool)> {
match schema {
schemars::schema::Schema::Object(o) => {
if let Some(format) = &o.format {
if format == "uuid" {
return Ok((Primitive::Uuid.to_string(), false));
} else if format == "double" || format == "uint" {
return Ok((Primitive::Number.to_string(), false));
} else {
anyhow::bail!("unknown format: {}", format);
}
}
if let Some(obj_val) = &o.object {
let mut fn_docs = String::new();
fn_docs.push_str("{\n");
// Let's print out the object's properties.
for (prop_name, prop) in obj_val.properties.iter() {
if prop_name.starts_with('_') {
continue;
}
if let Some(description) = get_description_string_from_schema(prop) {
fn_docs.push_str(&format!("\t// {}\n", description));
}
fn_docs.push_str(&format!(
"\t\"{}\": {},\n",
prop_name,
get_type_string_from_schema(prop)?.0,
));
}
fn_docs.push('}');
return Ok((fn_docs, true));
}
if let Some(array_val) = &o.array {
if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items {
// Let's print out the object's properties.
return Ok((
format!("[{}]", get_type_string_from_schema(items)?.0),
false,
));
} else if let Some(items) = &array_val.contains {
return Ok((
format!("[{}]", get_type_string_from_schema(items)?.0),
false,
));
}
}
if let Some(subschemas) = &o.subschemas {
let mut fn_docs = String::new();
if let Some(items) = &subschemas.one_of {
for (i, item) in items.iter().enumerate() {
// Let's print out the object's properties.
fn_docs.push_str(&get_type_string_from_schema(item)?.0.to_string());
if i < items.len() - 1 {
fn_docs.push_str(" |\n");
}
}
} else if let Some(items) = &subschemas.any_of {
for (i, item) in items.iter().enumerate() {
// Let's print out the object's properties.
fn_docs.push_str(&get_type_string_from_schema(item)?.0.to_string());
if i < items.len() - 1 {
fn_docs.push_str(" |\n");
}
}
} else {
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
}
return Ok((fn_docs, true));
}
if let Some(schemars::schema::SingleOrVec::Single(_string)) = &o.instance_type {
return Ok((Primitive::String.to_string(), false));
}
anyhow::bail!("unknown type: {:#?}", o)
}
schemars::schema::Schema::Bool(_) => Ok((Primitive::Bool.to_string(), false)),
}
}

View File

@ -3,7 +3,9 @@
use std::collections::HashMap;
use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(not(test))]
use wasm_bindgen::prelude::*;
@ -13,7 +15,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ProgramMemory {
@ -66,7 +68,7 @@ impl Default for ProgramMemory {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum ProgramReturn {
@ -101,7 +103,7 @@ impl ProgramReturn {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum MemoryItem {
@ -112,12 +114,7 @@ pub enum MemoryItem {
},
SketchGroup(SketchGroup),
ExtrudeGroup(ExtrudeGroup),
ExtrudeTransform {
position: Position,
rotation: Rotation,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
ExtrudeTransform(ExtrudeTransform),
Function {
#[serde(skip)]
func: Option<MemoryFunction>,
@ -127,6 +124,16 @@ pub enum MemoryItem {
},
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExtrudeTransform {
pub position: Position,
pub rotation: Rotation,
#[serde(rename = "__meta")]
pub meta: Vec<Metadata>,
}
pub type MemoryFunction = fn(
s: &[MemoryItem],
memory: &ProgramMemory,
@ -141,9 +148,7 @@ impl From<MemoryItem> for Vec<SourceRange> {
MemoryItem::UserVal { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::ExtrudeTransform { meta, .. } => {
meta.iter().map(|m| m.source_range).collect()
}
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
}
}
@ -190,15 +195,22 @@ impl MemoryItem {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// A sketch group is a collection of paths.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SketchGroup {
/// The id of the sketch group.
pub id: uuid::Uuid,
/// The paths in the sketch group.
pub value: Vec<Path>,
/// The starting path.
pub start: BasePath,
/// The position of the sketch group.
pub position: Position,
/// The rotation of the sketch group.
pub rotation: Rotation,
/// Metadata.
#[serde(rename = "__meta")]
pub meta: Vec<Metadata>,
}
@ -238,15 +250,22 @@ impl SketchGroup {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// An extrude group is a collection of extrude surfaces.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExtrudeGroup {
/// The id of the extrude group.
pub id: uuid::Uuid,
/// The extrude surfaces.
pub value: Vec<ExtrudeSurface>,
/// The height of the extrude group.
pub height: f64,
/// The position of the extrude group.
pub position: Position,
/// The rotation of the extrude group.
pub rotation: Rotation,
/// Metadata.
#[serde(rename = "__meta")]
pub meta: Vec<Metadata>,
}
@ -261,7 +280,7 @@ impl ExtrudeGroup {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum BodyType {
@ -270,19 +289,19 @@ pub enum BodyType {
Block,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Position(pub [f64; 3]);
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Rotation(pub [f64; 4]);
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS)]
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct SourceRange(pub [usize; 2]);
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point2d {
pub x: f64,
@ -301,7 +320,7 @@ impl From<Point2d> for [f64; 2] {
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point3d {
pub x: f64,
@ -309,10 +328,12 @@ pub struct Point3d {
pub z: f64,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Metadata.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
/// The source range.
pub source_range: SourceRange,
}
@ -322,45 +343,61 @@ impl From<SourceRange> for Metadata {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// A base path.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct BasePath {
/// The from point.
pub from: [f64; 2],
/// The to point.
pub to: [f64; 2],
/// The name of the path.
pub name: String,
/// Metadata.
#[serde(rename = "__geoMeta")]
pub geo_meta: GeoMeta,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Geometry metadata.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct GeoMeta {
/// The id of the geometry.
pub id: uuid::Uuid,
/// Metadata.
#[serde(flatten)]
pub metadata: Metadata,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// A path.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Path {
/// A path that goes to a point.
ToPoint {
#[serde(flatten)]
base: BasePath,
},
/// A path that is horizontal.
Horizontal {
#[serde(flatten)]
base: BasePath,
/// The x coordinate.
x: f64,
},
/// An angled line to.
AngledLineTo {
#[serde(flatten)]
base: BasePath,
/// The x coordinate.
x: Option<f64>,
/// The y coordinate.
y: Option<f64>,
},
/// A base path.
Base {
#[serde(flatten)]
base: BasePath,
@ -396,14 +433,20 @@ impl Path {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// An extrude surface.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ExtrudeSurface {
/// An extrude plane.
ExtrudePlane {
/// The position.
position: Position,
/// The rotation.
rotation: Rotation,
/// The name.
name: String,
/// Metadata.
#[serde(flatten)]
geo_meta: GeoMeta,
},
@ -435,7 +478,7 @@ impl ExtrudeSurface {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct PipeInfo {
@ -470,6 +513,7 @@ fn execute(
engine: &mut EngineConnection,
) -> Result<ProgramMemory, KclError> {
let mut pipe_info = PipeInfo::default();
let stdlib = crate::std::StdLib::new();
// Iterate over the body of the program.
for statement in &program.body {
@ -525,8 +569,12 @@ fn execute(
memory.add(&var_name, value.clone(), source_range)?;
}
Value::BinaryExpression(binary_expression) => {
let result =
binary_expression.get_result(memory, &mut pipe_info, engine)?;
let result = binary_expression.get_result(
memory,
&mut pipe_info,
&stdlib,
engine,
)?;
memory.add(&var_name, result, source_range)?;
}
Value::FunctionExpression(function_expression) => {
@ -563,12 +611,17 @@ fn execute(
)?;
}
Value::CallExpression(call_expression) => {
let result = call_expression.execute(memory, &mut pipe_info, engine)?;
let result =
call_expression.execute(memory, &mut pipe_info, &stdlib, engine)?;
memory.add(&var_name, result, source_range)?;
}
Value::PipeExpression(pipe_expression) => {
let result =
pipe_expression.get_result(memory, &mut pipe_info, engine)?;
let result = pipe_expression.get_result(
memory,
&mut pipe_info,
&stdlib,
engine,
)?;
memory.add(&var_name, result, source_range)?;
}
Value::PipeSubstitution(pipe_substitution) => {
@ -578,13 +631,21 @@ fn execute(
}));
}
Value::ArrayExpression(array_expression) => {
let result =
array_expression.execute(memory, &mut pipe_info, engine)?;
let result = array_expression.execute(
memory,
&mut pipe_info,
&stdlib,
engine,
)?;
memory.add(&var_name, result, source_range)?;
}
Value::ObjectExpression(object_expression) => {
let result =
object_expression.execute(memory, &mut pipe_info, engine)?;
let result = object_expression.execute(
memory,
&mut pipe_info,
&stdlib,
engine,
)?;
memory.add(&var_name, result, source_range)?;
}
Value::MemberExpression(member_expression) => {
@ -592,8 +653,12 @@ fn execute(
memory.add(&var_name, result, source_range)?;
}
Value::UnaryExpression(unary_expression) => {
let result =
unary_expression.get_result(memory, &mut pipe_info, engine)?;
let result = unary_expression.get_result(
memory,
&mut pipe_info,
&stdlib,
engine,
)?;
memory.add(&var_name, result, source_range)?;
}
}
@ -601,7 +666,7 @@ fn execute(
}
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
Value::BinaryExpression(bin_expr) => {
let result = bin_expr.get_result(memory, &mut pipe_info, engine)?;
let result = bin_expr.get_result(memory, &mut pipe_info, &stdlib, engine)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::Identifier(identifier) => {
@ -671,7 +736,6 @@ mod tests {
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
let tokens = crate::tokeniser::lexer(code);
let program = crate::parser::abstract_syntax_tree(&tokens)?;
println!("{:#?}", program);
let mut mem: ProgramMemory = Default::default();
let mut engine = EngineConnection::new("dev.kittycad.io", "some-token", "").await?;
let memory = execute(program, &mut mem, BodyType::Root, &mut engine)?;

View File

@ -1,4 +1,5 @@
mod abstract_syntax_tree_types;
mod docs;
mod engine;
mod errors;
mod executor;

View File

@ -2,16 +2,32 @@
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExtrudeGroup, MemoryItem},
executor::{ExtrudeGroup, ExtrudeTransform, MemoryItem, SketchGroup},
std::Args,
};
use anyhow::Result;
use derive_docs::stdlib;
use schemars::JsonSchema;
/// Extrudes by a given amount.
pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
let (length, sketch_group) = args.get_number_sketch_group()?;
let result = inner_extrude(length, sketch_group, args)?;
Ok(MemoryItem::ExtrudeGroup(result))
}
/// Extrudes by a given amount.
#[stdlib {
name = "extrude"
}]
fn inner_extrude(
length: f64,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<ExtrudeGroup, KclError> {
let id = uuid::Uuid::new_v4();
let cmd = kittycad::types::ModelingCmd::Extrude {
@ -21,7 +37,7 @@ pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
};
args.send_modeling_cmd(id, cmd)?;
Ok(MemoryItem::ExtrudeGroup(ExtrudeGroup {
Ok(ExtrudeGroup {
id,
// TODO, this is just an empty array now, should be deleted. This
// comment was originally in the JS code.
@ -30,14 +46,27 @@ pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
position: sketch_group.position,
rotation: sketch_group.rotation,
meta: sketch_group.meta,
}))
})
}
/// Returns the extrude wall transform.
pub fn get_extrude_wall_transform(args: &mut Args) -> Result<MemoryItem, KclError> {
let (surface_name, extrude_group) = args.get_path_name_extrude_group()?;
let result = inner_get_extrude_wall_transform(&surface_name, extrude_group, args)?;
Ok(MemoryItem::ExtrudeTransform(result))
}
/// Returns the extrude wall transform.
#[stdlib {
name = "getExtrudeWallTransform"
}]
fn inner_get_extrude_wall_transform(
surface_name: &str,
extrude_group: ExtrudeGroup,
args: &mut Args,
) -> Result<ExtrudeTransform, KclError> {
let surface = extrude_group
.get_path_by_name(&surface_name)
.get_path_by_name(surface_name)
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
@ -48,7 +77,7 @@ pub fn get_extrude_wall_transform(args: &mut Args) -> Result<MemoryItem, KclErro
})
})?;
Ok(MemoryItem::ExtrudeTransform {
Ok(ExtrudeTransform {
position: surface.get_position(),
rotation: surface.get_rotation(),
meta: extrude_group.meta,

View File

@ -10,69 +10,81 @@ mod utils;
use std::collections::HashMap;
use anyhow::Result;
use derive_docs::stdlib;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
abstract_syntax_tree_types::parse_json_number_as_f64,
engine::EngineConnection,
errors::{KclError, KclErrorDetails},
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
std::extrude::{extrude, get_extrude_wall_transform},
std::segment::{
angle_to_match_length_x, angle_to_match_length_y, last_segment_x, last_segment_y,
segment_angle, segment_end_x, segment_end_y, segment_length,
},
std::sketch::{
angled_line, angled_line_of_x_length, angled_line_of_y_length, angled_line_that_intersects,
angled_line_to_x, angled_line_to_y, close, line, line_to, start_sketch_at, x_line,
x_line_to, y_line, y_line_to,
},
};
use anyhow::Result;
use lazy_static::lazy_static;
pub type FnMap = HashMap<String, StdFn>;
pub type StdFn = fn(&mut Args) -> Result<MemoryItem, KclError>;
lazy_static! {
pub static ref INTERNAL_FNS: FnMap =
{
HashMap::from([
// Extrude functions.
("extrude".to_string(), extrude as StdFn),
("getExtrudeWallTransform".to_string(), get_extrude_wall_transform as StdFn),
pub struct StdLib {
#[allow(dead_code)]
internal_fn_names: Vec<Box<(dyn crate::docs::StdLibFn)>>,
("min".to_string(), min as StdFn),
("legLen".to_string(), leg_length as StdFn),
("legAngX".to_string(),leg_angle_x as StdFn),
("legAngY".to_string(), leg_angle_y as StdFn),
// Sketch segment functions.
("segEndX".to_string(), segment_end_x as StdFn),
("segEndY".to_string(), segment_end_y as StdFn),
("lastSegX".to_string(), last_segment_x as StdFn),
("lastSegY".to_string(), last_segment_y as StdFn),
("segLen".to_string(), segment_length as StdFn),
("segAng".to_string(), segment_angle as StdFn),
("angleToMatchLengthX".to_string(), angle_to_match_length_x as StdFn),
("angleToMatchLengthY".to_string(), angle_to_match_length_y as StdFn),
pub fns: FnMap,
}
// Sketch functions.
("lineTo".to_string(), line_to as StdFn),
("xLineTo".to_string(), x_line_to as StdFn),
("yLineTo".to_string(), y_line_to as StdFn),
("line".to_string(), line as StdFn),
("xLine".to_string(), x_line as StdFn),
("yLine".to_string(), y_line as StdFn),
("angledLine".to_string(), angled_line as StdFn),
("angledLineOfXLength".to_string(), angled_line_of_x_length as StdFn),
("angledLineToX".to_string(), angled_line_to_x as StdFn),
("angledLineOfYLength".to_string(), angled_line_of_y_length as StdFn),
("angledLineToY".to_string(), angled_line_to_y as StdFn),
("angledLineThatIntersects".to_string(), angled_line_that_intersects as StdFn),
("startSketchAt".to_string(), start_sketch_at as StdFn),
("close".to_string(), close as StdFn),
])
impl StdLib {
pub fn new() -> Self {
let internal_fn_names: Vec<Box<(dyn crate::docs::StdLibFn)>> = vec![
Box::new(Min),
Box::new(LegLen),
Box::new(LegAngX),
Box::new(LegAngY),
Box::new(crate::std::extrude::Extrude),
Box::new(crate::std::extrude::GetExtrudeWallTransform),
Box::new(crate::std::segment::SegEndX),
Box::new(crate::std::segment::SegEndY),
Box::new(crate::std::segment::LastSegX),
Box::new(crate::std::segment::LastSegY),
Box::new(crate::std::segment::SegLen),
Box::new(crate::std::segment::SegAng),
Box::new(crate::std::segment::AngleToMatchLengthX),
Box::new(crate::std::segment::AngleToMatchLengthY),
Box::new(crate::std::sketch::LineTo),
Box::new(crate::std::sketch::Line),
Box::new(crate::std::sketch::XLineTo),
Box::new(crate::std::sketch::XLine),
Box::new(crate::std::sketch::YLineTo),
Box::new(crate::std::sketch::YLine),
Box::new(crate::std::sketch::AngledLineToX),
Box::new(crate::std::sketch::AngledLineToY),
Box::new(crate::std::sketch::AngledLine),
Box::new(crate::std::sketch::AngledLineOfXLength),
Box::new(crate::std::sketch::AngledLineOfYLength),
Box::new(crate::std::sketch::AngledLineThatIntersects),
Box::new(crate::std::sketch::StartSketchAt),
Box::new(crate::std::sketch::Close),
];
};
let mut fns = HashMap::new();
for internal_fn_name in &internal_fn_names {
fns.insert(
internal_fn_name.name().to_string(),
internal_fn_name.std_lib_fn(),
);
}
Self {
internal_fn_names,
fns,
}
}
}
impl Default for StdLib {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
@ -471,34 +483,202 @@ impl<'a> Args<'a> {
}
/// Returns the minimum of the given arguments.
/// TODO fix min
pub fn min(args: &mut Args) -> Result<MemoryItem, KclError> {
let nums = args.get_number_array()?;
let result = inner_min(nums);
args.make_user_val_from_f64(result)
}
/// Returns the minimum of the given arguments.
#[stdlib {
name = "min",
}]
fn inner_min(args: Vec<f64>) -> f64 {
let mut min = std::f64::MAX;
for arg in args.get_number_array()? {
if arg < min {
min = arg;
for arg in args.iter() {
if *arg < min {
min = *arg;
}
}
args.make_user_val_from_f64(min)
min
}
/// Returns the length of the given leg.
pub fn leg_length(args: &mut Args) -> Result<MemoryItem, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
let result = (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt();
let result = inner_leg_length(hypotenuse, leg);
args.make_user_val_from_f64(result)
}
/// Returns the length of the given leg.
#[stdlib {
name = "legLen",
}]
fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 {
(hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt()
}
/// Returns the angle of the given leg for x.
pub fn leg_angle_x(args: &mut Args) -> Result<MemoryItem, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
let result = (leg.min(hypotenuse) / hypotenuse).acos() * 180.0 / std::f64::consts::PI;
let result = inner_leg_angle_x(hypotenuse, leg);
args.make_user_val_from_f64(result)
}
/// Returns the angle of the given leg for x.
#[stdlib {
name = "legAngX",
}]
fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
(leg.min(hypotenuse) / hypotenuse).acos() * 180.0 / std::f64::consts::PI
}
/// Returns the angle of the given leg for y.
pub fn leg_angle_y(args: &mut Args) -> Result<MemoryItem, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
let result = (leg.min(hypotenuse) / hypotenuse).asin() * 180.0 / std::f64::consts::PI;
let result = inner_leg_angle_y(hypotenuse, leg);
args.make_user_val_from_f64(result)
}
/// Returns the angle of the given leg for y.
#[stdlib {
name = "legAngY",
}]
fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 {
(leg.min(hypotenuse) / hypotenuse).asin() * 180.0 / std::f64::consts::PI
}
/// The primitive types that can be used in a KCL file.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
#[serde(rename_all = "lowercase")]
#[display(style = "lowercase")]
pub enum Primitive {
/// A boolean value.
Bool,
/// A number value.
Number,
/// A string value.
String,
/// A uuid value.
Uuid,
}
#[cfg(test)]
mod tests {
use crate::std::StdLib;
#[test]
fn test_generate_stdlib_markdown_docs() {
let stdlib = StdLib::new();
let mut buf = String::new();
buf.push_str("<!--- DO NOT EDIT THIS FILE. IT IS AUTOMATICALLY GENERATED. -->\n\n");
buf.push_str("# KCL Standard Library\n\n");
// Generate a table of contents.
buf.push_str("## Table of Contents\n\n");
buf.push_str("* [Functions](#functions)\n");
for internal_fn in &stdlib.internal_fn_names {
if internal_fn.unpublished() || internal_fn.deprecated() {
continue;
}
buf.push_str(&format!(
"\t* [`{}`](#{})\n",
internal_fn.name(),
internal_fn.name()
));
}
buf.push_str("\n\n");
buf.push_str("## Functions\n\n");
for internal_fn in &stdlib.internal_fn_names {
if internal_fn.unpublished() {
continue;
}
let mut fn_docs = String::new();
if internal_fn.deprecated() {
fn_docs.push_str(&format!("### {} DEPRECATED\n\n", internal_fn.name()));
} else {
fn_docs.push_str(&format!("### {}\n\n", internal_fn.name()));
}
fn_docs.push_str(&format!("{}\n\n", internal_fn.summary()));
fn_docs.push_str(&format!("{}\n\n", internal_fn.description()));
fn_docs.push_str("```\n");
fn_docs.push_str(&format!("{}(", internal_fn.name()));
for (i, arg) in internal_fn.args().iter().enumerate() {
if i > 0 {
fn_docs.push_str(", ");
}
fn_docs.push_str(&format!("{}: {}", arg.name, arg.type_));
}
fn_docs.push_str(") -> ");
fn_docs.push_str(&internal_fn.return_value().type_);
fn_docs.push_str("\n```\n\n");
fn_docs.push_str("#### Arguments\n\n");
for arg in internal_fn.args() {
let (format, should_be_indented) = arg.get_type_string().unwrap();
if let Some(description) = arg.description() {
fn_docs.push_str(&format!(
"* `{}`: `{}` - {}\n",
arg.name, arg.type_, description
));
} else {
fn_docs.push_str(&format!("* `{}`: `{}`\n", arg.name, arg.type_));
}
if should_be_indented {
fn_docs.push_str(&format!("```\n{}\n```\n", format));
}
}
fn_docs.push_str("\n#### Returns\n\n");
let return_type = internal_fn.return_value();
if let Some(description) = return_type.description() {
fn_docs.push_str(&format!("* `{}` - {}\n", return_type.type_, description));
} else {
fn_docs.push_str(&format!("* `{}`\n", return_type.type_));
}
let (format, should_be_indented) = return_type.get_type_string().unwrap();
if should_be_indented {
fn_docs.push_str(&format!("```\n{}\n```\n", format));
}
fn_docs.push_str("\n\n\n");
buf.push_str(&fn_docs);
}
expectorate::assert_contents("../../docs/kcl.md", &buf);
}
#[test]
fn test_generate_stdlib_json_schema() {
let stdlib = StdLib::new();
let mut json_data = vec![];
for internal_fn in &stdlib.internal_fn_names {
json_data.push(internal_fn.to_json().unwrap());
}
expectorate::assert_contents(
"../../docs/kcl.json",
&serde_json::to_string_pretty(&json_data).unwrap(),
);
}
}

View File

@ -2,17 +2,33 @@
use crate::{
errors::{KclError, KclErrorDetails},
executor::MemoryItem,
executor::{MemoryItem, SketchGroup},
std::{utils::get_angle, Args},
};
use anyhow::Result;
use derive_docs::stdlib;
use schemars::JsonSchema;
/// Returns the segment end of x.
pub fn segment_end_x(args: &mut Args) -> Result<MemoryItem, KclError> {
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
let result = inner_segment_end_x(&segment_name, sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the segment end of x.
#[stdlib {
name = "segEndX",
}]
fn inner_segment_end_x(
segment_name: &str,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<f64, KclError> {
let line = sketch_group
.get_base_by_name_or_start(&segment_name)
.get_base_by_name_or_start(segment_name)
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
@ -23,14 +39,28 @@ pub fn segment_end_x(args: &mut Args) -> Result<MemoryItem, KclError> {
})
})?;
args.make_user_val_from_f64(line.to[0])
Ok(line.to[0])
}
/// Returns the segment end of y.
pub fn segment_end_y(args: &mut Args) -> Result<MemoryItem, KclError> {
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
let result = inner_segment_end_y(&segment_name, sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the segment end of y.
#[stdlib {
name = "segEndY",
}]
fn inner_segment_end_y(
segment_name: &str,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<f64, KclError> {
let line = sketch_group
.get_base_by_name_or_start(&segment_name)
.get_base_by_name_or_start(segment_name)
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
@ -41,12 +71,22 @@ pub fn segment_end_y(args: &mut Args) -> Result<MemoryItem, KclError> {
})
})?;
args.make_user_val_from_f64(line.to[1])
Ok(line.to[1])
}
/// Returns the last segment of x.
pub fn last_segment_x(args: &mut Args) -> Result<MemoryItem, KclError> {
let sketch_group = args.get_sketch_group()?;
let result = inner_last_segment_x(sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the last segment of x.
#[stdlib {
name = "lastSegX",
}]
fn inner_last_segment_x(sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> {
let last_line = sketch_group
.value
.last()
@ -61,12 +101,22 @@ pub fn last_segment_x(args: &mut Args) -> Result<MemoryItem, KclError> {
})?
.get_base();
args.make_user_val_from_f64(last_line.to[0])
Ok(last_line.to[0])
}
/// Returns the last segment of y.
pub fn last_segment_y(args: &mut Args) -> Result<MemoryItem, KclError> {
let sketch_group = args.get_sketch_group()?;
let result = inner_last_segment_y(sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the last segment of y.
#[stdlib {
name = "lastSegY",
}]
fn inner_last_segment_y(sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> {
let last_line = sketch_group
.value
.last()
@ -81,15 +131,26 @@ pub fn last_segment_y(args: &mut Args) -> Result<MemoryItem, KclError> {
})?
.get_base();
args.make_user_val_from_f64(last_line.to[1])
Ok(last_line.to[1])
}
/// Returns the length of the segment.
pub fn segment_length(args: &mut Args) -> Result<MemoryItem, KclError> {
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
let path = sketch_group
.get_path_by_name(&segment_name)
.ok_or_else(|| {
let result = inner_segment_length(&segment_name, sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the length of the segment.
#[stdlib {
name = "segLen",
}]
fn inner_segment_length(
segment_name: &str,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a segment name that exists in the given SketchGroup, found `{}`",
@ -101,15 +162,28 @@ pub fn segment_length(args: &mut Args) -> Result<MemoryItem, KclError> {
let line = path.get_base();
let result = ((line.from[1] - line.to[1]).powi(2) + (line.from[0] - line.to[0]).powi(2)).sqrt();
args.make_user_val_from_f64(result)
Ok(result)
}
/// Returns the angle of the segment.
pub fn segment_angle(args: &mut Args) -> Result<MemoryItem, KclError> {
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
let path = sketch_group
.get_path_by_name(&segment_name)
.ok_or_else(|| {
let result = inner_segment_angle(&segment_name, sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the angle of the segment.
#[stdlib {
name = "segAng",
}]
fn inner_segment_angle(
segment_name: &str,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a segment name that exists in the given SketchGroup, found `{}`",
@ -121,15 +195,28 @@ pub fn segment_angle(args: &mut Args) -> Result<MemoryItem, KclError> {
let line = path.get_base();
let result = get_angle(&line.from, &line.to);
args.make_user_val_from_f64(result)
Ok(result)
}
/// Returns the angle to match the given length for x.
pub fn angle_to_match_length_x(args: &mut Args) -> Result<MemoryItem, KclError> {
let (segment_name, to, sketch_group) = args.get_segment_name_to_number_sketch_group()?;
let path = sketch_group
.get_path_by_name(&segment_name)
.ok_or_else(|| {
let result = inner_angle_to_match_length_x(&segment_name, to, sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the angle to match the given length for x.
#[stdlib {
name = "angleToMatchLengthX",
}]
fn inner_angle_to_match_length_x(
segment_name: &str,
to: f64,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a segment name that exists in the given SketchGroup, found `{}`",
@ -161,18 +248,30 @@ pub fn angle_to_match_length_x(args: &mut Args) -> Result<MemoryItem, KclError>
let angle_r = diff / length.acos();
if diff > length {
args.make_user_val_from_f64(0.0)
Ok(0.0)
} else {
args.make_user_val_from_f64(angle_r * 180.0 / std::f64::consts::PI)
Ok(angle_r * 180.0 / std::f64::consts::PI)
}
}
/// Returns the angle to match the given length for y.
pub fn angle_to_match_length_y(args: &mut Args) -> Result<MemoryItem, KclError> {
let (segment_name, to, sketch_group) = args.get_segment_name_to_number_sketch_group()?;
let path = sketch_group
.get_path_by_name(&segment_name)
.ok_or_else(|| {
let result = inner_angle_to_match_length_y(&segment_name, to, sketch_group, args)?;
args.make_user_val_from_f64(result)
}
/// Returns the angle to match the given length for y.
#[stdlib {
name = "angleToMatchLengthY",
}]
fn inner_angle_to_match_length_y(
segment_name: &str,
to: f64,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a segment name that exists in the given SketchGroup, found `{}`",
@ -204,8 +303,8 @@ pub fn angle_to_match_length_y(args: &mut Args) -> Result<MemoryItem, KclError>
let angle_r = diff / length.asin();
if diff > length {
args.make_user_val_from_f64(0.0)
Ok(0.0)
} else {
args.make_user_val_from_f64(angle_r * 180.0 / std::f64::consts::PI)
Ok(angle_r * 180.0 / std::f64::consts::PI)
}
}

View File

@ -1,6 +1,8 @@
//! Functions related to sketching.
use derive_docs::stdlib;
use kittycad::types::{ModelingCmd, Point3D};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
@ -14,11 +16,19 @@ use crate::{
use anyhow::Result;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Data to draw a line to a point.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum LineToData {
PointWithTag { to: [f64; 2], tag: String },
/// A point with a tag.
PointWithTag {
/// The to point.
to: [f64; 2],
/// The tag.
tag: String,
},
/// A point.
Point([f64; 2]),
}
@ -31,6 +41,9 @@ pub fn line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
}
/// Draw a line to a point.
#[stdlib {
name = "lineTo",
}]
fn inner_line_to(
data: LineToData,
sketch_group: SketchGroup,
@ -65,17 +78,39 @@ fn inner_line_to(
Ok(new_sketch_group)
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Data to draw a line to a point on an axis.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum AxisLineToData {
PointWithTag { to: f64, tag: String },
/// A point with a tag.
PointWithTag {
/// The to point.
to: f64,
/// The tag.
tag: String,
},
/// A point.
Point(f64),
}
/// Draw a line to a point on the x-axis.
pub fn x_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AxisLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_x_line_to(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line to a point on the x-axis.
#[stdlib {
name = "xLineTo",
}]
fn inner_x_line_to(
data: AxisLineToData,
sketch_group: SketchGroup,
args: &Args,
) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let line_to_data = match data {
@ -87,12 +122,27 @@ pub fn x_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
};
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
/// Draw a line to a point on the y-axis.
pub fn y_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AxisLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_y_line_to(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line to a point on the y-axis.
#[stdlib {
name = "yLineTo",
}]
fn inner_y_line_to(
data: AxisLineToData,
sketch_group: SketchGroup,
args: &Args,
) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let line_to_data = match data {
@ -104,23 +154,35 @@ pub fn y_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
};
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Data to draw a line.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum LineData {
PointWithTag { to: PointOrDefault, tag: String },
/// A point with a tag.
PointWithTag {
/// The to point.
to: PointOrDefault,
/// The tag.
tag: String,
},
/// A point.
Point([f64; 2]),
/// A string like `default`.
Default(String),
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// A point or a default value.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum PointOrDefault {
/// A point.
Point([f64; 2]),
/// A string like `default`.
Default(String),
}
@ -141,6 +203,10 @@ pub fn line(args: &mut Args) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line.
#[stdlib {
name = "line",
}]
fn inner_line(
data: LineData,
sketch_group: SketchGroup,
@ -195,55 +261,95 @@ fn inner_line(
Ok(new_sketch_group)
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Data to draw a line on an axis.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum AxisLineData {
PointWithTag { length: f64, tag: String },
Point(f64),
/// The length with a tag.
LengthWithTag {
/// The length of the line.
length: f64,
/// The tag.
tag: String,
},
/// The length.
Length(f64),
}
/// Draw a line on the x-axis.
pub fn x_line(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AxisLineData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_x_line(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line on the x-axis.
#[stdlib {
name = "xLine",
}]
fn inner_x_line(
data: AxisLineData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let line_data = match data {
AxisLineData::PointWithTag { length, tag } => LineData::PointWithTag {
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
to: PointOrDefault::Point([length, 0.0]),
tag,
},
AxisLineData::Point(length) => LineData::Point([length, 0.0]),
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
};
let new_sketch_group = inner_line(line_data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
/// Draw a line on the y-axis.
pub fn y_line(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AxisLineData, SketchGroup) = args.get_data_and_sketch_group()?;
let line_data = match data {
AxisLineData::PointWithTag { length, tag } => LineData::PointWithTag {
to: PointOrDefault::Point([0.0, length]),
tag,
},
AxisLineData::Point(length) => LineData::Point([0.0, length]),
};
let new_sketch_group = inner_line(line_data, sketch_group, args)?;
let new_sketch_group = inner_y_line(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Draw a line on the y-axis.
#[stdlib {
name = "yLine",
}]
fn inner_y_line(
data: AxisLineData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let line_data = match data {
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
to: PointOrDefault::Point([0.0, length]),
tag,
},
AxisLineData::Length(length) => LineData::Point([0.0, length]),
};
let new_sketch_group = inner_line(line_data, sketch_group, args)?;
Ok(new_sketch_group)
}
/// Data to draw an angled line.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum AngledLineData {
/// An angle and length with a tag.
AngleWithTag {
/// The angle of the line.
angle: f64,
/// The length of the line.
length: f64,
/// The tag.
tag: String,
},
/// An angle and length.
AngleAndLength([f64; 2]),
}
@ -251,6 +357,19 @@ pub enum AngledLineData {
pub fn angled_line(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AngledLineData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_angled_line(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an angled line.
#[stdlib {
name = "angledLine",
}]
fn inner_angled_line(
data: AngledLineData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let (angle, length) = match &data {
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
@ -283,13 +402,26 @@ pub fn angled_line(args: &mut Args) -> Result<MemoryItem, KclError> {
let mut new_sketch_group = sketch_group.clone();
new_sketch_group.value.push(current_path);
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
/// Draw an angled line of a given x length.
pub fn angled_line_of_x_length(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AngledLineData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_angled_line_of_x_length(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an angled line of a given x length.
#[stdlib {
name = "angledLineOfXLength",
}]
fn inner_angled_line_of_x_length(
data: AngledLineData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let (angle, length) = match &data {
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
AngledLineData::AngleAndLength(angle_and_length) => {
@ -312,14 +444,24 @@ pub fn angled_line_of_x_length(args: &mut Args) -> Result<MemoryItem, KclError>
args,
)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Data to draw an angled line to a point.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum AngledLineToData {
AngleWithTag { angle: f64, to: f64, tag: String },
/// An angle and point with a tag.
AngleWithTag {
/// The angle of the line.
angle: f64,
/// The point to draw to.
to: f64,
/// The tag.
tag: String,
},
/// An angle and point to draw to.
AngleAndPoint([f64; 2]),
}
@ -327,6 +469,19 @@ pub enum AngledLineToData {
pub fn angled_line_to_x(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AngledLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_angled_line_to_x(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an angled line to a given x coordinate.
#[stdlib {
name = "angledLineToX",
}]
fn inner_angled_line_to_x(
data: AngledLineToData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let (angle, x_to) = match &data {
AngledLineToData::AngleWithTag { angle, to, .. } => (*angle, *to),
@ -349,13 +504,27 @@ pub fn angled_line_to_x(args: &mut Args) -> Result<MemoryItem, KclError> {
sketch_group,
args,
)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
/// Draw an angled line of a given y length.
pub fn angled_line_of_y_length(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AngledLineData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_angled_line_of_y_length(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an angled line of a given y length.
#[stdlib {
name = "angledLineOfYLength",
}]
fn inner_angled_line_of_y_length(
data: AngledLineData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let (angle, length) = match &data {
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
AngledLineData::AngleAndLength(angle_and_length) => {
@ -378,13 +547,26 @@ pub fn angled_line_of_y_length(args: &mut Args) -> Result<MemoryItem, KclError>
args,
)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
/// Draw an angled line to a given y coordinate.
pub fn angled_line_to_y(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AngledLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_angled_line_to_y(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an angled line to a given y coordinate.
#[stdlib {
name = "angledLineToY",
}]
fn inner_angled_line_to_y(
data: AngledLineToData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let (angle, y_to) = match &data {
AngledLineToData::AngleWithTag { angle, to, .. } => (*angle, *to),
@ -407,10 +589,11 @@ pub fn angled_line_to_y(args: &mut Args) -> Result<MemoryItem, KclError> {
sketch_group,
args,
)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
/// Data for drawing an angled line that intersects with a given line.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
// TODO: make sure the docs on the args below are correct.
@ -429,6 +612,19 @@ pub struct AngeledLineThatIntersectsData {
pub fn angled_line_that_intersects(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (AngeledLineThatIntersectsData, SketchGroup) =
args.get_data_and_sketch_group()?;
let new_sketch_group = inner_angled_line_that_intersects(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an angled line that intersects with a given line.
#[stdlib {
name = "angledLineThatIntersects",
}]
fn inner_angled_line_that_intersects(
data: AngeledLineThatIntersectsData,
sketch_group: SketchGroup,
args: &mut Args,
) -> Result<SketchGroup, KclError> {
let intersect_path = sketch_group
.get_path_by_name(&data.intersect_tag)
.ok_or_else(|| {
@ -457,13 +653,22 @@ pub fn angled_line_that_intersects(args: &mut Args) -> Result<MemoryItem, KclErr
};
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
/// Start a sketch at a given point.
pub fn start_sketch_at(args: &mut Args) -> Result<MemoryItem, KclError> {
let data: LineData = args.get_data()?;
let sketch_group = inner_start_sketch_at(data, args)?;
Ok(MemoryItem::SketchGroup(sketch_group))
}
/// Start a sketch at a given point.
#[stdlib {
name = "startSketchAt",
}]
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result<SketchGroup, KclError> {
let default = [0.0, 0.0];
let to = match &data {
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
@ -509,12 +714,23 @@ pub fn start_sketch_at(args: &mut Args) -> Result<MemoryItem, KclError> {
start: current_path,
meta: vec![args.source_range.into()],
};
Ok(MemoryItem::SketchGroup(sketch_group))
Ok(sketch_group)
}
/// Close the current sketch.
pub fn close(args: &mut Args) -> Result<MemoryItem, KclError> {
let sketch_group = args.get_sketch_group()?;
let new_sketch_group = inner_close(sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Close the current sketch.
#[stdlib {
name = "close",
}]
fn inner_close(sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let to: Point2d = sketch_group.start.from.into();
@ -541,7 +757,7 @@ pub fn close(args: &mut Args) -> Result<MemoryItem, KclError> {
},
});
Ok(MemoryItem::SketchGroup(new_sketch_group))
Ok(new_sketch_group)
}
#[cfg(test)]