Compare commits

...

9 Commits

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

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

* tsc

* make lint happy

* remove dumby data

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

---------

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

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

* fix: playwright tests

* fix: return 0 if you cant find the projectfolder

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

---------

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 09:27:57 -08:00
82 changed files with 1276 additions and 21919 deletions

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,160 +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 |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

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

View File

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

View File

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

View File

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

View File

@ -1,317 +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 |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeExpression`| | No |
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeSubstitution`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayExpression`| | No |
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayRangeExpression`| | No |
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ObjectExpression`| | No |
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `None`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -443,9 +443,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.20"
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [
"clap_builder",
"clap_derive",
@ -453,9 +453,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.20"
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [
"anstream",
"anstyle",
@ -3065,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",
@ -3101,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"

View File

@ -16,7 +16,7 @@ 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"] }

View File

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

View File

@ -27,8 +27,9 @@ pub use crate::ast::types::{
use crate::{
docs::StdLibFn,
errors::KclError,
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier},
executor::{ExecState, ExecutorContext, KclValue, Metadata, TagIdentifier},
parser::PIPE_OPERATOR,
source_range::{ModuleId, SourceRange},
std::kcl_stdlib::KclStdLibFn,
};
@ -62,7 +63,7 @@ pub struct Node<T> {
impl<T> Node<T> {
pub fn metadata(&self) -> Metadata {
Metadata {
source_range: SourceRange([self.start, self.end, self.module_id.0 as usize]),
source_range: SourceRange::new(self.start, self.end, self.module_id),
}
}
@ -122,7 +123,7 @@ impl<T> Node<T> {
}
pub fn as_source_range(&self) -> SourceRange {
SourceRange([self.start, self.end, self.module_id.as_usize()])
SourceRange::new(self.start, self.end, self.module_id)
}
pub fn as_source_ranges(&self) -> Vec<SourceRange> {
@ -150,21 +151,21 @@ impl<T: fmt::Display> fmt::Display for Node<T> {
}
}
impl<T> From<Node<T>> for crate::executor::SourceRange {
impl<T> From<Node<T>> for SourceRange {
fn from(v: Node<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
impl<T> From<&Node<T>> for crate::executor::SourceRange {
impl<T> From<&Node<T>> for SourceRange {
fn from(v: &Node<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
impl<T> From<&BoxNode<T>> for crate::executor::SourceRange {
impl<T> From<&BoxNode<T>> for SourceRange {
fn from(v: &BoxNode<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
@ -540,7 +541,6 @@ impl Program {
/// #!/usr/bin/env python
/// ```
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct Shebang {
@ -553,29 +553,6 @@ impl Shebang {
}
}
/// Identifier of a source file. Uses a u32 to keep the size small.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct ModuleId(pub u32);
impl ModuleId {
pub fn from_usize(id: usize) -> Self {
Self(u32::try_from(id).expect("module ID should fit in a u32"))
}
pub fn as_usize(&self) -> usize {
usize::try_from(self.0).expect("module ID should fit in a usize")
}
/// Top-level file is the one being executed.
/// Represented by module ID of 0, i.e. the default value.
pub fn is_top_level(&self) -> bool {
*self == Self::default()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
@ -609,13 +586,13 @@ impl BodyItem {
impl From<BodyItem> for SourceRange {
fn from(item: BodyItem) -> Self {
Self([item.start(), item.end(), item.module_id().as_usize()])
Self::new(item.start(), item.end(), item.module_id())
}
}
impl From<&BodyItem> for SourceRange {
fn from(item: &BodyItem) -> Self {
Self([item.start(), item.end(), item.module_id().as_usize()])
Self::new(item.start(), item.end(), item.module_id())
}
}
@ -631,6 +608,7 @@ pub enum Expr {
BinaryExpression(BoxNode<BinaryExpression>),
FunctionExpression(BoxNode<FunctionExpression>),
CallExpression(BoxNode<CallExpression>),
CallExpressionKw(BoxNode<CallExpressionKw>),
PipeExpression(BoxNode<PipeExpression>),
PipeSubstitution(BoxNode<PipeSubstitution>),
ArrayExpression(BoxNode<ArrayExpression>),
@ -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)
}
@ -1270,12 +1266,42 @@ pub struct CallExpression {
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 {
@ -1295,6 +1321,25 @@ 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 {
@ -1351,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)]
@ -2199,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())
}
}
@ -2236,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())
}
}
@ -2828,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 {

View File

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

View File

@ -1,11 +1,11 @@
use sha2::{Digest as DigestTrait, Sha256};
use super::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ElseIf, Expr,
ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ImportStatement, Literal,
LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator,
UnaryExpression, VariableDeclaration, VariableDeclarator,
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode,
NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program,
ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
};
/// Position-independent digest of the AST node.
@ -93,6 +93,7 @@ impl Expr {
Expr::BinaryExpression(be) => be.compute_digest(),
Expr::FunctionExpression(fe) => fe.compute_digest(),
Expr::CallExpression(ce) => ce.compute_digest(),
Expr::CallExpressionKw(ce) => ce.compute_digest(),
Expr::PipeExpression(pe) => pe.compute_digest(),
Expr::PipeSubstitution(ps) => ps.compute_digest(),
Expr::ArrayExpression(ae) => ae.compute_digest(),
@ -117,6 +118,7 @@ impl BinaryPart {
BinaryPart::Identifier(id) => id.compute_digest(),
BinaryPart::BinaryExpression(be) => be.compute_digest(),
BinaryPart::CallExpression(ce) => ce.compute_digest(),
BinaryPart::CallExpressionKw(ce) => ce.compute_digest(),
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
BinaryPart::MemberExpression(me) => me.compute_digest(),
BinaryPart::IfExpression(e) => e.compute_digest(),
@ -372,6 +374,22 @@ impl CallExpression {
});
}
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());
}
});
}
impl IfExpression {
compute_digest!(|slf, hasher| {
hasher.update(slf.cond.compute_digest());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,20 +19,18 @@ use kittycad_modeling_cmds::length_unit::LengthUnit;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
pub use crate::kcl_value::KclValue;
use crate::{
ast::types::{
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, TagDeclarator, TagNode,
},
ast::types::{BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, Node, NodeRef, TagDeclarator, TagNode},
engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem},
settings::types::UnitLength,
source_range::{ModuleId, SourceRange},
std::{args::Arg, StdLib},
ExecError, Program,
};
@ -993,7 +991,6 @@ pub enum BodyType {
/// Info about a module. Right now, this is pretty minimal. We hope to cache
/// modules here in the future.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export)]
pub struct ModuleInfo {
/// The ID of the module.
@ -1002,101 +999,6 @@ pub struct ModuleInfo {
path: std::path::PathBuf,
}
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export)]
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 3]);
impl From<[usize; 3]> for SourceRange {
fn from(value: [usize; 3]) -> Self {
Self(value)
}
}
impl From<&SourceRange> for miette::SourceSpan {
fn from(source_range: &SourceRange) -> Self {
let length = source_range.end() - source_range.start();
let start = miette::SourceOffset::from(source_range.start());
Self::new(start, length)
}
}
impl From<SourceRange> for miette::SourceSpan {
fn from(source_range: SourceRange) -> Self {
Self::from(&source_range)
}
}
impl SourceRange {
/// Create a new source range.
pub fn new(start: usize, end: usize, module_id: ModuleId) -> Self {
Self([start, end, module_id.as_usize()])
}
/// A source range that doesn't correspond to any source code.
pub fn synthetic() -> Self {
Self::default()
}
/// Get the start of the range.
pub fn start(&self) -> usize {
self.0[0]
}
/// Get the end of the range.
pub fn end(&self) -> usize {
self.0[1]
}
/// Get the module ID of the range.
pub fn module_id(&self) -> ModuleId {
ModuleId::from_usize(self.0[2])
}
/// Check if the range contains a position.
pub fn contains(&self, pos: usize) -> bool {
pos >= self.start() && pos <= self.end()
}
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
if line > 0 {
line = line.saturating_sub(1);
}
let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
let lines = code.get(..self.end()).unwrap_or_default().lines();
if lines.clone().count() == 0 {
return LspPosition { line: 0, character: 0 };
}
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let line = lines.clone().count() - 1;
let column = lines.last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn to_lsp_range(&self, code: &str) -> LspRange {
let start = self.start_to_lsp_position(code);
let end = self.end_to_lsp_position(code);
LspRange { start, end }
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point2d {
@ -2109,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?;
}
@ -2145,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 } => {
@ -2714,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())
],
}),
);
}
@ -2749,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())],
}),
);
}
@ -2845,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())],
})
);
@ -2854,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())],
})
);
@ -2865,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())],
})
);
@ -2877,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())],
})
);
@ -2888,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())],
})
);
@ -2899,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())],
})
);
@ -2952,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())],
})
);
@ -2965,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())],
})
);
@ -3009,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")]
@ -3128,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(),
})),
),
@ -3146,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(),
})),
),
@ -3173,7 +3079,7 @@ let w = f() + f()
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2), mem(3)],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 3".to_owned(),
})),
),

View File

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

View File

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

View File

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

View File

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

View File

@ -72,6 +72,7 @@ mod parser;
mod settings;
#[cfg(test)]
mod simulation_tests;
mod source_range;
mod std;
#[cfg(not(target_arch = "wasm32"))]
pub mod test_server;
@ -82,16 +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 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.
@ -101,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"))]
@ -115,9 +119,10 @@ pub mod std_utils {
pub use crate::std::utils::{get_tangential_arc_to_info, is_points_ccw_wasm, TangentialArcInfoInput};
}
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::log::{log, logln};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
#![allow(dead_code)]
use std::env;
#[cfg(feature = "dhat-heap")]
use dhat::{HeapStats, Profiler};
use std::env;
use web_time::Instant;
const LOG_ENV_VAR: &str = "ZOO_LOG";

View File

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

View File

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

View File

@ -1,9 +1,9 @@
use parser_impl::ParseContext;
use crate::{
ast::types::{ModuleId, Node, Program},
ast::types::{Node, Program},
errors::{KclError, KclErrorDetails},
executor::SourceRange,
source_range::{ModuleId, SourceRange},
token::{Token, TokenType},
};

View File

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

View File

@ -1,7 +1,7 @@
use std::{cell::RefCell, collections::HashMap, str::FromStr};
use winnow::{
combinator::{alt, delimited, opt, peek, preceded, repeat, separated, terminated},
combinator::{alt, delimited, opt, peek, preceded, repeat, separated, separated_pair, terminated},
dispatch,
error::{ErrMode, StrContext, StrContextValue},
prelude::*,
@ -12,20 +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;
@ -478,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),
};
@ -1511,6 +1512,7 @@ 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),
@ -1603,7 +1605,7 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
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([id.start, ctxt_end, module_id.as_usize()])),
Some(SourceRange::new(id.start, ctxt_end, module_id)),
"Unnecessary `=` in function declaration",
Some(""),
));
@ -1620,7 +1622,7 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
// 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),
}));
}
@ -2028,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'
@ -2147,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 {
@ -2205,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;
@ -2237,13 +2257,40 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
})
}
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,
},
})
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
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.
@ -2301,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: |");
}
@ -2925,7 +2975,10 @@ const mySk1 = startSketchAt([0, 0])"#;
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");
}
@ -3245,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]
@ -3368,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]
@ -3759,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')
@ -3826,6 +3900,7 @@ int(42.3)"#;
#[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
@ -3834,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();
@ -3864,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
@ -3872,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();
@ -4082,6 +4158,8 @@ const my14 = 4 ^ 2 - 3 ^ 2 * 2
r#"x = 3
obj = { x, y: 4}"#
);
snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y: z)"#);
snapshot_test!(kw_function_all_named, r#"val = foo(x: a, y: b)"#);
}
#[allow(unused)]

View File

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

View File

@ -0,0 +1,74 @@
---
source: kcl/src/parser/parser_impl.rs
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declarations": [
{
"end": 21,
"id": {
"end": 3,
"name": "val",
"start": 0,
"type": "Identifier"
},
"init": {
"arguments": [
{
"type": "LabeledArg",
"label": {
"type": "Identifier",
"name": "x"
},
"arg": {
"end": 14,
"name": "a",
"start": 13,
"type": "Identifier",
"type": "Identifier"
}
},
{
"type": "LabeledArg",
"label": {
"type": "Identifier",
"name": "y"
},
"arg": {
"end": 20,
"name": "b",
"start": 19,
"type": "Identifier",
"type": "Identifier"
}
}
],
"callee": {
"end": 9,
"name": "foo",
"start": 6,
"type": "Identifier"
},
"end": 21,
"start": 6,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
"start": 0,
"type": "VariableDeclarator"
}
],
"end": 21,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 21,
"start": 0
}

View File

@ -0,0 +1,66 @@
---
source: kcl/src/parser/parser_impl.rs
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declarations": [
{
"end": 18,
"id": {
"end": 3,
"name": "val",
"start": 0,
"type": "Identifier"
},
"init": {
"arguments": [
{
"type": "LabeledArg",
"label": {
"type": "Identifier",
"name": "y"
},
"arg": {
"end": 17,
"name": "z",
"start": 16,
"type": "Identifier",
"type": "Identifier"
}
}
],
"callee": {
"end": 9,
"name": "foo",
"start": 6,
"type": "Identifier"
},
"end": 18,
"start": 6,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 11,
"name": "x",
"start": 10,
"type": "Identifier",
"type": "Identifier"
}
},
"start": 0,
"type": "VariableDeclarator"
}
],
"end": 18,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 18,
"start": 0
}

View File

@ -1,8 +1,9 @@
use insta::rounded_redaction;
use crate::{
ast::types::{ModuleId, Node, Program},
ast::types::{Node, Program},
errors::KclError,
source_range::ModuleId,
};
/// Deserialize the data from a snapshot.

View File

@ -0,0 +1,129 @@
use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
/// 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)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct ModuleId(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, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[ts(export, as = "TsSourceRange")]
pub struct SourceRange([usize; 3]);
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
struct TsSourceRange(#[ts(type = "[number, number]")] [usize; 2]);
impl From<[usize; 3]> for SourceRange {
fn from(value: [usize; 3]) -> Self {
Self(value)
}
}
impl From<SourceRange> for TsSourceRange {
fn from(value: SourceRange) -> Self {
Self([value.start(), value.end()])
}
}
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 }
}
}

View File

@ -4,19 +4,20 @@ use anyhow::Result;
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use super::shapes::PolygonType;
use crate::{
ast::types::TagNode,
errors::{KclError, KclErrorDetails},
executor::{
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
SolidSet, SourceRange, TagIdentifier,
SolidSet, TagIdentifier,
},
kcl_value::KclObjectFields,
source_range::SourceRange,
std::{shapes::SketchOrSurface, sketch::FaceTag, FnAsArg},
ModuleId,
};
use super::shapes::PolygonType;
#[derive(Debug, Clone)]
pub struct Arg {
/// The evaluated argument.
@ -1221,7 +1222,6 @@ impl<'a> FromKclValue<'a> for crate::executor::GeoMeta {
let obj = arg.as_object()?;
let_field_of!(obj, id);
let_field_of!(obj, source_range "sourceRange");
let source_range = SourceRange(source_range);
Some(Self {
id,
metadata: Metadata { source_range },
@ -1314,9 +1314,22 @@ impl_from_kcl_for_vec!(super::fillet::EdgeReference);
impl_from_kcl_for_vec!(ExtrudeSurface);
impl_from_kcl_for_vec!(Sketch);
impl<'a> FromKclValue<'a> for crate::executor::SourceRange {
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
FromKclValue::from_kcl_val(arg).map(crate::executor::SourceRange)
let KclValue::Array { value, meta: _ } = arg else {
return None;
};
if value.len() != 3 {
return None;
}
let v0 = value.first()?;
let v1 = value.get(1)?;
let v2 = value.get(2)?;
Some(SourceRange::new(
v0.as_usize()?,
v1.as_usize()?,
ModuleId::from_usize(v2.as_usize()?),
))
}
}

View File

@ -6,8 +6,9 @@ use super::{
};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, SourceRange},
executor::{ExecState, KclValue},
function_param::FunctionParam,
source_range::SourceRange,
};
/// Apply a function to each element of an array.

View File

@ -16,18 +16,16 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::args::Arg;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
ExecState, Geometries, Geometry, KclValue, Point2d, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange,
},
executor::{ExecState, Geometries, Geometry, KclValue, Point2d, Point3d, Sketch, SketchSet, Solid, SolidSet},
function_param::FunctionParam,
kcl_value::KclObjectFields,
std::Args,
SourceRange,
};
use super::args::Arg;
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Data for a linear pattern on a 2D sketch.

View File

@ -12,8 +12,8 @@ use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::ast::types::TagNode;
use crate::{
ast::types::TagNode,
errors::{KclError, KclErrorDetails},
executor::{
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
@ -2250,7 +2250,10 @@ mod tests {
use pretty_assertions::assert_eq;
use crate::{executor::TagIdentifier, std::sketch::calculate_circle_center, std::sketch::PlaneData};
use crate::{
executor::TagIdentifier,
std::sketch::{calculate_circle_center, PlaneData},
};
#[test]
fn test_deserialize_plane_data() {

View File

@ -4,7 +4,8 @@ use kittycad_modeling_cmds::shared::Angle;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange},
executor::Point2d,
source_range::SourceRange,
};
/// Get the angle between these points
@ -234,7 +235,7 @@ mod tests {
use pretty_assertions::assert_eq;
use super::{get_x_component, get_y_component, Angle};
use crate::executor::SourceRange;
use crate::SourceRange;
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
(-315, [1, 1]),
@ -354,7 +355,7 @@ mod tests {
super::Point2d { x: -1.0, y: 1.0 },
super::Point2d { x: -1.0, y: 0.0 },
1.0,
SourceRange(Default::default()),
SourceRange::default(),
)
.unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
@ -365,7 +366,7 @@ mod tests {
super::Point2d { x: -2.0, y: 0.0 },
super::Point2d { x: -1.0, y: 0.0 },
1.0,
SourceRange(Default::default()),
SourceRange::default(),
)
.unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
@ -376,7 +377,7 @@ mod tests {
super::Point2d { x: -20.0, y: 0.0 },
super::Point2d { x: -10.0, y: 0.0 },
10.0,
SourceRange(Default::default()),
SourceRange::default(),
)
.unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
@ -387,7 +388,7 @@ mod tests {
super::Point2d { x: 5.0, y: 5.0 },
super::Point2d { x: 10.0, y: -10.0 },
10.0,
SourceRange(Default::default()),
SourceRange::default(),
);
if let Err(err) = result {

View File

@ -8,9 +8,9 @@ use tower_lsp::lsp_types::SemanticTokenType;
use winnow::{error::ParseError, stream::ContainsToken};
use crate::{
ast::types::{ItemVisibility, ModuleId, VariableKind},
ast::types::{ItemVisibility, VariableKind},
errors::KclError,
executor::SourceRange,
source_range::{ModuleId, SourceRange},
};
mod tokeniser;
@ -22,7 +22,6 @@ pub(crate) use tokeniser::RESERVED_WORDS;
/// The types of tokens.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize, JsonSchema, FromStr, Display)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass(eq, eq_int))]
#[serde(rename_all = "camelCase")]
#[display(style = "camelCase")]
pub enum TokenType {
@ -66,6 +65,8 @@ pub enum TokenType {
Unknown,
/// The ? symbol, used for optional values.
QuestionMark,
/// The @ symbol.
At,
}
/// Most KCL tokens correspond to LSP semantic tokens (but not all).
@ -92,6 +93,7 @@ impl TryFrom<TokenType> for SemanticTokenType {
| TokenType::DoublePeriod
| TokenType::Hash
| TokenType::Dollar
| TokenType::At
| TokenType::Unknown => {
anyhow::bail!("unsupported token type: {:?}", token_type)
}
@ -156,7 +158,6 @@ impl TokenType {
}
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
pub struct Token {
#[serde(rename = "type")]
pub token_type: TokenType,
@ -204,7 +205,7 @@ impl Token {
}
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> {
@ -238,13 +239,13 @@ impl Token {
impl From<Token> for SourceRange {
fn from(token: Token) -> Self {
Self([token.start, token.end, token.module_id.as_usize()])
Self::new(token.start, token.end, token.module_id)
}
}
impl From<&Token> for SourceRange {
fn from(token: &Token) -> Self {
Self([token.start, token.end, token.module_id.as_usize()])
Self::new(token.start, token.end, token.module_id)
}
}
@ -264,7 +265,7 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
// the end of input (input.len()) on eof errors.
return KclError::Lexical(crate::errors::KclErrorDetails {
source_ranges: vec![SourceRange([offset, offset, module_id.as_usize()])],
source_ranges: vec![SourceRange::new(offset, offset, module_id)],
message: "unexpected EOF while parsing".to_string(),
});
}
@ -275,7 +276,7 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
// TODO: Add the Winnow parser context to the error.
// See https://github.com/KittyCAD/modeling-app/issues/784
KclError::Lexical(crate::errors::KclErrorDetails {
source_ranges: vec![SourceRange([offset, offset + 1, module_id.as_usize()])],
source_ranges: vec![SourceRange::new(offset, offset + 1, module_id)],
message: format!("found unknown token '{}'", bad_token),
})
}

View File

@ -11,7 +11,7 @@ use winnow::{
};
use crate::{
ast::types::ModuleId,
source_range::ModuleId,
token::{Token, TokenType},
};
@ -92,6 +92,7 @@ pub fn token(i: &mut Input<'_>) -> PResult<Token> {
'}' | ')' | ']' => brace_end,
',' => comma,
'?' => question_mark,
'@' => at,
'0'..='9' => number,
':' => colon,
'.' => alt((number, double_period, period)),
@ -268,6 +269,16 @@ fn question_mark(i: &mut Input<'_>) -> PResult<Token> {
))
}
fn at(i: &mut Input<'_>) -> PResult<Token> {
let (value, range) = '@'.with_span().parse_next(i)?;
Ok(Token::from_range(
range,
i.state.module_id,
TokenType::At,
value.to_string(),
))
}
fn colon(i: &mut Input<'_>) -> PResult<Token> {
let (value, range) = ':'.with_span().parse_next(i)?;
Ok(Token::from_range(

View File

@ -3,9 +3,10 @@ use std::fmt::Write;
use crate::{
ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression, ImportStatement, ItemVisibility, Literal,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NonCodeValue, ObjectExpression,
Parameter, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
CallExpressionKw, Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression, ImportStatement,
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
NonCodeValue, ObjectExpression, Parameter, PipeExpression, Program, TagDeclarator, UnaryExpression,
VariableDeclaration, VariableKind,
},
parser::PIPE_OPERATOR,
};
@ -166,6 +167,7 @@ impl Expr {
result
}
Expr::CallExpression(call_exp) => call_exp.recast(options, indentation_level, ctxt),
Expr::CallExpressionKw(call_exp) => call_exp.recast(options, indentation_level, ctxt),
Expr::Identifier(ident) => ident.name.to_string(),
Expr::TagDeclarator(tag) => tag.recast(),
Expr::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
@ -188,6 +190,9 @@ impl BinaryPart {
BinaryPart::CallExpression(call_expression) => {
call_expression.recast(options, indentation_level, ExprContext::Other)
}
BinaryPart::CallExpressionKw(call_expression) => {
call_expression.recast(options, indentation_level, ExprContext::Other)
}
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
BinaryPart::IfExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
@ -214,6 +219,37 @@ impl CallExpression {
}
}
impl CallExpressionKw {
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
let indent = if ctxt == ExprContext::Pipe {
"".to_string()
} else {
options.get_indentation(indentation_level)
};
let name = &self.callee.name;
let mut arg_list = if let Some(first_arg) = &self.unlabeled {
vec![first_arg.recast(options, indentation_level, ctxt)]
} else {
Vec::new()
};
arg_list.extend(
self.arguments
.iter()
.map(|arg| arg.recast(options, indentation_level, ctxt)),
);
let args = arg_list.join(", ");
format!("{indent}{name}({args})")
}
}
impl LabeledArg {
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
let label = &self.label.name;
let arg = self.arg.recast(options, indentation_level, ctxt);
format!("{label}: {arg}")
}
}
impl VariableDeclaration {
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
let indentation = options.get_indentation(indentation_level);
@ -345,6 +381,7 @@ fn expr_is_trivial(expr: &Expr) -> bool {
Expr::BinaryExpression(_)
| Expr::FunctionExpression(_)
| Expr::CallExpression(_)
| Expr::CallExpressionKw(_)
| Expr::PipeExpression(_)
| Expr::ArrayExpression(_)
| Expr::ArrayRangeExpression(_)
@ -504,6 +541,7 @@ impl UnaryExpression {
| BinaryPart::Identifier(_)
| BinaryPart::MemberExpression(_)
| BinaryPart::IfExpression(_)
| BinaryPart::CallExpressionKw(_)
| BinaryPart::CallExpression(_) => {
format!("{}{}", &self.operator, self.argument.recast(options, 0))
}
@ -645,7 +683,7 @@ mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::ast::types::{FormatOptions, ModuleId};
use crate::{ast::types::FormatOptions, source_range::ModuleId};
#[test]
fn test_recast_if_else_if_same() {

View File

@ -1,6 +1,6 @@
use crate::{
ast::types::{self, NodeRef},
executor::SourceRange,
source_range::SourceRange,
};
/// The "Node" type wraps all the AST elements we're able to find in a KCL
@ -22,6 +22,7 @@ pub enum Node<'a> {
BinaryExpression(NodeRef<'a, types::BinaryExpression>),
FunctionExpression(NodeRef<'a, types::FunctionExpression>),
CallExpression(NodeRef<'a, types::CallExpression>),
CallExpressionKw(NodeRef<'a, types::CallExpressionKw>),
PipeExpression(NodeRef<'a, types::PipeExpression>),
PipeSubstitution(NodeRef<'a, types::PipeSubstitution>),
ArrayExpression(NodeRef<'a, types::ArrayExpression>),
@ -54,6 +55,7 @@ impl From<&Node<'_>> for SourceRange {
Node::BinaryExpression(n) => SourceRange::from(*n),
Node::FunctionExpression(n) => SourceRange::from(*n),
Node::CallExpression(n) => SourceRange::from(*n),
Node::CallExpressionKw(n) => SourceRange::from(*n),
Node::PipeExpression(n) => SourceRange::from(*n),
Node::PipeSubstitution(n) => SourceRange::from(*n),
Node::ArrayExpression(n) => SourceRange::from(*n),
@ -63,9 +65,9 @@ impl From<&Node<'_>> for SourceRange {
Node::UnaryExpression(n) => SourceRange::from(*n),
Node::Parameter(p) => SourceRange::from(&p.identifier),
Node::ObjectProperty(n) => SourceRange::from(*n),
Node::MemberObject(m) => SourceRange([m.start(), m.end(), m.module_id().as_usize()]),
Node::MemberObject(m) => SourceRange::new(m.start(), m.end(), m.module_id()),
Node::IfExpression(n) => SourceRange::from(*n),
Node::LiteralIdentifier(l) => SourceRange([l.start(), l.end(), l.module_id().as_usize()]),
Node::LiteralIdentifier(l) => SourceRange::new(l.start(), l.end(), l.module_id()),
}
}
}
@ -102,6 +104,7 @@ impl_from!(Node, Identifier);
impl_from!(Node, BinaryExpression);
impl_from!(Node, FunctionExpression);
impl_from!(Node, CallExpression);
impl_from!(Node, CallExpressionKw);
impl_from!(Node, PipeExpression);
impl_from!(Node, PipeSubstitution);
impl_from!(Node, ArrayExpression);

View File

@ -103,6 +103,7 @@ where
BinaryPart::Identifier(id) => f.walk(id.as_ref().into()),
BinaryPart::BinaryExpression(be) => f.walk(be.as_ref().into()),
BinaryPart::CallExpression(ce) => f.walk(ce.as_ref().into()),
BinaryPart::CallExpressionKw(ce) => f.walk(ce.as_ref().into()),
BinaryPart::UnaryExpression(ue) => walk_unary_expression(ue, f),
BinaryPart::MemberExpression(me) => walk_member_expression(me, f),
BinaryPart::IfExpression(e) => walk_if_expression(e, f),
@ -159,6 +160,26 @@ where
}
Ok(true)
}
Expr::CallExpressionKw(ce) => {
if !f.walk(ce.as_ref().into())? {
return Ok(false);
}
if !f.walk((&ce.callee).into())? {
return Ok(false);
}
if let Some(ref e) = ce.unlabeled {
if !walk_value::<WalkT>(e, f)? {
return Ok(false);
}
}
for e in &ce.arguments {
if !walk_value::<WalkT>(&e.arg, f)? {
return Ok(false);
}
}
Ok(true)
}
Expr::PipeExpression(pe) => {
if !f.walk(pe.as_ref().into())? {
return Ok(false);