Skip to content

Commit

Permalink
Test code examples in docs and add docs for per-file settings
Browse files Browse the repository at this point in the history
Signed-off-by: Nick Cameron <[email protected]>
  • Loading branch information
nrc committed Feb 24, 2025
1 parent d78648f commit 37b5ae1
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 42 deletions.
11 changes: 8 additions & 3 deletions docs/kcl/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
File renamed without changes.
14 changes: 7 additions & 7 deletions docs/kcl/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
```

Expand All @@ -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.
Expand All @@ -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"
```
Expand All @@ -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"
```
Expand Down
30 changes: 30 additions & 0 deletions docs/kcl/settings.md
Original file line number Diff line number Diff line change
@@ -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).
51 changes: 24 additions & 27 deletions docs/kcl/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```

Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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, %)
Expand Down Expand Up @@ -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, %)
Expand Down
84 changes: 83 additions & 1 deletion src/wasm-lib/kcl/src/docs/gen_std_tests.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<handlebars::Handlebars<'static>> {
let mut hbs = handlebars::Handlebars::new();
Expand Down Expand Up @@ -345,7 +352,18 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, 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,
});

Expand Down Expand Up @@ -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(())
}
11 changes: 8 additions & 3 deletions src/wasm-lib/kcl/src/docs/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
2 changes: 1 addition & 1 deletion src/wasm-lib/kcl/src/execution/annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
})),
Expand Down

0 comments on commit 37b5ae1

Please sign in to comment.