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:
17021
docs/kcl.json
Normal file
17021
docs/kcl.json
Normal file
File diff suppressed because it is too large
Load Diff
3048
docs/kcl.md
Normal file
3048
docs/kcl.md
Normal file
File diff suppressed because it is too large
Load Diff
1432
src/wasm-lib/Cargo.lock
generated
1432
src/wasm-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
]
|
||||
|
23
src/wasm-lib/derive-docs/Cargo.toml
Normal file
23
src/wasm-lib/derive-docs/Cargo.toml
Normal 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"
|
548
src/wasm-lib/derive-docs/src/lib.rs
Normal file
548
src/wasm-lib/derive-docs/src/lib.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
76
src/wasm-lib/derive-docs/tests/lineTo.gen
Normal file
76
src/wasm-lib/derive-docs/tests/lineTo.gen
Normal 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(())
|
||||
}
|
71
src/wasm-lib/derive-docs/tests/min.gen
Normal file
71
src/wasm-lib/derive-docs/tests/min.gen
Normal 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
|
||||
}
|
@ -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
195
src/wasm-lib/src/docs.rs
Normal 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)),
|
||||
}
|
||||
}
|
@ -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)?;
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod abstract_syntax_tree_types;
|
||||
mod docs;
|
||||
mod engine;
|
||||
mod errors;
|
||||
mod executor;
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,63 +131,100 @@ 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(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
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 `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
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(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
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 `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
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(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
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 `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let length = ((line.from[1] - line.to[1]).powi(2) + (line.from[0] - line.to[0]).powi(2)).sqrt();
|
||||
@ -161,26 +248,38 @@ 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(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
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 `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let length = ((line.from[1] - line.to[1]).powi(2) + (line.from[0] - line.to[0]).powi(2)).sqrt();
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
Reference in New Issue
Block a user