Compare commits

...

18 Commits

Author SHA1 Message Date
5ae1aecd74 Reduce Python API surface area to what is necessary for kcl.py (#4637)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-03 17:34:58 +13:00
68ae7e98f9 Refactor SourceRange and ModuleId to make them better encapsualated (#4636)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-03 16:39:51 +13:00
56771d561a Bump rustls from 0.23.13 to 0.23.19 and rustls-pki-types (#4632) 2024-12-03 15:49:15 +13:00
f09411817c KCL AST: Call functions with keyword arguments (#4599)
Call expressions only, haven't done function expressions yet.

Part of https://github.com/KittyCAD/modeling-app/issues/4600
2024-12-02 21:23:18 +00:00
max
bed7ae3b8b Refactor addFillet into addEdgeTreatment Function Supporting Chamfers (#4593)
* refactor code mod and tests

* tsc

* make lint happy

* remove dumby data

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-12-02 21:43:59 +01:00
c43510732c KCL docs: Remove FunctionExpression (#4633)
KCL functions are a weird edge case, and the `FunctionExpression` field should not be included in its public API. That field is only there for implementation details, it shouldn't be exposed to users.

What's worse is that `FunctionExpression` includes a `Program` so every single AST node wound up being included in our docs.
2024-12-02 14:36:49 -06:00
51f0b669a4 fix: only count something as a directory if it has children (#4595)
* fix: only count something as a directory if it has children

* fix: playwright tests

* fix: return 0 if you cant find the projectfolder

* fix: remove folder count from e2e tests since it is unused currently

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
2024-12-02 15:16:43 -05:00
3cbedcd3e7 Bump clap from 4.5.20 to 4.5.21 in /src/wasm-lib (#4623)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.20 to 4.5.21.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.20...clap_complete-v4.5.21)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 09:28:57 -08:00
5d2fa43150 Bump dawidd6/action-download-artifact from 6 to 7 (#4621)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 6 to 7.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 09:27:57 -08:00
ec49b0752e Remove auto creation of the draft release (#4588)
Fixes #4586
2024-12-02 10:30:24 -05:00
3b171fb881 Update nightly and release icons from Figma (#4597)
* Update nightly and windows icons

* Remove icon.ico, keep only icon.png, see https://www.electron.build/icons.html#windows-nsis

* Revert "Remove icon.ico, keep only icon.png, see https://www.electron.build/icons.html#windows-nsis"

This reverts commit b97f81b07d.

* Update windows icons

* Reset windows ico

* Test ico no margin

* Converted with freeconvert

* Use convertico.com for conversion
2024-12-02 10:24:14 -05:00
0548409da0 Bump to Rust 1.83 (#4604) 2024-11-28 18:31:11 +00:00
dd052b35fd KCL: Remove unnecessary 'optional: bool' field on CallExpression (#4584)
It was put there in the original KCL JS-to-Rust rewrite, and I don't think it's used at all.
2024-11-27 18:01:42 -06:00
46be4e7eef Log simple performance metrics (#4596)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-11-28 11:27:17 +13:00
412d1b7a99 Update KCL Types doc (#4591)
* use `=` instead of `:`, fix formatting

* remove `%` from `segEnd` and `segLen` calls
2024-11-27 11:14:59 -05:00
cfdd22af74 Add ability to immediately enter sketch mode by double-clicking an existing sketch (#4573)
* Implement the functionality

* Another fmt

* Fix handler to not rely on modelingMachine's context,
because that creates an implicit race

* Write an E2E test

* Fix tsc and fmt

* Use artifactGraph helpers for more concise code

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

* Fix up imports and whatnot from commit 2bfc5f5c

* Make early return more clear with curly braces

* Whoops should have linted

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-11-27 10:08:23 -05:00
68a11e7aa5 Remove the lexer from KCL's API (#4589)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-11-27 03:30:28 +00:00
3139e18dc7 Make = and => optional in function declarations (#4577)
* Make `=` and `=>` optional in function declarations

And requires `:` for return types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Tests

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Format types in function decls

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Require  in anon function decls

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-11-27 15:46:58 +13:00
223 changed files with 8361 additions and 31177 deletions

View File

@ -382,13 +382,3 @@ jobs:
glob: '*'
parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
- name: Create draft release
uses: softprops/action-gh-release@v2
if: ${{ env.IS_RELEASE == 'true' }}
with:
name: ${{ env.VERSION }}
tag_name: ${{ env.VERSION }}
draft: true
generate_release_notes: true
files: 'out/Zoo*'

View File

@ -68,7 +68,7 @@ jobs:
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
@ -255,7 +255,7 @@ jobs:
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}

View File

@ -132,6 +132,12 @@ jobs:
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
- name: Upload release files to Github
if: ${{ github.event_name == 'release' }}
uses: softprops/action-gh-release@v2
with:
files: 'out/Zoo*'
announce_release:
needs: [publish-apps-release]

View File

@ -136,7 +136,7 @@ https://github.com/KittyCAD/modeling-app/issues/new
#### 2. Push a new tag
Create a new tag and push it to the repo (eg. `v0.28.0` for `$VERSION`)
Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
```
VERSION=$(./scripts/semantic-release.sh)
@ -146,16 +146,14 @@ git push origin --tags
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts.
Once the workflow succeeds, a draft release will be created at https://github.com/KittyCAD/modeling-app/releases.
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
#### 3. Manually test artifacts from the Cut Release PR
#### 3. Manually test artifacts
##### Release builds
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
Alternatively, the draft release will also include these builds.
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
##### Updater-test builds
@ -178,9 +176,11 @@ If the prompt doesn't show up, start the app in command line to grab the electro
#### 4. Publish the release
Head over to https://github.com/KittyCAD/modeling-app/releases, paste in the changelog discussed in the issue, and publish the draft release created by the `build-apps` workflow from step 2.
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well.
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter. On success, the files will be uploaded to the public bucket and the announcement on Discord will be sent.
Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.
A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
#### 5. Close the issue
@ -450,3 +450,9 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
## KCL
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
### Logging
To display logging (to the terminal or console) set `ZOO_LOG=1`. This will log some warnings and simple performance metrics. To view these in test runs, use `-- --nocapture`.
To enable memory metrics, build with `--features dhat-heap`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -58,7 +58,7 @@ mountingPlate = extrude(thickness, mountingPlateSketch)
```js
// Sketch on the face of a chamfer.
fn cube = (pos, scale) => {
fn cube(pos, scale) {
sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> line([0, scale], %)

File diff suppressed because one or more lines are too long

View File

@ -37,7 +37,7 @@ assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 2 }, %)
|> extrude(5, %)
|> patternTransform(n, (id) => {
|> patternTransform(n, fn(id) {
return { translate = [4 * id, 0, 0] }
}, %)
```

View File

@ -29,7 +29,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
```js
r = 10 // radius
fn drawCircle = (id) => {
fn drawCircle(id) {
return startSketchOn("XY")
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
}
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
```js
r = 10 // radius
// Call `map`, using an anonymous function instead of a named one.
circles = map([1..3], (id) => {
circles = map([1..3], (id) {
return startSketchOn("XY")
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
})

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,7 @@ patternTransform2d(total_instances: u32, transform_function: FunctionParam, soli
```js
// Each instance will be shifted along the X axis.
fn transform = (id) => {
fn transform(id) {
return { translate = [4 * id, 0] }
}

View File

@ -30,14 +30,14 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
```js
// This function adds two numbers.
fn add = (a, b) => {
fn add(a, b) {
return a + b
}
// This function adds an array of numbers.
// It uses the `reduce` function, to call the `add` function on every
// element of the `arr` parameter. The starting value is 0.
fn sum = (arr) => {
fn sum(arr) {
return reduce(arr, 0, add)
}
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
// an anonymous `add` function as its parameter, instead of declaring a
// named function outside.
arr = [1, 2, 3]
sum = reduce(arr, 0, (i, result_so_far) => {
sum = reduce(arr, 0, (i, result_so_far) {
return i + result_so_far
})
@ -74,7 +74,7 @@ assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
```js
// Declare a function that sketches a decagon.
fn decagon = (radius) => {
fn decagon(radius) {
// Each side of the decagon is turned this many degrees from the previous angle.
stepAngle = 1 / 10 * tau()
@ -84,7 +84,7 @@ fn decagon = (radius) => {
// Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function,
// which takes a partially-sketched decagon and adds one more edge to it.
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) => {
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
// Draw one edge of the decagon.
x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius

View File

@ -36,7 +36,7 @@ cube = startSketchAt([0, 0])
|> close(%)
|> extrude(5, %)
fn cylinder = (radius, tag) => {
fn cylinder(radius, tag) {
return startSketchAt([0, 0])
|> circle({
radius = radius,

View File

@ -36,7 +36,7 @@ cube = startSketchAt([0, 0])
|> close(%)
|> extrude(5, %)
fn cylinder = (radius, tag) => {
fn cylinder(radius, tag) {
return startSketchAt([0, 0])
|> circle({
radius = radius,

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,7 @@ If you want to get a value from an array you can use the index like so:
An object is defined with `{}` braces. Here is an example object:
```
myObj = {a: 0, b: "thing"}
myObj = { a = 0, b = "thing" }
```
We support two different ways of getting properties from objects, you can call
@ -90,12 +90,12 @@ startSketchOn('XZ')
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001, %) - 90,
segAng(rectangleSegmentA001) - 90,
196.99
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001, %),
-segLen(rectangleSegmentA001, %)
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
@ -120,18 +120,18 @@ However if the code was written like this:
```
fn rect = (origin) => {
return startSketchOn('XZ')
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001, %) - 90,
196.99
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001, %),
-segLen(rectangleSegmentA001, %)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
196.99
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
}
rect([0, 0])
@ -148,26 +148,29 @@ For example the following code works.
```
fn rect = (origin) => {
return startSketchOn('XZ')
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001, %) - 90,
196.99
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001, %),
-segLen(rectangleSegmentA001, %)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
196.99
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
}
rect([0, 0])
myRect = rect([20, 0])
myRect
myRect
|> extrude(10, %)
|> fillet({radius: 0.5, tags: [myRect.tags.rectangleSegmentA001]}, %)
|> fillet({
radius = 0.5,
tags = [myRect.tags.rectangleSegmentA001]
}, %)
```
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside

View File

@ -1,161 +0,0 @@
---
title: "BinaryOperator"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Add two numbers.
**enum:** `+`
----
Subtract two numbers.
**enum:** `-`
----
Multiply two numbers.
**enum:** `*`
----
Divide two numbers.
**enum:** `/`
----
Modulo two numbers.
**enum:** `%`
----
Raise a number to a power.
**enum:** `^`
----
Are two numbers equal?
**enum:** `==`
----
Are two numbers not equal?
**enum:** `!=`
----
Is left greater than right
**enum:** `>`
----
Is left greater than or equal to right
**enum:** `>=`
----
Is left less than right
**enum:** `<`
----
Is left less than or equal to right
**enum:** `<=`
----

View File

@ -1,161 +0,0 @@
---
title: "BinaryPart"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `optional` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,97 +0,0 @@
---
title: "BodyItem"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ImportStatement`| | No |
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
| `path` |`string`| | No |
| `raw_path` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ExpressionStatement`| | No |
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `VariableDeclaration`| | No |
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ReturnStatement`| | No |
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,41 +0,0 @@
---
title: "CommentStyle"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Like // foo
**enum:** `line`
----
Like /* foo */
**enum:** `block`
----

View File

@ -1,24 +0,0 @@
---
title: "ElseIf"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,16 +0,0 @@
---
title: "EnvironmentRef"
excerpt: "An index pointing to an environment."
layout: manual
---
An index pointing to an environment.
**Type:** `integer` (`uint`)

View File

@ -1,318 +0,0 @@
---
title: "Expr"
excerpt: "An expression can be evaluated to yield a single KCL value."
layout: manual
---
An expression can be evaluated to yield a single KCL value.
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `value` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `optional` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeExpression`| | No |
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeSubstitution`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayExpression`| | No |
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayRangeExpression`| | No |
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ObjectExpression`| | No |
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `None`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,24 +0,0 @@
---
title: "FunctionExpression"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,23 +0,0 @@
---
title: "Identifier"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,24 +0,0 @@
---
title: "ImportItem"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,16 +0,0 @@
---
title: "ItemVisibility"
excerpt: ""
layout: manual
---
**enum:** `default`, `export`

View File

@ -317,7 +317,6 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,56 +0,0 @@
---
title: "LiteralIdentifier"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,47 +0,0 @@
---
title: "LiteralValue"
excerpt: ""
layout: manual
---
**This schema accepts any of the following:**
**Type:** `number` (`double`)
----
**Type:** `string`
----
**Type:** `boolean`
----

View File

@ -1,57 +0,0 @@
---
title: "MemberObject"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,22 +0,0 @@
---
title: "NonCodeMeta"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `nonCodeNodes` |`object`| | No |
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -1,23 +0,0 @@
---
title: "NonCodeNode"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,86 +0,0 @@
---
title: "NonCodeValue"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `inlineComment`| | No |
| `value` |`string`| | No |
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
----
A block comment. An example of this is the following: ```python,no_run /* This is a block comment */ 1 + 1 ``` Now this is important. The block comment is attached to the next line. This is always the case. Also the block comment doesn't have a new line above it. If it did it would be a `NewLineBlockComment`.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `blockComment`| | No |
| `value` |`string`| | No |
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
----
A block comment that has a new line above it. The user explicitly added a new line above the block comment.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `newLineBlockComment`| | No |
| `value` |`string`| | No |
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `newLine`| | No |
----

View File

@ -1,24 +0,0 @@
---
title: "ObjectProperty"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,23 +0,0 @@
---
title: "Parameter"
excerpt: "Parameter of a KCL function."
layout: manual
---
Parameter of a KCL function.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `identifier` |[`Identifier`](/docs/kcl/types/Identifier)| The parameter's label or name. | No |
| `optional` |`boolean`| Is the parameter optional? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -1,26 +0,0 @@
---
title: "Program"
excerpt: "A KCL program top level, or function body."
layout: manual
---
A KCL program top level, or function body.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
| `shebang` |[`Shebang`](/docs/kcl/types/Shebang)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,23 +0,0 @@
---
title: "Shebang"
excerpt: "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```"
layout: manual
---
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `content` |`string`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,15 +0,0 @@
---
title: "Uint"
excerpt: ""
layout: manual
---
**Type:** `integer` (`uint32`)

View File

@ -1,41 +0,0 @@
---
title: "UnaryOperator"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Negate a number.
**enum:** `-`
----
Negate a boolean.
**enum:** `!`
----

View File

@ -1,24 +0,0 @@
---
title: "VariableDeclarator"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,41 +0,0 @@
---
title: "VariableKind"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Declare a named constant.
**enum:** `const`
----
Declare a function.
**enum:** `fn`
----

View File

@ -45,7 +45,6 @@ test.describe('integrations tests', () => {
{
title: 'test-sample',
fileCount: 1,
folderCount: 1,
},
],
sortBy: 'last-modified-desc',
@ -233,7 +232,6 @@ test.describe('when using the file tree to', () => {
{
title: projectName,
fileCount: 2,
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
},
],
sortBy: 'last-modified-desc',

View File

@ -4,7 +4,6 @@ import { expect } from '@playwright/test'
interface ProjectCardState {
title: string
fileCount: number
folderCount: number
}
interface HomePageState {
@ -61,15 +60,13 @@ export class HomePageFixture {
const projectCards = await this.projectCard.all()
const projectCardStates: Array<ProjectCardState> = []
for (const projectCard of projectCards) {
const [title, fileCount, folderCount] = await Promise.all([
const [title, fileCount] = await Promise.all([
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
Number(await projectCard.locator(this.projectCardFile).textContent()),
Number(await projectCard.locator(this.projectCardFolder).textContent()),
])
projectCardStates.push({
title: title,
fileCount,
folderCount,
})
}
return projectCardStates

View File

@ -28,6 +28,7 @@ type SceneSerialised = {
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
type DblClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
type DragFromHandler = (
dragParams: mouseDragFromParams
@ -68,7 +69,7 @@ export class SceneFixture {
x: number,
y: number,
{ steps }: { steps: number } = { steps: 20 }
): [ClickHandler, MoveHandler] =>
): [ClickHandler, MoveHandler, DblClickHandler] =>
[
(clickParams?: mouseParams) => {
if (clickParams?.pixelDiff) {
@ -90,6 +91,16 @@ export class SceneFixture {
}
return this.page.mouse.move(x, y, { steps })
},
(clickParams?: mouseParams) => {
if (clickParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
() => this.page.mouse.dblclick(x, y),
clickParams.pixelDiff
)
}
return this.page.mouse.dblclick(x, y)
},
] as const
makeDragHelpers = (
x: number,

View File

@ -552,6 +552,82 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
})
})
test(`Verify user can double-click to edit a sketch`, async ({
app,
editor,
toolbar,
scene,
}) => {
const initialCode = `closedSketch = startSketchOn('XZ')
|> circle({ center = [8, 5], radius = 2 }, %)
openSketch = startSketchOn('XY')
|> startProfileAt([-5, 0], %)
|> lineTo([0, 5], %)
|> xLine(5, %)
|> tangentialArcTo([10, 0], %)
`
await app.initialise(initialCode)
const pointInsideCircle = {
x: app.viewPortSize.width * 0.63,
y: app.viewPortSize.height * 0.5,
}
const pointOnPathAfterSketching = {
x: app.viewPortSize.width * 0.58,
y: app.viewPortSize.height * 0.5,
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
scene.makeMouseHelpers(
pointOnPathAfterSketching.x,
pointOnPathAfterSketching.y
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_clickCircle, moveToCircle, dblClickCircle] = scene.makeMouseHelpers(
pointInsideCircle.x,
pointInsideCircle.y
)
const exitSketch = async () => {
await test.step(`Exit sketch mode`, async () => {
await toolbar.exitSketchBtn.click()
await expect(toolbar.exitSketchBtn).not.toBeVisible()
await expect(toolbar.startSketchBtn).toBeEnabled()
})
}
await test.step(`Double-click on the closed sketch`, async () => {
await moveToCircle()
await dblClickCircle()
await expect(toolbar.startSketchBtn).not.toBeVisible()
await expect(toolbar.exitSketchBtn).toBeVisible()
await editor.expectState({
activeLines: [`|>circle({center=[8,5],radius=2},%)`],
highlightedCode: 'circle({center=[8,5],radius=2},%)',
diagnostics: [],
})
})
await exitSketch()
await test.step(`Double-click on the open sketch`, async () => {
await moveToOpenPath()
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
// There is a full execution after exiting sketch that clears the scene.
await app.page.waitForTimeout(500)
await dblClickOpenPath()
await expect(toolbar.startSketchBtn).not.toBeVisible()
await expect(toolbar.exitSketchBtn).toBeVisible()
// Wait for enter sketch mode to complete
await app.page.waitForTimeout(500)
await editor.expectState({
activeLines: [`|>xLine(5,%)`],
highlightedCode: 'xLine(5,%)',
diagnostics: [],
})
})
})
test(`Offset plane point-and-click`, async ({
app,
scene,

View File

@ -82,7 +82,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager'
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addFillet'
import {
ExportIntent,
EngineConnectionStateType,
@ -576,8 +576,10 @@ export const ModelingMachineProvider = ({
if (selectionRanges.graphSelections.length <= 0) return false
return true
},
'has valid fillet selection': ({ context: { selectionRanges } }) => {
return hasValidFilletSelection({
'has valid edge treatment selection': ({
context: { selectionRanges },
}) => {
return hasValidEdgeTreatmentSelection({
selectionRanges,
ast: kclManager.ast,
code: codeManager.code,

View File

@ -18,6 +18,8 @@ import { useRouteLoaderData } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { IndexLoaderData } from 'lib/types'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { err, reportRejection } from 'lib/trap'
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
enum StreamState {
Playing = 'playing',
@ -280,12 +282,49 @@ export const Stream = () => {
}
}
/**
* On double-click of sketch entities we automatically enter sketch mode with the selected sketch,
* allowing for quick editing of sketches. TODO: This should be moved to a more central place.
*/
const enterSketchModeIfSelectingSketch: MouseEventHandler<HTMLDivElement> = (
e
) => {
if (
!isNetworkOkay ||
!videoRef.current ||
state.matches('Sketch') ||
state.matches({ idle: 'showPlanes' }) ||
sceneInfra.camControls.wasDragging === true ||
!btnName(e.nativeEvent).left
) {
return
}
sendSelectEventToEngine(e, videoRef.current)
.then(({ entity_id }) => {
if (!entity_id) {
// No entity selected. This is benign
return
}
const path = getArtifactOfTypes(
{ key: entity_id, types: ['path', 'solid2D', 'segment'] },
engineCommandManager.artifactGraph
)
if (err(path)) {
return path
}
sceneInfra.modelingSend({ type: 'Enter sketch' })
})
.catch(reportRejection)
}
return (
<div
className="absolute inset-0 z-0"
id="stream"
data-testid="stream"
onClick={handleMouseUp}
onDoubleClick={enterSketchModeIfSelectingSketch}
onContextMenu={(e) => e.preventDefault()}
onContextMenuCapture={(e) => e.preventDefault()}
>

View File

@ -384,7 +384,6 @@ const myVar = funcN(1, 2)`
raw: '2',
},
],
optional: false,
},
},
],
@ -465,7 +464,6 @@ describe('testing pipe operator special', () => {
],
},
],
optional: false,
},
{
type: 'CallExpression',
@ -508,7 +506,6 @@ describe('testing pipe operator special', () => {
end: 60,
},
],
optional: false,
},
{
type: 'CallExpression',
@ -556,7 +553,6 @@ describe('testing pipe operator special', () => {
value: 'myPath',
},
],
optional: false,
},
{
type: 'CallExpression',
@ -598,7 +594,6 @@ describe('testing pipe operator special', () => {
end: 115,
},
],
optional: false,
},
{
type: 'CallExpression',
@ -625,7 +620,6 @@ describe('testing pipe operator special', () => {
end: 130,
},
],
optional: false,
},
],
},
@ -711,7 +705,6 @@ describe('testing pipe operator special', () => {
end: 35,
},
],
optional: false,
},
],
},
@ -1765,7 +1758,6 @@ describe('test UnaryExpression', () => {
raw: '100',
},
],
optional: false,
},
})
})
@ -1837,11 +1829,9 @@ describe('testing nested call expressions', () => {
raw: '3',
},
],
optional: false,
},
},
],
optional: false,
})
})
})
@ -1879,7 +1869,6 @@ describe('should recognise callExpresions in binaryExpressions', () => {
name: 'seg02',
},
],
optional: false,
},
right: {
type: 'Literal',

View File

@ -727,7 +727,6 @@ export function createCallExpressionStdLib(
name,
},
optional: false,
arguments: args,
}
}
@ -749,7 +748,6 @@ export function createCallExpression(
name,
},
optional: false,
arguments: args,
}
}

View File

@ -10,10 +10,14 @@ import {
VariableDeclarator,
} from '../wasm'
import {
EdgeTreatmentType,
getPathToExtrudeForSegmentSelection,
hasValidFilletSelection,
isTagUsedInFillet,
modifyAstWithFilletAndTag,
hasValidEdgeTreatmentSelection,
isTagUsedInEdgeTreatment,
modifyAstWithEdgeTreatmentAndTag,
FilletParameters,
ChamferParameters,
EdgeTreatmentParameters,
} from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst'
@ -21,7 +25,6 @@ import { err } from 'lib/trap'
import { Selections } from 'lib/selections'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { VITE_KC_DEV_TOKEN } from 'env'
import { KclCommandValue } from 'lib/commandTypes'
import { isOverlap } from 'lib/utils'
import { codeRefFromRange } from 'lang/std/artifactGraph'
@ -253,10 +256,10 @@ extrude003 = extrude(-15, sketch003)`
})
})
const runModifyAstCloneWithFilletAndTag = async (
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
code: string,
selectionSnippets: Array<string>,
radiusValue: number,
parameters: EdgeTreatmentParameters,
expectedCode: string
) => {
// ast
@ -274,13 +277,6 @@ const runModifyAstCloneWithFilletAndTag = async (
]
)
// radius
const radius: KclCommandValue = {
valueAst: createLiteral(radiusValue),
valueText: radiusValue.toString(),
valueCalculated: radiusValue.toString(),
}
// executeAst
await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph
@ -299,8 +295,8 @@ const runModifyAstCloneWithFilletAndTag = async (
otherSelections: [],
}
// apply fillet to selection
const result = modifyAstWithFilletAndTag(ast, selection, radius)
// apply edge treatment to seleciton
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
if (err(result)) {
return result
}
@ -310,9 +306,42 @@ const runModifyAstCloneWithFilletAndTag = async (
expect(newCode).toContain(expectedCode)
}
describe('Testing applyFilletToSelection', () => {
it('should add a fillet to a specific segment', async () => {
const code = `sketch001 = startSketchOn('XY')
const createFilletParameters = (radiusValue: number): FilletParameters => ({
type: EdgeTreatmentType.Fillet,
radius: {
valueAst: createLiteral(radiusValue),
valueText: radiusValue.toString(),
valueCalculated: radiusValue.toString(),
},
})
const createChamferParameters = (lengthValue: number): ChamferParameters => ({
type: EdgeTreatmentType.Chamfer,
length: {
valueAst: createLiteral(lengthValue),
valueText: lengthValue.toString(),
valueCalculated: lengthValue.toString(),
},
})
// Iterate tests over all edge treatment types
Object.values(EdgeTreatmentType).forEach(
(edgeTreatmentType: EdgeTreatmentType) => {
// create parameters based on the edge treatment type
let parameterName: string
let parameters: EdgeTreatmentParameters
if (edgeTreatmentType === EdgeTreatmentType.Fillet) {
parameterName = 'radius'
parameters = createFilletParameters(3)
} else if (edgeTreatmentType === EdgeTreatmentType.Chamfer) {
parameterName = 'length'
parameters = createChamferParameters(3)
} else {
// Handle future edge treatments
return new Error(`Unsupported edge treatment type: ${edgeTreatmentType}`)
}
// run tests
describe(`Testing modifyAstCloneWithEdgeTreatmentAndTag with ${edgeTreatmentType}s`, () => {
it(`should add a ${edgeTreatmentType} to a specific segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
@ -320,9 +349,8 @@ describe('Testing applyFilletToSelection', () => {
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
const segmentSnippets = ['line([0, -20], %)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
@ -330,17 +358,17 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to the sketch pipe', async () => {
const code = `sketch001 = startSketchOn('XY')
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} to the sketch pipe`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
@ -348,9 +376,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
const segmentSnippets = ['line([0, -20], %)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
@ -358,17 +385,17 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)
|> fillet({ radius = 3, tags = [seg01] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to an already tagged segment', async () => {
const code = `sketch001 = startSketchOn('XY')
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} to an already tagged segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
@ -376,9 +403,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %, $seg01)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
const segmentSnippets = ['line([0, -20], %, $seg01)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
@ -386,17 +412,17 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet with existing tag on other segment', async () => {
const code = `sketch001 = startSketchOn('XY')
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} with existing tag on other segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
@ -404,9 +430,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
const segmentSnippets = ['line([-20, 0], %)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
@ -414,17 +439,17 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg02] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet with existing fillet on other segment', async () => {
const code = `sketch001 = startSketchOn('XY')
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} with existing fillet on other segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
@ -433,9 +458,8 @@ extrude001 = extrude(-15, sketch001)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 5, tags = [seg01] }, %)`
const segmentSnippets = ['line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
const segmentSnippets = ['line([-20, 0], %)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
@ -444,27 +468,27 @@ extrude001 = extrude(-15, sketch001)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 5, tags = [seg01] }, %)
|> fillet({ radius = 3, tags = [seg02] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to two segments of a single extrusion', async () => {
const code = `sketch001 = startSketchOn('XY')
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} with existing chamfer on other segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([20, 0], %)', 'line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
extrude001 = extrude(-15, sketch001)
|> chamfer({ length: 5, tags: [seg01] }, %)`
const segmentSnippets = ['line([-20, 0], %)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
@ -472,17 +496,45 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01, seg02] }, %)`
|> chamfer({ length: 5, tags: [seg01] }, %)
|> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add fillets to two bodies', async () => {
const code = `sketch001 = startSketchOn('XY')
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} to two segments of a single extrusion`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([20, 0], %)', 'line([-20, 0], %)']
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %, $seg02)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01, seg02] }, %)`
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add ${edgeTreatmentType}s to two bodies`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
@ -498,13 +550,12 @@ sketch002 = startSketchOn('XY')
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude002 = extrude(-25, sketch002)` // <--- body 2
const segmentSnippets = [
'line([20, 0], %)',
'line([-20, 0], %)',
'line([0, -15], %)',
]
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
const segmentSnippets = [
'line([20, 0], %)',
'line([-20, 0], %)',
'line([0, -15], %)',
]
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
@ -512,7 +563,7 @@ extrude002 = extrude(-25, sketch002)` // <--- body 2
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01, seg02] }, %)
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01, seg02] }, %)
sketch002 = startSketchOn('XY')
|> startProfileAt([30, 10], %)
|> line([15, 0], %)
@ -521,18 +572,20 @@ sketch002 = startSketchOn('XY')
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude002 = extrude(-25, sketch002)
|> fillet({ radius = 3, tags = [seg03] }, %)` // <-- able to add a new one
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg03] }, %)` // <-- able to add a new one
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
})
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
})
}
)
describe('Testing isTagUsedInFillet', () => {
describe('Testing isTagUsedInEdgeTreatment', () => {
const code = `sketch001 = startSketchOn('XZ')
|> startProfileAt([7.72, 4.13], %)
|> line([7.11, 3.48], %, $seg01)
@ -565,7 +618,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression'
)
if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
})
it('should correctly identify getPreviousAdjacentEdge edges', () => {
@ -584,7 +637,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression'
)
if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual(['getPreviousAdjacentEdge'])
})
it('should correctly identify no edges', () => {
@ -603,7 +656,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression'
)
if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual([])
})
})
@ -638,7 +691,7 @@ describe('Testing button states', () => {
}
// state
const buttonState = hasValidFilletSelection({
const buttonState = hasValidEdgeTreatmentSelection({
ast,
selectionRanges,
code,

View File

@ -44,32 +44,49 @@ import {
} from 'lib/singletons'
import { Node } from 'wasm-lib/kcl/bindings/Node'
// Apply Fillet To Selection
// Edge Treatment Types
export enum EdgeTreatmentType {
Chamfer = 'chamfer',
Fillet = 'fillet',
}
export function applyFilletToSelection(
export interface ChamferParameters {
type: EdgeTreatmentType.Chamfer
length: KclCommandValue
}
export interface FilletParameters {
type: EdgeTreatmentType.Fillet
radius: KclCommandValue
}
export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
// Apply Edge Treatment (Fillet or Chamfer) To Selection
export function applyEdgeTreatmentToSelection(
ast: Node<Program>,
selection: Selections,
radius: KclCommandValue
parameters: EdgeTreatmentParameters
): void | Error {
// 1. clone and modify with fillet and tag
const result = modifyAstWithFilletAndTag(ast, selection, radius)
// 1. clone and modify with edge treatment and tag
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
if (err(result)) return result
const { modifiedAst, pathToFilletNode } = result
const { modifiedAst, pathToEdgeTreatmentNode } = result
// 2. update ast
// eslint-disable-next-line @typescript-eslint/no-floating-promises
updateAstAndFocus(modifiedAst, pathToFilletNode)
updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
}
export function modifyAstWithFilletAndTag(
export function modifyAstWithEdgeTreatmentAndTag(
ast: Node<Program>,
selections: Selections,
radius: KclCommandValue
): { modifiedAst: Node<Program>; pathToFilletNode: Array<PathToNode> } | Error {
parameters: EdgeTreatmentParameters
):
| { modifiedAst: Node<Program>; pathToEdgeTreatmentNode: Array<PathToNode> }
| Error {
let clonedAst = structuredClone(ast)
const clonedAstForGetExtrude = structuredClone(ast)
const astResult = insertRadiusIntoAst(clonedAst, radius)
const astResult = insertParametersIntoAst(clonedAst, parameters)
if (err(astResult)) return astResult
const artifactGraph = engineCommandManager.artifactGraph
@ -119,21 +136,26 @@ export function modifyAstWithFilletAndTag(
}
}
// Step 2: Apply fillet(s) for each extrude node (body)
let pathToFilletNodes: Array<PathToNode> = []
// Step 2: Apply edge treatments for each extrude node (body)
let pathToEdgeTreatmentNodes: Array<PathToNode> = []
for (const [pathToExtrudeNode, tagInfos] of extrudeToTagsMap.entries()) {
// Create a fillet expression with multiple tags
const radiusValue =
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
// Create an edge treatment expression with multiple tags
// edge treatment parameter
const parameterResult = getParameterNameAndValue(parameters)
if (err(parameterResult)) return parameterResult
const { parameterName, parameterValue } = parameterResult
// tag calls
const tagCalls = tagInfos.map(({ tag, artifact }) => {
return getEdgeTagCall(tag, artifact)
})
const firstTag = tagCalls[0] // can be Identifier or CallExpression (for opposite and adjacent edges)
const filletCall = createCallExpressionStdLib('fillet', [
// edge treatment call
const edgeTreatmentCall = createCallExpressionStdLib(parameters.type, [
createObjectExpression({
radius: radiusValue,
[parameterName]: parameterValue,
tags: createArrayExpression(tagCalls),
}),
createPipeSubstitution(),
@ -147,64 +169,89 @@ export function modifyAstWithFilletAndTag(
if (err(locatedExtrudeDeclarator)) return locatedExtrudeDeclarator
const { extrudeDeclarator } = locatedExtrudeDeclarator
// Modify the extrude expression to include this fillet expression
// CallExpression - no fillet
// PipeExpression - fillet exists or extrude in sketch pipe
// Modify the extrude expression to include this edge treatment expression
// CallExpression - no edge treatment
// PipeExpression - edge treatment exists or body in sketch pipe
let pathToFilletNode: PathToNode = []
let pathToEdgeTreatmentNode: PathToNode
if (extrudeDeclarator.init.type === 'CallExpression') {
// 1. case when no fillet exists
// 1. case when no edge treatment exists
// modify ast with new fillet call by mutating the extrude node
// modify ast with new edge treatment call by mutating the extrude node
extrudeDeclarator.init = createPipeExpression([
extrudeDeclarator.init,
filletCall,
edgeTreatmentCall,
])
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
// get path to the edge treatment node
pathToEdgeTreatmentNode = getPathToNodeOfEdgeTreatmentLiteral(
pathToExtrudeNode,
extrudeDeclarator,
firstTag
firstTag,
parameters
)
pathToFilletNodes.push(pathToFilletNode)
pathToEdgeTreatmentNodes.push(pathToEdgeTreatmentNode)
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
// 2. case when fillet exists or extrude in sketch pipe
// 2. case when edge treatment exists or extrude in sketch pipe
// mutate the extrude node with the new fillet call
extrudeDeclarator.init.body.push(filletCall)
// mutate the extrude node with the new edge treatment call
extrudeDeclarator.init.body.push(edgeTreatmentCall)
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
// get path to the edge treatment node
pathToEdgeTreatmentNode = getPathToNodeOfEdgeTreatmentLiteral(
pathToExtrudeNode,
extrudeDeclarator,
firstTag
firstTag,
parameters
)
pathToFilletNodes.push(pathToFilletNode)
pathToEdgeTreatmentNodes.push(pathToEdgeTreatmentNode)
} else {
return new Error('Unsupported extrude type.')
}
}
return { modifiedAst: clonedAst, pathToFilletNode: pathToFilletNodes }
return {
modifiedAst: clonedAst,
pathToEdgeTreatmentNode: pathToEdgeTreatmentNodes,
}
}
function insertRadiusIntoAst(
function insertParametersIntoAst(
ast: Program,
radius: KclCommandValue
parameters: EdgeTreatmentParameters
): { ast: Program } | Error {
try {
// Validate and update AST
const newAst = structuredClone(ast)
// handle radius parameter
if (
'variableName' in radius &&
radius.variableName &&
radius.insertIndex !== undefined
parameters.type === EdgeTreatmentType.Fillet &&
'variableName' in parameters.radius &&
parameters.radius.variableName &&
parameters.radius.insertIndex !== undefined
) {
const newAst = structuredClone(ast)
newAst.body.splice(radius.insertIndex, 0, radius.variableDeclarationAst)
return { ast: newAst }
newAst.body.splice(
parameters.radius.insertIndex,
0,
parameters.radius.variableDeclarationAst
)
}
return { ast }
// handle length parameter
if (
parameters.type === EdgeTreatmentType.Chamfer &&
'variableName' in parameters.length &&
parameters.length.variableName &&
parameters.length.insertIndex !== undefined
) {
newAst.body.splice(
parameters.length.insertIndex,
0,
parameters.length.variableDeclarationAst
)
}
// handle upcoming parameters here (for blend, bevel, etc.)
return { ast: newAst }
} catch (error) {
return new Error(`Failed to handle AST: ${(error as Error).message}`)
}
@ -248,10 +295,10 @@ export function getPathToExtrudeForSegmentSelection(
async function updateAstAndFocus(
modifiedAst: Node<Program>,
pathToFilletNode: Array<PathToNode>
pathToEdgeTreatmentNode: Array<PathToNode>
) {
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: pathToFilletNode,
focusPath: pathToEdgeTreatmentNode,
})
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
@ -340,27 +387,38 @@ function locateExtrudeDeclarator(
return { extrudeDeclarator }
}
function getPathToNodeOfFilletLiteral(
function getPathToNodeOfEdgeTreatmentLiteral(
pathToExtrudeNode: PathToNode,
extrudeDeclarator: VariableDeclarator,
tag: Identifier | CallExpression
tag: Identifier | CallExpression,
parameters: EdgeTreatmentParameters
): PathToNode {
let pathToFilletObj: PathToNode = []
let inFillet = false
let pathToEdgeTreatmentObj: PathToNode = []
let inEdgeTreatment = false
traverse(extrudeDeclarator.init, {
enter(node, path) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = true
if (
node.type === 'CallExpression' &&
node.callee.name === parameters.type
) {
inEdgeTreatment = true
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
if (!hasTag(node, tag)) return false
pathToFilletObj = getPathToRadiusLiteral(node, path)
pathToEdgeTreatmentObj = getPathToEdgeTreatmentParameterLiteral(
node,
path,
parameters
)
}
},
leave(node) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = false
if (
node.type === 'CallExpression' &&
node.callee.name === parameters.type
) {
inEdgeTreatment = false
}
},
})
@ -375,7 +433,7 @@ function getPathToNodeOfFilletLiteral(
return [
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
...pathToFilletObj,
...pathToEdgeTreatmentObj,
]
}
@ -408,23 +466,62 @@ function hasTag(
})
}
function getPathToRadiusLiteral(node: ObjectExpression, path: any): PathToNode {
let pathToFilletObj = path
function getPathToEdgeTreatmentParameterLiteral(
node: ObjectExpression,
path: any,
parameters: EdgeTreatmentParameters
): PathToNode {
let pathToEdgeTreatmentObj = path
const parameterResult = getParameterNameAndValue(parameters)
if (err(parameterResult)) return pathToEdgeTreatmentObj
const { parameterName } = parameterResult
node.properties.forEach((prop, index) => {
if (prop.key.name === 'radius') {
pathToFilletObj.push(
if (prop.key.name === parameterName) {
pathToEdgeTreatmentObj.push(
['properties', 'ObjectExpression'],
[index, 'index'],
['value', 'Property']
)
}
})
return pathToFilletObj
return pathToEdgeTreatmentObj
}
function getParameterNameAndValue(
parameters: EdgeTreatmentParameters
): { parameterName: string; parameterValue: Expr } | Error {
if (parameters.type === EdgeTreatmentType.Fillet) {
const parameterValue =
'variableName' in parameters.radius
? parameters.radius.variableIdentifierAst
: parameters.radius.valueAst
return { parameterName: 'radius', parameterValue }
} else if (parameters.type === EdgeTreatmentType.Chamfer) {
const parameterValue =
'variableName' in parameters.length
? parameters.length.variableIdentifierAst
: parameters.length.valueAst
return { parameterName: 'length', parameterValue }
} else {
return new Error('Unsupported edge treatment type}')
}
}
// Type Guards
function isEdgeTreatmentType(name: string): name is EdgeTreatmentType {
return name === EdgeTreatmentType.Chamfer || name === EdgeTreatmentType.Fillet
}
function isEdgeType(name: string): name is EdgeTypes {
return (
name === 'getNextAdjacentEdge' ||
name === 'getPreviousAdjacentEdge' ||
name === 'getOppositeEdge'
)
}
// Button states
export const hasValidFilletSelection = ({
export const hasValidEdgeTreatmentSelection = ({
selectionRanges,
ast,
code,
@ -433,11 +530,14 @@ export const hasValidFilletSelection = ({
ast: Node<Program>
code: string
}) => {
// check if there is anything filletable in the scene
// check if there is anything valid for the edge treatment in the scene
let extrudeExists = false
traverse(ast, {
enter(node) {
if (node.type === 'CallExpression' && node.callee.name === 'extrude') {
if (
node.type === 'CallExpression' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
) {
extrudeExists = true
}
},
@ -494,32 +594,39 @@ export const hasValidFilletSelection = ({
},
})
// check if tag is used in fillet
// check if tag is used in edge treatment
if (tagExists && selection.artifact) {
// create tag call
let tagCall: Expr = getEdgeTagCall(tag, selection.artifact)
// check if tag is used in fillet
let inFillet = false
let tagUsedInFillet = false
// check if tag is used in edge treatment
let inEdgeTreatment = false
let tagUsedInEdgeTreatment = false
traverse(ast, {
enter(node) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = true
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = true
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
if (hasTag(node, tagCall)) {
tagUsedInFillet = true
tagUsedInEdgeTreatment = true
}
}
},
leave(node) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = false
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = false
}
},
})
if (tagUsedInFillet) {
if (tagUsedInEdgeTreatment) {
return false
}
}
@ -533,7 +640,7 @@ type EdgeTypes =
| 'getPreviousAdjacentEdge'
| 'getOppositeEdge'
export const isTagUsedInFillet = ({
export const isTagUsedInEdgeTreatment = ({
ast,
callExp,
}: {
@ -543,16 +650,21 @@ export const isTagUsedInFillet = ({
const tag = getTagFromCallExpression(callExp)
if (err(tag)) return []
let inFillet = false
let inEdgeTreatment = false
let inObj = false
let inTagHelper: EdgeTypes | '' = ''
const edges: Array<EdgeTypes> = []
traverse(ast, {
enter: (node) => {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = true
// Check if we are entering an edge treatment call
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = true
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
node.properties.forEach((prop) => {
if (
prop.key.name === 'tags' &&
@ -564,17 +676,15 @@ export const isTagUsedInFillet = ({
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
node.type === 'CallExpression' &&
(node.callee.name === 'getOppositeEdge' ||
node.callee.name === 'getNextAdjacentEdge' ||
node.callee.name === 'getPreviousAdjacentEdge')
isEdgeType(node.callee.name)
) {
inTagHelper = node.callee.name
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
!inTagHelper &&
node.type === 'Identifier' &&
node.name === tag
@ -583,7 +693,7 @@ export const isTagUsedInFillet = ({
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
inTagHelper &&
node.type === 'Identifier' &&
node.name === tag
@ -592,10 +702,13 @@ export const isTagUsedInFillet = ({
}
},
leave: (node) => {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = false
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = false
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
node.properties.forEach((prop) => {
if (
prop.key.name === 'tags' &&
@ -607,11 +720,9 @@ export const isTagUsedInFillet = ({
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
node.type === 'CallExpression' &&
(node.callee.name === 'getOppositeEdge' ||
node.callee.name === 'getNextAdjacentEdge' ||
node.callee.name === 'getPreviousAdjacentEdge')
isEdgeType(node.callee.name)
) {
inTagHelper = ''
}

View File

@ -63,7 +63,7 @@ log(5, myVar)
})
it('function declaration with call', () => {
const code = [
'fn funcN = (a, b) => {',
'fn funcN(a, b) {',
' return a + b',
'}',
'theVar = 60',
@ -101,7 +101,7 @@ log(5, myVar)
})
it('recast BinaryExpression piped into CallExpression', () => {
const code = [
'fn myFn = (a) => {',
'fn myFn(a) {',
' return a + 1',
'}',
'myVar = 5 + 1',
@ -245,7 +245,7 @@ key = 'c'
expect(recasted).toBe(code)
})
it('comments in a fn block', () => {
const code = `fn myFn = () => {
const code = `fn myFn() {
// this is a comment
yo = { a = { b = { c = '123' } } }

View File

@ -1,382 +0,0 @@
import { lexer, initPromise } from './wasm'
import { err } from 'lib/trap'
beforeAll(async () => {
await initPromise
})
describe('testing lexer', () => {
it('async lexer works too', async () => {
const code = '1 + 2'
const code2 = `const yo = {key: 'value'}`
const code3 = `const yo = 45 /* this is a comment
const ya = 6 */
const yi=45`
expect(lexer(code)).toEqual(lexer(code))
expect(lexer(code2)).toEqual(lexer(code2))
expect(lexer(code3)).toEqual(lexer(code3))
})
it('test lexer', () => {
expect(stringSummaryLexer('1 + 2')).toEqual([
"number '1' from 0 to 1",
"whitespace ' ' from 1 to 3",
"operator '+' from 3 to 4",
"whitespace ' ' from 4 to 5",
"number '2' from 5 to 6",
])
expect(stringSummaryLexer('54 + 22500 + 6')).toEqual([
"number '54' from 0 to 2",
"whitespace ' ' from 2 to 3",
"operator '+' from 3 to 4",
"whitespace ' ' from 4 to 5",
"number '22500' from 5 to 10",
"whitespace ' ' from 10 to 11",
"operator '+' from 11 to 12",
"whitespace ' ' from 12 to 13",
"number '6' from 13 to 14",
])
expect(stringSummaryLexer('a + bo + t5 - 6')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
"word 'bo' from 4 to 6",
"whitespace ' ' from 6 to 7",
"operator '+' from 7 to 8",
"whitespace ' ' from 8 to 9",
"word 't5' from 9 to 11",
"whitespace ' ' from 11 to 12",
"operator '-' from 12 to 13",
"whitespace ' ' from 13 to 14",
"number '6' from 14 to 15",
])
expect(stringSummaryLexer('a + "a str" - 6')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
'string \'"a str"\' from 4 to 11',
"whitespace ' ' from 11 to 12",
"operator '-' from 12 to 13",
"whitespace ' ' from 13 to 14",
"number '6' from 14 to 15",
])
expect(stringSummaryLexer("a + 'str'")).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
"string ''str'' from 4 to 9",
])
expect(stringSummaryLexer("a +'str'")).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"string ''str'' from 3 to 8",
])
expect(stringSummaryLexer('a + (sick)')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
"brace '(' from 4 to 5",
"word 'sick' from 5 to 9",
"brace ')' from 9 to 10",
])
expect(stringSummaryLexer('a + { sick}')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
"brace '{' from 4 to 5",
"whitespace ' ' from 5 to 6",
"word 'sick' from 6 to 10",
"brace '}' from 10 to 11",
])
expect(stringSummaryLexer("log('hi')")).toEqual([
"word 'log' from 0 to 3",
"brace '(' from 3 to 4",
"string ''hi'' from 4 to 8",
"brace ')' from 8 to 9",
])
expect(stringSummaryLexer("log('hi', 'hello')")).toEqual([
"word 'log' from 0 to 3",
"brace '(' from 3 to 4",
"string ''hi'' from 4 to 8",
"comma ',' from 8 to 9",
"whitespace ' ' from 9 to 10",
"string ''hello'' from 10 to 17",
"brace ')' from 17 to 18",
])
expect(stringSummaryLexer('fn funcName = (param1, param2) => {}')).toEqual([
"keyword 'fn' from 0 to 2",
"whitespace ' ' from 2 to 3",
"word 'funcName' from 3 to 11",
"whitespace ' ' from 11 to 12",
"operator '=' from 12 to 13",
"whitespace ' ' from 13 to 14",
"brace '(' from 14 to 15",
"word 'param1' from 15 to 21",
"comma ',' from 21 to 22",
"whitespace ' ' from 22 to 23",
"word 'param2' from 23 to 29",
"brace ')' from 29 to 30",
"whitespace ' ' from 30 to 31",
"operator '=>' from 31 to 33",
"whitespace ' ' from 33 to 34",
"brace '{' from 34 to 35",
"brace '}' from 35 to 36",
])
})
it('test negative and decimal numbers', () => {
expect(stringSummaryLexer('-1')).toEqual([
"operator '-' from 0 to 1",
"number '1' from 1 to 2",
])
expect(stringSummaryLexer('-1.5')).toEqual([
"operator '-' from 0 to 1",
"number '1.5' from 1 to 4",
])
expect(stringSummaryLexer('1.5')).toEqual([
"number '1.5' from 0 to 3",
])
expect(stringSummaryLexer('1.5 + 2.5')).toEqual([
"number '1.5' from 0 to 3",
"whitespace ' ' from 3 to 4",
"operator '+' from 4 to 5",
"whitespace ' ' from 5 to 6",
"number '2.5' from 6 to 9",
])
expect(stringSummaryLexer('1.5 - 2.5')).toEqual([
"number '1.5' from 0 to 3",
"whitespace ' ' from 3 to 4",
"operator '-' from 4 to 5",
"whitespace ' ' from 5 to 6",
"number '2.5' from 6 to 9",
])
expect(stringSummaryLexer('1.5 + -2.5')).toEqual([
"number '1.5' from 0 to 3",
"whitespace ' ' from 3 to 4",
"operator '+' from 4 to 5",
"whitespace ' ' from 5 to 6",
"operator '-' from 6 to 7",
"number '2.5' from 7 to 10",
])
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
"operator '-' from 0 to 1",
"number '1.5' from 1 to 4",
"whitespace ' ' from 4 to 5",
"operator '+' from 5 to 6",
"whitespace ' ' from 6 to 7",
"number '2.5' from 7 to 10",
])
})
it('testing piping operator', () => {
const result = stringSummaryLexer(`sketch mySketch {
lineTo(2, 3)
} |> rx(45, %)`)
expect(result).toEqual([
"type 'sketch' from 0 to 6",
"whitespace ' ' from 6 to 7",
"word 'mySketch' from 7 to 15",
"whitespace ' ' from 15 to 16",
"brace '{' from 16 to 17",
"whitespace '\n ' from 17 to 24",
"word 'lineTo' from 24 to 30",
"brace '(' from 30 to 31",
"number '2' from 31 to 32",
"comma ',' from 32 to 33",
"whitespace ' ' from 33 to 34",
"number '3' from 34 to 35",
"brace ')' from 35 to 36",
"whitespace '\n ' from 36 to 41",
"brace '}' from 41 to 42",
"whitespace ' ' from 42 to 43",
"operator '|>' from 43 to 45",
"whitespace ' ' from 45 to 46",
"word 'rx' from 46 to 48",
"brace '(' from 48 to 49",
"number '45' from 49 to 51",
"comma ',' from 51 to 52",
"whitespace ' ' from 52 to 53",
"operator '%' from 53 to 54",
"brace ')' from 54 to 55",
])
})
it('testing array declaration', () => {
const result = stringSummaryLexer(`const yo = [1, 2]`)
expect(result).toEqual([
"keyword 'const' from 0 to 5",
"whitespace ' ' from 5 to 6",
"word 'yo' from 6 to 8",
"whitespace ' ' from 8 to 9",
"operator '=' from 9 to 10",
"whitespace ' ' from 10 to 11",
"brace '[' from 11 to 12",
"number '1' from 12 to 13",
"comma ',' from 13 to 14",
"whitespace ' ' from 14 to 15",
"number '2' from 15 to 16",
"brace ']' from 16 to 17",
])
})
it('testing object declaration', () => {
const result = stringSummaryLexer(`const yo = {key: 'value'}`)
expect(result).toEqual([
"keyword 'const' from 0 to 5",
"whitespace ' ' from 5 to 6",
"word 'yo' from 6 to 8",
"whitespace ' ' from 8 to 9",
"operator '=' from 9 to 10",
"whitespace ' ' from 10 to 11",
"brace '{' from 11 to 12",
"word 'key' from 12 to 15",
"colon ':' from 15 to 16",
"whitespace ' ' from 16 to 17",
"string ''value'' from 17 to 24",
"brace '}' from 24 to 25",
])
})
it('testing object property access', () => {
const result = stringSummaryLexer(`const yo = {key: 'value'}
const prop = yo.key
const prop2 = yo['key']
const key = 'key'
const prop3 = yo[key]`)
expect(result).toEqual([
"keyword 'const' from 0 to 5",
"whitespace ' ' from 5 to 6",
"word 'yo' from 6 to 8",
"whitespace ' ' from 8 to 9",
"operator '=' from 9 to 10",
"whitespace ' ' from 10 to 11",
"brace '{' from 11 to 12",
"word 'key' from 12 to 15",
"colon ':' from 15 to 16",
"whitespace ' ' from 16 to 17",
"string ''value'' from 17 to 24",
"brace '}' from 24 to 25",
"whitespace '\n' from 25 to 26",
"keyword 'const' from 26 to 31",
"whitespace ' ' from 31 to 32",
"word 'prop' from 32 to 36",
"whitespace ' ' from 36 to 37",
"operator '=' from 37 to 38",
"whitespace ' ' from 38 to 39",
"word 'yo' from 39 to 41",
"period '.' from 41 to 42",
"word 'key' from 42 to 45",
"whitespace '\n' from 45 to 46",
"keyword 'const' from 46 to 51",
"whitespace ' ' from 51 to 52",
"word 'prop2' from 52 to 57",
"whitespace ' ' from 57 to 58",
"operator '=' from 58 to 59",
"whitespace ' ' from 59 to 60",
"word 'yo' from 60 to 62",
"brace '[' from 62 to 63",
"string ''key'' from 63 to 68",
"brace ']' from 68 to 69",
"whitespace '\n' from 69 to 70",
"keyword 'const' from 70 to 75",
"whitespace ' ' from 75 to 76",
"word 'key' from 76 to 79",
"whitespace ' ' from 79 to 80",
"operator '=' from 80 to 81",
"whitespace ' ' from 81 to 82",
"string ''key'' from 82 to 87",
"whitespace '\n' from 87 to 88",
"keyword 'const' from 88 to 93",
"whitespace ' ' from 93 to 94",
"word 'prop3' from 94 to 99",
"whitespace ' ' from 99 to 100",
"operator '=' from 100 to 101",
"whitespace ' ' from 101 to 102",
"word 'yo' from 102 to 104",
"brace '[' from 104 to 105",
"word 'key' from 105 to 108",
"brace ']' from 108 to 109",
])
})
it('testing tokenising line comments', () => {
const result = stringSummaryLexer(`const yo = 45 // this is a comment
const yo = 6`)
expect(result).toEqual([
"keyword 'const' from 0 to 5",
"whitespace ' ' from 5 to 6",
"word 'yo' from 6 to 8",
"whitespace ' ' from 8 to 9",
"operator '=' from 9 to 10",
"whitespace ' ' from 10 to 11",
"number '45' from 11 to 13",
"whitespace ' ' from 13 to 14",
"lineComment '// this is a comment' from 14 to 34",
"whitespace '\n' from 34 to 35",
"keyword 'const' from 35 to 40",
"whitespace ' ' from 40 to 41",
"word 'yo' from 41 to 43",
"whitespace ' ' from 43 to 44",
"operator '=' from 44 to 45",
"whitespace ' ' from 45 to 46",
"number '6' from 46 to 47",
])
})
it('testing tokenising line comments by itself', () => {
const result = stringSummaryLexer(`log('hi')
// comment on a line by itself
const yo=45`)
expect(result).toEqual([
"word 'log' from 0 to 3",
"brace '(' from 3 to 4",
"string ''hi'' from 4 to 8",
"brace ')' from 8 to 9",
"whitespace '\n' from 9 to 10",
"lineComment '// comment on a line by itself' from 10 to 40",
"whitespace '\n' from 40 to 41",
"keyword 'const' from 41 to 46",
"whitespace ' ' from 46 to 47",
"word 'yo' from 47 to 49",
"operator '=' from 49 to 50",
"number '45' from 50 to 52",
])
})
it('testing tokenising block comments', () => {
const result = stringSummaryLexer(`const yo = 45 /* this is a comment
const ya = 6 */
const yi=45`)
expect(result).toEqual([
"keyword 'const' from 0 to 5",
"whitespace ' ' from 5 to 6",
"word 'yo' from 6 to 8",
"whitespace ' ' from 8 to 9",
"operator '=' from 9 to 10",
"whitespace ' ' from 10 to 11",
"number '45' from 11 to 13",
"whitespace ' ' from 13 to 14",
`blockComment '/* this is a comment
const ya = 6 */' from 14 to 50`,
"whitespace '\n' from 50 to 51",
"keyword 'const' from 51 to 56",
"whitespace ' ' from 56 to 57",
"word 'yi' from 57 to 59",
"operator '=' from 59 to 60",
"number '45' from 60 to 62",
])
})
})
// helpers
const stringSummaryLexer = (input: string) => {
const tokens = lexer(input)
if (err(tokens)) return []
return tokens.map(
({ type, value, start, end }) =>
`${type.padEnd(12, ' ')} ${`'${value}'`.padEnd(10, ' ')} from ${String(
start
).padEnd(3, ' ')} to ${end}`
)
}

View File

@ -3,7 +3,6 @@ import init, {
recast_wasm,
execute_wasm,
kcl_lint,
lexer_wasm,
modify_ast_for_sketch_wasm,
is_points_ccw,
get_tangential_arc_to_info,
@ -24,7 +23,6 @@ import { EngineCommandManager } from './std/engineConnection'
import { Discovered } from '../wasm-lib/kcl/bindings/Discovered'
import { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
import type { Program } from '../wasm-lib/kcl/bindings/Program'
import type { Token } from '../wasm-lib/kcl/bindings/Token'
import { Coords2d } from './std/sketch'
import { fileSystemManager } from 'lang/std/fileSystemManager'
import { CoreDumpInfo } from 'wasm-lib/kcl/bindings/CoreDumpInfo'
@ -507,10 +505,6 @@ export const modifyGrid = async (
}
}
export function lexer(str: string): Token[] | Error {
return lexer_wasm(str)
}
export const modifyAstForSketch = async (
engineCommandManager: EngineCommandManager,
ast: Node<Program>,

View File

@ -46,9 +46,21 @@ describe('desktop utilities', () => {
'project-without-kcl-files',
'another-valid-project',
],
'/test/projects/valid-project': ['file1.kcl', 'file2.stp'],
'/test/projects/valid-project': [
'file1.kcl',
'file2.stp',
'file3.kcl',
'directory1',
],
'/test/projects/valid-project/directory1': [],
'/test/projects/project-without-kcl-files': ['file3.glb'],
'/test/projects/another-valid-project': ['file4.kcl'],
'/test/projects/another-valid-project': [
'file4.kcl',
'directory2',
'directory3',
],
'/test/projects/another-valid-project/directory2': [],
'/test/projects/another-valid-project/directory3': [],
}
beforeEach(() => {
@ -119,6 +131,15 @@ describe('desktop utilities', () => {
)
})
it('correctly counts directories and files', async () => {
const projects = await listProjects(mockConfig)
// Verify that directories and files are counted correctly
expect(projects[0].directory_count).toEqual(1)
expect(projects[0].kcl_file_count).toEqual(2)
expect(projects[1].directory_count).toEqual(2)
expect(projects[1].kcl_file_count).toEqual(1)
})
it('handles empty project directory', async () => {
// Adjust mockFileSystem to simulate empty directory
mockFileSystem['/test/projects'] = []

View File

@ -307,7 +307,10 @@ const directoryCount = (file: FileEntry) => {
let count = 0
if (file.children) {
for (let entry of file.children) {
count += 1
// We only want to count FileEntries with children, e.g. folders
if (entry.children !== null) {
count += 1
}
directoryCount(entry)
}
}

View File

@ -46,7 +46,11 @@ import {
extrudeSketch,
revolveSketch,
} from 'lang/modifyAst'
import { applyFilletToSelection } from 'lang/modifyAst/addFillet'
import {
applyEdgeTreatmentToSelection,
EdgeTreatmentType,
FilletParameters,
} from 'lang/modifyAst/addFillet'
import { getNodeFromPath } from '../lang/queryAst'
import {
applyConstraintEqualAngle,
@ -383,7 +387,7 @@ export const modelingMachine = setup({
guards: {
'Selection is on face': () => false,
'has valid sweep selection': () => false,
'has valid fillet selection': () => false,
'has valid edge treatment selection': () => false,
'Has exportable geometry': () => false,
'has valid selection for deletion': () => false,
'has made first point': ({ context }) => {
@ -739,14 +743,19 @@ export const modelingMachine = setup({
// Extract inputs
const ast = kclManager.ast
const { selection, radius } = event.data
const parameters: FilletParameters = {
type: EdgeTreatmentType.Fillet,
radius,
}
// Apply fillet to selection
const applyFilletToSelectionResult = applyFilletToSelection(
const applyEdgeTreatmentToSelectionResult = applyEdgeTreatmentToSelection(
ast,
selection,
radius
parameters
)
if (err(applyFilletToSelectionResult)) return applyFilletToSelectionResult
if (err(applyEdgeTreatmentToSelectionResult))
return applyEdgeTreatmentToSelectionResult
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
@ -1563,7 +1572,7 @@ export const modelingMachine = setup({
Fillet: {
target: 'idle',
guard: 'has valid fillet selection', // TODO: fix selections
guard: 'has valid edge treatment selection',
actions: ['AST fillet'],
reenter: false,
},

View File

@ -443,9 +443,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.20"
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [
"clap_builder",
"clap_derive",
@ -453,9 +453,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.20"
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [
"anstream",
"anstyle",
@ -774,6 +774,22 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "dhat"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cd11d84628e233de0ce467de10b8633f4ddaecafadefc86e13b84b8739b827"
dependencies = [
"backtrace",
"lazy_static",
"mintex",
"parking_lot 0.12.3",
"rustc-hash 1.1.0",
"serde",
"serde_json",
"thousands",
]
[[package]]
name = "diff"
version = "0.1.13"
@ -1705,6 +1721,7 @@ dependencies = [
"dashmap 6.1.0",
"databake",
"derive-docs",
"dhat",
"expectorate",
"fnv",
"form_urlencoded",
@ -1749,6 +1766,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"web-time",
"winnow",
"zip",
]
@ -2056,6 +2074,12 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
[[package]]
name = "mintex"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bec4598fddb13cc7b528819e697852653252b760f1228b7642679bf2ff2cd07"
[[package]]
name = "mio"
version = "1.0.2"
@ -2644,7 +2668,7 @@ dependencies = [
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustc-hash 2.0.0",
"rustls",
"socket2",
"thiserror 1.0.68",
@ -2661,7 +2685,7 @@ dependencies = [
"bytes",
"rand 0.8.5",
"ring",
"rustc-hash",
"rustc-hash 2.0.0",
"rustls",
"slab",
"thiserror 1.0.68",
@ -3001,6 +3025,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.0.0"
@ -3035,9 +3065,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.13"
version = "0.23.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
dependencies = [
"once_cell",
"ring",
@ -3071,9 +3101,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55"
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
[[package]]
name = "rustls-webpki"
@ -3637,6 +3667,12 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "thousands"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
[[package]]
name = "thread_local"
version = "1.1.8"

View File

@ -16,11 +16,12 @@ async-recursion = "1.1.1"
async-trait = "0.1.83"
base64 = "0.22.1"
chrono = "0.4.38"
clap = { version = "4.5.20", default-features = false, optional = true, features = ["std", "derive"] }
clap = { version = "4.5.21", default-features = false, optional = true, features = ["std", "derive"] }
convert_case = "0.6.0"
dashmap = "6.1.0"
databake = { version = "0.1.8", features = ["derive"] }
derive-docs = { version = "0.1.29", path = "../derive-docs" }
dhat = { version = "0.3", optional = true }
fnv = "1.0.7"
form_urlencoded = "1.2.1"
futures = { version = "0.3.31" }
@ -51,6 +52,7 @@ url = { version = "2.5.3", features = ["serde"] }
urlencoding = "2.1.3"
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
validator = { version = "0.19.0", features = ["derive"] }
web-time = "1.1"
winnow = "0.6.18"
zip = { version = "2.0.0", default-features = false }
@ -72,6 +74,7 @@ tower-lsp = { version = "0.20.0", features = ["proposed"] }
[features]
default = ["engine"]
cli = ["dep:clap"]
dhat-heap = ["dep:dhat"]
# For the lsp server, when run with stdout for rpc we want to disable println.
# This is used for editor extensions that use the lsp server.
disable-println = []

View File

@ -8,17 +8,16 @@ use kittycad_modeling_cmds as kcmc;
use crate::{
ast::types::{
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, PipeExpression, PipeSubstitution,
VariableDeclarator,
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
PipeSubstitution, VariableDeclarator,
},
engine::EngineManager,
errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange},
executor::Point2d,
source_range::{ModuleId, SourceRange},
Program,
};
use super::types::{ModuleId, Node};
type Point3d = kcmc::shared::Point3d<f64>;
#[derive(Debug)]

View File

@ -27,8 +27,9 @@ pub use crate::ast::types::{
use crate::{
docs::StdLibFn,
errors::KclError,
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier},
executor::{ExecState, ExecutorContext, KclValue, Metadata, TagIdentifier},
parser::PIPE_OPERATOR,
source_range::{ModuleId, SourceRange},
std::kcl_stdlib::KclStdLibFn,
};
@ -62,7 +63,7 @@ pub struct Node<T> {
impl<T> Node<T> {
pub fn metadata(&self) -> Metadata {
Metadata {
source_range: SourceRange([self.start, self.end, self.module_id.0 as usize]),
source_range: SourceRange::new(self.start, self.end, self.module_id),
}
}
@ -122,7 +123,7 @@ impl<T> Node<T> {
}
pub fn as_source_range(&self) -> SourceRange {
SourceRange([self.start, self.end, self.module_id.as_usize()])
SourceRange::new(self.start, self.end, self.module_id)
}
pub fn as_source_ranges(&self) -> Vec<SourceRange> {
@ -150,21 +151,21 @@ impl<T: fmt::Display> fmt::Display for Node<T> {
}
}
impl<T> From<Node<T>> for crate::executor::SourceRange {
impl<T> From<Node<T>> for SourceRange {
fn from(v: Node<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
impl<T> From<&Node<T>> for crate::executor::SourceRange {
impl<T> From<&Node<T>> for SourceRange {
fn from(v: &Node<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
impl<T> From<&BoxNode<T>> for crate::executor::SourceRange {
impl<T> From<&BoxNode<T>> for SourceRange {
fn from(v: &BoxNode<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
@ -540,7 +541,6 @@ impl Program {
/// #!/usr/bin/env python
/// ```
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct Shebang {
@ -553,29 +553,6 @@ impl Shebang {
}
}
/// Identifier of a source file. Uses a u32 to keep the size small.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct ModuleId(pub u32);
impl ModuleId {
pub fn from_usize(id: usize) -> Self {
Self(u32::try_from(id).expect("module ID should fit in a u32"))
}
pub fn as_usize(&self) -> usize {
usize::try_from(self.0).expect("module ID should fit in a usize")
}
/// Top-level file is the one being executed.
/// Represented by module ID of 0, i.e. the default value.
pub fn is_top_level(&self) -> bool {
*self == Self::default()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
@ -609,13 +586,13 @@ impl BodyItem {
impl From<BodyItem> for SourceRange {
fn from(item: BodyItem) -> Self {
Self([item.start(), item.end(), item.module_id().as_usize()])
Self::new(item.start(), item.end(), item.module_id())
}
}
impl From<&BodyItem> for SourceRange {
fn from(item: &BodyItem) -> Self {
Self([item.start(), item.end(), item.module_id().as_usize()])
Self::new(item.start(), item.end(), item.module_id())
}
}
@ -631,6 +608,7 @@ pub enum Expr {
BinaryExpression(BoxNode<BinaryExpression>),
FunctionExpression(BoxNode<FunctionExpression>),
CallExpression(BoxNode<CallExpression>),
CallExpressionKw(BoxNode<CallExpressionKw>),
PipeExpression(BoxNode<PipeExpression>),
PipeSubstitution(BoxNode<PipeSubstitution>),
ArrayExpression(BoxNode<ArrayExpression>),
@ -644,7 +622,7 @@ pub enum Expr {
impl Expr {
pub fn get_lsp_folding_range(&self) -> Option<FoldingRange> {
let recasted = self.recast(&FormatOptions::default(), 0, false);
let recasted = self.recast(&FormatOptions::default(), 0, crate::unparser::ExprContext::Other);
// If the code only has one line then we don't need to fold it.
if recasted.lines().count() <= 1 {
return None;
@ -674,6 +652,7 @@ impl Expr {
Expr::Literal(_literal) => None,
Expr::FunctionExpression(_func_exp) => None,
Expr::CallExpression(_call_exp) => None,
Expr::CallExpressionKw(_call_exp) => None,
Expr::Identifier(_ident) => None,
Expr::TagDeclarator(_tag) => None,
Expr::PipeExpression(pipe_exp) => Some(&pipe_exp.non_code_meta),
@ -699,6 +678,7 @@ impl Expr {
Expr::Literal(_) => {}
Expr::FunctionExpression(ref mut func_exp) => func_exp.replace_value(source_range, new_value),
Expr::CallExpression(ref mut call_exp) => call_exp.replace_value(source_range, new_value),
Expr::CallExpressionKw(ref mut call_exp) => call_exp.replace_value(source_range, new_value),
Expr::Identifier(_) => {}
Expr::TagDeclarator(_) => {}
Expr::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
@ -717,6 +697,7 @@ impl Expr {
Expr::BinaryExpression(binary_expression) => binary_expression.start,
Expr::FunctionExpression(function_expression) => function_expression.start,
Expr::CallExpression(call_expression) => call_expression.start,
Expr::CallExpressionKw(call_expression) => call_expression.start,
Expr::PipeExpression(pipe_expression) => pipe_expression.start,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.start,
Expr::ArrayExpression(array_expression) => array_expression.start,
@ -737,6 +718,7 @@ impl Expr {
Expr::BinaryExpression(binary_expression) => binary_expression.end,
Expr::FunctionExpression(function_expression) => function_expression.end,
Expr::CallExpression(call_expression) => call_expression.end,
Expr::CallExpressionKw(call_expression) => call_expression.end,
Expr::PipeExpression(pipe_expression) => pipe_expression.end,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.end,
Expr::ArrayExpression(array_expression) => array_expression.end,
@ -758,6 +740,7 @@ impl Expr {
function_expression.get_hover_value_for_position(pos, code)
}
Expr::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
Expr::CallExpressionKw(call_expression) => call_expression.get_hover_value_for_position(pos, code),
Expr::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code),
Expr::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code),
Expr::ArrayRangeExpression(array_range) => array_range.get_hover_value_for_position(pos, code),
@ -786,6 +769,7 @@ impl Expr {
}
Expr::FunctionExpression(_function_identifier) => {}
Expr::CallExpression(ref mut call_expression) => call_expression.rename_identifiers(old_name, new_name),
Expr::CallExpressionKw(ref mut call_expression) => call_expression.rename_identifiers(old_name, new_name),
Expr::PipeExpression(ref mut pipe_expression) => pipe_expression.rename_identifiers(old_name, new_name),
Expr::PipeSubstitution(_) => {}
Expr::ArrayExpression(ref mut array_expression) => array_expression.rename_identifiers(old_name, new_name),
@ -812,6 +796,7 @@ impl Expr {
Expr::FunctionExpression(function_identifier) => function_identifier.get_constraint_level(),
Expr::CallExpression(call_expression) => call_expression.get_constraint_level(),
Expr::CallExpressionKw(call_expression) => call_expression.get_constraint_level(),
Expr::PipeExpression(pipe_expression) => pipe_expression.get_constraint_level(),
Expr::PipeSubstitution(pipe_substitution) => ConstraintLevel::Ignore {
source_ranges: vec![pipe_substitution.into()],
@ -829,13 +814,13 @@ impl Expr {
impl From<Expr> for SourceRange {
fn from(value: Expr) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
impl From<&Expr> for SourceRange {
fn from(value: &Expr) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
@ -848,6 +833,7 @@ pub enum BinaryPart {
Identifier(BoxNode<Identifier>),
BinaryExpression(BoxNode<BinaryExpression>),
CallExpression(BoxNode<CallExpression>),
CallExpressionKw(BoxNode<CallExpressionKw>),
UnaryExpression(BoxNode<UnaryExpression>),
MemberExpression(BoxNode<MemberExpression>),
IfExpression(BoxNode<IfExpression>),
@ -855,13 +841,13 @@ pub enum BinaryPart {
impl From<BinaryPart> for SourceRange {
fn from(value: BinaryPart) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
impl From<&BinaryPart> for SourceRange {
fn from(value: &BinaryPart) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
@ -873,6 +859,7 @@ impl BinaryPart {
BinaryPart::Identifier(identifier) => identifier.get_constraint_level(),
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
BinaryPart::CallExpressionKw(call_expression) => call_expression.get_constraint_level(),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
BinaryPart::IfExpression(e) => e.get_constraint_level(),
@ -889,6 +876,9 @@ impl BinaryPart {
BinaryPart::CallExpression(ref mut call_expression) => {
call_expression.replace_value(source_range, new_value)
}
BinaryPart::CallExpressionKw(ref mut call_expression) => {
call_expression.replace_value(source_range, new_value)
}
BinaryPart::UnaryExpression(ref mut unary_expression) => {
unary_expression.replace_value(source_range, new_value)
}
@ -903,6 +893,7 @@ impl BinaryPart {
BinaryPart::Identifier(identifier) => identifier.start,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.start,
BinaryPart::CallExpression(call_expression) => call_expression.start,
BinaryPart::CallExpressionKw(call_expression) => call_expression.start,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start,
BinaryPart::MemberExpression(member_expression) => member_expression.start,
BinaryPart::IfExpression(e) => e.start,
@ -915,6 +906,7 @@ impl BinaryPart {
BinaryPart::Identifier(identifier) => identifier.end,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.end,
BinaryPart::CallExpression(call_expression) => call_expression.end,
BinaryPart::CallExpressionKw(call_expression) => call_expression.end,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end,
BinaryPart::MemberExpression(member_expression) => member_expression.end,
BinaryPart::IfExpression(e) => e.end,
@ -930,6 +922,7 @@ impl BinaryPart {
binary_expression.get_hover_value_for_position(pos, code)
}
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
BinaryPart::CallExpressionKw(call_expression) => call_expression.get_hover_value_for_position(pos, code),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code),
BinaryPart::MemberExpression(member_expression) => {
@ -949,6 +942,9 @@ impl BinaryPart {
BinaryPart::CallExpression(ref mut call_expression) => {
call_expression.rename_identifiers(old_name, new_name)
}
BinaryPart::CallExpressionKw(ref mut call_expression) => {
call_expression.rename_identifiers(old_name, new_name)
}
BinaryPart::UnaryExpression(ref mut unary_expression) => {
unary_expression.rename_identifiers(old_name, new_name)
}
@ -1181,7 +1177,7 @@ impl Node<ImportItem> {
self.alias = Some(Identifier::new(new_name));
}
// Return implicit name.
return Some(self.identifier().to_owned());
Some(self.identifier().to_owned())
}
}
}
@ -1264,19 +1260,48 @@ pub struct ExpressionStatement {
pub struct CallExpression {
pub callee: Node<Identifier>,
pub arguments: Vec<Expr>,
pub optional: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<Digest>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
#[serde(tag = "type")]
pub struct CallExpressionKw {
pub callee: Node<Identifier>,
pub unlabeled: Option<Expr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub arguments: Vec<LabeledArg>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<Digest>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
#[serde(tag = "type")]
pub struct LabeledArg {
pub label: Identifier,
pub arg: Expr,
}
impl From<Node<CallExpression>> for Expr {
fn from(call_expression: Node<CallExpression>) -> Self {
Expr::CallExpression(Box::new(call_expression))
}
}
impl From<Node<CallExpressionKw>> for Expr {
fn from(call_expression: Node<CallExpressionKw>) -> Self {
Expr::CallExpressionKw(Box::new(call_expression))
}
}
impl Node<CallExpression> {
/// Return the constraint level for this call expression.
pub fn get_constraint_level(&self) -> ConstraintLevel {
@ -1296,12 +1321,30 @@ impl Node<CallExpression> {
}
}
impl Node<CallExpressionKw> {
/// Return the constraint level for this call expression.
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.arguments.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
// Iterate over the arguments and get the constraint level for each one.
let mut constraint_levels = ConstraintLevels::new();
for arg in &self.arguments {
constraint_levels.push(arg.arg.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
}
impl CallExpression {
pub fn new(name: &str, arguments: Vec<Expr>) -> Result<Node<Self>, KclError> {
Ok(Node::no_src(Self {
callee: Identifier::new(name),
arguments,
optional: false,
digest: None,
}))
}
@ -1353,6 +1396,68 @@ impl CallExpression {
}
}
impl CallExpressionKw {
pub fn new(name: &str, unlabeled: Option<Expr>, arguments: Vec<LabeledArg>) -> Result<Node<Self>, KclError> {
Ok(Node::no_src(Self {
callee: Identifier::new(name),
unlabeled,
arguments,
digest: None,
}))
}
/// Iterate over all arguments (labeled or not)
pub fn iter_arguments(&self) -> impl Iterator<Item = &Expr> {
self.unlabeled.iter().chain(self.arguments.iter().map(|arg| &arg.arg))
}
/// Is at least one argument the '%' i.e. the substitution operator?
pub fn has_substitution_arg(&self) -> bool {
self.arguments
.iter()
.any(|arg| matches!(arg.arg, Expr::PipeSubstitution(_)))
}
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
for arg in &mut self.arguments {
arg.arg.replace_value(source_range, new_value.clone());
}
}
/// Returns a hover value that includes the given character position.
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
let callee_source_range: SourceRange = self.callee.clone().into();
if callee_source_range.contains(pos) {
return Some(Hover::Function {
name: self.callee.name.clone(),
range: callee_source_range.to_lsp_range(code),
});
}
for (index, arg) in self.iter_arguments().enumerate() {
let source_range: SourceRange = arg.into();
if source_range.contains(pos) {
return Some(Hover::Signature {
name: self.callee.name.clone(),
parameter_index: index as u32,
range: source_range.to_lsp_range(code),
});
}
}
None
}
/// Rename all identifiers that have the old name to the new given name.
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
self.callee.rename(old_name, new_name);
for arg in &mut self.arguments {
arg.arg.rename_identifiers(old_name, new_name);
}
}
}
/// A function declaration.
#[derive(Debug, Clone, Default, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
#[ts(export)]
@ -2201,13 +2306,13 @@ impl MemberObject {
impl From<MemberObject> for SourceRange {
fn from(obj: MemberObject) -> Self {
Self([obj.start(), obj.end(), obj.module_id().as_usize()])
Self::new(obj.start(), obj.end(), obj.module_id())
}
}
impl From<&MemberObject> for SourceRange {
fn from(obj: &MemberObject) -> Self {
Self([obj.start(), obj.end(), obj.module_id().as_usize()])
Self::new(obj.start(), obj.end(), obj.module_id())
}
}
@ -2238,13 +2343,13 @@ impl LiteralIdentifier {
impl From<LiteralIdentifier> for SourceRange {
fn from(id: LiteralIdentifier) -> Self {
Self([id.start(), id.end(), id.module_id().as_usize()])
Self::new(id.start(), id.end(), id.module_id())
}
}
impl From<&LiteralIdentifier> for SourceRange {
fn from(id: &LiteralIdentifier) -> Self {
Self([id.start(), id.end(), id.module_id().as_usize()])
Self::new(id.start(), id.end(), id.module_id())
}
}
@ -2830,7 +2935,6 @@ pub enum Hover {
/// Format options.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct FormatOptions {
@ -3069,9 +3173,9 @@ ghi("things")
folding_ranges[1].collapsed_text,
Some("startSketchOn('XY')".to_string())
);
assert_eq!(folding_ranges[2].start_line, 390);
assert_eq!(folding_ranges[2].start_line, 384);
assert_eq!(folding_ranges[2].end_line, 403);
assert_eq!(folding_ranges[2].collapsed_text, Some("fn ghi = (x) => {".to_string()));
assert_eq!(folding_ranges[2].collapsed_text, Some("fn ghi(x) {".to_string()));
}
#[test]
@ -3270,7 +3374,7 @@ const cylinder = startSketchOn('-XZ')
#[tokio::test(flavor = "multi_thread")]
async fn test_parse_return_type_on_functions() {
let some_program_string = r#"fn thing = () => {thing: number, things: string[], more?: string} {
let some_program_string = r#"fn thing(): {thing: number, things: string[], more?: string} {
return 1
}"#;
let module_id = ModuleId::default();
@ -3296,8 +3400,8 @@ const cylinder = startSketchOn('-XZ')
name: "thing".to_owned(),
digest: None
},
13,
18,
23,
module_id,
),
type_: Some(FnArgType::Primitive(FnArgPrimitive::Number)),
@ -3310,8 +3414,8 @@ const cylinder = startSketchOn('-XZ')
name: "things".to_owned(),
digest: None
},
33,
39,
28,
34,
module_id,
),
type_: Some(FnArgType::Array(FnArgPrimitive::String)),
@ -3324,8 +3428,8 @@ const cylinder = startSketchOn('-XZ')
name: "more".to_owned(),
digest: None
},
51,
55,
46,
50,
module_id,
),
type_: Some(FnArgType::Primitive(FnArgPrimitive::String)),

View File

@ -1,15 +1,10 @@
use crate::executor::SourceRange;
use super::BoxNode;
use super::ConstraintLevel;
use super::Hover;
use super::Node;
use super::NodeList;
use super::{Digest, Expr};
use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{BoxNode, ConstraintLevel, Digest, Expr, Hover, Node, NodeList};
use crate::SourceRange;
// TODO: This should be its own type, similar to Program,
// but guaranteed to have an Expression as its final item.
// https://github.com/KittyCAD/modeling-app/issues/4015
@ -50,7 +45,7 @@ impl Node<IfExpression> {
impl Node<ElseIf> {
#[allow(dead_code)]
fn source_ranges(&self) -> Vec<SourceRange> {
vec![SourceRange([self.start, self.end, self.module_id.as_usize()])]
vec![SourceRange::new(self.start, self.end, self.module_id)]
}
}

View File

@ -1,11 +1,11 @@
use sha2::{Digest as DigestTrait, Sha256};
use super::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ElseIf, Expr,
ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ImportStatement, Literal,
LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator,
UnaryExpression, VariableDeclaration, VariableDeclarator,
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode,
NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program,
ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
};
/// Position-independent digest of the AST node.
@ -93,6 +93,7 @@ impl Expr {
Expr::BinaryExpression(be) => be.compute_digest(),
Expr::FunctionExpression(fe) => fe.compute_digest(),
Expr::CallExpression(ce) => ce.compute_digest(),
Expr::CallExpressionKw(ce) => ce.compute_digest(),
Expr::PipeExpression(pe) => pe.compute_digest(),
Expr::PipeSubstitution(ps) => ps.compute_digest(),
Expr::ArrayExpression(ae) => ae.compute_digest(),
@ -117,6 +118,7 @@ impl BinaryPart {
BinaryPart::Identifier(id) => id.compute_digest(),
BinaryPart::BinaryExpression(be) => be.compute_digest(),
BinaryPart::CallExpression(ce) => ce.compute_digest(),
BinaryPart::CallExpressionKw(ce) => ce.compute_digest(),
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
BinaryPart::MemberExpression(me) => me.compute_digest(),
BinaryPart::IfExpression(e) => e.compute_digest(),
@ -369,7 +371,22 @@ impl CallExpression {
for argument in slf.arguments.iter_mut() {
hasher.update(argument.compute_digest());
}
hasher.update(if slf.optional { [1] } else { [0] });
});
}
impl CallExpressionKw {
compute_digest!(|slf, hasher| {
hasher.update(slf.callee.compute_digest());
if let Some(ref mut unlabeled) = slf.unlabeled {
hasher.update(unlabeled.compute_digest());
} else {
hasher.update("no_unlabeled");
}
hasher.update(slf.arguments.len().to_ne_bytes());
for argument in slf.arguments.iter_mut() {
hasher.update(argument.label.compute_digest());
hasher.update(argument.arg.compute_digest());
}
});
}

View File

@ -1,19 +1,18 @@
use std::collections::HashMap;
use async_recursion::async_recursion;
use super::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Expr,
IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, ObjectExpression,
TagDeclarator, UnaryExpression, UnaryOperator,
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
CallExpressionKw, Expr, IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
Node, ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo,
TagIdentifier,
},
executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier},
source_range::SourceRange,
std::{args::Arg, FunctionKind},
};
use async_recursion::async_recursion;
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
@ -28,6 +27,7 @@ impl BinaryPart {
}
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
BinaryPart::CallExpression(call_expression) => call_expression.execute(exec_state, ctx).await,
BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
@ -333,6 +333,7 @@ async fn inner_execute_pipe_body(
| Expr::BinaryExpression(_)
| Expr::FunctionExpression(_)
| Expr::CallExpression(_)
| Expr::CallExpressionKw(_)
| Expr::PipeExpression(_)
| Expr::PipeSubstitution(_)
| Expr::ArrayExpression(_)
@ -356,6 +357,12 @@ async fn inner_execute_pipe_body(
Ok(final_output)
}
impl Node<CallExpressionKw> {
pub async fn execute(&self, _exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
todo!()
}
}
impl Node<CallExpression> {
#[async_recursion]
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {

View File

@ -3,9 +3,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use crate::ast::types::{Expr, Literal};
use super::Node;
use crate::ast::types::{Expr, Literal};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]

View File

@ -4,9 +4,8 @@ use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{ast::types::ConstraintLevel, executor::KclValue};
use super::Node;
use crate::{ast::types::ConstraintLevel, executor::KclValue};
const KCL_NONE_ID: &str = "KCL_NONE_ID";

View File

@ -1,4 +1,5 @@
use super::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject, ModuleId};
use super::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject};
use crate::source_range::ModuleId;
impl BodyItem {
pub fn module_id(&self) -> ModuleId {
@ -20,6 +21,7 @@ impl Expr {
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
Expr::FunctionExpression(function_expression) => function_expression.module_id,
Expr::CallExpression(call_expression) => call_expression.module_id,
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
Expr::ArrayExpression(array_expression) => array_expression.module_id,
@ -40,6 +42,7 @@ impl BinaryPart {
BinaryPart::Identifier(identifier) => identifier.module_id,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::IfExpression(e) => e.module_id,

View File

@ -18,14 +18,14 @@ use kittycad_modeling_cmds as kcmc;
use tokio::sync::{mpsc, oneshot, RwLock};
use tokio_tungstenite::tungstenite::Message as WsMsg;
use super::ExecutionKind;
use crate::{
engine::EngineManager,
errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};
use super::ExecutionKind;
#[derive(Debug, PartialEq)]
enum SocketHealth {
Active,
@ -41,8 +41,8 @@ pub struct EngineConnection {
#[allow(dead_code)]
tcp_read_handle: Arc<TcpReadHandle>,
socket_health: Arc<Mutex<SocketHealth>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
@ -282,8 +282,8 @@ impl EngineConnection {
}
Err(e) => {
match &e {
WebSocketReadError::Read(e) => eprintln!("could not read from WS: {:?}", e),
WebSocketReadError::Deser(e) => eprintln!("could not deserialize msg from WS: {:?}", e),
WebSocketReadError::Read(e) => crate::logln!("could not read from WS: {:?}", e),
WebSocketReadError::Deser(e) => crate::logln!("could not deserialize msg from WS: {:?}", e),
}
*socket_health_tcp_read.lock().unwrap() = SocketHealth::Inactive;
return Err(e);
@ -311,11 +311,11 @@ impl EngineConnection {
#[async_trait::async_trait]
impl EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
@ -334,7 +334,7 @@ impl EngineManager for EngineConnection {
async fn default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
{
let opt = self.default_planes.read().await.as_ref().cloned();
@ -352,7 +352,7 @@ impl EngineManager for EngineConnection {
async fn clear_scene_post_hook(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), KclError> {
// Remake the default planes, since they would have been removed after the scene was cleared.
let new_planes = self.new_default_planes(id_generator, source_range).await?;
@ -364,9 +364,9 @@ impl EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
let (tx, rx) = oneshot::channel();

View File

@ -17,17 +17,17 @@ use kcmc::{
};
use kittycad_modeling_cmds::{self as kcmc};
use super::ExecutionKind;
use crate::{
errors::KclError,
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};
use super::ExecutionKind;
#[derive(Debug, Clone)]
pub struct EngineConnection {
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
}
@ -43,11 +43,11 @@ impl EngineConnection {
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
@ -66,7 +66,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
Ok(DefaultPlanes::default())
}
@ -74,7 +74,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
) -> Result<(), KclError> {
Ok(())
}
@ -82,9 +82,9 @@ impl crate::engine::EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
cmd: WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
match cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {

View File

@ -12,6 +12,7 @@ use crate::{
engine::ExecutionKind,
errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};
#[wasm_bindgen(module = "/../../lang/std/engineConnection.ts")]
@ -41,8 +42,8 @@ extern "C" {
#[derive(Debug, Clone)]
pub struct EngineConnection {
manager: Arc<EngineCommandManager>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
}
@ -63,11 +64,11 @@ impl EngineConnection {
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
@ -86,7 +87,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
// Get the default planes.
let promise = self.manager.get_default_planes().map_err(|e| {
@ -128,7 +129,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), KclError> {
self.manager.clear_default_planes().map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -158,9 +159,9 @@ impl crate::engine::EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
id_to_source_range: std::collections::HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {

View File

@ -33,6 +33,7 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator, Point3d},
SourceRange,
};
lazy_static::lazy_static! {
@ -61,10 +62,10 @@ impl ExecutionKind {
#[async_trait::async_trait]
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the batch of commands to be sent to the engine.
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>;
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>;
/// Get the batch of end commands to be sent to the engine.
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>;
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
/// Get the current execution kind.
fn execution_kind(&self) -> ExecutionKind;
@ -77,7 +78,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn default_planes(
&self,
id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
) -> Result<DefaultPlanes, crate::errors::KclError>;
/// Helpers to be called after clearing a scene.
@ -85,22 +86,22 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn clear_scene_post_hook(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), crate::errors::KclError>;
/// Send a modeling command and wait for the response message.
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<kcmc::websocket::WebSocketResponse, crate::errors::KclError>;
async fn clear_scene(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), crate::errors::KclError> {
self.batch_modeling_cmd(
uuid::Uuid::new_v4(),
@ -123,7 +124,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn batch_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let execution_kind = self.execution_kind();
@ -147,7 +148,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn batch_end_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
@ -166,7 +167,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: ModelingCmd,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
self.batch_modeling_cmd(id, source_range, &cmd).await?;
@ -181,7 +182,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// Whether or not to flush the end commands as well.
// We only do this at the very end of the file.
batch_end: bool,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
let all_requests = if batch_end {
let mut requests = self.batch().lock().unwrap().clone();
@ -303,7 +304,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
x_axis: Point3d,
y_axis: Point3d,
color: Option<Color>,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<uuid::Uuid, KclError> {
// Create new default planes.
let default_size = 100.0;
@ -339,7 +340,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn new_default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
let plane_settings: HashMap<PlaneName, (Uuid, Point3d, Point3d, Option<Color>)> = HashMap::from([
(
@ -450,7 +451,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
fn parse_websocket_response(
&self,
response: WebSocketResponse,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
match response {
WebSocketResponse::Success(success) => Ok(success.resp),
@ -469,7 +470,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// The last response we are looking for.
id: uuid::Uuid,
// The mapping of source ranges to command IDs.
id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
// The response from the engine.
responses: HashMap<uuid::Uuid, BatchResponse>,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {

View File

@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::{ast::types::ModuleId, executor::SourceRange, lsp::IntoDiagnostic};
use crate::{
lsp::IntoDiagnostic,
source_range::{ModuleId, SourceRange},
};
/// How did the KCL execution fail
#[derive(thiserror::Error, Debug)]

View File

@ -19,20 +19,18 @@ use kittycad_modeling_cmds::length_unit::LengthUnit;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
pub use crate::kcl_value::KclValue;
use crate::{
ast::types::{
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, TagDeclarator, TagNode,
},
ast::types::{BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, Node, NodeRef, TagDeclarator, TagNode},
engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem},
settings::types::UnitLength,
source_range::{ModuleId, SourceRange},
std::{args::Arg, StdLib},
ExecError, Program,
};
@ -993,7 +991,6 @@ pub enum BodyType {
/// Info about a module. Right now, this is pretty minimal. We hope to cache
/// modules here in the future.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export)]
pub struct ModuleInfo {
/// The ID of the module.
@ -1002,101 +999,6 @@ pub struct ModuleInfo {
path: std::path::PathBuf,
}
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export)]
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 3]);
impl From<[usize; 3]> for SourceRange {
fn from(value: [usize; 3]) -> Self {
Self(value)
}
}
impl From<&SourceRange> for miette::SourceSpan {
fn from(source_range: &SourceRange) -> Self {
let length = source_range.end() - source_range.start();
let start = miette::SourceOffset::from(source_range.start());
Self::new(start, length)
}
}
impl From<SourceRange> for miette::SourceSpan {
fn from(source_range: SourceRange) -> Self {
Self::from(&source_range)
}
}
impl SourceRange {
/// Create a new source range.
pub fn new(start: usize, end: usize, module_id: ModuleId) -> Self {
Self([start, end, module_id.as_usize()])
}
/// A source range that doesn't correspond to any source code.
pub fn synthetic() -> Self {
Self::default()
}
/// Get the start of the range.
pub fn start(&self) -> usize {
self.0[0]
}
/// Get the end of the range.
pub fn end(&self) -> usize {
self.0[1]
}
/// Get the module ID of the range.
pub fn module_id(&self) -> ModuleId {
ModuleId::from_usize(self.0[2])
}
/// Check if the range contains a position.
pub fn contains(&self, pos: usize) -> bool {
pos >= self.start() && pos <= self.end()
}
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
if line > 0 {
line = line.saturating_sub(1);
}
let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
let lines = code.get(..self.end()).unwrap_or_default().lines();
if lines.clone().count() == 0 {
return LspPosition { line: 0, character: 0 };
}
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let line = lines.clone().count() - 1;
let column = lines.last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn to_lsp_range(&self, code: &str) -> LspRange {
let start = self.start_to_lsp_position(code);
let end = self.end_to_lsp_position(code);
LspRange { start, end }
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point2d {
@ -1909,6 +1811,7 @@ impl ExecutorContext {
program: &Program,
exec_state: &mut ExecState,
) -> Result<Option<ModelingSessionData>, KclError> {
let _stats = crate::log::LogPerfStats::new("Interpretation");
// TODO: Use the top-level file's path.
exec_state.add_module(std::path::PathBuf::from(""));
// Before we even start executing the program, set the units.
@ -2108,7 +2011,7 @@ impl ExecutorContext {
// True here tells the engine to flush all the end commands as well like fillets
// and chamfers where the engine would otherwise eat the ID of the segments.
true,
SourceRange([program.end, program.end, program.module_id.as_usize()]),
SourceRange::new(program.end, program.end, program.module_id),
)
.await?;
}
@ -2144,6 +2047,7 @@ impl ExecutorContext {
}
}
Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?,
Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
StatementKind::Declaration { name } => {
@ -2713,7 +2617,10 @@ const answer = returnX()"#;
err,
KclError::UndefinedValue(KclErrorDetails {
message: "memory item key `x` is not defined".to_owned(),
source_ranges: vec![SourceRange([64, 65, 0]), SourceRange([97, 106, 0])],
source_ranges: vec![
SourceRange::new(64, 65, ModuleId::default()),
SourceRange::new(97, 106, ModuleId::default())
],
}),
);
}
@ -2748,7 +2655,7 @@ let shape = layer() |> patternTransform(10, transform, %)
err,
KclError::UndefinedValue(KclErrorDetails {
message: "memory item key `x` is not defined".to_owned(),
source_ranges: vec![SourceRange([80, 81, 0])],
source_ranges: vec![SourceRange::new(80, 81, ModuleId::default())],
}),
);
}
@ -2844,7 +2751,7 @@ let notNull = !myNull
parse_execute(code1).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([56, 63, 0])],
source_ranges: vec![SourceRange::new(56, 63, ModuleId::default())],
})
);
@ -2853,7 +2760,7 @@ let notNull = !myNull
parse_execute(code2).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([14, 16, 0])],
source_ranges: vec![SourceRange::new(14, 16, ModuleId::default())],
})
);
@ -2864,7 +2771,7 @@ let notEmptyString = !""
parse_execute(code3).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: string (text)".to_owned(),
source_ranges: vec![SourceRange([22, 25, 0])],
source_ranges: vec![SourceRange::new(22, 25, ModuleId::default())],
})
);
@ -2876,7 +2783,7 @@ let notMember = !obj.a
parse_execute(code4).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([36, 42, 0])],
source_ranges: vec![SourceRange::new(36, 42, ModuleId::default())],
})
);
@ -2887,7 +2794,7 @@ let notArray = !a";
parse_execute(code5).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: array (list)".to_owned(),
source_ranges: vec![SourceRange([27, 29, 0])],
source_ranges: vec![SourceRange::new(27, 29, ModuleId::default())],
})
);
@ -2898,7 +2805,7 @@ let notObject = !x";
parse_execute(code6).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: object".to_owned(),
source_ranges: vec![SourceRange([28, 30, 0])],
source_ranges: vec![SourceRange::new(28, 30, ModuleId::default())],
})
);
@ -2951,7 +2858,7 @@ let notTagIdentifier = !myTag";
parse_execute(code10).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: !".to_owned(),
source_ranges: vec![SourceRange([14, 15, 0])],
source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())],
})
);
@ -2964,7 +2871,7 @@ let notPipeSub = 1 |> identity(!%))";
parse_execute(code11).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: |>".to_owned(),
source_ranges: vec![SourceRange([54, 56, 0])],
source_ranges: vec![SourceRange::new(54, 56, ModuleId::default())],
})
);
@ -3008,10 +2915,10 @@ test([0, 0])
"#;
let result = parse_execute(ast).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
r#"undefined value: KclErrorDetails { source_ranges: [SourceRange([10, 34, 0])], message: "Result of user-defined function test is undefined" }"#.to_owned()
);
assert!(result
.unwrap_err()
.to_string()
.contains("Result of user-defined function test is undefined"),);
}
#[tokio::test(flavor = "multi_thread")]
@ -3127,7 +3034,7 @@ let w = f() + f()
vec![req_param("x")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1 arguments, got 0".to_owned(),
})),
),
@ -3145,7 +3052,7 @@ let w = f() + f()
vec![req_param("x"), opt_param("y")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 0".to_owned(),
})),
),
@ -3172,7 +3079,7 @@ let w = f() + f()
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2), mem(3)],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 3".to_owned(),
})),
),

View File

@ -5,6 +5,7 @@ use anyhow::Result;
use crate::{
errors::{KclError, KclErrorDetails},
fs::FileSystem,
SourceRange,
};
#[derive(Debug, Clone)]
@ -27,7 +28,7 @@ impl FileSystem for FileManager {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<u8>, KclError> {
tokio::fs::read(&path).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -40,7 +41,7 @@ impl FileSystem for FileManager {
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<String, KclError> {
tokio::fs::read_to_string(&path).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -53,7 +54,7 @@ impl FileSystem for FileManager {
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError> {
tokio::fs::metadata(&path).await.map(|_| true).or_else(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
@ -70,7 +71,7 @@ impl FileSystem for FileManager {
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
let mut files = vec![];
let mut stack = vec![path.as_ref().to_path_buf()];

View File

@ -1,5 +1,9 @@
//! Functions for interacting with files on a machine.
use anyhow::Result;
use crate::SourceRange;
#[cfg(not(target_arch = "wasm32"))]
pub mod local;
#[cfg(not(target_arch = "wasm32"))]
@ -9,7 +13,6 @@ pub use local::FileManager;
#[cfg(not(test))]
pub mod wasm;
use anyhow::Result;
#[cfg(target_arch = "wasm32")]
#[cfg(not(test))]
pub use wasm::FileManager;
@ -20,27 +23,27 @@ pub trait FileSystem: Clone {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<u8>, crate::errors::KclError>;
/// Read a file from the local file system.
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<String, crate::errors::KclError>;
/// Check if a file exists on the local file system.
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError>;
/// Get all the files in a directory recursively.
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError>;
}

View File

@ -7,6 +7,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
fs::FileSystem,
wasm::JsFuture,
SourceRange,
};
#[wasm_bindgen(module = "/../../lang/std/fileSystemManager.ts")]
@ -43,7 +44,7 @@ impl FileSystem for FileManager {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<u8>, KclError> {
let promise = self
.manager
@ -81,7 +82,7 @@ impl FileSystem for FileManager {
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<String, KclError> {
let bytes = self.read(path, source_range).await?;
let string = String::from_utf8(bytes).map_err(|e| {
@ -97,7 +98,7 @@ impl FileSystem for FileManager {
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError> {
let promise = self
.manager
@ -139,7 +140,7 @@ impl FileSystem for FileManager {
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
let promise = self
.manager

View File

@ -37,7 +37,7 @@ impl<'a> FunctionParam<'a> {
}
}
impl<'a> JsonSchema for FunctionParam<'a> {
impl JsonSchema for FunctionParam<'_> {
fn schema_name() -> String {
"FunctionParam".to_owned()
}

View File

@ -74,6 +74,7 @@ pub enum KclValue {
Function {
#[serde(skip)]
func: Option<MemoryFunction>,
#[schemars(skip)]
expression: crate::ast::types::BoxNode<FunctionExpression>,
memory: Box<ProgramMemory>,
#[serde(rename = "__meta")]
@ -122,7 +123,7 @@ impl From<Vec<Box<Solid>>> for KclValue {
impl From<KclValue> for Vec<SourceRange> {
fn from(item: KclValue) -> Self {
match item {
KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
@ -151,7 +152,7 @@ fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
impl From<&KclValue> for Vec<SourceRange> {
fn from(item: &KclValue) -> Self {
match item {
KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),

View File

@ -8,11 +8,54 @@
#[allow(unused_macros)]
macro_rules! println {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
std::println!($($rest)*)
}
}
#[allow(unused_macros)]
macro_rules! eprintln {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
std::eprintln!($($rest)*)
}
}
#[allow(unused_macros)]
macro_rules! print {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
std::print!($($rest)*)
}
}
#[allow(unused_macros)]
macro_rules! eprint {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
std::eprint!($($rest)*)
}
}
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
mod ast;
mod coredump;
mod docs;
@ -23,11 +66,13 @@ mod fs;
mod function_param;
mod kcl_value;
pub mod lint;
mod log;
mod lsp;
mod parser;
mod settings;
#[cfg(test)]
mod simulation_tests;
mod source_range;
mod std;
#[cfg(not(target_arch = "wasm32"))]
pub mod test_server;
@ -38,17 +83,17 @@ mod walk;
#[cfg(target_arch = "wasm32")]
mod wasm;
pub use ast::modify::modify_ast_for_sketch;
pub use ast::types::{FormatOptions, ModuleId};
pub use ast::{modify::modify_ast_for_sketch, types::FormatOptions};
pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind};
pub use errors::{ConnectionError, ExecError, KclError};
pub use executor::{ExecState, ExecutorContext, ExecutorSettings, SourceRange};
pub use lsp::copilot::Backend as CopilotLspBackend;
pub use lsp::kcl::Backend as KclLspBackend;
pub use lsp::kcl::Server as KclLspServerSubCommand;
pub use executor::{ExecState, ExecutorContext, ExecutorSettings};
pub use lsp::{
copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
};
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
pub use token::lexer;
pub use source_range::{ModuleId, SourceRange};
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
// Ideally we wouldn't export these things at all, they should only be used for testing.
@ -58,9 +103,11 @@ pub mod exec {
#[cfg(target_arch = "wasm32")]
pub mod wasm_engine {
pub use crate::coredump::wasm::{CoreDumpManager, CoreDumper};
pub use crate::engine::conn_wasm::{EngineCommandManager, EngineConnection};
pub use crate::fs::wasm::FileSystemManager;
pub use crate::{
coredump::wasm::{CoreDumpManager, CoreDumper},
engine::conn_wasm::{EngineCommandManager, EngineConnection},
fs::wasm::FileSystemManager,
};
}
#[cfg(not(target_arch = "wasm32"))]
@ -74,6 +121,9 @@ pub mod std_utils {
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::log::{log, logln};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {
#[serde(flatten)]

View File

@ -3,9 +3,9 @@ use convert_case::Casing;
use crate::{
ast::types::{ObjectProperty, VariableDeclarator},
executor::SourceRange,
lint::rule::{def_finding, Discovered, Finding},
walk::Node,
SourceRange,
};
def_finding!(

View File

@ -1,11 +1,13 @@
use std::collections::HashMap;
use anyhow::Result;
use crate::{
ast::types::{BinaryPart, Expr, LiteralValue, ObjectExpression, UnaryOperator},
executor::SourceRange,
lint::rule::{def_finding, Discovered, Finding},
walk::Node,
SourceRange,
};
use anyhow::Result;
use std::collections::HashMap;
def_finding!(
Z0003,

View File

@ -5,10 +5,10 @@ use anyhow::Result;
use crate::{
ast::types::{CallExpression, NodeRef},
docs::StdLibFn,
executor::SourceRange,
lint::rule::{def_finding, Discovered, Finding},
std::{FunctionKind, StdLib},
walk::Node,
SourceRange,
};
def_finding!(

View File

@ -3,7 +3,7 @@ use schemars::JsonSchema;
use serde::Serialize;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::{executor::SourceRange, lsp::IntoDiagnostic, walk::Node};
use crate::{lsp::IntoDiagnostic, walk::Node, SourceRange};
/// Check the provided AST for any found rule violations.
///
@ -30,16 +30,16 @@ where
#[serde(rename_all = "camelCase")]
pub struct Discovered {
/// Zoo Lint Finding information.
pub finding: Finding,
pub(super) finding: Finding,
/// Further information about the specific finding.
pub description: String,
pub(super) description: String,
/// Source code location.
pub pos: SourceRange,
pub(super) pos: SourceRange,
/// Is this discovered issue overridden by the programmer?
pub overridden: bool,
pub(super) overridden: bool,
}
#[cfg(feature = "pyo3")]
@ -56,8 +56,8 @@ impl Discovered {
}
#[getter]
pub fn pos(&self) -> SourceRange {
self.pos
pub fn pos(&self) -> (usize, usize) {
(self.pos.start(), self.pos.end())
}
#[getter]

117
src/wasm-lib/kcl/src/log.rs Normal file
View File

@ -0,0 +1,117 @@
#![allow(dead_code)]
use std::env;
#[cfg(feature = "dhat-heap")]
use dhat::{HeapStats, Profiler};
use web_time::Instant;
const LOG_ENV_VAR: &str = "ZOO_LOG";
lazy_static::lazy_static! {
static ref ENABLED: bool = {
let env_var = env::var(LOG_ENV_VAR);
let Ok(env_var) = env_var else {
return false;
};
!env_var.is_empty()
};
}
#[cfg(feature = "dhat-heap")]
lazy_static::lazy_static! {
static ref PROFILER: Profiler = Profiler::builder().testing().build();
}
/// Log a message
pub(crate) fn log(msg: impl Into<String>) {
if *ENABLED {
log_inner(msg.into());
}
}
#[allow(unused_macros)]
macro_rules! logln {
($($rest:tt)*) => {
crate::log::log(format!($($rest)*))
}
}
pub(crate) use logln;
#[cfg(not(feature = "disable-println"))]
#[inline]
fn log_inner(msg: String) {
eprintln!("{msg}");
}
#[cfg(all(feature = "disable-println", target_arch = "wasm32"))]
#[inline]
fn log_inner(msg: String) {
web_sys::console::log_1(&msg.into());
}
#[cfg(all(feature = "disable-println", not(target_arch = "wasm32")))]
#[inline]
fn log_inner(_msg: String) {}
/// A helper struct for recording and logging basic performance metrics.
///
/// It will log the metrics when dropped or if `log_now` is called.
pub(crate) struct LogPerfStats<'a> {
msg: &'a str,
start_time: Instant,
#[cfg(feature = "dhat-heap")]
start_stats: HeapStats,
cancelled: bool,
}
impl<'a> LogPerfStats<'a> {
#[cfg(not(feature = "dhat-heap"))]
pub fn new(msg: &'a str) -> Self {
LogPerfStats {
msg,
start_time: Instant::now(),
cancelled: false,
}
}
#[cfg(feature = "dhat-heap")]
pub fn new(msg: &'a str) -> Self {
lazy_static::initialize(&PROFILER);
LogPerfStats {
msg,
start_time: Instant::now(),
start_stats: HeapStats::get(),
cancelled: false,
}
}
pub fn log_now(&self) {
let time = Instant::now().duration_since(self.start_time).as_secs_f64() * 1000.0;
logln!("{}\n time: {time:.3}ms", self.msg);
#[cfg(feature = "dhat-heap")]
{
let stats = HeapStats::get();
let blocks = stats.total_blocks - self.start_stats.total_blocks;
let bytes = (stats.total_bytes - self.start_stats.total_bytes) as f64 / 1_000_000.0;
let cur = stats.curr_bytes as f64 / 1000.0;
let max = stats.curr_bytes as f64 / 1000.0;
logln!(" memory:");
logln!(" allocations: {bytes:.5} MB ({blocks} blocks)");
logln!(" currently allocated: {cur:.3} KB");
logln!(" max allocated: {max:.3} KB");
}
}
/// After `cancel`ing, this object will not log its stats on drop (you can still can `log_now`).
pub fn cancel(&mut self) {
self.cancelled = true;
}
}
impl Drop for LogPerfStats<'_> {
fn drop(&mut self) {
if !self.cancelled {
self.log_now();
}
}
}

View File

@ -27,10 +27,12 @@ use tower_lsp::{
use crate::lsp::{
backend::Backend as _,
copilot::cache::CopilotCache,
copilot::types::{
CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
copilot::{
cache::CopilotCache,
types::{
CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
},
},
};

View File

@ -41,11 +41,11 @@ use tower_lsp::{
};
use crate::{
ast::types::{Expr, ModuleId, Node, VariableKind},
ast::types::{Expr, Node, VariableKind},
lsp::{backend::Backend as _, util::IntoDiagnostic},
parser::PIPE_OPERATOR,
token::TokenType,
ExecState, Program, SourceRange,
ExecState, ModuleId, Program, SourceRange,
};
lazy_static::lazy_static! {

View File

@ -1,9 +1,9 @@
use parser_impl::ParseContext;
use crate::{
ast::types::{ModuleId, Node, Program},
ast::types::{Node, Program},
errors::{KclError, KclErrorDetails},
executor::SourceRange,
source_range::{ModuleId, SourceRange},
token::{Token, TokenType},
};
@ -81,11 +81,16 @@ pub fn parse_tokens(tokens: Vec<Token>) -> ParseResult {
/// Invariants:
/// - if there are no errors, then the Option will be Some
/// - if the Option is None, then there will be at least one error in the ParseContext.
#[derive(Debug, Clone)]
pub(crate) struct ParseResult(pub Result<(Option<Node<Program>>, ParseContext), KclError>);
impl ParseResult {
#[cfg(test)]
#[track_caller]
pub fn unwrap(self) -> Node<Program> {
if self.0.is_err() || self.0.as_ref().unwrap().0.is_none() {
eprint!("{self:#?}");
}
self.0.unwrap().0.unwrap()
}

View File

@ -1,7 +1,7 @@
use crate::{
ast::types::{BinaryExpression, BinaryOperator, BinaryPart, Node},
errors::{KclError, KclErrorDetails},
executor::SourceRange,
SourceRange,
};
/// Parses a list of tokens (in infix order, i.e. as the user typed them)
@ -66,7 +66,7 @@ fn source_range(tokens: &[BinaryExpressionToken]) -> Vec<SourceRange> {
})
.collect();
match (sources.first(), sources.last()) {
(Some((start, _, module_id)), Some((_, end, _))) => vec![SourceRange([*start, *end, module_id.as_usize()])],
(Some((start, _, module_id)), Some((_, end, _))) => vec![SourceRange::new(*start, *end, *module_id)],
_ => Vec::new(),
}
}
@ -126,7 +126,7 @@ impl From<BinaryOperator> for BinaryExpressionToken {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::types::{Literal, ModuleId};
use crate::{ast::types::Literal, source_range::ModuleId};
#[test]
fn parse_and_evaluate() {

View File

@ -1,7 +1,7 @@
use std::{cell::RefCell, collections::HashMap, str::FromStr};
use winnow::{
combinator::{alt, delimited, opt, peek, preceded, repeat, separated, terminated},
combinator::{alt, delimited, opt, peek, preceded, repeat, separated, separated_pair, terminated},
dispatch,
error::{ErrMode, StrContext, StrContextValue},
prelude::*,
@ -12,19 +12,20 @@ use self::error::ParseError;
use crate::{
ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, BoxNode,
CallExpression, CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression,
Identifier, IfExpression, ImportItem, ImportStatement, ItemVisibility, Literal, LiteralIdentifier,
LiteralValue, MemberExpression, MemberObject, Node, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, Shebang, TagDeclarator,
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
CallExpression, CallExpressionKw, CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType,
FunctionExpression, Identifier, IfExpression, ImportItem, ImportStatement, ItemVisibility, LabeledArg, Literal,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NonCodeMeta, NonCodeNode, NonCodeValue,
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
Shebang, TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
},
docs::StdLibFn,
errors::{KclError, KclErrorDetails},
executor::SourceRange,
parser::{
math::BinaryExpressionToken, parser_impl::error::ContextError, PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
},
token::{Token, TokenType},
unparser::ExprContext,
SourceRange,
};
pub(crate) mod error;
@ -37,6 +38,7 @@ thread_local! {
pub type TokenSlice<'slice, 'input> = &'slice mut &'input [Token];
pub fn run_parser(i: TokenSlice) -> super::ParseResult {
let _stats = crate::log::LogPerfStats::new("Parsing");
ParseContext::init();
let result = program.parse(i).save_err();
@ -476,6 +478,7 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
Expr::Identifier(x) => BinaryPart::Identifier(x),
Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
Expr::CallExpression(x) => BinaryPart::CallExpression(x),
Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x),
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
Expr::IfExpression(x) => BinaryPart::IfExpression(x),
};
@ -941,26 +944,46 @@ fn if_expr(i: TokenSlice) -> PResult<BoxNode<IfExpression>> {
))
}
fn function_expr(i: TokenSlice) -> PResult<Expr> {
let fn_tok = opt(fun).parse_next(i)?;
ignore_whitespace(i);
let (result, has_arrow) = function_decl.parse_next(i)?;
if fn_tok.is_none() && !has_arrow {
let err = KclError::Syntax(KclErrorDetails {
source_ranges: result.as_source_ranges(),
message: "Anonymous function requires `fn` before `(`".to_owned(),
});
return Err(ErrMode::Cut(err.into()));
}
Ok(Expr::FunctionExpression(Box::new(result)))
}
// Looks like
// (arg0, arg1) => {
// (arg0, arg1) {
// const x = arg0 + arg1;
// return x
// }
fn function_expression(i: TokenSlice) -> PResult<Node<FunctionExpression>> {
fn function_decl(i: TokenSlice) -> PResult<(Node<FunctionExpression>, bool)> {
fn return_type(i: TokenSlice) -> PResult<FnArgType> {
colon(i)?;
ignore_whitespace(i);
argument_type(i)
}
let open = open_paren(i)?;
let start = open.start;
let params = parameters(i)?;
close_paren(i)?;
ignore_whitespace(i);
big_arrow(i)?;
let arrow = opt(big_arrow).parse_next(i)?;
ignore_whitespace(i);
// Optional type arguments.
let return_type = opt(argument_type).parse_next(i)?;
// Optional return type.
let return_type = opt(return_type).parse_next(i)?;
ignore_whitespace(i);
open_brace(i)?;
let body = function_body(i)?;
let end = close_brace(i)?.end;
Ok(Node::new(
let result = Node::new(
FunctionExpression {
params,
body,
@ -970,7 +993,21 @@ fn function_expression(i: TokenSlice) -> PResult<Node<FunctionExpression>> {
start,
end,
open.module_id,
))
);
let has_arrow = if let Some(arrow) = arrow {
ParseContext::warn(ParseError::with_suggestion(
arrow.as_source_range(),
Some(result.as_source_range()),
"Unnecessary `=>` in function declaration",
Some(""),
));
true
} else {
false
};
Ok((result, has_arrow))
}
/// E.g. `person.name`
@ -1475,11 +1512,12 @@ fn expr_allowed_in_pipe_expr(i: TokenSlice) -> PResult<Expr> {
tag.map(Box::new).map(Expr::TagDeclarator),
literal.map(Expr::Literal),
fn_call.map(Box::new).map(Expr::CallExpression),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
identifier.map(Box::new).map(Expr::Identifier),
array,
object.map(Box::new).map(Expr::ObjectExpression),
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
function_expression.map(Box::new).map(Expr::FunctionExpression),
function_expr,
if_expr.map(Expr::IfExpression),
unnecessarily_bracketed,
))
@ -1544,38 +1582,47 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
let (kind, mut start, dec_end, module_id) = if let Some((kind, token)) = &decl_token {
(*kind, token.start, token.end, token.module_id)
} else {
// TODO warn on const
(VariableKind::Const, id.start, id.end, id.module_id)
};
if let Some(token) = visibility_token {
start = token.start;
}
ignore_whitespace(i);
equals(i)?;
// After this point, the parser is DEFINITELY parsing a variable declaration, because
// `fn`, `let`, `const` etc are all unambiguous. If you've parsed one of those tokens --
// and we certainly have because `kind` was parsed above -- then the following tokens
// MUST continue the variable declaration, otherwise the program is invalid.
//
// This means, from here until this function returns, any errors should be ErrMode::Cut,
// not ErrMode::Backtrack. Because the parser is definitely parsing a variable declaration.
// If there's an error, there's no point backtracking -- instead the parser should fail.
ignore_whitespace(i);
let val = if kind == VariableKind::Fn {
function_expression
.map(Box::new)
let eq = opt(equals).parse_next(i)?;
ignore_whitespace(i);
let val = function_decl
.map(|t| Box::new(t.0))
.map(Expr::FunctionExpression)
.context(expected("a KCL function expression, like () => { return 1 }"))
.parse_next(i)
.context(expected("a KCL function expression, like () { return 1 }"))
.parse_next(i);
if let Some(t) = eq {
let ctxt_end = val.as_ref().map(|e| e.end()).unwrap_or(t.end);
ParseContext::warn(ParseError::with_suggestion(
t.as_source_range(),
Some(SourceRange::new(id.start, ctxt_end, module_id)),
"Unnecessary `=` in function declaration",
Some(""),
));
}
val
} else {
equals(i)?;
ignore_whitespace(i);
expression
.try_map(|val| {
// Function bodies can be used if and only if declaring a function.
// Check the 'if' direction:
if matches!(val, Expr::FunctionExpression(_)) {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![SourceRange([start, dec_end, module_id.as_usize()])],
source_ranges: vec![SourceRange::new(start, dec_end, module_id)],
message: format!("Expected a `fn` variable kind, found: `{}`", kind),
}));
}
@ -1957,6 +2004,17 @@ fn question_mark(i: TokenSlice) -> PResult<()> {
Ok(())
}
fn fun(i: TokenSlice) -> PResult<Token> {
any.try_map(|token: Token| match token.token_type {
TokenType::Keyword if token.value == "fn" => Ok(token),
_ => Err(KclError::Syntax(KclErrorDetails {
source_ranges: token.as_source_ranges(),
message: format!("expected 'fn', found {}", token.value.as_str(),),
})),
})
.parse_next(i)
}
/// Parse a comma, optionally followed by some whitespace.
fn comma_sep(i: TokenSlice) -> PResult<()> {
(opt(whitespace), comma, opt(whitespace))
@ -1972,6 +2030,23 @@ fn arguments(i: TokenSlice) -> PResult<Vec<Expr>> {
.parse_next(i)
}
fn labeled_argument(i: TokenSlice) -> PResult<LabeledArg> {
separated_pair(identifier, (one_of(TokenType::Colon), opt(whitespace)), expression)
.map(|(label, arg)| LabeledArg {
label: label.inner,
arg,
})
.parse_next(i)
}
/// Arguments are passed into a function,
/// preceded by the name of the parameter (the label).
fn labeled_arguments(i: TokenSlice) -> PResult<Vec<LabeledArg>> {
separated(0.., labeled_argument, comma_sep)
.context(expected("function arguments"))
.parse_next(i)
}
/// A type of a function argument.
/// This can be:
/// - a primitive type, e.g. 'number' or 'string' or 'bool'
@ -1980,6 +2055,7 @@ fn arguments(i: TokenSlice) -> PResult<Vec<Expr>> {
fn argument_type(i: TokenSlice) -> PResult<FnArgType> {
let type_ = alt((
// Object types
// TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`.
(open_brace, parameters, close_brace).map(|(_, params, _)| Ok(FnArgType::Object { properties: params })),
// Array types
(one_of(TokenType::Type), open_bracket, close_bracket).map(|(token, _, _)| {
@ -2010,13 +2086,11 @@ fn argument_type(i: TokenSlice) -> PResult<FnArgType> {
}
fn parameter(i: TokenSlice) -> PResult<(Token, std::option::Option<FnArgType>, bool)> {
let (arg_name, optional, _, _, _, type_) = (
let (arg_name, optional, _, type_) = (
any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
opt(question_mark),
opt(whitespace),
opt(colon),
opt(whitespace),
opt(argument_type),
opt((colon, opt(whitespace), argument_type).map(|tup| tup.2)),
)
.parse_next(i)?;
Ok((arg_name, type_, optional.is_some()))
@ -2092,7 +2166,7 @@ fn binding_name(i: TokenSlice) -> PResult<Node<Identifier>> {
.parse_next(i)
}
fn typecheck_all(std_fn: Box<dyn StdLibFn>, args: &[Expr]) -> PResult<()> {
fn typecheck_all(std_fn: Box<dyn StdLibFn>, args: &[&Expr]) -> PResult<()> {
// Type check the arguments.
for (i, spec_arg) in std_fn.args(false).iter().enumerate() {
let Some(arg) = &args.get(i) else {
@ -2150,7 +2224,8 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
let _ = terminated(open_paren, opt(whitespace)).parse_next(i)?;
let args = arguments(i)?;
if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) {
typecheck_all(std_fn, &args)?;
let just_args: Vec<_> = args.iter().collect();
typecheck_all(std_fn, &just_args)?;
}
let end = preceded(opt(whitespace), close_paren).parse_next(i)?.end;
@ -2158,7 +2233,7 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
// so we'll hack this in here.
if fn_name.name == "int" {
assert_eq!(args.len(), 1);
let mut arg_str = args[0].recast(&crate::FormatOptions::default(), 0, false);
let mut arg_str = args[0].recast(&crate::FormatOptions::default(), 0, ExprContext::Other);
if arg_str.contains('.') && !arg_str.ends_with(".0") {
arg_str = format!("round({arg_str})");
}
@ -2177,7 +2252,30 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
inner: CallExpression {
callee: fn_name,
arguments: args,
optional: false,
digest: None,
},
})
}
fn fn_call_kw(i: TokenSlice) -> PResult<Node<CallExpressionKw>> {
let fn_name = identifier(i)?;
opt(whitespace).parse_next(i)?;
let _ = open_paren.parse_next(i)?;
ignore_whitespace(i);
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
let args = labeled_arguments(i)?;
ignore_whitespace(i);
let end = close_paren.parse_next(i)?.end;
Ok(Node {
start: fn_name.start,
end,
module_id: fn_name.module_id,
inner: CallExpressionKw {
callee: fn_name,
unlabeled: initial_unlabeled_arg,
arguments: args,
digest: None,
},
})
@ -2189,7 +2287,10 @@ mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::ast::types::{BodyItem, Expr, ModuleId, VariableKind};
use crate::{
ast::types::{BodyItem, Expr, VariableKind},
ModuleId,
};
fn assert_reserved(word: &str) {
// Try to use it as a variable name.
@ -2232,7 +2333,7 @@ mod tests {
#[test]
fn weird_program_unclosed_paren() {
let tokens = crate::token::lexer("fn firstPrime=(", ModuleId::default()).unwrap();
let tokens = crate::token::lexer("fn firstPrime(", ModuleId::default()).unwrap();
let last = tokens.last().unwrap();
let err: super::error::ErrorKind = program.parse(&tokens).unwrap_err().into();
let err = err.unwrap_parse_error();
@ -2247,7 +2348,10 @@ mod tests {
let tokens = crate::token::lexer("|", ModuleId::default()).unwrap();
let err: super::error::ErrorKind = program.parse(&tokens).unwrap_err().into();
let err = err.unwrap_parse_error();
assert_eq!(vec![err.source_range], vec![SourceRange([0, 1, 0])]);
assert_eq!(
vec![err.source_range],
vec![SourceRange::new(0, 1, ModuleId::default())]
);
assert_eq!(err.message, "Unexpected token: |");
}
@ -2284,7 +2388,7 @@ mod tests {
#[test]
fn test_comments_in_function1() {
let test_program = r#"() => {
let test_program = r#"() {
// comment 0
const a = 1
// comment 1
@ -2294,7 +2398,7 @@ mod tests {
}"#;
let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
let mut slice = tokens.as_slice();
let expr = function_expression.parse_next(&mut slice).unwrap();
let expr = function_decl.map(|t| t.0).parse_next(&mut slice).unwrap();
assert_eq!(expr.params, vec![]);
let comment_start = expr.body.non_code_meta.start_nodes.first().unwrap();
let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
@ -2306,13 +2410,13 @@ mod tests {
#[test]
fn test_comments_in_function2() {
let test_program = r#"() => {
let test_program = r#"() {
const yo = { a = { b = { c = '123' } } } /* block
comment */
}"#;
let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
let mut slice = tokens.as_slice();
let expr = function_expression.parse_next(&mut slice).unwrap();
let expr = function_decl.map(|t| t.0).parse_next(&mut slice).unwrap();
let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
assert_eq!(comment0.value(), "block\ncomment");
}
@ -2364,25 +2468,25 @@ const mySk1 = startSketchAt([0, 0])"#;
#[test]
fn test_whitespace_in_function() {
let test_program = r#"() => {
let test_program = r#"() {
return sg
return sg
}"#;
let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
let mut slice = tokens.as_slice();
let _expr = function_expression.parse_next(&mut slice).unwrap();
let _expr = function_decl.parse_next(&mut slice).unwrap();
}
#[test]
fn test_empty_lines_in_function() {
let test_program = "() => {
let test_program = "() {
return 2
}";
let module_id = ModuleId::from_usize(1);
let tokens = crate::token::lexer(test_program, module_id).unwrap();
let mut slice = tokens.as_slice();
let expr = function_expression.parse_next(&mut slice).unwrap();
let expr = function_decl.map(|t| t.0).parse_next(&mut slice).unwrap();
assert_eq!(
expr,
Node::new(
@ -2398,14 +2502,14 @@ const mySk1 = startSketchAt([0, 0])"#;
raw: "2".to_owned(),
digest: None,
},
32,
33,
29,
30,
module_id,
))),
digest: None,
},
25,
33,
22,
30,
module_id,
))],
non_code_meta: NonCodeMeta {
@ -2415,8 +2519,8 @@ const mySk1 = startSketchAt([0, 0])"#;
value: NonCodeValue::NewLine,
digest: None
},
7,
25,
4,
22,
module_id,
)],
digest: None,
@ -2424,15 +2528,15 @@ const mySk1 = startSketchAt([0, 0])"#;
shebang: None,
digest: None,
},
7,
47,
4,
44,
module_id,
),
return_type: None,
digest: None,
},
0,
47,
44,
module_id,
)
);
@ -2868,10 +2972,13 @@ const mySk1 = startSketchAt([0, 0])"#;
let test_fn = "(let) => { return 1 }";
let module_id = ModuleId::from_usize(2);
let tokens = crate::token::lexer(test_fn, module_id).unwrap();
let err = function_expression.parse(&tokens).unwrap_err().into_inner();
let err = function_decl.parse(&tokens).unwrap_err().into_inner();
let cause = err.cause.unwrap();
// This is the token `let`
assert_eq!(cause.source_ranges(), vec![SourceRange([1, 4, 2])]);
assert_eq!(
cause.source_ranges(),
vec![SourceRange::new(1, 4, ModuleId::from_usize(2))]
);
assert_eq!(cause.message(), "Cannot assign a variable to a reserved keyword: let");
}
@ -2941,12 +3048,12 @@ const mySk1 = startSketchAt([0, 0])"#;
#[test]
fn test_user_function() {
let input = "() => {
let input = "() {
return 2
}";
let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
let actual = function_expression.parse(&tokens);
let actual = function_decl.parse(&tokens);
assert!(actual.is_ok(), "could not parse test function");
}
@ -3182,7 +3289,7 @@ const mySk1 = startSketchAt([0, 0])"#;
fn assert_no_err(p: &str) -> (Node<Program>, ParseContext) {
let result = crate::parser::top_level_parse(p);
let result = result.0.unwrap();
assert!(result.1.errors.is_empty());
assert!(result.1.errors.is_empty(), "found: {:#?}", result.1.errors);
(result.0.unwrap(), result.1)
}
@ -3191,7 +3298,8 @@ const mySk1 = startSketchAt([0, 0])"#;
let result = crate::parser::top_level_parse(p);
let err = &result.unwrap_errs()[0];
assert_eq!(err.message, msg);
assert_eq!(&err.source_range.0[..2], &src);
assert_eq!(err.source_range.start(), src[0]);
assert_eq!(err.source_range.end(), src[1]);
}
#[track_caller]
@ -3267,6 +3375,16 @@ const height = [obj["a"] -1, 0]"#;
crate::parser::top_level_parse(code).unwrap();
}
#[test]
fn test_anon_fn() {
crate::parser::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
}
#[test]
fn test_anon_fn_no_fn() {
assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
}
#[test]
fn test_parse_half_pipe() {
let code = "const height = 10
@ -3304,7 +3422,8 @@ const secondExtrude = startSketchOn('XY')
// TODO: Better errors when program cannot tokenize.
// https://github.com/KittyCAD/modeling-app/issues/696
assert_eq!(details.message, "found unknown token 'ޜ'");
assert_eq!(&details.source_ranges[0].0[..2], &[1, 2]);
assert_eq!(details.source_ranges[0].start(), 1);
assert_eq!(details.source_ranges[0].end(), 2);
}
#[test]
@ -3586,7 +3705,7 @@ e
#[test]
fn test_keyword_ok_in_fn_args_return() {
let some_program_string = r#"fn thing = (param) => {
let some_program_string = r#"fn thing(param) {
return true
}
@ -3695,6 +3814,25 @@ let myBox = box([0,0], -3, -16, -10)
);
}
#[test]
fn arg_labels() {
let input = r#"length: 3"#;
let module_id = ModuleId::default();
let tokens = crate::token::lexer(input, module_id).unwrap();
let mut sl: &[Token] = &tokens;
super::labeled_arguments(&mut sl).unwrap();
}
#[test]
fn kw_fn() {
for input in ["val = foo(x, y: z)", "val = foo(y: z)"] {
let module_id = ModuleId::default();
let tokens = crate::token::lexer(input, module_id).unwrap();
let sl = &tokens;
super::program.parse(sl).unwrap();
}
}
#[test]
fn test_parse_tag_named_std_lib() {
let some_program_string = r#"startSketchOn('XY')
@ -3738,11 +3876,31 @@ int(42.3)"#;
let replaced = ctxt.warnings[0].apply_suggestion(&replaced).unwrap();
assert_eq!(replaced, "1.0\nround(42.3)");
}
#[test]
fn warn_fn_decl() {
let some_program_string = r#"fn foo = () => {
return 0
}"#;
let (_, ctxt) = assert_no_err(some_program_string);
assert_eq!(ctxt.warnings.len(), 2);
let replaced = ctxt.warnings[0].apply_suggestion(some_program_string).unwrap();
let replaced = ctxt.warnings[1].apply_suggestion(&replaced).unwrap();
// Note the whitespace here is bad, but we're just testing the suggestion spans really. In
// real life we might reformat after applying suggestions.
assert_eq!(
replaced,
r#"fn foo () {
return 0
}"#
);
}
}
#[cfg(test)]
mod snapshot_math_tests {
use super::*;
use crate::ModuleId;
// This macro generates a test function with the given function name.
// The macro takes a KCL program, ensures it tokenizes and parses, then compares
@ -3751,7 +3909,7 @@ mod snapshot_math_tests {
($func_name:ident, $test_kcl_program:expr) => {
#[test]
fn $func_name() {
let module_id = crate::ast::types::ModuleId::default();
let module_id = ModuleId::default();
let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
ParseContext::init();
@ -3781,6 +3939,7 @@ mod snapshot_math_tests {
#[cfg(test)]
mod snapshot_tests {
use super::*;
use crate::ModuleId;
// This macro generates a test function with the given function name.
// The macro takes a KCL program, ensures it tokenizes and parses, then compares
@ -3789,7 +3948,7 @@ mod snapshot_tests {
($func_name:ident, $test_kcl_program:expr) => {
#[test]
fn $func_name() {
let module_id = crate::ast::types::ModuleId::default();
let module_id = ModuleId::default();
let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
print_tokens(&tokens);
ParseContext::init();
@ -3999,6 +4158,8 @@ const my14 = 4 ^ 2 - 3 ^ 2 * 2
r#"x = 3
obj = { x, y: 4}"#
);
snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y: z)"#);
snapshot_test!(kw_function_all_named, r#"val = foo(x: a, y: b)"#);
}
#[allow(unused)]

View File

@ -2,8 +2,8 @@ use winnow::{error::StrContext, stream::Stream};
use crate::{
errors::{KclError, KclErrorDetails},
executor::SourceRange,
token::Token,
SourceRange,
};
/// Accumulate context while backtracking errors

View File

@ -1,6 +1,5 @@
---
source: kcl/src/parser/parser_impl.rs
assertion_line: 3649
expression: actual
snapshot_kind: text
---
@ -52,7 +51,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 39,
"optional": false,
"start": 18,
"type": "CallExpression",
"type": "CallExpression"
@ -97,7 +95,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 63,
"optional": false,
"start": 47,
"type": "CallExpression",
"type": "CallExpression"
@ -149,7 +146,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 96,
"optional": false,
"start": 71,
"type": "CallExpression",
"type": "CallExpression"
@ -201,7 +197,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 121,
"optional": false,
"start": 104,
"type": "CallExpression",
"type": "CallExpression"
@ -230,7 +225,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 143,
"optional": false,
"start": 129,
"type": "CallExpression",
"type": "CallExpression"

View File

@ -1,6 +1,5 @@
---
source: kcl/src/parser/parser_impl.rs
assertion_line: 3720
expression: actual
snapshot_kind: text
---
@ -68,7 +67,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 23,
"optional": false,
"start": 0,
"type": "CallExpression",
"type": "CallExpression"

View File

@ -1,6 +1,5 @@
---
source: kcl/src/parser/parser_impl.rs
assertion_line: 3722
expression: actual
snapshot_kind: text
---
@ -64,7 +63,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 80,
"optional": false,
"start": 62,
"type": "CallExpression",
"type": "CallExpression"

View File

@ -83,7 +83,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 66,
"optional": false,
"start": 54,
"type": "CallExpression",
"type": "CallExpression"

View File

@ -1,6 +1,5 @@
---
source: kcl/src/parser/parser_impl.rs
assertion_line: 3737
expression: actual
snapshot_kind: text
---
@ -52,7 +51,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 37,
"optional": false,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
@ -104,7 +102,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 75,
"optional": false,
"start": 49,
"type": "CallExpression",
"type": "CallExpression"
@ -149,7 +146,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 104,
"optional": false,
"start": 87,
"type": "CallExpression",
"type": "CallExpression"
@ -201,7 +197,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 145,
"optional": false,
"start": 116,
"type": "CallExpression",
"type": "CallExpression"
@ -222,7 +217,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 165,
"optional": false,
"start": 157,
"type": "CallExpression",
"type": "CallExpression"

View File

@ -1,6 +1,5 @@
---
source: kcl/src/parser/parser_impl.rs
assertion_line: 3745
expression: actual
snapshot_kind: text
---
@ -52,7 +51,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 37,
"optional": false,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
@ -97,7 +95,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 58,
"optional": false,
"start": 41,
"type": "CallExpression",
"type": "CallExpression"
@ -118,7 +115,6 @@ snapshot_kind: text
"type": "Identifier"
},
"end": 70,
"optional": false,
"start": 62,
"type": "CallExpression",
"type": "CallExpression"

Some files were not shown because too many files have changed in this diff Show More