KCL: New simulation test pipeline (#4351)

The idea behind this is to test all the various stages of executing KCL
separately, i.e.

 - Start with a program
 - Tokenize it
 - Parse those tokens into an AST
 - Recast the AST
 - Execute the AST, outputting
   - a PNG of the rendered model
   - serialized program memory

Each of these steps reads some input and writes some output to disk.
The output of one step becomes the input to the next step. These
intermediate artifacts are also snapshotted (like expectorate or 2020)
to ensure we're aware of any changes to how KCL works. A change could
be a bug, or it could be harmless, or deliberate, but keeping it checked
into the repo means we can easily track changes.

Note: UUIDs sent back by the engine are currently nondeterministic, so
they would break all the snapshot tests. So, the snapshots use a regex
filter and replace anything that looks like a uuid with [uuid] when
writing program memory to a snapshot. In the future I hope our UUIDs will
be seedable and easy to make deterministic. At that point, we can stop
filtering the UUIDs.

We run this pipeline on many different KCL programs. Each keeps its
inputs (KCL programs), outputs (PNG, program memory snapshot) and
intermediate artifacts (AST, token lists, etc) in that directory.

I also added a new `just` command to easily generate these tests.
You can run `just new-sim-test gear $(cat gear.kcl)` to set up a new
gear test directory and generate all the intermediate artifacts for the
first time. This doesn't need any macros, it just appends some new lines
of normal Rust source code to `tests.rs`, so it's easy to see exactly
what the code is doing.

This uses `cargo insta` for convenient snapshot testing of artifacts
as JSON, and `twenty-twenty` for snapshotting PNGs.

This was heavily inspired by Predrag Gruevski's talk at EuroRust 2024
about deterministic simulation testing, and how it can both reduce bugs
and also reduce testing/CI time. Very grateful to him for chatting with
me about this over the last couple of weeks.
This commit is contained in:
Adam Chalmers
2024-10-30 12:14:17 -05:00
committed by GitHub
parent 0d52851da2
commit 0c6c646fe7
18 changed files with 4253 additions and 26 deletions

View File

@ -0,0 +1,2 @@
test:
runner: nextest

View File

@ -1453,13 +1453,14 @@ checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
[[package]]
name = "insta"
version = "1.40.0"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60"
checksum = "a1f72d3e19488cf7d8ea52d2fc0f8754fc933398b337cd3cbdb28aaeb35159ef"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"regex",
"serde",
"similar",
]

View File

@ -57,6 +57,10 @@ debug = true
[profile.dev]
debug = 0
[profile.dev.package]
insta.opt-level = 3
similar.opt-level = 3
[profile.test]
debug = "line-tables-only"

View File

@ -1,10 +1,29 @@
cnr := "cargo nextest run"
cita := "cargo insta test --accept"
# Create a new KCL snapshot test from `tests/inputs/my-test.kcl`.
new-test name:
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
TWENTY_TWENTY=overwrite cargo nextest run --test executor -E 'test(=visuals::{{name}})'
TWENTY_TWENTY=overwrite {{cnr}} --test executor -E 'test(=visuals::{{name}})'
lint:
cargo clippy --workspace --all-targets -- -D warnings
redo-kcl-stdlib-docs:
EXPECTORATE=overwrite cargo nextest run -p kcl-lib docs::gen_std_tests::test_generate_stdlib
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
# Create a new KCL deterministic simulation test case.
new-sim-test test_name kcl_program:
# Each test file gets its own directory. This will contain the KCL program, and its
# snapshotted artifacts (e.g. serialized tokens, serialized ASTs, program memory,
# PNG snapshots, etc).
mkdir kcl/tests/{{test_name}}
echo "{{kcl_program}}" > kcl/tests/{{test_name}}/input.kcl
# Add the various tests for this new test case.
cat kcl/tests/simtest.tmpl | sed "s/TEST_NAME_HERE/{{test_name}}/" >> kcl/src/tests.rs
# Run all the tests for the first time, in the right order.
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse
{{cita}} -p kcl-lib -- tests::{{test_name}}::unparse
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute

View File

@ -77,16 +77,8 @@ engine = []
pyo3 = ["dep:pyo3"]
# Helper functions also used in benchmarks.
lsp-test-util = []
tabled = ["dep:tabled"]
[profile.release]
panic = "abort"
debug = true
[profile.bench]
debug = true # Flamegraphs of benchmarks require accurate debug symbols
[dev-dependencies]
base64 = "0.22.1"
criterion = { version = "0.5.1", features = ["async_tokio"] }
@ -94,7 +86,7 @@ expectorate = "1.1.0"
handlebars = "6.2.0"
iai = "0.1"
image = { version = "0.25.3", default-features = false, features = ["png"] }
insta = { version = "1.40.0", features = ["json"] }
insta = { version = "1.41.0", features = ["json", "filters"] }
itertools = "0.13.0"
pretty_assertions = "1.4.1"
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }

View File

@ -2454,7 +2454,19 @@ impl ExecutorContext {
id_generator: IdGenerator,
project_directory: Option<String>,
) -> Result<TakeSnapshot> {
let _ = self.run(program, None, id_generator, project_directory).await?;
self.execute_and_prepare(program, id_generator, project_directory)
.await
.map(|(_state, snap)| snap)
}
/// Execute the program, return the interpreter and outputs.
pub async fn execute_and_prepare(
&self,
program: &Program,
id_generator: IdGenerator,
project_directory: Option<String>,
) -> Result<(ExecState, TakeSnapshot)> {
let state = self.run(program, None, id_generator, project_directory).await?;
// Zoom to fit.
self.engine
@ -2487,7 +2499,7 @@ impl ExecutorContext {
else {
anyhow::bail!("Unexpected response from engine: {:?}", resp);
};
Ok(contents)
Ok((state, contents))
}
}

View File

@ -25,6 +25,8 @@ pub mod lint;
pub mod lsp;
pub mod parser;
pub mod settings;
#[cfg(test)]
mod simulation_tests;
pub mod std;
#[cfg(not(target_arch = "wasm32"))]
pub mod test_server;

View File

@ -3043,7 +3043,6 @@ e
let parser = crate::parser::Parser::new(tokens);
let result = parser.ast();
assert!(result.is_err());
dbg!(&result);
assert!(result
.err()
.unwrap()

View File

@ -0,0 +1,130 @@
use crate::{ast::types::Program, errors::KclError, parser::Parser, token::Token};
/// Deserialize the data from a snapshot.
fn get<T: serde::de::DeserializeOwned>(snapshot: &str) -> T {
let mut parts = snapshot.split("---");
let _empty = parts.next().unwrap();
let _header = parts.next().unwrap();
let snapshot_data = parts.next().unwrap();
serde_json::from_str(snapshot_data)
.and_then(serde_json::from_value)
.unwrap()
}
fn assert_snapshot<F, R>(test_name: &str, operation: &str, f: F)
where
F: FnOnce() -> R,
{
let mut settings = insta::Settings::clone_current();
// These make the snapshots more readable and match our dir structure.
settings.set_omit_expression(true);
settings.set_snapshot_path(format!("../tests/{test_name}"));
settings.set_prepend_module_to_snapshot(false);
settings.set_description(format!("{operation} {test_name}.kcl"));
// Sorting maps makes them easier to diff.
settings.set_sort_maps(true);
// Replace UUIDs with the string "[uuid]", because otherwise the tests would constantly
// be changing the UUID. This is a stopgap measure until we make the engine more deterministic.
settings.add_filter(
r"\b[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\b",
"[uuid]",
);
// Run `f` (the closure that was passed in) with these settings.
settings.bind(f);
}
fn read(filename: &'static str, test_name: &str) -> String {
std::fs::read_to_string(format!("tests/{test_name}/{filename}")).unwrap()
}
fn tokenize(test_name: &str) {
let input = read("input.kcl", test_name);
let token_res = crate::token::lexer(&input);
assert_snapshot(test_name, "Result of tokenizing", || {
insta::assert_json_snapshot!("tokens", token_res);
});
}
fn parse(test_name: &str) {
let input = read("tokens.snap", test_name);
let tokens: Result<Vec<Token>, KclError> = get(&input);
let Ok(tokens) = tokens else {
return;
};
// Parse the tokens into an AST.
let parse_res = Parser::new(tokens).ast();
assert_snapshot(test_name, "Result of parsing", || {
insta::assert_json_snapshot!("ast", parse_res);
});
}
fn unparse(test_name: &str) {
let input = read("ast.snap", test_name);
let ast_res: Result<Program, KclError> = get(&input);
let Ok(ast) = ast_res else {
return;
};
// Check recasting the AST produces the original string.
let actual = ast.recast(&Default::default(), 0);
let expected = read("input.kcl", test_name);
pretty_assertions::assert_eq!(
actual,
expected,
"Parse then unparse didn't recreate the original KCL file"
);
}
async fn execute(test_name: &str) {
// Read the AST from disk.
let input = read("ast.snap", test_name);
let ast_res: Result<Program, KclError> = get(&input);
let Ok(ast) = ast_res else {
return;
};
// Run the program.
let exec_res = crate::test_server::execute_and_snapshot_ast(ast, crate::settings::types::UnitLength::Mm).await;
match exec_res {
Ok((program_memory, png)) => {
twenty_twenty::assert_image(format!("tests/{test_name}/rendered_model.png"), &png, 0.99);
assert_snapshot(test_name, "Program memory after executing", || {
insta::assert_json_snapshot!("program_memory", program_memory);
});
}
Err(e) => {
assert_snapshot(test_name, "Error from executing", || {
insta::assert_snapshot!("execution_error", e);
});
}
}
}
mod cube {
const TEST_NAME: &str = "cube";
/// Test tokenizing KCL.
#[test]
fn tokenize() {
super::tokenize(TEST_NAME)
}
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME).await
}
}

View File

@ -0,0 +1,969 @@
---
source: kcl/src/tests.rs
expression: tokens
---
Ok:
- type: keyword
start: 0
end: 2
value: fn
- type: whitespace
start: 2
end: 3
value: " "
- type: word
start: 3
end: 9
value: square
- type: whitespace
start: 9
end: 10
value: " "
- type: operator
start: 10
end: 11
value: "="
- type: whitespace
start: 11
end: 12
value: " "
- type: brace
start: 12
end: 13
value: (
- type: word
start: 13
end: 19
value: length
- type: comma
start: 19
end: 20
value: ","
- type: whitespace
start: 20
end: 21
value: " "
- type: word
start: 21
end: 27
value: center
- type: brace
start: 27
end: 28
value: )
- type: whitespace
start: 28
end: 29
value: " "
- type: operator
start: 29
end: 31
value: "=>"
- type: whitespace
start: 31
end: 32
value: " "
- type: brace
start: 32
end: 33
value: "{"
- type: whitespace
start: 33
end: 36
value: "\n "
- type: word
start: 36
end: 37
value: l
- type: whitespace
start: 37
end: 38
value: " "
- type: operator
start: 38
end: 39
value: "="
- type: whitespace
start: 39
end: 40
value: " "
- type: word
start: 40
end: 46
value: length
- type: whitespace
start: 46
end: 47
value: " "
- type: operator
start: 47
end: 48
value: /
- type: whitespace
start: 48
end: 49
value: " "
- type: number
start: 49
end: 50
value: "2"
- type: whitespace
start: 50
end: 53
value: "\n "
- type: word
start: 53
end: 54
value: x
- type: whitespace
start: 54
end: 55
value: " "
- type: operator
start: 55
end: 56
value: "="
- type: whitespace
start: 56
end: 57
value: " "
- type: word
start: 57
end: 63
value: center
- type: brace
start: 63
end: 64
value: "["
- type: number
start: 64
end: 65
value: "0"
- type: brace
start: 65
end: 66
value: "]"
- type: whitespace
start: 66
end: 69
value: "\n "
- type: word
start: 69
end: 70
value: y
- type: whitespace
start: 70
end: 71
value: " "
- type: operator
start: 71
end: 72
value: "="
- type: whitespace
start: 72
end: 73
value: " "
- type: word
start: 73
end: 79
value: center
- type: brace
start: 79
end: 80
value: "["
- type: number
start: 80
end: 81
value: "1"
- type: brace
start: 81
end: 82
value: "]"
- type: whitespace
start: 82
end: 85
value: "\n "
- type: word
start: 85
end: 87
value: p0
- type: whitespace
start: 87
end: 88
value: " "
- type: operator
start: 88
end: 89
value: "="
- type: whitespace
start: 89
end: 90
value: " "
- type: brace
start: 90
end: 91
value: "["
- type: operator
start: 91
end: 92
value: "-"
- type: word
start: 92
end: 93
value: l
- type: whitespace
start: 93
end: 94
value: " "
- type: operator
start: 94
end: 95
value: +
- type: whitespace
start: 95
end: 96
value: " "
- type: word
start: 96
end: 97
value: x
- type: comma
start: 97
end: 98
value: ","
- type: whitespace
start: 98
end: 99
value: " "
- type: operator
start: 99
end: 100
value: "-"
- type: word
start: 100
end: 101
value: l
- type: whitespace
start: 101
end: 102
value: " "
- type: operator
start: 102
end: 103
value: +
- type: whitespace
start: 103
end: 104
value: " "
- type: word
start: 104
end: 105
value: y
- type: brace
start: 105
end: 106
value: "]"
- type: whitespace
start: 106
end: 109
value: "\n "
- type: word
start: 109
end: 111
value: p1
- type: whitespace
start: 111
end: 112
value: " "
- type: operator
start: 112
end: 113
value: "="
- type: whitespace
start: 113
end: 114
value: " "
- type: brace
start: 114
end: 115
value: "["
- type: operator
start: 115
end: 116
value: "-"
- type: word
start: 116
end: 117
value: l
- type: whitespace
start: 117
end: 118
value: " "
- type: operator
start: 118
end: 119
value: +
- type: whitespace
start: 119
end: 120
value: " "
- type: word
start: 120
end: 121
value: x
- type: comma
start: 121
end: 122
value: ","
- type: whitespace
start: 122
end: 124
value: " "
- type: word
start: 124
end: 125
value: l
- type: whitespace
start: 125
end: 126
value: " "
- type: operator
start: 126
end: 127
value: +
- type: whitespace
start: 127
end: 128
value: " "
- type: word
start: 128
end: 129
value: y
- type: brace
start: 129
end: 130
value: "]"
- type: whitespace
start: 130
end: 133
value: "\n "
- type: word
start: 133
end: 135
value: p2
- type: whitespace
start: 135
end: 136
value: " "
- type: operator
start: 136
end: 137
value: "="
- type: whitespace
start: 137
end: 138
value: " "
- type: brace
start: 138
end: 139
value: "["
- type: whitespace
start: 139
end: 140
value: " "
- type: word
start: 140
end: 141
value: l
- type: whitespace
start: 141
end: 142
value: " "
- type: operator
start: 142
end: 143
value: +
- type: whitespace
start: 143
end: 144
value: " "
- type: word
start: 144
end: 145
value: x
- type: comma
start: 145
end: 146
value: ","
- type: whitespace
start: 146
end: 148
value: " "
- type: word
start: 148
end: 149
value: l
- type: whitespace
start: 149
end: 150
value: " "
- type: operator
start: 150
end: 151
value: +
- type: whitespace
start: 151
end: 152
value: " "
- type: word
start: 152
end: 153
value: y
- type: brace
start: 153
end: 154
value: "]"
- type: whitespace
start: 154
end: 157
value: "\n "
- type: word
start: 157
end: 159
value: p3
- type: whitespace
start: 159
end: 160
value: " "
- type: operator
start: 160
end: 161
value: "="
- type: whitespace
start: 161
end: 162
value: " "
- type: brace
start: 162
end: 163
value: "["
- type: whitespace
start: 163
end: 164
value: " "
- type: word
start: 164
end: 165
value: l
- type: whitespace
start: 165
end: 166
value: " "
- type: operator
start: 166
end: 167
value: +
- type: whitespace
start: 167
end: 168
value: " "
- type: word
start: 168
end: 169
value: x
- type: comma
start: 169
end: 170
value: ","
- type: whitespace
start: 170
end: 171
value: " "
- type: operator
start: 171
end: 172
value: "-"
- type: word
start: 172
end: 173
value: l
- type: whitespace
start: 173
end: 174
value: " "
- type: operator
start: 174
end: 175
value: +
- type: whitespace
start: 175
end: 176
value: " "
- type: word
start: 176
end: 177
value: y
- type: brace
start: 177
end: 178
value: "]"
- type: whitespace
start: 178
end: 181
value: "\n "
- type: keyword
start: 181
end: 187
value: return
- type: whitespace
start: 187
end: 188
value: " "
- type: word
start: 188
end: 201
value: startSketchAt
- type: brace
start: 201
end: 202
value: (
- type: word
start: 202
end: 204
value: p0
- type: brace
start: 204
end: 205
value: )
- type: whitespace
start: 205
end: 210
value: "\n "
- type: operator
start: 210
end: 212
value: "|>"
- type: whitespace
start: 212
end: 213
value: " "
- type: word
start: 213
end: 219
value: lineTo
- type: brace
start: 219
end: 220
value: (
- type: word
start: 220
end: 222
value: p1
- type: comma
start: 222
end: 223
value: ","
- type: whitespace
start: 223
end: 224
value: " "
- type: operator
start: 224
end: 225
value: "%"
- type: brace
start: 225
end: 226
value: )
- type: whitespace
start: 226
end: 231
value: "\n "
- type: operator
start: 231
end: 233
value: "|>"
- type: whitespace
start: 233
end: 234
value: " "
- type: word
start: 234
end: 240
value: lineTo
- type: brace
start: 240
end: 241
value: (
- type: word
start: 241
end: 243
value: p2
- type: comma
start: 243
end: 244
value: ","
- type: whitespace
start: 244
end: 245
value: " "
- type: operator
start: 245
end: 246
value: "%"
- type: brace
start: 246
end: 247
value: )
- type: whitespace
start: 247
end: 252
value: "\n "
- type: operator
start: 252
end: 254
value: "|>"
- type: whitespace
start: 254
end: 255
value: " "
- type: word
start: 255
end: 261
value: lineTo
- type: brace
start: 261
end: 262
value: (
- type: word
start: 262
end: 264
value: p3
- type: comma
start: 264
end: 265
value: ","
- type: whitespace
start: 265
end: 266
value: " "
- type: operator
start: 266
end: 267
value: "%"
- type: brace
start: 267
end: 268
value: )
- type: whitespace
start: 268
end: 273
value: "\n "
- type: operator
start: 273
end: 275
value: "|>"
- type: whitespace
start: 275
end: 276
value: " "
- type: word
start: 276
end: 282
value: lineTo
- type: brace
start: 282
end: 283
value: (
- type: word
start: 283
end: 285
value: p0
- type: comma
start: 285
end: 286
value: ","
- type: whitespace
start: 286
end: 287
value: " "
- type: operator
start: 287
end: 288
value: "%"
- type: brace
start: 288
end: 289
value: )
- type: whitespace
start: 289
end: 294
value: "\n "
- type: operator
start: 294
end: 296
value: "|>"
- type: whitespace
start: 296
end: 297
value: " "
- type: word
start: 297
end: 302
value: close
- type: brace
start: 302
end: 303
value: (
- type: operator
start: 303
end: 304
value: "%"
- type: brace
start: 304
end: 305
value: )
- type: whitespace
start: 305
end: 306
value: "\n"
- type: brace
start: 306
end: 307
value: "}"
- type: whitespace
start: 307
end: 309
value: "\n\n"
- type: keyword
start: 309
end: 311
value: fn
- type: whitespace
start: 311
end: 312
value: " "
- type: word
start: 312
end: 316
value: cube
- type: whitespace
start: 316
end: 317
value: " "
- type: operator
start: 317
end: 318
value: "="
- type: whitespace
start: 318
end: 319
value: " "
- type: brace
start: 319
end: 320
value: (
- type: word
start: 320
end: 326
value: length
- type: comma
start: 326
end: 327
value: ","
- type: whitespace
start: 327
end: 328
value: " "
- type: word
start: 328
end: 334
value: center
- type: brace
start: 334
end: 335
value: )
- type: whitespace
start: 335
end: 336
value: " "
- type: operator
start: 336
end: 338
value: "=>"
- type: whitespace
start: 338
end: 339
value: " "
- type: brace
start: 339
end: 340
value: "{"
- type: whitespace
start: 340
end: 343
value: "\n "
- type: keyword
start: 343
end: 349
value: return
- type: whitespace
start: 349
end: 350
value: " "
- type: word
start: 350
end: 356
value: square
- type: brace
start: 356
end: 357
value: (
- type: word
start: 357
end: 363
value: length
- type: comma
start: 363
end: 364
value: ","
- type: whitespace
start: 364
end: 365
value: " "
- type: word
start: 365
end: 371
value: center
- type: brace
start: 371
end: 372
value: )
- type: whitespace
start: 372
end: 377
value: "\n "
- type: operator
start: 377
end: 379
value: "|>"
- type: whitespace
start: 379
end: 380
value: " "
- type: word
start: 380
end: 387
value: extrude
- type: brace
start: 387
end: 388
value: (
- type: word
start: 388
end: 394
value: length
- type: comma
start: 394
end: 395
value: ","
- type: whitespace
start: 395
end: 396
value: " "
- type: operator
start: 396
end: 397
value: "%"
- type: brace
start: 397
end: 398
value: )
- type: whitespace
start: 398
end: 399
value: "\n"
- type: brace
start: 399
end: 400
value: "}"
- type: whitespace
start: 400
end: 402
value: "\n\n"
- type: word
start: 402
end: 407
value: width
- type: whitespace
start: 407
end: 408
value: " "
- type: operator
start: 408
end: 409
value: "="
- type: whitespace
start: 409
end: 410
value: " "
- type: number
start: 410
end: 412
value: "20"
- type: whitespace
start: 412
end: 413
value: "\n"
- type: word
start: 413
end: 417
value: cube
- type: brace
start: 417
end: 418
value: (
- type: word
start: 418
end: 423
value: width
- type: comma
start: 423
end: 424
value: ","
- type: whitespace
start: 424
end: 425
value: " "
- type: brace
start: 425
end: 426
value: "["
- type: number
start: 426
end: 428
value: "20"
- type: comma
start: 428
end: 429
value: ","
- type: whitespace
start: 429
end: 430
value: " "
- type: number
start: 430
end: 431
value: "0"
- type: brace
start: 431
end: 432
value: "]"
- type: brace
start: 432
end: 433
value: )
- type: whitespace
start: 433
end: 434
value: "\n"

View File

@ -1,7 +1,8 @@
//! Types used to send data to the test server.
use crate::{
executor::{new_zoo_client, ExecutorContext, ExecutorSettings, IdGenerator},
ast::types::Program,
executor::{new_zoo_client, ExecutorContext, ExecutorSettings, IdGenerator, ProgramMemory},
settings::types::UnitLength,
};
@ -16,22 +17,37 @@ pub struct RequestBody {
/// This returns the bytes of the snapshot.
pub async fn execute_and_snapshot(code: &str, units: UnitLength) -> anyhow::Result<image::DynamicImage> {
let ctx = new_context(units, true).await?;
do_execute_and_snapshot(&ctx, code).await
let tokens = crate::token::lexer(code)?;
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast()?;
do_execute_and_snapshot(&ctx, program).await.map(|(_state, snap)| snap)
}
/// Executes a kcl program and takes a snapshot of the result.
/// This returns the bytes of the snapshot.
pub async fn execute_and_snapshot_ast(
ast: Program,
units: UnitLength,
) -> anyhow::Result<(ProgramMemory, image::DynamicImage)> {
let ctx = new_context(units, true).await?;
do_execute_and_snapshot(&ctx, ast)
.await
.map(|(state, snap)| (state.memory, snap))
}
pub async fn execute_and_snapshot_no_auth(code: &str, units: UnitLength) -> anyhow::Result<image::DynamicImage> {
let ctx = new_context(units, false).await?;
do_execute_and_snapshot(&ctx, code).await
}
async fn do_execute_and_snapshot(ctx: &ExecutorContext, code: &str) -> anyhow::Result<image::DynamicImage> {
let tokens = crate::token::lexer(code)?;
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast()?;
do_execute_and_snapshot(&ctx, program).await.map(|(_state, snap)| snap)
}
let snapshot = ctx
.execute_and_prepare_snapshot(&program, IdGenerator::default(), None)
.await?;
async fn do_execute_and_snapshot(
ctx: &ExecutorContext,
program: Program,
) -> anyhow::Result<(crate::executor::ExecState, image::DynamicImage)> {
let (exec_state, snapshot) = ctx.execute_and_prepare(&program, IdGenerator::default(), None).await?;
// Create a temporary file to write the output to.
let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
@ -39,7 +55,7 @@ async fn do_execute_and_snapshot(ctx: &ExecutorContext, code: &str) -> anyhow::R
std::fs::write(&output_file, snapshot.contents.0)?;
// Decode the snapshot, return it.
let img = image::ImageReader::open(output_file).unwrap().decode()?;
Ok(img)
Ok((exec_state, img))
}
async fn new_context(units: UnitLength, with_auth: bool) -> anyhow::Result<ExecutorContext> {

View File

@ -0,0 +1,795 @@
---
source: kcl/src/tests.rs
description: Result of parsing cube.kcl
snapshot_kind: text
---
{
"Ok": {
"start": 0,
"end": 344,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 0,
"end": 316,
"declarations": [
{
"type": "VariableDeclarator",
"start": 3,
"end": 316,
"id": {
"type": "Identifier",
"start": 3,
"end": 7,
"name": "cube"
},
"init": {
"type": "FunctionExpression",
"type": "FunctionExpression",
"start": 10,
"end": 316,
"params": [
{
"type": "Parameter",
"identifier": {
"type": "Identifier",
"start": 11,
"end": 17,
"name": "length"
},
"optional": false
},
{
"type": "Parameter",
"identifier": {
"type": "Identifier",
"start": 19,
"end": 25,
"name": "center"
},
"optional": false
}
],
"body": {
"start": 30,
"end": 316,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 34,
"end": 48,
"declarations": [
{
"type": "VariableDeclarator",
"start": 34,
"end": 48,
"id": {
"type": "Identifier",
"start": 34,
"end": 35,
"name": "l"
},
"init": {
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 38,
"end": 48,
"operator": "/",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 38,
"end": 44,
"name": "length"
},
"right": {
"type": "Literal",
"type": "Literal",
"start": 47,
"end": 48,
"value": 2,
"raw": "2"
}
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 51,
"end": 64,
"declarations": [
{
"type": "VariableDeclarator",
"start": 51,
"end": 64,
"id": {
"type": "Identifier",
"start": 51,
"end": 52,
"name": "x"
},
"init": {
"type": "MemberExpression",
"type": "MemberExpression",
"start": 55,
"end": 64,
"object": {
"type": "Identifier",
"type": "Identifier",
"start": 55,
"end": 61,
"name": "center"
},
"property": {
"type": "Literal",
"type": "Literal",
"start": 62,
"end": 63,
"value": 0,
"raw": "0"
},
"computed": false
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 67,
"end": 80,
"declarations": [
{
"type": "VariableDeclarator",
"start": 67,
"end": 80,
"id": {
"type": "Identifier",
"start": 67,
"end": 68,
"name": "y"
},
"init": {
"type": "MemberExpression",
"type": "MemberExpression",
"start": 71,
"end": 80,
"object": {
"type": "Identifier",
"type": "Identifier",
"start": 71,
"end": 77,
"name": "center"
},
"property": {
"type": "Literal",
"type": "Literal",
"start": 78,
"end": 79,
"value": 1,
"raw": "1"
},
"computed": false
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 83,
"end": 104,
"declarations": [
{
"type": "VariableDeclarator",
"start": 83,
"end": 104,
"id": {
"type": "Identifier",
"start": 83,
"end": 85,
"name": "p0"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 88,
"end": 104,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 89,
"end": 95,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 89,
"end": 91,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 90,
"end": 91,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 94,
"end": 95,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 97,
"end": 103,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 97,
"end": 99,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 98,
"end": 99,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 102,
"end": 103,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 107,
"end": 127,
"declarations": [
{
"type": "VariableDeclarator",
"start": 107,
"end": 127,
"id": {
"type": "Identifier",
"start": 107,
"end": 109,
"name": "p1"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 112,
"end": 127,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 113,
"end": 119,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 113,
"end": 115,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 114,
"end": 115,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 118,
"end": 119,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 121,
"end": 126,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 121,
"end": 122,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 125,
"end": 126,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 130,
"end": 149,
"declarations": [
{
"type": "VariableDeclarator",
"start": 130,
"end": 149,
"id": {
"type": "Identifier",
"start": 130,
"end": 132,
"name": "p2"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 135,
"end": 149,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 136,
"end": 141,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 136,
"end": 137,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 140,
"end": 141,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 143,
"end": 148,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 143,
"end": 144,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 147,
"end": 148,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 152,
"end": 172,
"declarations": [
{
"type": "VariableDeclarator",
"start": 152,
"end": 172,
"id": {
"type": "Identifier",
"start": 152,
"end": 154,
"name": "p3"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 157,
"end": 172,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 158,
"end": 163,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 158,
"end": 159,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 162,
"end": 163,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 165,
"end": 171,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 165,
"end": 167,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 166,
"end": 167,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 170,
"end": 171,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "ReturnStatement",
"type": "ReturnStatement",
"start": 176,
"end": 314,
"argument": {
"type": "PipeExpression",
"type": "PipeExpression",
"start": 183,
"end": 314,
"body": [
{
"type": "CallExpression",
"type": "CallExpression",
"start": 183,
"end": 200,
"callee": {
"type": "Identifier",
"start": 183,
"end": 196,
"name": "startSketchAt"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 197,
"end": 199,
"name": "p0"
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 206,
"end": 219,
"callee": {
"type": "Identifier",
"start": 206,
"end": 212,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 213,
"end": 215,
"name": "p1"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 217,
"end": 218
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 225,
"end": 238,
"callee": {
"type": "Identifier",
"start": 225,
"end": 231,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 232,
"end": 234,
"name": "p2"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 236,
"end": 237
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 244,
"end": 257,
"callee": {
"type": "Identifier",
"start": 244,
"end": 250,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 251,
"end": 253,
"name": "p3"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 255,
"end": 256
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 263,
"end": 276,
"callee": {
"type": "Identifier",
"start": 263,
"end": 269,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 270,
"end": 272,
"name": "p0"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 274,
"end": 275
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 282,
"end": 290,
"callee": {
"type": "Identifier",
"start": 282,
"end": 287,
"name": "close"
},
"arguments": [
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 288,
"end": 289
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 296,
"end": 314,
"callee": {
"type": "Identifier",
"start": 296,
"end": 303,
"name": "extrude"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 304,
"end": 310,
"name": "length"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 312,
"end": 313
}
],
"optional": false
}
]
}
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"6": [
{
"type": "NonCodeNode",
"start": 172,
"end": 176,
"value": {
"type": "newLine"
}
}
]
},
"start": []
}
}
}
}
],
"kind": "fn"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 318,
"end": 343,
"declarations": [
{
"type": "VariableDeclarator",
"start": 318,
"end": 343,
"id": {
"type": "Identifier",
"start": 318,
"end": 324,
"name": "myCube"
},
"init": {
"type": "CallExpression",
"type": "CallExpression",
"start": 327,
"end": 343,
"callee": {
"type": "Identifier",
"start": 327,
"end": 331,
"name": "cube"
},
"arguments": [
{
"type": "Literal",
"type": "Literal",
"start": 332,
"end": 334,
"value": 40,
"raw": "40"
},
{
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 336,
"end": 342,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 337,
"end": 338,
"value": 0,
"raw": "0"
},
{
"type": "Literal",
"type": "Literal",
"start": 340,
"end": 341,
"value": 0,
"raw": "0"
}
]
}
],
"optional": false
}
}
],
"kind": "const"
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"type": "NonCodeNode",
"start": 316,
"end": 318,
"value": {
"type": "newLine"
}
}
]
},
"start": []
}
}
}

View File

@ -0,0 +1,7 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 98
description: Error from executing cube.kcl
snapshot_kind: text
---
No API token found in environment variables. Use KITTYCAD_API_TOKEN or ZOO_API_TOKEN

View File

@ -0,0 +1,19 @@
fn cube = (length, center) => {
l = length / 2
x = center[0]
y = center[1]
p0 = [-l + x, -l + y]
p1 = [-l + x, l + y]
p2 = [l + x, l + y]
p3 = [l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
}
myCube = cube(40, [0, 0])

View File

@ -0,0 +1,976 @@
---
source: kcl/src/tests.rs
description: Program memory after executing cube.kcl
snapshot_kind: text
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"__meta": []
},
"cube": {
"type": "Function",
"expression": {
"type": "FunctionExpression",
"start": 10,
"end": 316,
"params": [
{
"type": "Parameter",
"identifier": {
"type": "Identifier",
"start": 11,
"end": 17,
"name": "length"
},
"optional": false
},
{
"type": "Parameter",
"identifier": {
"type": "Identifier",
"start": 19,
"end": 25,
"name": "center"
},
"optional": false
}
],
"body": {
"start": 30,
"end": 316,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 34,
"end": 48,
"declarations": [
{
"type": "VariableDeclarator",
"start": 34,
"end": 48,
"id": {
"type": "Identifier",
"start": 34,
"end": 35,
"name": "l"
},
"init": {
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 38,
"end": 48,
"operator": "/",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 38,
"end": 44,
"name": "length"
},
"right": {
"type": "Literal",
"type": "Literal",
"start": 47,
"end": 48,
"value": 2,
"raw": "2"
}
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 51,
"end": 64,
"declarations": [
{
"type": "VariableDeclarator",
"start": 51,
"end": 64,
"id": {
"type": "Identifier",
"start": 51,
"end": 52,
"name": "x"
},
"init": {
"type": "MemberExpression",
"type": "MemberExpression",
"start": 55,
"end": 64,
"object": {
"type": "Identifier",
"type": "Identifier",
"start": 55,
"end": 61,
"name": "center"
},
"property": {
"type": "Literal",
"type": "Literal",
"start": 62,
"end": 63,
"value": 0,
"raw": "0"
},
"computed": false
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 67,
"end": 80,
"declarations": [
{
"type": "VariableDeclarator",
"start": 67,
"end": 80,
"id": {
"type": "Identifier",
"start": 67,
"end": 68,
"name": "y"
},
"init": {
"type": "MemberExpression",
"type": "MemberExpression",
"start": 71,
"end": 80,
"object": {
"type": "Identifier",
"type": "Identifier",
"start": 71,
"end": 77,
"name": "center"
},
"property": {
"type": "Literal",
"type": "Literal",
"start": 78,
"end": 79,
"value": 1,
"raw": "1"
},
"computed": false
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 83,
"end": 104,
"declarations": [
{
"type": "VariableDeclarator",
"start": 83,
"end": 104,
"id": {
"type": "Identifier",
"start": 83,
"end": 85,
"name": "p0"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 88,
"end": 104,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 89,
"end": 95,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 89,
"end": 91,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 90,
"end": 91,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 94,
"end": 95,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 97,
"end": 103,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 97,
"end": 99,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 98,
"end": 99,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 102,
"end": 103,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 107,
"end": 127,
"declarations": [
{
"type": "VariableDeclarator",
"start": 107,
"end": 127,
"id": {
"type": "Identifier",
"start": 107,
"end": 109,
"name": "p1"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 112,
"end": 127,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 113,
"end": 119,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 113,
"end": 115,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 114,
"end": 115,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 118,
"end": 119,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 121,
"end": 126,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 121,
"end": 122,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 125,
"end": 126,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 130,
"end": 149,
"declarations": [
{
"type": "VariableDeclarator",
"start": 130,
"end": 149,
"id": {
"type": "Identifier",
"start": 130,
"end": 132,
"name": "p2"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 135,
"end": 149,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 136,
"end": 141,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 136,
"end": 137,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 140,
"end": 141,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 143,
"end": 148,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 143,
"end": 144,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 147,
"end": 148,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 152,
"end": 172,
"declarations": [
{
"type": "VariableDeclarator",
"start": 152,
"end": 172,
"id": {
"type": "Identifier",
"start": 152,
"end": 154,
"name": "p3"
},
"init": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 157,
"end": 172,
"elements": [
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 158,
"end": 163,
"operator": "+",
"left": {
"type": "Identifier",
"type": "Identifier",
"start": 158,
"end": 159,
"name": "l"
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 162,
"end": 163,
"name": "x"
}
},
{
"type": "BinaryExpression",
"type": "BinaryExpression",
"start": 165,
"end": 171,
"operator": "+",
"left": {
"type": "UnaryExpression",
"type": "UnaryExpression",
"start": 165,
"end": 167,
"operator": "-",
"argument": {
"type": "Identifier",
"type": "Identifier",
"start": 166,
"end": 167,
"name": "l"
}
},
"right": {
"type": "Identifier",
"type": "Identifier",
"start": 170,
"end": 171,
"name": "y"
}
}
]
}
}
],
"kind": "const"
},
{
"type": "ReturnStatement",
"type": "ReturnStatement",
"start": 176,
"end": 314,
"argument": {
"type": "PipeExpression",
"type": "PipeExpression",
"start": 183,
"end": 314,
"body": [
{
"type": "CallExpression",
"type": "CallExpression",
"start": 183,
"end": 200,
"callee": {
"type": "Identifier",
"start": 183,
"end": 196,
"name": "startSketchAt"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 197,
"end": 199,
"name": "p0"
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 206,
"end": 219,
"callee": {
"type": "Identifier",
"start": 206,
"end": 212,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 213,
"end": 215,
"name": "p1"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 217,
"end": 218
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 225,
"end": 238,
"callee": {
"type": "Identifier",
"start": 225,
"end": 231,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 232,
"end": 234,
"name": "p2"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 236,
"end": 237
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 244,
"end": 257,
"callee": {
"type": "Identifier",
"start": 244,
"end": 250,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 251,
"end": 253,
"name": "p3"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 255,
"end": 256
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 263,
"end": 276,
"callee": {
"type": "Identifier",
"start": 263,
"end": 269,
"name": "lineTo"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 270,
"end": 272,
"name": "p0"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 274,
"end": 275
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 282,
"end": 290,
"callee": {
"type": "Identifier",
"start": 282,
"end": 287,
"name": "close"
},
"arguments": [
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 288,
"end": 289
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 296,
"end": 314,
"callee": {
"type": "Identifier",
"start": 296,
"end": 303,
"name": "extrude"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 304,
"end": 310,
"name": "length"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 312,
"end": 313
}
],
"optional": false
}
]
}
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"6": [
{
"type": "NonCodeNode",
"start": 172,
"end": 176,
"value": {
"type": "newLine"
}
}
]
},
"start": []
}
}
},
"memory": {
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"__meta": []
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [
{
"sourceRange": [
10,
316
]
}
]
},
"myCube": {
"type": "Solid",
"type": "Solid",
"id": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
206,
219
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
225,
238
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
244,
257
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
263,
276
],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
206,
219
]
},
"from": [
-20.0,
-20.0
],
"tag": null,
"to": [
-20.0,
20.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
225,
238
]
},
"from": [
-20.0,
20.0
],
"tag": null,
"to": [
20.0,
20.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
244,
257
]
},
"from": [
20.0,
20.0
],
"tag": null,
"to": [
20.0,
-20.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
263,
276
]
},
"from": [
20.0,
-20.0
],
"tag": null,
"to": [
-20.0,
-20.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
282,
290
]
},
"from": [
-20.0,
-20.0
],
"tag": null,
"to": [
-20.0,
-20.0
],
"type": "ToPoint"
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"__meta": []
},
"start": {
"from": [
-20.0,
-20.0
],
"to": [
-20.0,
-20.0
],
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
183,
200
]
}
},
"__meta": [
{
"sourceRange": [
183,
200
]
}
]
},
"height": 40.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"__meta": [
{
"sourceRange": [
183,
200
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
mod TEST_NAME_HERE {
const TEST_NAME: &str = "TEST_NAME_HERE";
/// Test tokenizing KCL.
#[test]
fn tokenize() {
super::tokenize(TEST_NAME)
}
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME).await
}
}