Compare commits
18 Commits
achalmers/
...
v0.29.0
Author | SHA1 | Date | |
---|---|---|---|
5ae1aecd74 | |||
68ae7e98f9 | |||
56771d561a | |||
f09411817c | |||
bed7ae3b8b | |||
c43510732c | |||
51f0b669a4 | |||
3cbedcd3e7 | |||
5d2fa43150 | |||
ec49b0752e | |||
3b171fb881 | |||
0548409da0 | |||
dd052b35fd | |||
46be4e7eef | |||
412d1b7a99 | |||
cfdd22af74 | |||
68a11e7aa5 | |||
3139e18dc7 |
10
.github/workflows/build-apps.yml
vendored
@ -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*'
|
||||
|
4
.github/workflows/e2e-tests.yml
vendored
@ -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}}
|
||||
|
6
.github/workflows/publish-apps-release.yml
vendored
@ -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]
|
||||
|
20
README.md
@ -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`.
|
||||
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 259 KiB |
BIN
assets/icon.ico
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 114 KiB |
BIN
assets/icon.png
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
@ -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], %)
|
||||
|
@ -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] }
|
||||
}, %)
|
||||
```
|
||||
|
@ -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 }, %)
|
||||
})
|
||||
|
@ -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] }
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
20030
docs/kcl/std.json
@ -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
|
||||
|
@ -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:** `<=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "EnvironmentRef"
|
||||
excerpt: "An index pointing to an environment."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An index pointing to an environment.
|
||||
|
||||
**Type:** `integer` (`uint`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "ItemVisibility"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**enum:** `default`, `export`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
---
|
||||
title: "LiteralValue"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts any of the following:**
|
||||
|
||||
|
||||
**Type:** `number` (`double`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `string`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `boolean`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "Uint"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `integer` (`uint32`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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:** `!`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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()}
|
||||
>
|
||||
|
@ -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',
|
||||
|
@ -727,7 +727,6 @@ export function createCallExpressionStdLib(
|
||||
|
||||
name,
|
||||
},
|
||||
optional: false,
|
||||
arguments: args,
|
||||
}
|
||||
}
|
||||
@ -749,7 +748,6 @@ export function createCallExpression(
|
||||
|
||||
name,
|
||||
},
|
||||
optional: false,
|
||||
arguments: args,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 = ''
|
||||
}
|
||||
|
@ -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' } } }
|
||||
|
||||
|
@ -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}`
|
||||
)
|
||||
}
|
@ -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>,
|
||||
|
@ -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'] = []
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
56
src/wasm-lib/Cargo.lock
generated
@ -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"
|
||||
|
@ -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 = []
|
||||
|
@ -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)]
|
||||
|
@ -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)),
|
||||
|
@ -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)]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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)]
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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> {
|
||||
|
@ -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)]
|
||||
|
@ -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(),
|
||||
})),
|
||||
),
|
||||
|
@ -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()];
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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)]
|
||||
|
@ -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!(
|
||||
|
@ -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,
|
||||
|
@ -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!(
|
||||
|
@ -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
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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! {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -83,7 +83,6 @@ snapshot_kind: text
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 66,
|
||||
"optional": false,
|
||||
"start": 54,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|