diff --git a/docs/kcl/index.md b/docs/kcl/index.md index 867217966b..7e27951518 100644 --- a/docs/kcl/index.md +++ b/docs/kcl/index.md @@ -6,9 +6,14 @@ layout: manual ## Table of Contents -* [Types](kcl/types) -* [Modules](kcl/modules) -* [Known Issues](kcl/KNOWN-ISSUES) +### Language + +* [`Types`](kcl/types) +* [`Modules`](kcl/modules) +* [`Known Issues`](kcl/known-issues) + +### Standard library + * **`std`** * [`abs`](kcl/abs) * [`acos`](kcl/acos) diff --git a/docs/kcl/KNOWN-ISSUES.md b/docs/kcl/known-issues.md similarity index 100% rename from docs/kcl/KNOWN-ISSUES.md rename to docs/kcl/known-issues.md diff --git a/docs/kcl/modules.md b/docs/kcl/modules.md index 94f0828a64..8fdb75e3b4 100644 --- a/docs/kcl/modules.md +++ b/docs/kcl/modules.md @@ -20,7 +20,7 @@ export fn increment(x) { Other files in the project can now import functions that have been exported. This makes them available to use in another file. -``` +```norun // main.kcl import increment from "util.kcl" @@ -48,13 +48,13 @@ export fn decrement(x) { When importing, you can import multiple functions at once. -``` +```norun import increment, decrement from "util.kcl" ``` Imported symbols can be renamed for convenience or to avoid name collisions. -``` +```norun import increment as inc, decrement as dec from "util.kcl" ``` @@ -63,13 +63,13 @@ import increment as inc, decrement as dec from "util.kcl" `import` can also be used to import files from other CAD systems. The format of the statement is the same as for KCL files. You can only import the whole file, not items from it. E.g., -``` +```norun import "tests/inputs/cube.obj" // Use `cube` just like a KCL object. ``` -``` +```norun import "tests/inputs/cube-2.sldprt" as cube // Use `cube` just like a KCL object. @@ -78,7 +78,7 @@ import "tests/inputs/cube-2.sldprt" as cube You can make the file format explicit using a format attribute (useful if using a different extension), e.g., -``` +```norun @(format = obj) import "tests/inputs/cube" ``` @@ -87,7 +87,7 @@ For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by using an attirbute. Likewise, you can also specify a coordinate system. E.g., -``` +```norun @(unitLength = ft, coords = opengl) import "tests/inputs/cube.obj" ``` diff --git a/docs/kcl/settings.md b/docs/kcl/settings.md new file mode 100644 index 0000000000..82ffd4cdea --- /dev/null +++ b/docs/kcl/settings.md @@ -0,0 +1,30 @@ +--- +title: "KCL settings" +excerpt: "Documentation of settings for the KCL language and Zoo Modeling App." +layout: manual +--- + +# Per-file settings + +Settings which affect a single file are configured using the settings attribute. +This must be at the top of the KCL file (comments before the attribute are permitted). +E.g., + +``` +// The settings attribute. +@settings(defaultLengthUnit = in) + +// The rest of your KCL code goes below... + +x = 42 // Represents 42 inches. +``` + +The settings attribute may contain multiple properties separated by commas. +Valid properties are: + +- `defaultLengthUnit`: the default length unit to use for numbers declared in this file. + - Accepted values: `mm`, `cm`, `m`, `in` (inches), `ft` (feet), `yd` (yards). +- `defaultAngleUnit`: the default angle unit to use for numbers declared in this file. + - Accepted values: `deg` (degrees), `rad` (radians). + +These settings override any project-wide settings (configured in project.toml or via the UI). diff --git a/docs/kcl/types.md b/docs/kcl/types.md index d219554826..042d076150 100644 --- a/docs/kcl/types.md +++ b/docs/kcl/types.md @@ -74,18 +74,15 @@ fn myFn(x) { As you can see above `myFn` just returns whatever it is given. -KCL's early drafts used positional arguments, but we now use keyword arguments. If you declare a -function like this: +KCL's early drafts used positional arguments, but we now use keyword arguments: ``` +// If you declare a function like this fn add(left, right) { return left + right } -``` - -You can call it like this: -``` +// You can call it like this: total = add(left = 1, right = 2) ``` @@ -111,14 +108,14 @@ three = add(1, delta = 2) It can be hard to read repeated function calls, because of all the nested brackets. -``` +```norun i = 1 x = h(g(f(i))) ``` You can make this easier to read by breaking it into many declarations, but that is a bit annoying. -``` +```norun i = 1 x0 = f(i) x1 = g(x0) @@ -133,12 +130,12 @@ the `%` in the right-hand side. So, this means `x |> f(%) |> g(%)` is shorthand for `g(f(x))`. The code example above, with its somewhat-clunky `x0` and `x1` constants could be rewritten as -``` +```norun i = 1 x = i -|> f(%) -|> g(%) -|> h(%) + |> f(%) + |> g(%) + |> h(%) ``` This helps keep your code neat and avoid unnecessary declarations. @@ -147,12 +144,12 @@ This helps keep your code neat and avoid unnecessary declarations. Say you have a long pipeline of sketch functions, like this: -``` -startSketch() -|> line(%, end = [3, 4]) -|> line(%, end = [10, 10]) -|> line(%, end = [-13, -14]) -|> close(%) +```norun +startSketchOn('XZ') + |> line(%, end = [3, 4]) + |> line(%, end = [10, 10]) + |> line(%, end = [-13, -14]) + |> close(%) ``` In this example, each function call outputs a sketch, and it gets put into the next function call via @@ -162,12 +159,12 @@ If a function call uses an unlabeled first parameter, it will default to `%` if means that `|> line(%, end = [3, 4])` and `|> line(end = [3, 4])` are equivalent! So the above could be rewritten as -``` -startSketch() -|> line(end = [3, 4]) -|> line(end = [10, 10]) -|> line(end = [-13, -14]) -|> close() +```norun +startSketchOn('XZ') + |> line(end = [3, 4]) + |> line(end = [10, 10]) + |> line(end = [-13, -14]) + |> close() ``` Note that we are still in the process of migrating KCL's standard library to use keyword arguments. So some @@ -184,7 +181,7 @@ Tags are used to give a name (tag) to a specific path. The syntax for declaring a tag is `$myTag` you would use it in the following way: -``` +```norun startSketchOn('XZ') |> startProfileAt(origin, %) |> angledLine({angle = 0, length = 191.26}, %, $rectangleSegmentA001) @@ -216,7 +213,7 @@ use the tag `rectangleSegmentA001` in any function or expression in the file. However if the code was written like this: -``` +```norun fn rect(origin) { return startSketchOn('XZ') |> startProfileAt(origin, %) @@ -244,7 +241,7 @@ However you likely want to use those tags somewhere outside the `rect` function. Tags are accessible through the sketch group they are declared in. For example the following code works. -``` +```norun fn rect(origin) { return startSketchOn('XZ') |> startProfileAt(origin, %) diff --git a/src/wasm-lib/kcl/src/docs/gen_std_tests.rs b/src/wasm-lib/kcl/src/docs/gen_std_tests.rs index 0dc0b3f899..a4ce4912da 100644 --- a/src/wasm-lib/kcl/src/docs/gen_std_tests.rs +++ b/src/wasm-lib/kcl/src/docs/gen_std_tests.rs @@ -1,4 +1,8 @@ -use std::collections::{BTreeMap, HashMap}; +use std::{ + collections::{BTreeMap, HashMap}, + fs::File, + io::Read as _, +}; use anyhow::Result; use base64::Engine; @@ -7,15 +11,18 @@ use handlebars::Renderable; use indexmap::IndexMap; use itertools::Itertools; use serde_json::json; +use tokio::task::JoinSet; use crate::{ docs::{is_primitive, StdLibFn}, std::StdLib, + ExecutorContext, }; use super::kcl_doc::{ConstData, DocData, FnData}; const TYPES_DIR: &str = "../../../docs/kcl/types"; +const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"]; fn init_handlebars() -> Result> { let mut hbs = handlebars::Handlebars::new(); @@ -345,7 +352,18 @@ fn generate_index(combined: &IndexMap>, kcl_lib: &[Doc .collect(); sorted.sort_by(|t1, t2| t1.0.cmp(&t2.0)); let data: Vec<_> = sorted.into_iter().map(|(_, val)| val).collect(); + + let topics: Vec<_> = LANG_TOPICS + .iter() + .map(|name| { + json!({ + "name": name, + "file_name": name.to_lowercase().replace(' ', "-"), + }) + }) + .collect(); let data = json!({ + "lang_topics": topics, "modules": data, }); @@ -991,3 +1009,67 @@ fn test_generate_stdlib_json_schema() { &serde_json::to_string_pretty(&json_data).unwrap(), ); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_code_in_topics() { + let mut join_set = JoinSet::new(); + for name in LANG_TOPICS { + let filename = format!("../../../docs/kcl/{}.md", name.to_lowercase().replace(' ', "-")); + let mut file = File::open(&filename).unwrap(); + let mut text = String::new(); + file.read_to_string(&mut text).unwrap(); + + for (i, (eg, attr)) in find_examples(&text, &filename).into_iter().enumerate() { + if attr == "norun" { + continue; + } + + let f = filename.clone(); + join_set.spawn(async move { (format!("{f}, example {i}"), run_example(&eg).await) }); + } + } + let results: Vec<_> = join_set + .join_all() + .await + .into_iter() + .filter_map(|a| a.1.err().map(|e| format!("{}: {}", a.0, e))) + .collect(); + assert!(results.is_empty(), "Failures: {}", results.join(", ")) +} + +fn find_examples(text: &str, filename: &str) -> Vec<(String, String)> { + let mut buf = String::new(); + let mut attr = String::new(); + let mut in_eg = false; + let mut result = Vec::new(); + for line in text.lines() { + if let Some(rest) = line.strip_prefix("```") { + if in_eg { + result.push((buf, attr)); + buf = String::new(); + attr = String::new(); + in_eg = false; + } else { + attr = rest.to_owned(); + in_eg = true; + } + continue; + } + if in_eg { + buf.push('\n'); + buf.push_str(line) + } + } + + assert!(!in_eg, "Unclosed code tags in {}", filename); + + result +} + +async fn run_example(text: &str) -> Result<()> { + let program = crate::Program::parse_no_errs(text)?; + let ctx = ExecutorContext::new_with_default_client(crate::UnitLength::Mm).await?; + let mut exec_state = crate::execution::ExecState::new(&ctx.settings); + ctx.run(&program, &mut exec_state).await?; + Ok(()) +} diff --git a/src/wasm-lib/kcl/src/docs/templates/index.hbs b/src/wasm-lib/kcl/src/docs/templates/index.hbs index c94381cbef..17a08eeee1 100644 --- a/src/wasm-lib/kcl/src/docs/templates/index.hbs +++ b/src/wasm-lib/kcl/src/docs/templates/index.hbs @@ -6,9 +6,14 @@ layout: manual ## Table of Contents -* [Types](kcl/types) -* [Modules](kcl/modules) -* [Known Issues](kcl/KNOWN-ISSUES) +### Language + +{{#each lang_topics}} +* [`{{name}}`](kcl/{{file_name}}) +{{/each}} + +### Standard library + {{#each modules}} * **`{{name}}`** {{#each functions}} diff --git a/src/wasm-lib/kcl/src/execution/annotations.rs b/src/wasm-lib/kcl/src/execution/annotations.rs index 1bd6a24bdd..449e3c629b 100644 --- a/src/wasm-lib/kcl/src/execution/annotations.rs +++ b/src/wasm-lib/kcl/src/execution/annotations.rs @@ -65,7 +65,7 @@ impl UnitLen { "yd" => Ok(UnitLen::Yards), value => Err(KclError::Semantic(KclErrorDetails { message: format!( - "Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`" + "Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`" ), source_ranges: vec![source_range], })),