Compare commits

..

13 Commits

Author SHA1 Message Date
8d9dbf36c3 Bump vite from 5.4.6 to 5.4.12 (#5129)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.6 to 5.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-22 20:58:53 -05:00
440704ed9f Remove extra margin on some code editor menu items (#5094)
* Extra padding on 'Load a sample model' menu item
Fixes #5047

* Update src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.module.css

Co-authored-by: Frank Noirot <frank@zoo.dev>

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-01-22 16:57:27 +01:00
2261217a5d Rename debug pane label for artifact graph (#5092)
* Rename debug pane label for artifact graph

* Rename component
2025-01-22 15:37:51 +00:00
10da986649 Add dry-run validation for Sweep (#5097)
* Add dry-run validation for Sweep
Fixes #5095

* Add sweep test failing validation

* Make naming more consistent with engine

* Fix tests after big rename

* Fix tsc after main merge
2025-01-22 15:59:47 +01:00
10789d9c3c set scene units based on a module's default units (#5127)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-22 15:23:55 +13:00
67cc4f5835 Tweaks to clarify tooltips from tool dropdown menus (#5123)
* Separate content from ToolbarItemTooltip, make simple and "rich" versions

* Add support for dropdown-arrow-only tooltip

* Add toolbar-wide hover timeouts and clears to switch between simple and rich tooltips

* Fix the dropdown arrow button hover styling now that they're separate

* Add missing doc links to rich toolbar tooltips

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Re-run CI after snapshots

* fix codespell

* fmt

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-21 18:32:56 -05:00
2692f2b73a Add units to geometry structs (#5075)
* Make all geometry KclValue variants into struct variants

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

* Add units to geometry types

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-21 20:42:09 +00:00
965cb18059 Parse units on numeric literals and keep them in the AST (#5061)
* Code changes

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

* test changes

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

* Frontend changes

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

* Refactor asNum

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-01-22 08:29:30 +13:00
a022b8ef6c Fix suggestion for updating function decl syntax for anon functions (#5088)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-22 07:10:07 +13:00
4d24bf7c94 Add API Call ID log for debugging (#5107) 2025-01-20 19:49:02 +00:00
9a537da183 Show toolbar tooltips on hover only, hide when dropdowns are open (#5109)
* Show toolbar tooltips on hover only, hide when dropdowns are open

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Re-run CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-18 05:22:22 -05:00
df81b76b8b Bug fix follow-up for create project (#5105)
* fix dumb mistake in command flow for #5083

* Add e2e test for creating projects with the default interpolated name

* Drop that number to 12 ain't got all day

* Why do I have a kcl-samples submodule hanging around?

* Empty commit to remove the submodule
2025-01-17 23:10:28 +00:00
ac3f7ab712 Rust: Remove iai benchmark tests (#5102)
We don't get much value from these, we can always run criterion or valgrind locally.

If we want to measure instruction counts, we should be using codspeed.io instead because
they support visualizing and tracking over time.

If we want to track performance over time we should be using Kevin's perf monitor machine.
2025-01-17 15:42:51 -06:00
277 changed files with 99959 additions and 70168 deletions

View File

@ -1,44 +0,0 @@
on:
push:
branches:
- main
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-bench.yml
pull_request:
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-bench.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo bench
jobs:
cargo-bench:
name: Benchmark with iai
runs-on: ubuntu-latest-8-cores
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: |
cargo install cargo-criterion
sudo apt update
sudo apt install -y valgrind
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
- name: Benchmark kcl library
shell: bash
run: |-
cd src/wasm-lib/kcl; cargo bench --all-features -- iai
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}

File diff suppressed because it is too large Load Diff

28
docs/kcl/types/Face.md Normal file
View File

@ -0,0 +1,28 @@
---
title: "Face"
excerpt: "A face."
layout: manual
---
A face.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the face. | No |
| `value` |`string`| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -20,6 +20,7 @@ A helix.
| `revolutions` |`number`| Number of revolutions. | No | | `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No | | `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -20,6 +20,7 @@ A helix.
| `revolutions` |`number`| Number of revolutions. | No | | `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No | | `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -168,7 +168,6 @@ Any KCL value.
---- ----
A plane.
**Type:** `object` **Type:** `object`
@ -181,17 +180,10 @@ A plane.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No | | `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
| `id` |`string`| The id of the plane. | No | | `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
A face.
**Type:** `object` **Type:** `object`
@ -203,14 +195,8 @@ A face.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Face`| | No | | `type` |enum: [`Face`](/docs/kcl/types/Face)| | No |
| `id` |`string`| The id of the face. | No | | `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | No |
| `value` |`string`| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -246,7 +232,6 @@ A face.
---- ----
An solid is a collection of extrude surfaces.
**Type:** `object` **Type:** `object`
@ -259,14 +244,7 @@ An solid is a collection of extrude surfaces.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No | | `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
| `id` |`string`| The id of the solid. | No | | `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | No |
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
| `height` |`number`| The height of the solid. | No |
| `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
---- ----
@ -286,7 +264,6 @@ An solid is a collection of extrude surfaces.
---- ----
A helix.
**Type:** `object` **Type:** `object`
@ -299,11 +276,7 @@ A helix.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No | | `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
| `value` |`string`| The id of the helix. | No | | `value` |[`Helix`](/docs/kcl/types/Helix)| Any KCL value. | No |
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----

View File

@ -22,6 +22,7 @@ A plane.
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -21,6 +21,7 @@ A sketch is a collection of paths.
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -30,6 +30,7 @@ A sketch is a collection of paths.
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -31,6 +31,7 @@ A plane.
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -54,6 +55,7 @@ A face.
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -23,6 +23,7 @@ An solid is a collection of extrude surfaces.
| `startCapId` |`string`| The id of the extrusion start cap | No | | `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No | | `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| An solid is a collection of extrude surfaces. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -32,6 +32,7 @@ An solid is a collection of extrude surfaces.
| `startCapId` |`string`| The id of the extrusion start cap | No | | `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No | | `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid or a group of solids. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

107
docs/kcl/types/UnitLen.md Normal file
View File

@ -0,0 +1,107 @@
---
title: "UnitLen"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Mm`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Cm`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `M`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Inches`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Feet`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Yards`| | No |
----

View File

@ -280,7 +280,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible() await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible() await expect(page.getByText('Create project')).toBeVisible()
}) })
await test.step('Opening the router-template project should load', async () => { await test.step('Opening the router-template project should load', async () => {

View File

@ -38,14 +38,14 @@ test.describe('Debug pane', () => {
// Set the code in the code editor. // Set the code in the code editor.
await u.codeLocator.click() await u.codeLocator.click()
await page.keyboard.type(code, { delay: 0 }) await page.keyboard.type(code, { delay: 0 })
// Scroll to the feature tree. // Scroll to the artifact graph.
await tree.scrollIntoViewIfNeeded() await tree.scrollIntoViewIfNeeded()
// Expand the feature tree. // Expand the artifact graph.
await tree.getByText('Feature Tree').click() await tree.getByText('Artifact Graph').click()
// Just expanded the details, making the element taller, so scroll again. // Just expanded the details, making the element taller, so scroll again.
await tree.getByText('Plane').first().scrollIntoViewIfNeeded() await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
}) })
// Extract the artifact IDs from the debug feature tree. // Extract the artifact IDs from the debug artifact graph.
const initialSegmentIds = await segment.innerText({ timeout: 5_000 }) const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
// The artifact ID should include a UUID. // The artifact ID should include a UUID.
expect(initialSegmentIds).toMatch( expect(initialSegmentIds).toMatch(

View File

@ -135,4 +135,20 @@ export class CmdBarFixture {
await promptEditCommand.first().click() await promptEditCommand.first().click()
} }
} }
get cmdSearchInput() {
return this.page.getByTestId('cmd-bar-search')
}
get argumentInput() {
return this.page.getByTestId('cmd-bar-arg-value')
}
get cmdOptions() {
return this.page.getByTestId('cmd-bar-option')
}
chooseCommand = async (commandName: string) => {
await this.cmdOptions.getByText(commandName).click()
}
} }

View File

@ -103,7 +103,7 @@ export class HomePageFixture {
.toEqual(expectedState) .toEqual(expectedState)
} }
createAndGoToProject = async (projectTitle: string) => { createAndGoToProject = async (projectTitle = 'project-$nnn') => {
await expect(this.projectSection).not.toHaveText('Loading your Projects...') await expect(this.projectSection).not.toHaveText('Loading your Projects...')
await this.projectButtonNew.click() await this.projectButtonNew.click()
await this.projectTextName.click() await this.projectTextName.click()

View File

@ -63,6 +63,10 @@ export class ToolbarFixture {
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
} }
get logoLink() {
return this.page.getByTestId('app-logo')
}
startSketchPlaneSelection = async () => startSketchPlaneSelection = async () =>
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500) doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)

View File

@ -963,37 +963,31 @@ sketch002 = startSketchOn('XZ')
await toolbar.sweepButton.click() await toolbar.sweepButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
currentArgKey: 'profile', currentArgKey: 'target',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Path: '', Target: '',
Profile: '', Trajectory: '',
}, },
highlightedHeaderArg: 'profile', highlightedHeaderArg: 'target',
stage: 'arguments', stage: 'arguments',
}) })
await clickOnSketch1() await clickOnSketch1()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
currentArgKey: 'path', currentArgKey: 'trajectory',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Path: '', Target: '1 face',
Profile: '1 face', Trajectory: '',
}, },
highlightedHeaderArg: 'path', highlightedHeaderArg: 'trajectory',
stage: 'arguments', stage: 'arguments',
}) })
await clickOnSketch2() await clickOnSketch2()
await cmdBar.expectState({ await page.waitForTimeout(500)
commandName: 'Sweep',
headerArguments: {
Path: '1 face',
Profile: '1 face',
},
stage: 'review',
})
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
}) })
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1020,6 +1014,75 @@ sketch002 = startSketchOn('XZ')
}) })
}) })
test(`Sweep point-and-click failing validation`, async ({
context,
page,
homePage,
scene,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('YZ')
|> circle({
center = [0, 0],
radius = 500
}, %)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(-500, %)
|> lineTo([-2000, 500], %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 250 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'target',
currentArgValue: '',
headerArguments: {
Target: '',
Trajectory: '',
},
highlightedHeaderArg: 'target',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'trajectory',
currentArgValue: '',
headerArguments: {
Target: '1 face',
Trajectory: '',
},
highlightedHeaderArg: 'trajectory',
stage: 'arguments',
})
await clickOnSketch2()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await expect(
page.getByText('Unable to sweep with the provided selection')
).toBeVisible()
})
})
test(`Fillet point-and-click`, async ({ test(`Fillet point-and-click`, async ({
context, context,
page, page,

View File

@ -172,7 +172,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('broken-code')).toBeVisible() await expect(page.getByText('broken-code')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible() await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible() await expect(page.getByText('Create project')).toBeVisible()
}) })
await test.step('opening broken code project should clear the scene and show the error', async () => { await test.step('opening broken code project should clear the scene and show the error', async () => {
// Go back home. // Go back home.
@ -253,7 +253,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('empty')).toBeVisible() await expect(page.getByText('empty')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible() await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible() await expect(page.getByText('Create project')).toBeVisible()
}) })
await test.step('opening empty code project should clear the scene', async () => { await test.step('opening empty code project should clear the scene', async () => {
// Go back home. // Go back home.
@ -985,6 +985,126 @@ test.describe(`Project management commands`, () => {
}) })
} }
) )
test(`Create a new project with a colliding name`, async ({
context,
homePage,
toolbar,
cmdBar,
}) => {
const projectName = 'test-project'
await test.step(`Setup`, async () => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, projectName)
await Promise.all([fsp.mkdir(projectDir, { recursive: true })])
await Promise.all([
fsp.copyFile(
executorInputPath('router-template-slate.kcl'),
path.join(projectDir, 'main.kcl')
),
])
})
await homePage.expectState({
projectCards: [
{
title: projectName,
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
await test.step('Create a new project with the same name', async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create project')
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Create project',
currentArgKey: 'name',
currentArgValue: '',
headerArguments: {
Name: '',
},
highlightedHeaderArg: 'name',
})
await cmdBar.argumentInput.fill(projectName)
await cmdBar.progressCmdBar()
})
await test.step(`Check the project was created with a non-colliding name`, async () => {
await toolbar.logoLink.click()
await homePage.expectState({
projectCards: [
{
title: projectName + '-1',
fileCount: 1,
},
{
title: projectName,
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
await test.step('Create another project with the same name', async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create project')
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Create project',
currentArgKey: 'name',
currentArgValue: '',
headerArguments: {
Name: '',
},
highlightedHeaderArg: 'name',
})
await cmdBar.argumentInput.fill(projectName)
await cmdBar.progressCmdBar()
})
await test.step(`Check the second project was created with a non-colliding name`, async () => {
await toolbar.logoLink.click()
await homePage.expectState({
projectCards: [
{
title: projectName + '-2',
fileCount: 1,
},
{
title: projectName + '-1',
fileCount: 1,
},
{
title: projectName,
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
})
})
test(`Create a few projects using the default project name`, async ({
homePage,
toolbar,
}) => {
for (let i = 0; i < 12; i++) {
await test.step(`Create project ${i}`, async () => {
await homePage.expectState({
projectCards: Array.from({ length: i }, (_, i) => ({
title: `project-${i.toString().padStart(3, '0')}`,
fileCount: 1,
})).toReversed(),
sortBy: 'last-modified-desc',
})
await homePage.createAndGoToProject()
await toolbar.logoLink.click()
})
}
}) })
test( test(
@ -1391,7 +1511,7 @@ extrude001 = extrude(200, sketch001)`)
await page.getByTestId('app-logo').click() await page.getByTestId('app-logo').click()
await expect( await expect(
page.getByRole('button', { name: 'New project' }) page.getByRole('button', { name: 'Create project' })
).toBeVisible() ).toBeVisible()
for (let i = 1; i <= 10; i++) { for (let i = 1; i <= 10; i++) {
@ -1465,7 +1585,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible() await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible() await expect(page.getByText('Create project')).toBeVisible()
}) })
await test.step('Opening the router-template project should load the stream', async () => { await test.step('Opening the router-template project should load the stream', async () => {
@ -1494,7 +1614,7 @@ test(
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible() await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible() await expect(page.getByText('Create project')).toBeVisible()
}) })
} }
) )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1078,7 +1078,7 @@ export async function createProject({
returnHome?: boolean returnHome?: boolean
}) { }) {
await test.step(`Create project and navigate to it`, async () => { await test.step(`Create project and navigate to it`, async () => {
await page.getByRole('button', { name: 'New project' }).click() await page.getByRole('button', { name: 'Create project' }).click()
await page.getByRole('textbox', { name: 'Name' }).fill(name) await page.getByRole('textbox', { name: 'Name' }).fill(name)
await page.getByRole('button', { name: 'Continue' }).click() await page.getByRole('button', { name: 'Continue' }).click()

View File

@ -201,7 +201,7 @@
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.19.1", "typescript-eslint": "^8.19.1",
"vite": "^5.4.6", "vite": "^5.4.12",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0", "vitest": "^1.6.0",

View File

@ -1,4 +1,4 @@
import { useRef, useMemo, memo } from 'react' import { useRef, useMemo, memo, useCallback, useState } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util' import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
@ -34,8 +34,7 @@ export function Toolbar({
const bgClassName = '!bg-transparent' const bgClassName = '!bg-transparent'
const buttonBgClassName = const buttonBgClassName =
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10' 'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10'
const buttonBorderClassName = const buttonBorderClassName = '!border-transparent'
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
const sketchPathId = useMemo(() => { const sketchPathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
@ -50,6 +49,7 @@ export function Toolbar({
const { overallState } = useNetworkContext() const { overallState } = useNetworkContext()
const { isExecuting } = useKclContext() const { isExecuting } = useKclContext()
const { isStreamReady } = useAppState() const { isStreamReady } = useAppState()
const [showRichContent, setShowRichContent] = useState(false)
const disableAllButtons = const disableAllButtons =
(overallState !== NetworkHealthState.Ok && (overallState !== NetworkHealthState.Ok &&
@ -77,6 +77,40 @@ export function Toolbar({
[state, send, commandBarSend, sketchPathId] [state, send, commandBarSend, sketchPathId]
) )
const tooltipContentClassName = !showRichContent
? ''
: '!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch'
const richContentTimeout = useRef<number | null>(null)
const richContentClearTimeout = useRef<number | null>(null)
// On mouse enter, show rich content after a 1s delay
const handleMouseEnter = useCallback(() => {
// Cancel the clear timeout if it's already set
if (richContentClearTimeout.current) {
clearTimeout(richContentClearTimeout.current)
}
// Start our own timeout to show the rich content
richContentTimeout.current = window.setTimeout(() => {
setShowRichContent(true)
if (richContentClearTimeout.current) {
clearTimeout(richContentClearTimeout.current)
}
}, 1000)
}, [setShowRichContent])
// On mouse leave, clear the timeout and hide rich content
const handleMouseLeave = useCallback(() => {
// Clear the timeout to show rich content
if (richContentTimeout.current) {
clearTimeout(richContentTimeout.current)
}
// Start a timeout to hide the rich content
richContentClearTimeout.current = window.setTimeout(() => {
setShowRichContent(false)
if (richContentClearTimeout.current) {
clearTimeout(richContentClearTimeout.current)
}
}, 500)
}, [setShowRichContent])
/** /**
* Resolve all the callbacks and values for the current mode, * Resolve all the callbacks and values for the current mode,
* so we don't need to worry about the other modes * so we don't need to worry about the other modes
@ -174,43 +208,64 @@ export function Toolbar({
status: itemConfig.status, status: itemConfig.status,
}))} }))}
> >
<ActionButton <div
Element="button" className="contents"
id={maybeIconConfig[0].id} // Mouse events do not fire on disabled buttons
data-testid={maybeIconConfig[0].id} onMouseEnter={handleMouseEnter}
iconStart={{ onMouseLeave={handleMouseLeave}
icon: maybeIconConfig[0].icon,
className: iconClassName,
bgClassName: bgClassName,
}}
className={
'!border-transparent !px-0 pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
buttonBgClassName
}
aria-pressed={maybeIconConfig[0].isActive}
disabled={
disableAllButtons ||
maybeIconConfig[0].status !== 'available' ||
maybeIconConfig[0].disabled
}
name={maybeIconConfig[0].title}
// aria-description is still in ARIA 1.3 draft.
// eslint-disable-next-line jsx-a11y/aria-props
aria-description={maybeIconConfig[0].description}
onClick={() =>
maybeIconConfig[0].onClick(configCallbackProps)
}
> >
<span <ActionButton
className={!maybeIconConfig[0].showTitle ? 'sr-only' : ''} Element="button"
id={maybeIconConfig[0].id}
data-testid={maybeIconConfig[0].id}
iconStart={{
icon: maybeIconConfig[0].icon,
className: iconClassName,
bgClassName: bgClassName,
}}
className={
'!border-transparent !px-0 pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
buttonBgClassName
}
aria-pressed={maybeIconConfig[0].isActive}
disabled={
disableAllButtons ||
maybeIconConfig[0].status !== 'available' ||
maybeIconConfig[0].disabled
}
name={maybeIconConfig[0].title}
// aria-description is still in ARIA 1.3 draft.
// eslint-disable-next-line jsx-a11y/aria-props
aria-description={maybeIconConfig[0].description}
onClick={() =>
maybeIconConfig[0].onClick(configCallbackProps)
}
> >
{maybeIconConfig[0].title} <span
</span> className={!maybeIconConfig[0].showTitle ? 'sr-only' : ''}
</ActionButton> >
<ToolbarItemTooltip {maybeIconConfig[0].title}
itemConfig={maybeIconConfig[0]} </span>
configCallbackProps={configCallbackProps} <ToolbarItemTooltip
/> itemConfig={maybeIconConfig[0]}
configCallbackProps={configCallbackProps}
wrapperClassName="ui-open:!hidden"
contentClassName={tooltipContentClassName}
>
{showRichContent ? (
<ToolbarItemTooltipRichContent
itemConfig={maybeIconConfig[0]}
/>
) : (
<ToolbarItemTooltipShortContent
status={maybeIconConfig[0].status}
title={maybeIconConfig[0].title}
hotkey={maybeIconConfig[0].hotkey}
/>
)}
</ToolbarItemTooltip>
</ActionButton>
</div>
</ActionButtonDropdown> </ActionButtonDropdown>
) )
} }
@ -218,7 +273,13 @@ export function Toolbar({
// A single button // A single button
return ( return (
<div className="relative" key={itemConfig.id}> <div
className="relative"
key={itemConfig.id}
// Mouse events do not fire on disabled buttons
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<ActionButton <ActionButton
Element="button" Element="button"
key={itemConfig.id} key={itemConfig.id}
@ -255,7 +316,18 @@ export function Toolbar({
<ToolbarItemTooltip <ToolbarItemTooltip
itemConfig={itemConfig} itemConfig={itemConfig}
configCallbackProps={configCallbackProps} configCallbackProps={configCallbackProps}
/> contentClassName={tooltipContentClassName}
>
{showRichContent ? (
<ToolbarItemTooltipRichContent itemConfig={itemConfig} />
) : (
<ToolbarItemTooltipShortContent
status={itemConfig.status}
title={itemConfig.title}
hotkey={itemConfig.hotkey}
/>
)}
</ToolbarItemTooltip>
</div> </div>
) )
})} })}
@ -269,6 +341,12 @@ export function Toolbar({
) )
} }
interface ToolbarItemContentsProps extends React.PropsWithChildren {
itemConfig: ToolbarItemResolved
configCallbackProps: ToolbarItemCallbackProps
wrapperClassName?: string
contentClassName?: string
}
/** /**
* The single button and dropdown button share content, so we extract it here * The single button and dropdown button share content, so we extract it here
* It contains a tooltip with the title, description, and links * It contains a tooltip with the title, description, and links
@ -277,12 +355,10 @@ export function Toolbar({
const ToolbarItemTooltip = memo(function ToolbarItemContents({ const ToolbarItemTooltip = memo(function ToolbarItemContents({
itemConfig, itemConfig,
configCallbackProps, configCallbackProps,
}: { wrapperClassName = '',
itemConfig: ToolbarItemResolved contentClassName = '',
configCallbackProps: ToolbarItemCallbackProps children,
}) { }: ToolbarItemContentsProps) {
const { state } = useModelingContext()
useHotkeys( useHotkeys(
itemConfig.hotkey || '', itemConfig.hotkey || '',
() => { () => {
@ -305,11 +381,50 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
? ({ '-webkit-app-region': 'no-drag' } as React.CSSProperties) ? ({ '-webkit-app-region': 'no-drag' } as React.CSSProperties)
: {} : {}
} }
hoverOnly
position="bottom" position="bottom"
wrapperClassName="!p-4 !pointer-events-auto" wrapperClassName={'!p-4 !pointer-events-auto ' + wrapperClassName}
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch" contentClassName={contentClassName}
delay={0}
> >
{children}
</Tooltip>
)
})
const ToolbarItemTooltipShortContent = ({
status,
title,
hotkey,
}: {
status: string
title: string
hotkey?: string | string[]
}) => (
<span
className={`text-sm ${
status !== 'available' ? 'text-chalkboard-70 dark:text-chalkboard-40' : ''
}`}
>
{title}
{hotkey && (
<kbd className="inline-block ml-2 flex-none hotkey">{hotkey}</kbd>
)}
</span>
)
const ToolbarItemTooltipRichContent = ({
itemConfig,
}: {
itemConfig: ToolbarItemResolved
}) => {
const { state } = useModelingContext()
return (
<>
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50"> <div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
{itemConfig.icon && (
<CustomIcon className="w-5 h-5" name={itemConfig.icon} />
)}
<span <span
className={`text-sm flex-1 ${ className={`text-sm flex-1 ${
itemConfig.status !== 'available' itemConfig.status !== 'available'
@ -378,6 +493,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
</ul> </ul>
</> </>
)} )}
</Tooltip> </>
) )
}) }

View File

@ -1398,23 +1398,23 @@ export class SceneEntities {
const arg0 = arg(kclCircle3PointArgs[0]) const arg0 = arg(kclCircle3PointArgs[0])
if (!arg0) return kclManager.ast if (!arg0) return kclManager.ast
arg0[0].value = points[0].x arg0[0].value = { value: points[0].x, suffix: 'None' }
arg0[0].raw = points[0].x.toString() arg0[0].raw = points[0].x.toString()
arg0[1].value = points[0].y arg0[1].value = { value: points[0].y, suffix: 'None' }
arg0[1].raw = points[0].y.toString() arg0[1].raw = points[0].y.toString()
const arg1 = arg(kclCircle3PointArgs[1]) const arg1 = arg(kclCircle3PointArgs[1])
if (!arg1) return kclManager.ast if (!arg1) return kclManager.ast
arg1[0].value = points[1].x arg1[0].value = { value: points[1].x, suffix: 'None' }
arg1[0].raw = points[1].x.toString() arg1[0].raw = points[1].x.toString()
arg1[1].value = points[1].y arg1[1].value = { value: points[1].y, suffix: 'None' }
arg1[1].raw = points[1].y.toString() arg1[1].raw = points[1].y.toString()
const arg2 = arg(kclCircle3PointArgs[2]) const arg2 = arg(kclCircle3PointArgs[2])
if (!arg2) return kclManager.ast if (!arg2) return kclManager.ast
arg2[0].value = points[2].x arg2[0].value = { value: points[2].x, suffix: 'None' }
arg2[0].raw = points[2].x.toString() arg2[0].raw = points[2].x.toString()
arg2[1].value = points[2].y arg2[1].value = { value: points[2].y, suffix: 'None' }
arg2[1].raw = points[2].y.toString() arg2[1].raw = points[2].y.toString()
const astSnapshot = structuredClone(kclManager.ast) const astSnapshot = structuredClone(kclManager.ast)
@ -2051,8 +2051,8 @@ export class SceneEntities {
) )
if (!(sk instanceof Reason)) { if (!(sk instanceof Reason)) {
sketch = sk sketch = sk
} else if ((maybeSketch as Solid).sketch) { } else if (maybeSketch && (maybeSketch.value as Solid)?.sketch) {
sketch = (maybeSketch as Solid).sketch sketch = (maybeSketch.value as Solid).sketch
} }
if (!sketch) return if (!sketch) return
@ -2541,7 +2541,7 @@ export function sketchFromPathToNode({
const varDec = _varDec.node const varDec = _varDec.node
const result = programMemory.get(varDec?.id?.name || '') const result = programMemory.get(varDec?.id?.name || '')
if (result?.type === 'Solid') { if (result?.type === 'Solid') {
return result.sketch return result.value.sketch
} }
const sg = sketchFromKclValue(result, varDec?.id?.name) const sg = sketchFromKclValue(result, varDec?.id?.name)
if (err(sg)) { if (err(sg)) {

View File

@ -1,9 +1,11 @@
import { Popover } from '@headlessui/react' import { Popover } from '@headlessui/react'
import { ActionButtonProps } from './ActionButton' import { ActionButtonProps } from './ActionButton'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import Tooltip from './Tooltip'
type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & { type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
name?: string name?: string
dropdownTooltipText?: string
splitMenuItems: { splitMenuItems: {
id: string id: string
label: string label: string
@ -17,6 +19,7 @@ type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
export function ActionButtonDropdown({ export function ActionButtonDropdown({
splitMenuItems, splitMenuItems,
className, className,
dropdownTooltipText = 'More tools',
children, children,
...props ...props
}: ActionButtonSplitProps) { }: ActionButtonSplitProps) {
@ -26,7 +29,14 @@ export function ActionButtonDropdown({
{({ close }) => ( {({ close }) => (
<> <>
{children} {children}
<Popover.Button className="border-transparent dark:border-transparent p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary"> <Popover.Button
className={
'!border-transparent dark:!border-transparent ' +
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent ' +
'enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 ' +
'pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10 p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary'
}
>
<CustomIcon <CustomIcon
name="caretDown" name="caretDown"
className={ className={
@ -37,6 +47,14 @@ export function ActionButtonDropdown({
<span className="sr-only"> <span className="sr-only">
{props.name ? props.name + ': ' : ''}open menu {props.name ? props.name + ': ' : ''}open menu
</span> </span>
<Tooltip
delay={0}
position="bottom"
hoverOnly
wrapperClassName="ui-open:!hidden"
>
{dropdownTooltipText}
</Tooltip>
</Popover.Button> </Popover.Button>
<Popover.Panel <Popover.Panel
as="ul" as="ul"

View File

@ -134,6 +134,7 @@ function CommandArgOptionInput({
</label> </label>
<Combobox.Input <Combobox.Input
id="option-input" id="option-input"
data-testid="cmd-bar-arg-value"
ref={inputRef} ref={inputRef}
onChange={(event) => onChange={(event) =>
!event.target.disabled && setQuery(event.target.value) !event.target.disabled && setQuery(event.target.value)

View File

@ -52,6 +52,7 @@ function CommandComboBox({
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit" className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
/> />
<Combobox.Input <Combobox.Input
data-testid="cmd-bar-search"
onChange={(event) => setQuery(event.target.value)} onChange={(event) => setQuery(event.target.value)}
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none" className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
onKeyDown={(event) => { onKeyDown={(event) => {
@ -85,6 +86,7 @@ function CommandComboBox({
value={option} value={option}
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50" className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
disabled={optionIsDisabled(option)} disabled={optionIsDisabled(option)}
data-testid={`cmd-bar-option`}
> >
{'icon' in option && option.icon && ( {'icon' in option && option.icon && (
<CustomIcon name={option.icon} className="w-5 h-5" /> <CustomIcon name={option.icon} className="w-5 h-5" />

View File

@ -4,18 +4,18 @@ import { expandPlane, PlaneArtifactRich } from 'lang/std/artifactGraph'
import { ArtifactGraph } from 'lang/wasm' import { ArtifactGraph } from 'lang/wasm'
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj' import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
export function DebugFeatureTree() { export function DebugArtifactGraph() {
const featureTree = useMemo(() => { const artifactGraphTree = useMemo(() => {
return computeTree(engineCommandManager.artifactGraph) return computeTree(engineCommandManager.artifactGraph)
}, [engineCommandManager.artifactGraph]) }, [engineCommandManager.artifactGraph])
const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode'] const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
return ( return (
<details data-testid="debug-feature-tree" className="relative"> <details data-testid="debug-feature-tree" className="relative">
<summary>Feature Tree</summary> <summary>Artifact Graph</summary>
{featureTree.length > 0 ? ( {artifactGraphTree.length > 0 ? (
<pre className="text-xs"> <pre className="text-xs">
<DebugDisplayArray arr={featureTree} filterKeys={filterKeys} /> <DebugDisplayArray arr={artifactGraphTree} filterKeys={filterKeys} />
</pre> </pre>
) : ( ) : (
<p>(Empty)</p> <p>(Empty)</p>

View File

@ -1,4 +1,4 @@
import { DebugFeatureTree } from 'components/DebugFeatureTree' import { DebugArtifactGraph } from 'components/DebugArtifactGraph'
import { AstExplorer } from '../../AstExplorer' import { AstExplorer } from '../../AstExplorer'
import { EngineCommands } from '../../EngineCommands' import { EngineCommands } from '../../EngineCommands'
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp' import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
@ -14,7 +14,7 @@ export const DebugPane = () => {
<EngineCommands /> <EngineCommands />
<CamDebugSettings /> <CamDebugSettings />
<AstExplorer /> <AstExplorer />
<DebugFeatureTree /> <DebugArtifactGraph />
</div> </div>
</section> </section>
</div> </div>

View File

@ -3,6 +3,7 @@
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90; @apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit; @apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
@apply transition-colors ease-out; @apply transition-colors ease-out;
@apply m-0;
} }
:global(.dark) .button { :global(.dark) .button {

View File

@ -95,9 +95,11 @@ export const processMemory = (programMemory: ProgramMemory) => {
) { ) {
const sk = sketchFromKclValueOptional(val, key) const sk = sketchFromKclValueOptional(val, key)
if (val.type === 'Solid') { if (val.type === 'Solid') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => { processedMemory[key] = val.value.value.map(
return rest ({ ...rest }: ExtrudeSurface) => {
}) return rest
}
)
} else if (!(sk instanceof Reason)) { } else if (!(sk instanceof Reason)) {
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => { processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
return rest return rest

View File

@ -18,6 +18,7 @@ import {
getNextProjectIndex, getNextProjectIndex,
interpolateProjectNameWithIndex, interpolateProjectNameWithIndex,
doesProjectNameNeedInterpolated, doesProjectNameNeedInterpolated,
getUniqueProjectName,
} from 'lib/desktopFS' } from 'lib/desktopFS'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import useStateMachineCommands from 'hooks/useStateMachineCommands' import useStateMachineCommands from 'hooks/useStateMachineCommands'
@ -195,16 +196,12 @@ const ProjectsContextDesktop = ({
: settings.projects.defaultProjectName.current : settings.projects.defaultProjectName.current
).trim() ).trim()
if (doesProjectNameNeedInterpolated(name)) { const uniqueName = getUniqueProjectName(name, input.projects)
const nextIndex = getNextProjectIndex(name, input.projects) await createNewProjectDirectory(uniqueName)
name = interpolateProjectNameWithIndex(name, nextIndex)
}
await createNewProjectDirectory(name)
return { return {
message: `Successfully created "${name}"`, message: `Successfully created "${uniqueName}"`,
name, name: uniqueName,
} }
}), }),
renameProject: fromPromise(async ({ input }) => { renameProject: fromPromise(async ({ input }) => {

View File

@ -24,7 +24,10 @@ describe('testing AST', () => {
type: 'Literal', type: 'Literal',
start: 0, start: 0,
end: 1, end: 1,
value: 5, value: {
suffix: 'None',
value: 5,
},
raw: '5', raw: '5',
}, },
operator: '+', operator: '+',
@ -32,7 +35,10 @@ describe('testing AST', () => {
type: 'Literal', type: 'Literal',
start: 3, start: 3,
end: 4, end: 4,
value: 6, value: {
suffix: 'None',
value: 6,
},
raw: '6', raw: '6',
}, },
}, },

View File

@ -54,6 +54,9 @@ const mySketch001 = startSketchOn('XY')
}, },
], ],
id: expect.any(String), id: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [46, 71, 0] }], __meta: [{ sourceRange: [46, 71, 0] }],
}, },
}) })
@ -72,56 +75,65 @@ const mySketch001 = startSketchOn('XY')
const sketch001 = execState.memory.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'Solid', type: 'Solid',
id: expect.any(String), value: {
value: [ type: 'Solid',
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: null,
id: expect.any(String),
sourceRange: [77, 102, 0],
},
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: null,
id: expect.any(String),
sourceRange: [108, 132, 0],
},
],
sketch: {
id: expect.any(String), id: expect.any(String),
__meta: expect.any(Array), value: [
on: expect.any(Object),
start: expect.any(Object),
type: 'Sketch',
paths: [
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [0, 0], faceId: expect.any(String),
to: [-1.59, -1.54],
tag: null, tag: null,
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [77, 102, 0],
sourceRange: [77, 102, 0],
},
}, },
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [-1.59, -1.54], faceId: expect.any(String),
to: [0.46, -5.82],
tag: null, tag: null,
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [108, 132, 0],
sourceRange: [108, 132, 0],
},
}, },
], ],
sketch: {
id: expect.any(String),
units: {
type: 'Mm',
},
__meta: expect.any(Array),
on: expect.any(Object),
start: expect.any(Object),
type: 'Sketch',
paths: [
{
type: 'ToPoint',
from: [0, 0],
to: [-1.59, -1.54],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [77, 102, 0],
},
},
{
type: 'ToPoint',
from: [-1.59, -1.54],
to: [0.46, -5.82],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [108, 132, 0],
},
},
],
},
height: 2,
startCapId: expect.any(String),
endCapId: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [46, 71, 0] }],
}, },
height: 2,
startCapId: expect.any(String),
endCapId: expect.any(String),
__meta: [{ sourceRange: [46, 71, 0] }],
}) })
}) })
test('sketch extrude and sketch on one of the faces', async () => { test('sketch extrude and sketch on one of the faces', async () => {
@ -154,187 +166,205 @@ const sk2 = startSketchOn('XY')
expect(geos).toEqual([ expect(geos).toEqual([
{ {
type: 'Solid', type: 'Solid',
id: expect.any(String), value: {
value: [ type: 'Solid',
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: null,
id: expect.any(String),
sourceRange: [69, 89, 0],
},
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: {
end: 116,
start: 114,
type: 'TagDeclarator',
value: 'p',
},
id: expect.any(String),
sourceRange: [95, 117, 0],
},
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: null,
id: expect.any(String),
sourceRange: [123, 142, 0],
},
],
sketch: {
id: expect.any(String), id: expect.any(String),
__meta: expect.any(Array), value: [
on: expect.any(Object),
start: expect.any(Object),
type: 'Sketch',
tags: {
p: {
__meta: [
{
sourceRange: [114, 116, 0],
},
],
type: 'TagIdentifier',
value: 'p',
info: expect.any(Object),
},
},
paths: [
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [0, 0], faceId: expect.any(String),
to: [-2.5, 0],
tag: null, tag: null,
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [69, 89, 0],
sourceRange: [69, 89, 0],
},
}, },
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [-2.5, 0], faceId: expect.any(String),
to: [0, 10],
tag: { tag: {
end: 116, end: 116,
start: 114, start: 114,
type: 'TagDeclarator', type: 'TagDeclarator',
value: 'p', value: 'p',
}, },
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [95, 117, 0],
sourceRange: [95, 117, 0],
},
}, },
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [0, 10], faceId: expect.any(String),
to: [2.5, 0],
tag: null, tag: null,
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [123, 142, 0],
sourceRange: [123, 142, 0],
},
}, },
], ],
sketch: {
id: expect.any(String),
__meta: expect.any(Array),
on: expect.any(Object),
start: expect.any(Object),
type: 'Sketch',
units: {
type: 'Mm',
},
tags: {
p: {
__meta: [
{
sourceRange: [114, 116, 0],
},
],
type: 'TagIdentifier',
value: 'p',
info: expect.any(Object),
},
},
paths: [
{
type: 'ToPoint',
from: [0, 0],
to: [-2.5, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [69, 89, 0],
},
},
{
type: 'ToPoint',
from: [-2.5, 0],
to: [0, 10],
tag: {
end: 116,
start: 114,
type: 'TagDeclarator',
value: 'p',
},
__geoMeta: {
id: expect.any(String),
sourceRange: [95, 117, 0],
},
},
{
type: 'ToPoint',
from: [0, 10],
to: [2.5, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [123, 142, 0],
},
},
],
},
height: 2,
startCapId: expect.any(String),
endCapId: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [38, 63, 0] }],
}, },
height: 2,
startCapId: expect.any(String),
endCapId: expect.any(String),
__meta: [{ sourceRange: [38, 63, 0] }],
}, },
{ {
type: 'Solid', type: 'Solid',
id: expect.any(String), value: {
value: [ type: 'Solid',
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: null,
id: expect.any(String),
sourceRange: [373, 393, 0],
},
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: {
end: 419,
start: 417,
type: 'TagDeclarator',
value: 'o',
},
id: expect.any(String),
sourceRange: [399, 420, 0],
},
{
type: 'extrudePlane',
faceId: expect.any(String),
tag: null,
id: expect.any(String),
sourceRange: [426, 445, 0],
},
],
sketch: {
id: expect.any(String), id: expect.any(String),
__meta: expect.any(Array), value: [
on: expect.any(Object),
start: expect.any(Object),
type: 'Sketch',
tags: {
o: {
__meta: [
{
sourceRange: [417, 419, 0],
},
],
type: 'TagIdentifier',
value: 'o',
info: expect.any(Object),
},
},
paths: [
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [0, 0], faceId: expect.any(String),
to: [-2.5, 0],
tag: null, tag: null,
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [373, 393, 0],
sourceRange: [373, 393, 0],
},
}, },
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [-2.5, 0], faceId: expect.any(String),
to: [0, 3],
tag: { tag: {
end: 419, end: 419,
start: 417, start: 417,
type: 'TagDeclarator', type: 'TagDeclarator',
value: 'o', value: 'o',
}, },
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [399, 420, 0],
sourceRange: [399, 420, 0],
},
}, },
{ {
type: 'ToPoint', type: 'extrudePlane',
from: [0, 3], faceId: expect.any(String),
to: [2.5, 0],
tag: null, tag: null,
__geoMeta: { id: expect.any(String),
id: expect.any(String), sourceRange: [426, 445, 0],
sourceRange: [426, 445, 0],
},
}, },
], ],
sketch: {
id: expect.any(String),
units: {
type: 'Mm',
},
__meta: expect.any(Array),
on: expect.any(Object),
start: expect.any(Object),
type: 'Sketch',
tags: {
o: {
__meta: [
{
sourceRange: [417, 419, 0],
},
],
type: 'TagIdentifier',
value: 'o',
info: expect.any(Object),
},
},
paths: [
{
type: 'ToPoint',
from: [0, 0],
to: [-2.5, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [373, 393, 0],
},
},
{
type: 'ToPoint',
from: [-2.5, 0],
to: [0, 3],
tag: {
end: 419,
start: 417,
type: 'TagDeclarator',
value: 'o',
},
__geoMeta: {
id: expect.any(String),
sourceRange: [399, 420, 0],
},
},
{
type: 'ToPoint',
from: [0, 3],
to: [2.5, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [426, 445, 0],
},
},
],
},
height: 2,
startCapId: expect.any(String),
endCapId: expect.any(String),
__meta: [{ sourceRange: [342, 367, 0] }],
units: {
type: 'Mm',
},
}, },
height: 2,
startCapId: expect.any(String),
endCapId: expect.any(String),
__meta: [{ sourceRange: [342, 367, 0] }],
}, },
]) ])
}) })

View File

@ -221,6 +221,9 @@ const newVar = myVar + 1`
}, },
], ],
id: expect.any(String), id: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [39, 63, 0] }], __meta: [{ sourceRange: [39, 63, 0] }],
}, },
}) })

View File

@ -39,7 +39,7 @@ describe('Testing createLiteral', () => {
it('should create a literal', () => { it('should create a literal', () => {
const result = createLiteral(5) const result = createLiteral(5)
expect(result.type).toBe('Literal') expect(result.type).toBe('Literal')
expect(result.value).toBe(5) expect((result as any).value.value).toBe(5)
}) })
}) })
describe('Testing createIdentifier', () => { describe('Testing createIdentifier', () => {
@ -56,7 +56,7 @@ describe('Testing createCallExpression', () => {
expect(result.callee.type).toBe('Identifier') expect(result.callee.type).toBe('Identifier')
expect(result.callee.name).toBe('myFunc') expect(result.callee.name).toBe('myFunc')
expect(result.arguments[0].type).toBe('Literal') expect(result.arguments[0].type).toBe('Literal')
expect((result.arguments[0] as any).value).toBe(5) expect((result.arguments[0] as any).value.value).toBe(5)
}) })
}) })
describe('Testing createObjectExpression', () => { describe('Testing createObjectExpression', () => {
@ -68,7 +68,7 @@ describe('Testing createObjectExpression', () => {
expect(result.properties[0].type).toBe('ObjectProperty') expect(result.properties[0].type).toBe('ObjectProperty')
expect(result.properties[0].key.name).toBe('myProp') expect(result.properties[0].key.name).toBe('myProp')
expect(result.properties[0].value.type).toBe('Literal') expect(result.properties[0].value.type).toBe('Literal')
expect((result.properties[0].value as any).value).toBe(5) expect((result.properties[0].value as any).value.value).toBe(5)
}) })
}) })
describe('Testing createArrayExpression', () => { describe('Testing createArrayExpression', () => {
@ -76,7 +76,7 @@ describe('Testing createArrayExpression', () => {
const result = createArrayExpression([createLiteral(5)]) const result = createArrayExpression([createLiteral(5)])
expect(result.type).toBe('ArrayExpression') expect(result.type).toBe('ArrayExpression')
expect(result.elements[0].type).toBe('Literal') expect(result.elements[0].type).toBe('Literal')
expect((result.elements[0] as any).value).toBe(5) expect((result.elements[0] as any).value.value).toBe(5)
}) })
}) })
describe('Testing createPipeSubstitution', () => { describe('Testing createPipeSubstitution', () => {
@ -93,7 +93,7 @@ describe('Testing createVariableDeclaration', () => {
expect(result.declaration.id.type).toBe('Identifier') expect(result.declaration.id.type).toBe('Identifier')
expect(result.declaration.id.name).toBe('myVar') expect(result.declaration.id.name).toBe('myVar')
expect(result.declaration.init.type).toBe('Literal') expect(result.declaration.init.type).toBe('Literal')
expect((result.declaration.init as any).value).toBe(5) expect((result.declaration.init as any).value.value).toBe(5)
}) })
}) })
describe('Testing createPipeExpression', () => { describe('Testing createPipeExpression', () => {
@ -101,7 +101,7 @@ describe('Testing createPipeExpression', () => {
const result = createPipeExpression([createLiteral(5)]) const result = createPipeExpression([createLiteral(5)])
expect(result.type).toBe('PipeExpression') expect(result.type).toBe('PipeExpression')
expect(result.body[0].type).toBe('Literal') expect(result.body[0].type).toBe('Literal')
expect((result.body[0] as any).value).toBe(5) expect((result.body[0] as any).value.value).toBe(5)
}) })
}) })

View File

@ -743,14 +743,18 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
return splitPathAtPipeExpression(pathToNode.slice(0, -1)) return splitPathAtPipeExpression(pathToNode.slice(0, -1))
} }
export function createLiteral(value: LiteralValue): Node<Literal> { export function createLiteral(value: LiteralValue | number): Node<Literal> {
const raw = `${value}`
if (typeof value === 'number') {
value = { value, suffix: 'None' }
}
return { return {
type: 'Literal', type: 'Literal',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0, moduleId: 0,
value, value,
raw: `${value}`, raw,
} }
} }

View File

@ -660,7 +660,7 @@ myNestedVar = [
enter: (node, path) => { enter: (node, path) => {
if ( if (
node.type === 'Literal' && node.type === 'Literal' &&
String(node.value) === literalOfInterest String((node as any).value.value) === literalOfInterest
) { ) {
pathToNode = path pathToNode = path
} else if ( } else if (

View File

@ -717,16 +717,6 @@ function isTypeInArrayExp(
return node.elements.some((el) => isTypeInValue(el, syntaxType)) return node.elements.some((el) => isTypeInValue(el, syntaxType))
} }
export function isValueZero(val?: Expr): boolean {
return (
(val?.type === 'Literal' && Number(val.value) === 0) ||
(val?.type === 'UnaryExpression' &&
val.operator === '-' &&
val.argument.type === 'Literal' &&
Number(val.argument.value) === 0)
)
}
export function isLinesParallelAndConstrained( export function isLinesParallelAndConstrained(
ast: Program, ast: Program,
artifactGraph: ArtifactGraph, artifactGraph: ArtifactGraph,

View File

@ -1014,6 +1014,11 @@ class EngineConnection extends EventTarget {
this.pingPongSpan.pong = new Date() this.pingPongSpan.pong = new Date()
break break
case 'modeling_session_data':
let api_call_id = resp.data?.session?.api_call_id
console.log(`API Call ID: ${api_call_id}`)
break
// Only fires on successful authentication. // Only fires on successful authentication.
case 'ice_server_info': case 'ice_server_info':
let ice_servers = resp.data?.ice_servers let ice_servers = resp.data?.ice_servers

View File

@ -20,12 +20,12 @@ import {
sketchFromKclValue, sketchFromKclValue,
Literal, Literal,
SourceRange, SourceRange,
LiteralValue,
} from '../wasm' } from '../wasm'
import { import {
getNodeFromPath, getNodeFromPath,
getNodeFromPathCurry, getNodeFromPathCurry,
getNodePathFromSourceRange, getNodePathFromSourceRange,
isValueZero,
} from '../queryAst' } from '../queryAst'
import { import {
createArrayExpression, createArrayExpression,
@ -79,11 +79,32 @@ export type ConstraintType =
| 'setAngleBetween' | 'setAngleBetween'
const REF_NUM_ERR = new Error('Referenced segment does not have a to value') const REF_NUM_ERR = new Error('Referenced segment does not have a to value')
function asNum(val: LiteralValue): number | Error {
if (typeof val === 'object') return val.value
return REF_NUM_ERR
}
function forceNum(arg: Literal): number {
if (typeof arg.value === 'boolean' || typeof arg.value === 'string') {
return Number(arg.value)
} else {
return arg.value.value
}
}
function isUndef(val: any): val is undefined { function isUndef(val: any): val is undefined {
return typeof val === 'undefined' return typeof val === 'undefined'
} }
function isNum(val: any): val is number {
return typeof val === 'number' function isValueZero(val?: Expr): boolean {
return (
(val?.type === 'Literal' && forceNum(val) === 0) ||
(val?.type === 'UnaryExpression' &&
val.operator === '-' &&
val.argument.type === 'Literal' &&
Number(val.argument.value) === 0)
)
} }
function createCallWrapper( function createCallWrapper(
@ -190,7 +211,7 @@ const xyLineSetLength =
: referenceSeg : referenceSeg
? segRef ? segRef
: args[0].expr : args[0].expr
const literalARg = getArgLiteralVal(args[0].expr) const literalARg = asNum(args[0].expr.value)
if (err(literalARg)) return literalARg if (err(literalARg)) return literalARg
return createCallWrapper(xOrY, lineVal, tag, literalARg) return createCallWrapper(xOrY, lineVal, tag, literalARg)
} }
@ -211,13 +232,14 @@ const basicAngledLineCreateNode =
referencedSegment: path, referencedSegment: path,
}) => { }) => {
const refAng = path ? getAngle(path?.from, path?.to) : 0 const refAng = path ? getAngle(path?.from, path?.to) : 0
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const argValue = asNum(args[0].expr.value)
if (err(argValue)) return argValue
const nonForcedAng = const nonForcedAng =
varValToUse === 'ang' varValToUse === 'ang'
? inputs[0].expr ? inputs[0].expr
: referenceSeg === 'ang' : referenceSeg === 'ang'
? getClosesAngleDirection( ? getClosesAngleDirection(
args[0].expr.value, argValue,
refAng, refAng,
createSegAngle(referenceSegName) createSegAngle(referenceSegName)
) )
@ -230,8 +252,8 @@ const basicAngledLineCreateNode =
: args[1].expr : args[1].expr
const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform
const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform
const literalArg = getArgLiteralVal( const literalArg = asNum(
valToForce === 'ang' ? args[0].expr : args[1].expr valToForce === 'ang' ? args[0].expr.value : args[1].expr.value
) )
if (err(literalArg)) return literalArg if (err(literalArg)) return literalArg
return createCallWrapper( return createCallWrapper(
@ -283,7 +305,7 @@ const getMinAndSegAngVals = (
} }
const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) => const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) =>
Number(arg.value) < 0 ? createUnaryExpression(legLenVal) : legLenVal forceNum(arg) < 0 ? createUnaryExpression(legLenVal) : legLenVal
const getLegAng = (ang: number, legAngleVal: BinaryPart) => { const getLegAng = (ang: number, legAngleVal: BinaryPart) => {
const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360 const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360
@ -322,8 +344,7 @@ const setHorzVertDistanceCreateNode =
referencedSegment, referencedSegment,
}) => { }) => {
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[index].expr) const literalArg = asNum(args?.[index].expr.value)
if (err(literalArg)) return literalArg
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2) const valueUsedInTransform = roundOff(literalArg - refNum, 2)
@ -352,7 +373,7 @@ const setHorzVertDistanceForAngleLineCreateNode =
referencedSegment, referencedSegment,
}) => { }) => {
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[1].expr) const literalArg = asNum(args?.[1].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2) const valueUsedInTransform = roundOff(literalArg - refNum, 2)
const binExp = createBinaryExpressionWithUnary([ const binExp = createBinaryExpressionWithUnary([
@ -374,8 +395,8 @@ const setAbsDistanceCreateNode =
index = xOrY === 'x' ? 0 : 1 index = xOrY === 'x' ? 0 : 1
): CreateStdLibSketchCallExpr => ): CreateStdLibSketchCallExpr =>
({ tag, forceValueUsedInTransform, rawArgs: args }) => { ({ tag, forceValueUsedInTransform, rawArgs: args }) => {
const literalArg = getArgLiteralVal(args?.[index].expr) const literalArg = asNum(args?.[index].expr.value)
if (err(literalArg)) return REF_NUM_ERR if (err(literalArg)) return literalArg
const valueUsedInTransform = roundOff(literalArg, 2) const valueUsedInTransform = roundOff(literalArg, 2)
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform) const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
if (isXOrYLine) { if (isXOrYLine) {
@ -396,8 +417,8 @@ const setAbsDistanceCreateNode =
const setAbsDistanceForAngleLineCreateNode = const setAbsDistanceForAngleLineCreateNode =
(xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr => (xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr =>
({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => { ({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => {
const literalArg = getArgLiteralVal(args?.[1].expr) const literalArg = asNum(args?.[1].expr.value)
if (err(literalArg)) return REF_NUM_ERR if (err(literalArg)) return literalArg
const valueUsedInTransform = roundOff(literalArg, 2) const valueUsedInTransform = roundOff(literalArg, 2)
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform) const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
return createCallWrapper( return createCallWrapper(
@ -419,7 +440,7 @@ const setHorVertDistanceForXYLines =
}) => { }) => {
const index = xOrY === 'x' ? 0 : 1 const index = xOrY === 'x' ? 0 : 1
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[index].expr) const literalArg = asNum(args?.[index].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2) const valueUsedInTransform = roundOff(literalArg - refNum, 2)
const makeBinExp = createBinaryExpressionWithUnary([ const makeBinExp = createBinaryExpressionWithUnary([
@ -445,9 +466,9 @@ const setHorzVertDistanceConstraintLineCreateNode =
]) ])
const makeBinExp = (index: 0 | 1) => { const makeBinExp = (index: 0 | 1) => {
const arg = getArgLiteralVal(args?.[index].expr) const arg = asNum(args?.[index].expr.value)
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
if (err(arg) || !isNum(refNum)) return REF_NUM_ERR if (err(arg) || isUndef(refNum)) return REF_NUM_ERR
return createBinaryExpressionWithUnary([ return createBinaryExpressionWithUnary([
createSegEnd(referenceSegName, isX), createSegEnd(referenceSegName, isX),
createLiteral(roundOff(arg - refNum, 2)), createLiteral(roundOff(arg - refNum, 2)),
@ -468,9 +489,9 @@ const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({
forceValueUsedInTransform, forceValueUsedInTransform,
rawArgs: args, rawArgs: args,
}) => { }) => {
const val = args[1].expr.value, const val = asNum(args[1].expr.value),
angle = args[0].expr.value angle = asNum(args[0].expr.value)
if (!isNum(val) || !isNum(angle)) return REF_NUM_ERR if (err(val) || err(angle)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(val, 2) const valueUsedInTransform = roundOff(val, 2)
const varNamMap: { [key: number]: string } = { const varNamMap: { [key: number]: string } = {
0: 'ZERO', 0: 'ZERO',
@ -498,8 +519,8 @@ const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({
inputs, inputs,
rawArgs: args, rawArgs: args,
}) => { }) => {
const val = args[1].expr.value const val = asNum(args[1].expr.value)
if (!isNum(val)) return REF_NUM_ERR if (err(val)) return val
const valueUsedInTransform = roundOff(val, 2) const valueUsedInTransform = roundOff(val, 2)
return intersectCallWrapper({ return intersectCallWrapper({
fnName: 'angledLineThatIntersects', fnName: 'angledLineThatIntersects',
@ -524,8 +545,8 @@ const setAngleBetweenCreateNode =
const refAngle = referencedSegment const refAngle = referencedSegment
? getAngle(referencedSegment?.from, referencedSegment?.to) ? getAngle(referencedSegment?.from, referencedSegment?.to)
: 0 : 0
const val = args[0].expr.value const val = asNum(args[0].expr.value)
if (!isNum(val)) return REF_NUM_ERR if (err(val)) return val
let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle)) let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle))
let firstHalfValue = createSegAngle(referenceSegName) let firstHalfValue = createSegAngle(referenceSegName)
if (Math.abs(valueUsedInTransform) > 90) { if (Math.abs(valueUsedInTransform) > 90) {
@ -706,13 +727,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToX', 'angledLineToX',
[ [getAngleLengthSign(val, angleToMatchLengthXCall), inputs[0].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[0].expr,
],
tag tag
) )
}, },
@ -739,13 +758,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToY', 'angledLineToY',
[ [getAngleLengthSign(val, angleToMatchLengthYCall), inputs[1].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthYCall),
inputs[1].expr,
],
tag tag
) )
}, },
@ -763,7 +780,7 @@ const transformMap: TransformMap = {
forceValueUsedInTransform, forceValueUsedInTransform,
rawArgs: args, rawArgs: args,
}) => { }) => {
const val = getArgLiteralVal(args[0].expr) const val = asNum(args[0].expr.value)
if (err(val)) return val if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToY', 'angledLineToY',
@ -844,7 +861,7 @@ const transformMap: TransformMap = {
tooltip: 'yLine', tooltip: 'yLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('yLine', expr, tag) return createCallWrapper('yLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('yLine', createUnaryExpression(expr), tag) return createCallWrapper('yLine', createUnaryExpression(expr), tag)
@ -856,7 +873,7 @@ const transformMap: TransformMap = {
tooltip: 'xLine', tooltip: 'xLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('xLine', expr, tag) return createCallWrapper('xLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('xLine', createUnaryExpression(expr), tag) return createCallWrapper('xLine', createUnaryExpression(expr), tag)
@ -900,10 +917,11 @@ const transformMap: TransformMap = {
referenceSegName, referenceSegName,
getInputOfType(inputs, 'xRelative').expr getInputOfType(inputs, 'xRelative').expr
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineOfXLength', 'angledLineOfXLength',
[getLegAng(args[0].expr.value, legAngle), minVal], [getLegAng(val, legAngle), minVal],
tag tag
) )
}, },
@ -912,7 +930,7 @@ const transformMap: TransformMap = {
tooltip: 'xLine', tooltip: 'xLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('xLine', expr, tag) return createCallWrapper('xLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('xLine', createUnaryExpression(expr), tag) return createCallWrapper('xLine', createUnaryExpression(expr), tag)
@ -953,10 +971,11 @@ const transformMap: TransformMap = {
inputs[1].expr, inputs[1].expr,
'legAngY' 'legAngY'
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineOfXLength', 'angledLineOfXLength',
[getLegAng(args[0].expr.value, legAngle), minVal], [getLegAng(val, legAngle), minVal],
tag tag
) )
}, },
@ -965,7 +984,7 @@ const transformMap: TransformMap = {
tooltip: 'yLine', tooltip: 'yLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('yLine', expr, tag) return createCallWrapper('yLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('yLine', createUnaryExpression(expr), tag) return createCallWrapper('yLine', createUnaryExpression(expr), tag)
@ -1005,13 +1024,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToX', 'angledLineToX',
[ [getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[1].expr,
],
tag tag
) )
}, },
@ -1057,13 +1074,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToY', 'angledLineToY',
[ [getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[1].expr,
],
tag tag
) )
}, },
@ -1080,7 +1095,7 @@ const transformMap: TransformMap = {
equalLength: { equalLength: {
tooltip: 'xLine', tooltip: 'xLine',
createNode: ({ referenceSegName, tag, rawArgs: args }) => { createNode: ({ referenceSegName, tag, rawArgs: args }) => {
const argVal = getArgLiteralVal(args[0].expr) const argVal = asNum(args[0].expr.value)
if (err(argVal)) return argVal if (err(argVal)) return argVal
const segLen = createSegLen(referenceSegName) const segLen = createSegLen(referenceSegName)
if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal) if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal)
@ -1118,7 +1133,7 @@ const transformMap: TransformMap = {
equalLength: { equalLength: {
tooltip: 'yLine', tooltip: 'yLine',
createNode: ({ referenceSegName, tag, rawArgs: args }) => { createNode: ({ referenceSegName, tag, rawArgs: args }) => {
const argVal = getArgLiteralVal(args[0].expr) const argVal = asNum(args[0].expr.value)
if (err(argVal)) return argVal if (err(argVal)) return argVal
let segLen = createSegLen(referenceSegName) let segLen = createSegLen(referenceSegName)
if (argVal < 0) segLen = createUnaryExpression(segLen) if (argVal < 0) segLen = createUnaryExpression(segLen)
@ -1714,7 +1729,7 @@ export function transformAstSketchLines({
let kclVal = programMemory.get(varName) let kclVal = programMemory.get(varName)
let sketch let sketch
if (kclVal?.type === 'Solid') { if (kclVal?.type === 'Solid') {
sketch = kclVal.sketch sketch = kclVal.value.sketch
} else { } else {
sketch = sketchFromKclValue(kclVal, varName) sketch = sketchFromKclValue(kclVal, varName)
if (err(sketch)) { if (err(sketch)) {
@ -1823,11 +1838,6 @@ function createLastSeg(isX: boolean): Node<CallExpression> {
]) ])
} }
function getArgLiteralVal(arg: Literal): number | Error {
if (!isNum(arg.value)) return REF_NUM_ERR
return arg.value
}
export type ConstraintLevel = 'free' | 'partial' | 'full' export type ConstraintLevel = 'free' | 'partial' | 'full'
export function getConstraintLevelFromSourceRange( export function getConstraintLevelFromSourceRange(

View File

@ -539,7 +539,8 @@ export function sketchFromKclValueOptional(
): Sketch | Reason { ): Sketch | Reason {
if (obj?.value?.type === 'Sketch') return obj.value if (obj?.value?.type === 'Sketch') return obj.value
if (obj?.value?.type === 'Solid') return obj.value.sketch if (obj?.value?.type === 'Solid') return obj.value.sketch
if (obj?.type === 'Solid') return obj.sketch if (obj?.type === 'Sketch') return obj.value
if (obj?.type === 'Solid') return obj.value.sketch
if (!varName) { if (!varName) {
varName = 'a KCL value' varName = 'a KCL value'
} }

View File

@ -13,6 +13,7 @@ import {
loftValidator, loftValidator,
revolveAxisValidator, revolveAxisValidator,
shellValidator, shellValidator,
sweepValidator,
} from './validators' } from './validators'
type OutputFormat = Models['OutputFormat_type'] type OutputFormat = Models['OutputFormat_type']
@ -42,8 +43,8 @@ export type ModelingCommandSchema = {
distance: KclCommandValue distance: KclCommandValue
} }
Sweep: { Sweep: {
path: Selections target: Selections
profile: Selections trajectory: Selections
} }
Loft: { Loft: {
selection: Selections selection: Selections
@ -308,25 +309,24 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
'Create a 3D body by moving a sketch region along an arbitrary path.', 'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep', icon: 'sweep',
status: 'development', status: 'development',
needsReview: true, needsReview: false,
args: { args: {
profile: { target: {
inputType: 'selection', inputType: 'selection',
selectionTypes: ['solid2d'], selectionTypes: ['solid2d'],
required: true, required: true,
skip: true, skip: true,
multiple: false, multiple: false,
// TODO: add dry-run validation
warningMessage: warningMessage:
'The sweep workflow is new and under tested. Please break it and report issues.', 'The sweep workflow is new and under tested. Please break it and report issues.',
}, },
path: { trajectory: {
inputType: 'selection', inputType: 'selection',
selectionTypes: ['segment', 'path'], selectionTypes: ['segment', 'path'],
required: true, required: true,
skip: true, skip: false,
multiple: false, multiple: false,
// TODO: add dry-run validation validation: sweepValidator,
}, },
}, },
}, },

View File

@ -207,3 +207,64 @@ export const shellValidator = async ({
return 'Unable to shell with the provided selection' return 'Unable to shell with the provided selection'
} }
export const sweepValidator = async ({
context,
data,
}: {
context: CommandBarContext
data: { trajectory: Selections }
}): Promise<boolean | string> => {
if (!isSelections(data.trajectory)) {
console.log('Unable to sweep, selections are missing')
return 'Unable to sweep, selections are missing'
}
// Retrieve the parent path from the segment selection directly
const trajectoryArtifact = data.trajectory.graphSelections[0].artifact
if (!trajectoryArtifact) {
return "Unable to sweep, couldn't find the trajectory artifact"
}
if (trajectoryArtifact.type !== 'segment') {
return "Unable to sweep, couldn't find the target from a non-segment selection"
}
const trajectory = trajectoryArtifact.pathId
// Get the former arg in the command bar flow, and retrieve the path from the solid2d directly
const targetArg = context.argumentsToSubmit['target'] as Selections
const targetArtifact = targetArg.graphSelections[0].artifact
if (!targetArtifact) {
return "Unable to sweep, couldn't find the profile artifact"
}
if (targetArtifact.type !== 'solid2d') {
return "Unable to sweep, couldn't find the target from a non-solid2d selection"
}
const target = targetArtifact.pathId
const sweepCommand = async () => {
// TODO: second look on defaults here
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
const DEFAULT_SECTIONAL = false
const cmdArgs = {
target,
trajectory,
sectional: DEFAULT_SECTIONAL,
tolerance: DEFAULT_TOLERANCE,
}
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sweep',
...cmdArgs,
},
})
}
const attemptSweep = await dryRunWrapper(sweepCommand)
if (attemptSweep?.success) {
return true
}
return 'Unable to sweep with the provided selection'
}

58
src/lib/desktopFS.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { getUniqueProjectName } from './desktopFS'
import { FileEntry } from './project'
/** Create a dummy project */
function project(name: string, children?: FileEntry[]): FileEntry {
return {
name,
children: children || [
{ name: 'main.kcl', children: null, path: 'main.kcl' },
],
path: `/projects/${name}`,
}
}
describe(`Getting unique project names`, () => {
it(`should return the same name if no conflicts`, () => {
const projectName = 'new-project'
const projects = [project('existing-project'), project('another-project')]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe(projectName)
})
it(`should return a unique name if there is a conflict`, () => {
const projectName = 'existing-project'
const projects = [project('existing-project'), project('another-project')]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-1')
})
it(`should increment an ending index until a unique one is found`, () => {
const projectName = 'existing-project-1'
const projects = [
project('existing-project'),
project('existing-project-1'),
project('existing-project-2'),
]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-3')
})
it(`should prefer the formatting of the index identifier if present`, () => {
const projectName = 'existing-project-$nn'
const projects = [
project('existing-project'),
project('existing-project-1'),
project('existing-project-2'),
]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-03')
})
it(`be able to get an incrementing index regardless of padding zeroes`, () => {
const projectName = 'existing-project-$nn'
const projects = [
project('existing-project'),
project('existing-project-01'),
project('existing-project-2'),
]
const result = getUniqueProjectName(projectName, projects)
expect(result).toBe('existing-project-03')
})
})

View File

@ -54,8 +54,10 @@ export function getNextProjectIndex(
const matches = projects.map((project) => project.name?.match(regex)) const matches = projects.map((project) => project.name?.match(regex))
const indices = matches const indices = matches
.filter(Boolean) .filter(Boolean)
.map((match) => match![1]) .map((match) => (match !== null ? match[1] : '-1'))
.map(Number) .map((maybeMatchIndex) => {
return parseInt(maybeMatchIndex || '0', 10)
})
const maxIndex = Math.max(...indices, -1) const maxIndex = Math.max(...indices, -1)
return maxIndex + 1 return maxIndex + 1
} }
@ -83,6 +85,33 @@ export function doesProjectNameNeedInterpolated(projectName: string) {
return projectName.includes(INDEX_IDENTIFIER) return projectName.includes(INDEX_IDENTIFIER)
} }
/**
* Given a target name, which may include our magic index interpolation string,
* and a list of projects, return a unique name that doesn't conflict with any
* of the existing projects, incrementing any ending number if necessary.
* @param name
* @param projects
* @returns
*/
export function getUniqueProjectName(name: string, projects: FileEntry[]) {
// The name may have our magic index interpolation string in it
const needsInterpolation = doesProjectNameNeedInterpolated(name)
if (needsInterpolation) {
const nextIndex = getNextProjectIndex(name, projects)
return interpolateProjectNameWithIndex(name, nextIndex)
} else {
let newName = name
while (projects.some((project) => project.name === newName)) {
const nameEndsWithNumber = newName.match(/\d+$/)
newName = nameEndsWithNumber
? newName.replace(/\d+$/, (num) => `${parseInt(num, 10) + 1}`)
: `${name}-1`
}
return newName
}
}
function escapeRegExpChars(string: string) { function escapeRegExpChars(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
} }

View File

@ -195,7 +195,7 @@ export async function submitAndAwaitTextToKcl({
.toLowerCase()}${FILE_EXT}` .toLowerCase()}${FILE_EXT}`
if (isDesktop()) { if (isDesktop()) {
// We have to pre-emptively run our unique file name logic, // We have to preemptively run our unique file name logic,
// so that we can pass the unique file name to the toast, // so that we can pass the unique file name to the toast,
// and by extension the file-deletion-on-reject logic. // and by extension the file-deletion-on-reject logic.
newFileName = getNextFileName({ newFileName = getNextFileName({

View File

@ -280,7 +280,12 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
status: 'available', status: 'available',
title: 'Offset plane', title: 'Offset plane',
description: 'Create a plane parallel to an existing plane.', description: 'Create a plane parallel to an existing plane.',
links: [], links: [
{
label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl/offsetPlane',
},
],
}, },
{ {
id: 'plane-points', id: 'plane-points',
@ -305,7 +310,12 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
status: 'available', status: 'available',
title: 'Text-to-CAD', title: 'Text-to-CAD',
description: 'Generate geometry from a text prompt.', description: 'Generate geometry from a text prompt.',
links: [], links: [
{
label: 'API docs',
url: 'https://zoo.dev/docs/api/ml/generate-a-cad-model-from-text',
},
],
}, },
{ {
id: 'prompt-to-edit', id: 'prompt-to-edit',

View File

@ -1561,40 +1561,40 @@ export const modelingMachine = setup({
if (!input) return new Error('No input provided') if (!input) return new Error('No input provided')
// Extract inputs // Extract inputs
const ast = kclManager.ast const ast = kclManager.ast
const { profile, path } = input const { target, trajectory } = input
// Find the profile declaration // Find the profile declaration
const profileNodePath = getNodePathFromSourceRange( const targetNodePath = getNodePathFromSourceRange(
ast, ast,
profile.graphSelections[0].codeRef.range target.graphSelections[0].codeRef.range
) )
const profileNode = getNodeFromPath<VariableDeclarator>( const targetNode = getNodeFromPath<VariableDeclarator>(
ast, ast,
profileNodePath, targetNodePath,
'VariableDeclarator' 'VariableDeclarator'
) )
if (err(profileNode)) { if (err(targetNode)) {
return new Error("Couldn't parse profile selection") return new Error("Couldn't parse profile selection")
} }
const profileDeclarator = profileNode.node const targetDeclarator = targetNode.node
// Find the path declaration // Find the path declaration
const pathNodePath = getNodePathFromSourceRange( const trajectoryNodePath = getNodePathFromSourceRange(
ast, ast,
path.graphSelections[0].codeRef.range trajectory.graphSelections[0].codeRef.range
) )
const pathNode = getNodeFromPath<VariableDeclarator>( const trajectoryNode = getNodeFromPath<VariableDeclarator>(
ast, ast,
pathNodePath, trajectoryNodePath,
'VariableDeclarator' 'VariableDeclarator'
) )
if (err(pathNode)) { if (err(trajectoryNode)) {
return new Error("Couldn't parse path selection") return new Error("Couldn't parse path selection")
} }
const pathDeclarator = pathNode.node const trajectoryDeclarator = trajectoryNode.node
// Perform the sweep // Perform the sweep
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator) const sweepRes = addSweep(ast, targetDeclarator, trajectoryDeclarator)
const updateAstResult = await kclManager.updateAst( const updateAstResult = await kclManager.updateAst(
sweepRes.modifiedAst, sweepRes.modifiedAst,
true, true,

View File

@ -148,7 +148,7 @@ const Home = () => {
}} }}
data-testid="home-new-file" data-testid="home-new-file"
> >
New project Create project
</ActionButton> </ActionButton>
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">

View File

@ -1382,12 +1382,6 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "iai"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.61" version = "0.1.61"
@ -1739,7 +1733,6 @@ dependencies = [
"gltf-json", "gltf-json",
"handlebars", "handlebars",
"http 1.2.0", "http 1.2.0",
"iai",
"image", "image",
"indexmap 2.7.0", "indexmap 2.7.0",
"insta", "insta",

View File

@ -113,7 +113,6 @@ base64 = "0.22.1"
criterion = { version = "0.5.1", features = ["async_tokio"] } criterion = { version = "0.5.1", features = ["async_tokio"] }
expectorate = "1.1.0" expectorate = "1.1.0"
handlebars = "6.3.0" handlebars = "6.3.0"
iai = "0.1"
image = { version = "0.25.5", default-features = false, features = ["png"] } image = { version = "0.25.5", default-features = false, features = ["png"] }
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] } insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
itertools = "0.13.0" itertools = "0.13.0"
@ -129,10 +128,6 @@ workspace = true
name = "compiler_benchmark_criterion" name = "compiler_benchmark_criterion"
harness = false harness = false
[[bench]]
name = "compiler_benchmark_iai"
harness = false
[[bench]] [[bench]]
name = "digest_benchmark" name = "digest_benchmark"
harness = false harness = false
@ -142,15 +137,7 @@ name = "lsp_semantic_tokens_benchmark_criterion"
harness = false harness = false
required-features = ["lsp-test-util"] required-features = ["lsp-test-util"]
[[bench]]
name = "lsp_semantic_tokens_benchmark_iai"
harness = false
required-features = ["lsp-test-util"]
[[bench]] [[bench]]
name = "executor_benchmark_criterion" name = "executor_benchmark_criterion"
harness = false harness = false
[[bench]]
name = "executor_benchmark_iai"
harness = false

View File

@ -1,35 +0,0 @@
use iai::black_box;
pub fn parse(program: &str) {
black_box(kcl_lib::Program::parse(program).unwrap());
}
fn parse_kitt() {
parse(KITT_PROGRAM)
}
fn parse_pipes() {
parse(PIPES_PROGRAM)
}
fn parse_cube() {
parse(CUBE_PROGRAM)
}
fn parse_math() {
parse(MATH_PROGRAM)
}
fn parse_lsystem() {
parse(LSYSTEM_PROGRAM)
}
iai::main! {
parse_kitt,
parse_pipes,
parse_cube,
parse_math,
parse_lsystem,
}
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl");

View File

@ -1,27 +0,0 @@
use iai::black_box;
async fn execute_server_rack_heavy() {
let code = SERVER_RACK_HEAVY_PROGRAM;
black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
.await
.unwrap(),
);
}
async fn execute_server_rack_lite() {
let code = SERVER_RACK_LITE_PROGRAM;
black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
.await
.unwrap(),
);
}
iai::main! {
execute_server_rack_lite,
execute_server_rack_heavy,
}
const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-heavy.kcl");
const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-lite.kcl");

View File

@ -1,45 +0,0 @@
use iai::black_box;
use kcl_lib::kcl_lsp_server;
use tower_lsp::LanguageServer;
async fn kcl_lsp_semantic_tokens(code: &str) {
let server = kcl_lsp_server(false).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: code.to_string(),
},
})
.await;
// Send semantic tokens request.
black_box(
server
.semantic_tokens_full(tower_lsp::lsp_types::SemanticTokensParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
partial_result_params: Default::default(),
work_done_progress_params: Default::default(),
})
.await
.unwrap()
.unwrap(),
);
}
async fn semantic_tokens_global_tags() {
let code = GLOBAL_TAGS_FILE;
kcl_lsp_semantic_tokens(code).await;
}
iai::main! {
semantic_tokens_global_tags,
}
const GLOBAL_TAGS_FILE: &str = include_str!("../../tests/executor/inputs/global-tags.kcl");

View File

@ -370,8 +370,6 @@ impl From<KclError> for pyo3::PyErr {
pub struct CompilationError { pub struct CompilationError {
#[serde(rename = "sourceRange")] #[serde(rename = "sourceRange")]
pub source_range: SourceRange, pub source_range: SourceRange,
#[serde(rename = "contextRange")]
pub context_range: Option<SourceRange>,
pub message: String, pub message: String,
pub suggestion: Option<Suggestion>, pub suggestion: Option<Suggestion>,
pub severity: Severity, pub severity: Severity,
@ -382,7 +380,6 @@ impl CompilationError {
pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError { pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError {
CompilationError { CompilationError {
source_range, source_range,
context_range: None,
message: message.to_string(), message: message.to_string(),
suggestion: None, suggestion: None,
severity: Severity::Error, severity: Severity::Error,
@ -393,7 +390,6 @@ impl CompilationError {
pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError { pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError {
CompilationError { CompilationError {
source_range, source_range,
context_range: None,
message: message.to_string(), message: message.to_string(),
suggestion: None, suggestion: None,
severity: Severity::Fatal, severity: Severity::Fatal,
@ -402,22 +398,18 @@ impl CompilationError {
} }
pub(crate) fn with_suggestion( pub(crate) fn with_suggestion(
source_range: SourceRange, self,
context_range: Option<SourceRange>, suggestion_title: impl ToString,
message: impl ToString, suggestion_insert: impl ToString,
suggestion: Option<(impl ToString, impl ToString)>,
tag: Tag, tag: Tag,
) -> CompilationError { ) -> CompilationError {
CompilationError { CompilationError {
source_range, suggestion: Some(Suggestion {
context_range, title: suggestion_title.to_string(),
message: message.to_string(), insert: suggestion_insert.to_string(),
suggestion: suggestion.map(|(t, i)| Suggestion {
title: t.to_string(),
insert: i.to_string(),
}), }),
severity: Severity::Error,
tag, tag,
..self
} }
} }

View File

@ -11,6 +11,11 @@ pub(super) const SETTINGS: &str = "settings";
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit"; pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit"; pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) enum AnnotationScope {
Module,
}
pub(super) fn expect_properties<'a>( pub(super) fn expect_properties<'a>(
for_key: &'static str, for_key: &'static str,
annotation: &'a NonCodeValue, annotation: &'a NonCodeValue,

View File

@ -121,8 +121,8 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
})) }))
} }
(KclValue::Solid(solid), Property::String(prop)) if prop == "sketch" => Ok(KclValue::Sketch { (KclValue::Solid { value }, Property::String(prop)) if prop == "sketch" => Ok(KclValue::Sketch {
value: Box::new(solid.sketch), value: Box::new(value.sketch),
}), }),
(KclValue::Sketch { value: sk }, Property::String(prop)) if prop == "tags" => Ok(KclValue::Object { (KclValue::Sketch { value: sk }, Property::String(prop)) if prop == "tags" => Ok(KclValue::Object {
meta: vec![Metadata { meta: vec![Metadata {
@ -662,11 +662,11 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
exec_state.mut_memory().update_tag(&tag.value, tag.clone())?; exec_state.mut_memory().update_tag(&tag.value, tag.clone())?;
} }
} }
KclValue::Solid(ref mut solid) => { KclValue::Solid { ref mut value } => {
for value in &solid.value { for v in &value.value {
if let Some(tag) = value.get_tag() { if let Some(tag) = v.get_tag() {
// Get the past tag and update it. // Get the past tag and update it.
let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) { let mut t = if let Some(t) = value.sketch.tags.get(&tag.name) {
t.clone() t.clone()
} else { } else {
// It's probably a fillet or a chamfer. // It's probably a fillet or a chamfer.
@ -674,10 +674,10 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
TagIdentifier { TagIdentifier {
value: tag.name.clone(), value: tag.name.clone(),
info: Some(TagEngineInfo { info: Some(TagEngineInfo {
id: value.get_id(), id: v.get_id(),
surface: Some(value.clone()), surface: Some(v.clone()),
path: None, path: None,
sketch: solid.id, sketch: value.id,
}), }),
meta: vec![Metadata { meta: vec![Metadata {
source_range: tag.clone().into(), source_range: tag.clone().into(),
@ -693,21 +693,21 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
}; };
let mut info = info.clone(); let mut info = info.clone();
info.surface = Some(value.clone()); info.surface = Some(v.clone());
info.sketch = solid.id; info.sketch = value.id;
t.info = Some(info); t.info = Some(info);
exec_state.mut_memory().update_tag(&tag.name, t.clone())?; exec_state.mut_memory().update_tag(&tag.name, t.clone())?;
// update the sketch tags. // update the sketch tags.
solid.sketch.tags.insert(tag.name.clone(), t); value.sketch.tags.insert(tag.name.clone(), t);
} }
} }
// Find the stale sketch in memory and update it. // Find the stale sketch in memory and update it.
let cur_env_index = exec_state.memory().current_env.index(); let cur_env_index = exec_state.memory().current_env.index();
if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) { if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) {
current_env.update_sketch_tags(&solid.sketch); current_env.update_sketch_tags(&value.sketch);
} }
} }
_ => {} _ => {}
@ -929,13 +929,13 @@ impl Property {
LiteralIdentifier::Literal(literal) => { LiteralIdentifier::Literal(literal) => {
let value = literal.value.clone(); let value = literal.value.clone();
match value { match value {
LiteralValue::Number(x) => { LiteralValue::Number { value, .. } => {
if let Some(x) = crate::try_f64_to_usize(x) { if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x)) Ok(Property::UInt(x))
} else { } else {
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr, source_ranges: property_sr,
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"), message: format!("{value} is not a valid index, indices must be whole numbers >= 0"),
})) }))
} }
} }

View File

@ -62,19 +62,27 @@ pub enum KclValue {
}, },
TagIdentifier(Box<TagIdentifier>), TagIdentifier(Box<TagIdentifier>),
TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>), TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
Plane(Box<Plane>), Plane {
Face(Box<Face>), value: Box<Plane>,
},
Face {
value: Box<Face>,
},
Sketch { Sketch {
value: Box<Sketch>, value: Box<Sketch>,
}, },
Sketches { Sketches {
value: Vec<Box<Sketch>>, value: Vec<Box<Sketch>>,
}, },
Solid(Box<Solid>), Solid {
value: Box<Solid>,
},
Solids { Solids {
value: Vec<Box<Solid>>, value: Vec<Box<Solid>>,
}, },
Helix(Box<Helix>), Helix {
value: Box<Helix>,
},
ImportedGeometry(ImportedGeometry), ImportedGeometry(ImportedGeometry),
#[ts(skip)] #[ts(skip)]
Function { Function {
@ -120,7 +128,7 @@ impl From<Vec<Box<Sketch>>> for KclValue {
impl From<SolidSet> for KclValue { impl From<SolidSet> for KclValue {
fn from(eg: SolidSet) -> Self { fn from(eg: SolidSet) -> Self {
match eg { match eg {
SolidSet::Solid(eg) => KclValue::Solid(eg), SolidSet::Solid(eg) => KclValue::Solid { value: eg },
SolidSet::Solids(egs) => KclValue::Solids { value: egs }, SolidSet::Solids(egs) => KclValue::Solids { value: egs },
} }
} }
@ -129,7 +137,7 @@ impl From<SolidSet> for KclValue {
impl From<Vec<Box<Solid>>> for KclValue { impl From<Vec<Box<Solid>>> for KclValue {
fn from(eg: Vec<Box<Solid>>) -> Self { fn from(eg: Vec<Box<Solid>>) -> Self {
if eg.len() == 1 { if eg.len() == 1 {
KclValue::Solid(eg[0].clone()) KclValue::Solid { value: eg[0].clone() }
} else { } else {
KclValue::Solids { value: eg } KclValue::Solids { value: eg }
} }
@ -140,15 +148,15 @@ impl From<KclValue> for Vec<SourceRange> {
match item { match item {
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)], KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta), KclValue::Solid { value } => to_vec_sr(&value.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Sketch { value } => to_vec_sr(&value.meta), KclValue::Sketch { value } => to_vec_sr(&value.meta),
KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Helix(e) => to_vec_sr(&e.meta), KclValue::Helix { value } => to_vec_sr(&value.meta),
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta), KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
KclValue::Function { meta, .. } => to_vec_sr(&meta), KclValue::Function { meta, .. } => to_vec_sr(&meta),
KclValue::Plane(p) => to_vec_sr(&p.meta), KclValue::Plane { value } => to_vec_sr(&value.meta),
KclValue::Face(f) => to_vec_sr(&f.meta), KclValue::Face { value } => to_vec_sr(&value.meta),
KclValue::Bool { meta, .. } => to_vec_sr(&meta), KclValue::Bool { meta, .. } => to_vec_sr(&meta),
KclValue::Number { meta, .. } => to_vec_sr(&meta), KclValue::Number { meta, .. } => to_vec_sr(&meta),
KclValue::Int { meta, .. } => to_vec_sr(&meta), KclValue::Int { meta, .. } => to_vec_sr(&meta),
@ -171,15 +179,15 @@ impl From<&KclValue> for Vec<SourceRange> {
match item { match item {
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)], KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta), KclValue::Solid { value } => to_vec_sr(&value.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Sketch { value } => to_vec_sr(&value.meta), KclValue::Sketch { value } => to_vec_sr(&value.meta),
KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Helix(x) => to_vec_sr(&x.meta), KclValue::Helix { value } => to_vec_sr(&value.meta),
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta), KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
KclValue::Function { meta, .. } => to_vec_sr(meta), KclValue::Function { meta, .. } => to_vec_sr(meta),
KclValue::Plane(p) => to_vec_sr(&p.meta), KclValue::Plane { value } => to_vec_sr(&value.meta),
KclValue::Face(f) => to_vec_sr(&f.meta), KclValue::Face { value } => to_vec_sr(&value.meta),
KclValue::Bool { meta, .. } => to_vec_sr(meta), KclValue::Bool { meta, .. } => to_vec_sr(meta),
KclValue::Number { meta, .. } => to_vec_sr(meta), KclValue::Number { meta, .. } => to_vec_sr(meta),
KclValue::Int { meta, .. } => to_vec_sr(meta), KclValue::Int { meta, .. } => to_vec_sr(meta),
@ -205,13 +213,13 @@ impl KclValue {
KclValue::Object { value: _, meta } => meta.clone(), KclValue::Object { value: _, meta } => meta.clone(),
KclValue::TagIdentifier(x) => x.meta.clone(), KclValue::TagIdentifier(x) => x.meta.clone(),
KclValue::TagDeclarator(x) => vec![x.metadata()], KclValue::TagDeclarator(x) => vec![x.metadata()],
KclValue::Plane(x) => x.meta.clone(), KclValue::Plane { value } => value.meta.clone(),
KclValue::Face(x) => x.meta.clone(), KclValue::Face { value } => value.meta.clone(),
KclValue::Sketch { value } => value.meta.clone(), KclValue::Sketch { value } => value.meta.clone(),
KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::Solid(x) => x.meta.clone(), KclValue::Solid { value } => value.meta.clone(),
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::Helix(x) => x.meta.clone(), KclValue::Helix { value } => value.meta.clone(),
KclValue::ImportedGeometry(x) => x.meta.clone(), KclValue::ImportedGeometry(x) => x.meta.clone(),
KclValue::Function { meta, .. } => meta.clone(), KclValue::Function { meta, .. } => meta.clone(),
KclValue::Module { meta, .. } => meta.clone(), KclValue::Module { meta, .. } => meta.clone(),
@ -230,7 +238,7 @@ impl KclValue {
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> { pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
match self { match self {
KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())), KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())),
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())), KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
KclValue::Array { value, .. } => { KclValue::Array { value, .. } => {
let solids: Vec<_> = value let solids: Vec<_> = value
@ -266,15 +274,15 @@ impl KclValue {
KclValue::Uuid { .. } => "Unique ID (uuid)", KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator", KclValue::TagDeclarator(_) => "TagDeclarator",
KclValue::TagIdentifier(_) => "TagIdentifier", KclValue::TagIdentifier(_) => "TagIdentifier",
KclValue::Solid(_) => "Solid", KclValue::Solid { .. } => "Solid",
KclValue::Solids { .. } => "Solids", KclValue::Solids { .. } => "Solids",
KclValue::Sketch { .. } => "Sketch", KclValue::Sketch { .. } => "Sketch",
KclValue::Sketches { .. } => "Sketches", KclValue::Sketches { .. } => "Sketches",
KclValue::Helix(_) => "Helix", KclValue::Helix { .. } => "Helix",
KclValue::ImportedGeometry(_) => "ImportedGeometry", KclValue::ImportedGeometry(_) => "ImportedGeometry",
KclValue::Function { .. } => "Function", KclValue::Function { .. } => "Function",
KclValue::Plane(_) => "Plane", KclValue::Plane { .. } => "Plane",
KclValue::Face(_) => "Face", KclValue::Face { .. } => "Face",
KclValue::Bool { .. } => "boolean (true/false value)", KclValue::Bool { .. } => "boolean (true/false value)",
KclValue::Number { .. } => "number", KclValue::Number { .. } => "number",
KclValue::Int { .. } => "integer", KclValue::Int { .. } => "integer",
@ -288,7 +296,7 @@ impl KclValue {
pub(crate) fn from_literal(literal: LiteralValue, meta: Vec<Metadata>) -> Self { pub(crate) fn from_literal(literal: LiteralValue, meta: Vec<Metadata>) -> Self {
match literal { match literal {
LiteralValue::Number(value) => KclValue::Number { value, meta }, LiteralValue::Number { value, .. } => KclValue::Number { value, meta },
LiteralValue::String(value) => KclValue::String { value, meta }, LiteralValue::String(value) => KclValue::String { value, meta },
LiteralValue::Bool(value) => KclValue::Bool { value, meta }, LiteralValue::Bool(value) => KclValue::Bool { value, meta },
} }
@ -383,7 +391,7 @@ impl KclValue {
} }
pub fn as_plane(&self) -> Option<&Plane> { pub fn as_plane(&self) -> Option<&Plane> {
if let KclValue::Plane(value) = &self { if let KclValue::Plane { value } = &self {
Some(value) Some(value)
} else { } else {
None None
@ -391,7 +399,7 @@ impl KclValue {
} }
pub fn as_solid(&self) -> Option<&Solid> { pub fn as_solid(&self) -> Option<&Solid> {
if let KclValue::Solid(value) = &self { if let KclValue::Solid { value } = &self {
Some(value) Some(value)
} else { } else {
None None
@ -614,6 +622,19 @@ impl From<crate::UnitLength> for UnitLen {
} }
} }
impl From<UnitLen> for crate::UnitLength {
fn from(unit: UnitLen) -> Self {
match unit {
UnitLen::Cm => crate::UnitLength::Cm,
UnitLen::Feet => crate::UnitLength::Ft,
UnitLen::Inches => crate::UnitLength::In,
UnitLen::M => crate::UnitLength::M,
UnitLen::Mm => crate::UnitLength::Mm,
UnitLen::Yards => crate::UnitLength::Yd,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] #[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@ -2,6 +2,7 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use annotations::AnnotationScope;
use anyhow::Result; use anyhow::Result;
use artifact::build_artifact_graph; use artifact::build_artifact_graph;
use async_recursion::async_recursion; use async_recursion::async_recursion;
@ -391,7 +392,7 @@ impl ProgramMemory {
env.bindings env.bindings
.values() .values()
.filter_map(|item| match item { .filter_map(|item| match item {
KclValue::Solid(eg) if eg.sketch.id == sketch_id => Some(eg.clone()), KclValue::Solid { value } if value.sketch.id == sketch_id => Some(value.clone()),
_ => None, _ => None,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -505,8 +506,8 @@ impl DynamicState {
fn append(&mut self, memory: &ProgramMemory) { fn append(&mut self, memory: &ProgramMemory) {
for env in &memory.environments { for env in &memory.environments {
for item in env.bindings.values() { for item in env.bindings.values() {
if let KclValue::Solid(eg) = item { if let KclValue::Solid { value } = item {
self.solid_ids.push(SolidLazyIds::from(eg.as_ref())); self.solid_ids.push(SolidLazyIds::from(value.as_ref()));
} }
} }
} }
@ -759,6 +760,7 @@ pub struct Helix {
pub angle_start: f64, pub angle_start: f64,
/// Is the helix rotation counter clockwise? /// Is the helix rotation counter clockwise?
pub ccw: bool, pub ccw: bool,
pub units: UnitLen,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -780,6 +782,7 @@ pub struct Plane {
pub y_axis: Point3d, pub y_axis: Point3d,
/// The z-axis (normal). /// The z-axis (normal).
pub z_axis: Point3d, pub z_axis: Point3d,
pub units: UnitLen,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -795,6 +798,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0), y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, 1.0), z_axis: Point3d::new(0.0, 0.0, 1.0),
value: PlaneType::XY, value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::NegXY => Plane { crate::std::sketch::PlaneData::NegXY => Plane {
@ -804,6 +808,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0), y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, -1.0), z_axis: Point3d::new(0.0, 0.0, -1.0),
value: PlaneType::XY, value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::XZ => Plane { crate::std::sketch::PlaneData::XZ => Plane {
@ -813,6 +818,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, -1.0, 0.0), z_axis: Point3d::new(0.0, -1.0, 0.0),
value: PlaneType::XZ, value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::NegXZ => Plane { crate::std::sketch::PlaneData::NegXZ => Plane {
@ -822,6 +828,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, 1.0, 0.0), z_axis: Point3d::new(0.0, 1.0, 0.0),
value: PlaneType::XZ, value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::YZ => Plane { crate::std::sketch::PlaneData::YZ => Plane {
@ -831,6 +838,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(1.0, 0.0, 0.0), z_axis: Point3d::new(1.0, 0.0, 0.0),
value: PlaneType::YZ, value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::NegYZ => Plane { crate::std::sketch::PlaneData::NegYZ => Plane {
@ -840,6 +848,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(-1.0, 0.0, 0.0), z_axis: Point3d::new(-1.0, 0.0, 0.0),
value: PlaneType::YZ, value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::Plane { crate::std::sketch::PlaneData::Plane {
@ -854,6 +863,7 @@ impl Plane {
y_axis: *y_axis, y_axis: *y_axis,
z_axis: *z_axis, z_axis: *z_axis,
value: PlaneType::Custom, value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
} }
@ -900,6 +910,7 @@ pub struct Face {
pub z_axis: Point3d, pub z_axis: Point3d,
/// The solid the face is on. /// The solid the face is on.
pub solid: Box<Solid>, pub solid: Box<Solid>,
pub units: UnitLen,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -1018,6 +1029,7 @@ pub struct Sketch {
/// is sketched on face etc. /// is sketched on face etc.
#[serde(skip)] #[serde(skip)]
pub original_id: uuid::Uuid, pub original_id: uuid::Uuid,
pub units: UnitLen,
/// Metadata. /// Metadata.
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
@ -1141,6 +1153,7 @@ pub struct Solid {
/// Chamfers or fillets on this solid. /// Chamfers or fillets on this solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cuts: Vec<EdgeCut>, pub edge_cuts: Vec<EdgeCut>,
pub units: UnitLen,
/// Metadata. /// Metadata.
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
@ -2304,6 +2317,36 @@ impl ExecutorContext {
} }
} }
async fn handle_annotations(
&self,
annotations: impl Iterator<Item = (&NonCodeValue, SourceRange)>,
scope: AnnotationScope,
exec_state: &mut ExecState,
) -> Result<(), KclError> {
for (annotation, source_range) in annotations {
if annotation.annotation_name() == Some(annotations::SETTINGS) {
if scope == AnnotationScope::Module {
let old_units = exec_state.length_unit();
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
let new_units = exec_state.length_unit();
if old_units != new_units {
self.engine.set_units(new_units.into(), source_range).await?;
}
} else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Settings can only be modified at the top level scope of a file".to_owned(),
source_ranges: vec![source_range],
}));
}
}
// TODO warn on unknown annotations
}
Ok(())
}
/// Execute an AST's program. /// Execute an AST's program.
#[async_recursion] #[async_recursion]
pub(crate) async fn inner_execute<'a>( pub(crate) async fn inner_execute<'a>(
@ -2312,21 +2355,16 @@ impl ExecutorContext {
exec_state: &mut ExecState, exec_state: &mut ExecState,
body_type: BodyType, body_type: BodyType,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {
if let Some((annotation, source_range)) = program self.handle_annotations(
.non_code_meta program
.start_nodes .non_code_meta
.iter() .start_nodes
.filter_map(|n| { .iter()
n.annotation(annotations::SETTINGS) .filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))),
.map(|result| (result, n.as_source_range())) AnnotationScope::Module,
}) exec_state,
.next() )
{ .await?;
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
}
let mut last_expr = None; let mut last_expr = None;
// Iterate over the body of the program. // Iterate over the body of the program.
@ -2509,6 +2547,7 @@ impl ExecutorContext {
exec_kind: ExecutionKind, exec_kind: ExecutionKind,
source_range: SourceRange, source_range: SourceRange,
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> { ) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> {
let old_units = exec_state.length_unit();
// TODO It sucks that we have to clone the whole module AST here // TODO It sucks that we have to clone the whole module AST here
let info = exec_state.global.module_infos[&module_id].clone(); let info = exec_state.global.module_infos[&module_id].clone();
@ -2525,7 +2564,11 @@ impl ExecutorContext {
.inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root) .inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
.await; .await;
let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state); std::mem::swap(&mut exec_state.mod_local, &mut local_state);
if new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution); self.engine.replace_execution_kind(original_execution);
let result = result.map_err(|err| { let result = result.map_err(|err| {

View File

@ -163,7 +163,7 @@ fn get_xyz(point: &ObjectExpression) -> Option<(f64, f64, f64)> {
fn unlitafy(lit: &LiteralValue) -> Option<f64> { fn unlitafy(lit: &LiteralValue) -> Option<f64> {
Some(match lit { Some(match lit {
LiteralValue::Number(value) => *value, LiteralValue::Number { value, .. } => *value,
_ => { _ => {
return None; return None;
} }

View File

@ -1,6 +1,6 @@
use sha2::{Digest as DigestTrait, Sha256}; use sha2::{Digest as DigestTrait, Sha256};
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, VariableKind}; use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, LiteralValue, VariableKind};
use crate::parsing::ast::types::{ use crate::parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
@ -277,6 +277,26 @@ impl Literal {
}); });
} }
impl LiteralValue {
fn digestable_id(&self) -> Vec<u8> {
match self {
LiteralValue::Number { value, suffix } => {
let mut result: Vec<u8> = value.to_ne_bytes().into();
result.extend((*suffix as u32).to_ne_bytes());
result
}
LiteralValue::String(st) => st.as_bytes().into(),
LiteralValue::Bool(b) => {
if *b {
vec![1]
} else {
vec![0]
}
}
}
}
}
impl Identifier { impl Identifier {
compute_digest!(|slf, hasher| { compute_digest!(|slf, hasher| {
let name = slf.name.as_bytes(); let name = slf.name.as_bytes();

View File

@ -18,6 +18,8 @@ use crate::{
Program, Program,
}; };
use super::types::LiteralValue;
type Point3d = kcmc::shared::Point3d<f64>; type Point3d = kcmc::shared::Point3d<f64>;
#[derive(Debug)] #[derive(Debug)]
@ -201,8 +203,8 @@ fn create_start_sketch_on(
"startProfileAt", "startProfileAt",
vec![ vec![
ArrayExpression::new(vec![ ArrayExpression::new(vec![
Literal::new(round_before_recast(start[0]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[0]))).into(),
Literal::new(round_before_recast(start[1]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[1]))).into(),
]) ])
.into(), .into(),
PipeSubstitution::new().into(), PipeSubstitution::new().into(),
@ -221,8 +223,8 @@ fn create_start_sketch_on(
"line", "line",
vec![ vec![
ArrayExpression::new(vec![ ArrayExpression::new(vec![
Literal::new(round_before_recast(end[0]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[0]))).into(),
Literal::new(round_before_recast(end[1]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[1]))).into(),
]) ])
.into(), .into(),
PipeSubstitution::new().into(), PipeSubstitution::new().into(),
@ -254,8 +256,8 @@ fn create_start_sketch_on(
"line", "line",
vec![ vec![
ArrayExpression::new(vec![ ArrayExpression::new(vec![
Literal::new(round_before_recast(line[0]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[0]))).into(),
Literal::new(round_before_recast(line[1]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[1]))).into(),
]) ])
.into(), .into(),
PipeSubstitution::new().into(), PipeSubstitution::new().into(),

View File

@ -1,31 +1,49 @@
use std::fmt;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use super::Node; use super::Node;
use crate::parsing::ast::types::{Expr, Literal}; use crate::parsing::{
ast::types::{Expr, Literal},
token::NumericSuffix,
};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(untagged, rename_all = "snake_case")] #[serde(untagged, rename_all = "snake_case")]
pub enum LiteralValue { pub enum LiteralValue {
Number(f64), Number { value: f64, suffix: NumericSuffix },
String(String), String(String),
Bool(bool), Bool(bool),
} }
impl LiteralValue { impl LiteralValue {
pub fn digestable_id(&self) -> Vec<u8> { pub fn from_f64_no_uom(value: f64) -> Self {
LiteralValue::Number {
value,
suffix: NumericSuffix::None,
}
}
}
impl fmt::Display for LiteralValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
LiteralValue::Number(frac) => frac.to_ne_bytes().into(), LiteralValue::Number { value, suffix } => {
LiteralValue::String(st) => st.as_bytes().into(), let int_value = *value as u64;
LiteralValue::Bool(b) => { if int_value as f64 == *value {
if *b { write!(f, "{int_value}")?;
vec![1]
} else { } else {
vec![0] write!(f, "{value}")?;
} }
if *suffix != NumericSuffix::None {
write!(f, "{suffix}")?;
}
Ok(())
} }
LiteralValue::String(s) => write!(f, "\"{s}\""),
LiteralValue::Bool(b) => write!(f, "{b}"),
} }
} }
} }
@ -36,49 +54,12 @@ impl From<Node<Literal>> for Expr {
} }
} }
impl From<LiteralValue> for JValue {
fn from(value: LiteralValue) -> Self {
match value {
LiteralValue::Number(x) => x.into(),
LiteralValue::String(x) => x.into(),
LiteralValue::Bool(b) => b.into(),
}
}
}
impl From<f64> for LiteralValue {
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<i64> for LiteralValue {
fn from(value: i64) -> Self {
Self::Number(value as f64)
}
}
impl From<String> for LiteralValue { impl From<String> for LiteralValue {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self::String(value) Self::String(value)
} }
} }
impl From<u32> for LiteralValue {
fn from(value: u32) -> Self {
Self::Number(value as f64)
}
}
impl From<u16> for LiteralValue {
fn from(value: u16) -> Self {
Self::Number(value as f64)
}
}
impl From<u8> for LiteralValue {
fn from(value: u8) -> Self {
Self::Number(value as f64)
}
}
impl From<&'static str> for LiteralValue { impl From<&'static str> for LiteralValue {
fn from(value: &'static str) -> Self { fn from(value: &'static str) -> Self {
// TODO: Make this Cow<str> // TODO: Make this Cow<str>

View File

@ -13,7 +13,6 @@ use anyhow::Result;
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use tower_lsp::lsp_types::{ use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind, CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
}; };
@ -1012,9 +1011,9 @@ impl NonCodeNode {
} }
} }
pub fn annotation(&self, expected_name: &str) -> Option<&NonCodeValue> { pub fn annotation(&self) -> Option<&NonCodeValue> {
match &self.value { match &self.value {
a @ NonCodeValue::Annotation { name, .. } if name.name == expected_name => Some(a), a @ NonCodeValue::Annotation { .. } => Some(a),
_ => None, _ => None,
} }
} }
@ -1072,6 +1071,15 @@ pub enum NonCodeValue {
}, },
} }
impl NonCodeValue {
pub fn annotation_name(&self) -> Option<&str> {
match self {
NonCodeValue::Annotation { name, .. } => Some(&name.name),
_ => None,
}
}
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -1867,7 +1875,7 @@ impl Node<Literal> {
impl Literal { impl Literal {
pub fn new(value: LiteralValue) -> Node<Self> { pub fn new(value: LiteralValue) -> Node<Self> {
Node::no_src(Self { Node::no_src(Self {
raw: JValue::from(value.clone()).to_string(), raw: value.to_string(),
value, value,
digest: None, digest: None,
}) })
@ -1878,7 +1886,7 @@ impl From<Node<Literal>> for KclValue {
fn from(literal: Node<Literal>) -> Self { fn from(literal: Node<Literal>) -> Self {
let meta = vec![literal.metadata()]; let meta = vec![literal.metadata()];
match literal.inner.value { match literal.inner.value {
LiteralValue::Number(value) => KclValue::Number { value, meta }, LiteralValue::Number { value, .. } => KclValue::Number { value, meta },
LiteralValue::String(value) => KclValue::String { value, meta }, LiteralValue::String(value) => KclValue::String { value, meta },
LiteralValue::Bool(value) => KclValue::Bool { value, meta }, LiteralValue::Bool(value) => KclValue::Bool { value, meta },
} }

View File

@ -126,7 +126,13 @@ impl From<BinaryOperator> for BinaryExpressionToken {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{parsing::ast::types::Literal, source_range::ModuleId}; use crate::{
parsing::{
ast::types::{Literal, LiteralValue},
token::NumericSuffix,
},
source_range::ModuleId,
};
#[test] #[test]
fn parse_and_evaluate() { fn parse_and_evaluate() {
@ -134,7 +140,10 @@ mod tests {
fn lit(n: u8) -> BinaryPart { fn lit(n: u8) -> BinaryPart {
BinaryPart::Literal(Box::new(Node::new( BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: n.into(), value: LiteralValue::Number {
value: n as f64,
suffix: NumericSuffix::None,
},
raw: n.to_string(), raw: n.to_string(),
digest: None, digest: None,
}, },

View File

@ -483,7 +483,7 @@ pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> PResult<Node<Litera
let (value, token) = any let (value, token) = any
.try_map(|token: Token| match token.token_type { .try_map(|token: Token| match token.token_type {
TokenType::Number => { TokenType::Number => {
let x: f64 = token.numeric_value().ok_or_else(|| { let value: f64 = token.numeric_value().ok_or_else(|| {
CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value)) CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
})?; })?;
@ -494,7 +494,13 @@ pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> PResult<Node<Litera
)); ));
} }
Ok((LiteralValue::Number(x), token)) Ok((
LiteralValue::Number {
value,
suffix: token.numeric_suffix(),
},
token,
))
} }
_ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")), _ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")),
}) })
@ -844,13 +850,13 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
}; };
if sep.token_type == TokenType::Colon { if sep.token_type == TokenType::Colon {
ParseContext::warn(CompilationError::with_suggestion( ParseContext::warn(
sep.into(), CompilationError::err(
Some(result.as_source_range()), sep.into(),
"Using `:` to initialize objects is deprecated, prefer using `=`.", "Using `:` to initialize objects is deprecated, prefer using `=`.",
Some(("Replace `:` with `=`", " =")), )
Tag::Deprecated, .with_suggestion("Replace `:` with `=`", " =", Tag::Deprecated),
)); );
} }
Ok(result) Ok(result)
@ -1069,9 +1075,19 @@ fn function_expr(i: &mut TokenSlice) -> PResult<Expr> {
let fn_tok = opt(fun).parse_next(i)?; let fn_tok = opt(fun).parse_next(i)?;
ignore_whitespace(i); ignore_whitespace(i);
let (result, has_arrow) = function_decl.parse_next(i)?; let (result, has_arrow) = function_decl.parse_next(i)?;
if fn_tok.is_none() && !has_arrow { if fn_tok.is_none() {
let err = CompilationError::fatal(result.as_source_range(), "Anonymous function requires `fn` before `(`"); if has_arrow {
return Err(ErrMode::Cut(err.into())); ParseContext::warn(
CompilationError::err(
result.as_source_range().start_as_range(),
"Missing `fn` in function declaration",
)
.with_suggestion("Add `fn`", "fn", Tag::None),
);
} else {
let err = CompilationError::fatal(result.as_source_range(), "Anonymous function requires `fn` before `(`");
return Err(ErrMode::Cut(err.into()));
}
} }
Ok(Expr::FunctionExpression(Box::new(result))) Ok(Expr::FunctionExpression(Box::new(result)))
} }
@ -1113,18 +1129,16 @@ fn function_decl(i: &mut TokenSlice) -> PResult<(Node<FunctionExpression>, bool)
open.module_id, open.module_id,
); );
let has_arrow = if let Some(arrow) = arrow { let has_arrow =
ParseContext::warn(CompilationError::with_suggestion( if let Some(arrow) = arrow {
arrow.as_source_range(), ParseContext::warn(
Some(result.as_source_range()), CompilationError::err(arrow.as_source_range(), "Unnecessary `=>` in function declaration")
"Unnecessary `=>` in function declaration", .with_suggestion("Remove `=>`", "", Tag::Unnecessary),
Some(("Remove `=>`", "")), );
Tag::Unnecessary, true
)); } else {
true false
} else { };
false
};
Ok((result, has_arrow)) Ok((result, has_arrow))
} }
@ -1825,67 +1839,60 @@ fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
ignore_whitespace(i); ignore_whitespace(i);
let val = if kind == VariableKind::Fn { let val =
let eq = opt(equals).parse_next(i)?; if kind == VariableKind::Fn {
ignore_whitespace(i); let eq = opt(equals).parse_next(i)?;
ignore_whitespace(i);
let val = function_decl let val = function_decl
.map(|t| Box::new(t.0)) .map(|t| Box::new(t.0))
.map(Expr::FunctionExpression) .map(Expr::FunctionExpression)
.context(expected("a KCL function expression, like () { return 1 }")) .context(expected("a KCL function expression, like () { return 1 }"))
.parse_next(i); .parse_next(i);
if let Some(t) = eq { if let Some(t) = eq {
let ctxt_end = val.as_ref().map(|e| e.end()).unwrap_or(t.end); ParseContext::warn(
ParseContext::warn(CompilationError::with_suggestion( CompilationError::err(t.as_source_range(), "Unnecessary `=` in function declaration")
t.as_source_range(), .with_suggestion("Remove `=`", "", Tag::Unnecessary),
Some(SourceRange::new(id.start, ctxt_end, id.module_id)), );
"Unnecessary `=` in function declaration", }
Some(("Remove `=`", "")),
Tag::Unnecessary, val
)); } else {
equals(i)?;
ignore_whitespace(i);
let val = expression
.try_map(|val| {
// Function bodies can be used if and only if declaring a function.
// Check the 'if' direction:
if matches!(val, Expr::FunctionExpression(_)) {
return Err(CompilationError::fatal(
SourceRange::new(start, dec_end, id.module_id),
format!("Expected a `fn` variable kind, found: `{}`", kind),
));
}
Ok(val)
})
.context(expected("a KCL value, which is being bound to a variable"))
.parse_next(i);
if let Some((_, tok)) = decl_token {
ParseContext::warn(
CompilationError::err(
tok.as_source_range(),
format!(
"Using `{}` to declare constants is deprecated; no keyword is required",
tok.value
),
)
.with_suggestion(format!("Remove `{}`", tok.value), "", Tag::Deprecated),
);
}
val
} }
.map_err(|e| e.cut())?;
val
} else {
equals(i)?;
ignore_whitespace(i);
let val = expression
.try_map(|val| {
// Function bodies can be used if and only if declaring a function.
// Check the 'if' direction:
if matches!(val, Expr::FunctionExpression(_)) {
return Err(CompilationError::fatal(
SourceRange::new(start, dec_end, id.module_id),
format!("Expected a `fn` variable kind, found: `{}`", kind),
));
}
Ok(val)
})
.context(expected("a KCL value, which is being bound to a variable"))
.parse_next(i);
if let Some((_, tok)) = decl_token {
ParseContext::warn(CompilationError::with_suggestion(
tok.as_source_range(),
Some(SourceRange::new(
id.start,
val.as_ref().map(|e| e.end()).unwrap_or(dec_end),
id.module_id,
)),
format!(
"Using `{}` to declare constants is deprecated; no keyword is required",
tok.value
),
Some((format!("Remove `{}`", tok.value), "")),
Tag::Deprecated,
));
}
val
}
.map_err(|e| e.cut())?;
let end = val.end(); let end = val.end();
Ok(Box::new(Node { Ok(Box::new(Node {
@ -2856,7 +2863,10 @@ mySk1 = startSketchAt([0, 0])"#;
ReturnStatement { ReturnStatement {
argument: Expr::Literal(Box::new(Node::new( argument: Expr::Literal(Box::new(Node::new(
Literal { Literal {
value: 2u32.into(), value: LiteralValue::Number {
value: 2.0,
suffix: NumericSuffix::None
},
raw: "2".to_owned(), raw: "2".to_owned(),
digest: None, digest: None,
}, },
@ -3057,7 +3067,15 @@ mySk1 = startSketchAt([0, 0])"#;
match &rhs.right { match &rhs.right {
BinaryPart::Literal(lit) => { BinaryPart::Literal(lit) => {
assert!(lit.start == 9 && lit.end == 10); assert!(lit.start == 9 && lit.end == 10);
assert!(lit.value == 3u32.into() && &lit.raw == "3" && lit.digest.is_none()); assert!(
lit.value
== LiteralValue::Number {
value: 3.0,
suffix: NumericSuffix::None
}
&& &lit.raw == "3"
&& lit.digest.is_none()
);
} }
_ => panic!(), _ => panic!(),
} }
@ -3128,11 +3146,23 @@ mySk1 = startSketchAt([0, 0])"#;
let BinaryPart::Literal(left) = actual.inner.left else { let BinaryPart::Literal(left) = actual.inner.left else {
panic!("should be expression"); panic!("should be expression");
}; };
assert_eq!(left.value, 1u32.into()); assert_eq!(
left.value,
LiteralValue::Number {
value: 1.0,
suffix: NumericSuffix::None
}
);
let BinaryPart::Literal(right) = actual.inner.right else { let BinaryPart::Literal(right) = actual.inner.right else {
panic!("should be expression"); panic!("should be expression");
}; };
assert_eq!(right.value, 2u32.into()); assert_eq!(
right.value,
LiteralValue::Number {
value: 2.0,
suffix: NumericSuffix::None
}
);
} }
} }
@ -3449,7 +3479,10 @@ mySk1 = startSketchAt([0, 0])"#;
operator: BinaryOperator::Add, operator: BinaryOperator::Add,
left: BinaryPart::Literal(Box::new(Node::new( left: BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: 5u32.into(), value: LiteralValue::Number {
value: 5.0,
suffix: NumericSuffix::None,
},
raw: "5".to_owned(), raw: "5".to_owned(),
digest: None, digest: None,
}, },
@ -3498,7 +3531,10 @@ mySk1 = startSketchAt([0, 0])"#;
BinaryExpression { BinaryExpression {
left: BinaryPart::Literal(Box::new(Node::new( left: BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: 5u32.into(), value: LiteralValue::Number {
value: 5.0,
suffix: NumericSuffix::None,
},
raw: "5".to_string(), raw: "5".to_string(),
digest: None, digest: None,
}, },
@ -3509,7 +3545,10 @@ mySk1 = startSketchAt([0, 0])"#;
operator: BinaryOperator::Add, operator: BinaryOperator::Add,
right: BinaryPart::Literal(Box::new(Node::new( right: BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: 6u32.into(), value: LiteralValue::Number {
value: 6.0,
suffix: NumericSuffix::None,
},
raw: "6".to_string(), raw: "6".to_string(),
digest: None, digest: None,
}, },
@ -4345,6 +4384,20 @@ sketch001 = startSketchOn('XZ') |> startProfileAt([90.45, 119.09, %)"#;
return 0 return 0
}"# }"#
); );
let some_program_string = r#"myMap = map([0..5], (n) => {
return n * 2
})"#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 2);
let replaced = errs[0].apply_suggestion(some_program_string).unwrap();
let replaced = errs[1].apply_suggestion(&replaced).unwrap();
assert_eq!(
replaced,
r#"myMap = map([0..5], fn(n) {
return n * 2
})"#
);
} }
#[test] #[test]

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3851
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 4, "start": 4,
"end": 5 "end": 5

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3852
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 2, "start": 2,
"end": 3 "end": 3

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3853
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 3, "start": 3,
"end": 4 "end": 4

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3854
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -22,7 +23,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 4, "start": 4,
"end": 5 "end": 5
@ -30,7 +34,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 8, "start": 8,
"end": 9 "end": 9

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3855
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -22,7 +23,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 6, "start": 6,
"end": 7 "end": 7
@ -30,7 +34,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 10, "start": 10,
"end": 11 "end": 11

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3856
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -14,7 +12,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -26,7 +27,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 6, "start": 6,
"end": 7 "end": 7
@ -34,7 +38,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 10, "start": 10,
"end": 11 "end": 11
@ -48,7 +55,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 4.0, "value": {
"value": 4.0,
"suffix": "None"
},
"raw": "4", "raw": "4",
"start": 16, "start": 16,
"end": 17 "end": 17

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3857
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -26,7 +27,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 6, "start": 6,
"end": 7 "end": 7
@ -34,7 +38,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 10, "start": 10,
"end": 11 "end": 11
@ -45,7 +52,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 4.0, "value": {
"value": 4.0,
"suffix": "None"
},
"raw": "4", "raw": "4",
"start": 16, "start": 16,
"end": 17 "end": 17

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3858
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -30,7 +31,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 7, "start": 7,
"end": 8 "end": 8
@ -38,7 +42,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 11, "start": 11,
"end": 12 "end": 12
@ -49,7 +56,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 4.0, "value": {
"value": 4.0,
"suffix": "None"
},
"raw": "4", "raw": "4",
"start": 17, "start": 17,
"end": 18 "end": 18
@ -60,7 +70,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0, "value": {
"value": 5.0,
"suffix": "None"
},
"raw": "5", "raw": "5",
"start": 21, "start": 21,
"end": 22 "end": 22

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3859
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0, "value": {
"value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -22,7 +23,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 8, "start": 8,
"end": 9 "end": 9
@ -30,7 +34,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 12, "start": 12,
"end": 13 "end": 13

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3860
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -49,7 +47,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 6.0, "value": {
"value": 6.0,
"suffix": "None"
},
"raw": "6", "raw": "6",
"start": 21, "start": 21,
"end": 22 "end": 22

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3861
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0, "value": {
"value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0, "value": {
"value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 7, "start": 7,
"end": 8 "end": 8

View File

@ -25,7 +25,10 @@ expression: actual
"start": 27, "start": 27,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 31, "end": 31,
@ -33,7 +36,10 @@ expression: actual
"start": 30, "start": 30,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 32, "end": 32,
@ -63,7 +69,10 @@ expression: actual
"start": 47, "start": 47,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 52, "end": 52,
@ -71,7 +80,10 @@ expression: actual
"start": 50, "start": 50,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": {
"value": 10.0,
"suffix": "None"
}
} }
], ],
"end": 53, "end": 53,
@ -108,7 +120,10 @@ expression: actual
"start": 81, "start": 81,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
}, },
"end": 82, "end": 82,
"operator": "-", "operator": "-",
@ -122,7 +137,10 @@ expression: actual
"start": 84, "start": 84,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
} }
], ],
"end": 86, "end": 86,
@ -158,7 +176,10 @@ expression: actual
"start": 104, "start": 104,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
}, },
{ {
"argument": { "argument": {
@ -167,7 +188,10 @@ expression: actual
"start": 108, "start": 108,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 15.0 "value": {
"value": 15.0,
"suffix": "None"
}
}, },
"end": 110, "end": 110,
"operator": "-", "operator": "-",
@ -207,7 +231,10 @@ expression: actual
"start": 131, "start": 131,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": {
"value": 10.0,
"suffix": "None"
}
}, },
{ {
"end": 136, "end": 136,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3964
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"argument": { "argument": {
@ -40,7 +41,10 @@ snapshot_kind: text
"start": 18, "start": 18,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
"end": 19, "end": 19,
"operator": "-", "operator": "-",

View File

@ -21,7 +21,10 @@ expression: actual
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": {
"value": 10.0,
"suffix": "None"
}
}, },
"endInclusive": true, "endInclusive": true,
"start": 10, "start": 10,
@ -31,7 +34,10 @@ expression: actual
"start": 11, "start": 11,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
"type": "ArrayRangeExpression", "type": "ArrayRangeExpression",
"type": "ArrayRangeExpression" "type": "ArrayRangeExpression"

View File

@ -23,7 +23,10 @@ expression: actual
"start": 50, "start": 50,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0 "value": {
"value": 2.0,
"suffix": "None"
}
}, },
"end": 51, "end": 51,
"start": 43, "start": 43,

View File

@ -25,7 +25,10 @@ expression: actual
"start": 26, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 29, "end": 29,
@ -33,7 +36,10 @@ expression: actual
"start": 28, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 30, "end": 30,
@ -63,7 +69,10 @@ expression: actual
"start": 51, "start": 51,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 55, "end": 55,
@ -71,7 +80,10 @@ expression: actual
"start": 54, "start": 54,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 56, "end": 56,
@ -114,7 +126,10 @@ expression: actual
"start": 89, "start": 89,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
{ {
"end": 93, "end": 93,
@ -122,7 +137,10 @@ expression: actual
"start": 92, "start": 92,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 94, "end": 94,
@ -158,7 +176,10 @@ expression: actual
"start": 118, "start": 118,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
{ {
"end": 122, "end": 122,
@ -166,7 +187,10 @@ expression: actual
"start": 121, "start": 121,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 123, "end": 123,

View File

@ -25,7 +25,10 @@ expression: actual
"start": 26, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 29, "end": 29,
@ -33,7 +36,10 @@ expression: actual
"start": 28, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 30, "end": 30,
@ -63,7 +69,10 @@ expression: actual
"start": 43, "start": 43,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
{ {
"end": 47, "end": 47,
@ -71,7 +80,10 @@ expression: actual
"start": 46, "start": 46,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 48, "end": 48,

View File

@ -23,7 +23,10 @@ expression: actual
"start": 10, "start": 10,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"callee": { "callee": {
@ -45,7 +48,10 @@ expression: actual
"start": 18, "start": 18,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0 "value": {
"value": 2.0,
"suffix": "None"
}
}, },
{ {
"end": 22, "end": 22,

View File

@ -46,7 +46,10 @@ expression: actual
"start": 34, "start": 34,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 38, "end": 38,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3996
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 18, "end": 18,
@ -39,7 +40,10 @@ snapshot_kind: text
"start": 17, "start": 17,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 19, "end": 19,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3997
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 18, "end": 18,
@ -39,7 +40,10 @@ snapshot_kind: text
"start": 17, "start": 17,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 19, "end": 19,
@ -66,7 +70,10 @@ snapshot_kind: text
"start": 28, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0 "value": {
"value": 3.0,
"suffix": "None"
}
}, },
{ {
"end": 32, "end": 32,
@ -74,7 +81,10 @@ snapshot_kind: text
"start": 31, "start": 31,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0 "value": {
"value": 3.0,
"suffix": "None"
}
} }
], ],
"end": 33, "end": 33,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3998
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 12, "start": 12,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 16, "end": 16,
@ -39,7 +40,10 @@ snapshot_kind: text
"start": 15, "start": 15,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 17, "end": 17,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3999
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 18, "end": 18,
@ -39,7 +40,10 @@ snapshot_kind: text
"start": 17, "start": 17,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 19, "end": 19,
@ -66,7 +70,10 @@ snapshot_kind: text
"start": 28, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0 "value": {
"value": 3.0,
"suffix": "None"
}
}, },
{ {
"end": 32, "end": 32,
@ -74,7 +81,10 @@ snapshot_kind: text
"start": 31, "start": 31,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0 "value": {
"value": 3.0,
"suffix": "None"
}
} }
], ],
"end": 33, "end": 33,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 4000
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 18, "end": 18,
@ -39,7 +40,10 @@ snapshot_kind: text
"start": 17, "start": 17,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 19, "end": 19,
@ -66,7 +70,10 @@ snapshot_kind: text
"start": 27, "start": 27,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0 "value": {
"value": 3.0,
"suffix": "None"
}
}, },
{ {
"end": 31, "end": 31,
@ -74,7 +81,10 @@ snapshot_kind: text
"start": 30, "start": 30,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 3.0 "value": {
"value": 3.0,
"suffix": "None"
}
} }
], ],
"end": 32, "end": 32,

View File

@ -23,7 +23,10 @@ expression: actual
"start": 26, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 29, "end": 29,
@ -31,7 +34,10 @@ expression: actual
"start": 28, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 30, "end": 30,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 4002
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -16,7 +14,10 @@ snapshot_kind: text
"start": 4, "start": 4,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
}, },
{ {
"end": 14, "end": 14,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 4003
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -16,7 +14,10 @@ snapshot_kind: text
"start": 0, "start": 0,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
}, },
"operator": "+", "operator": "+",
"right": { "right": {

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 4004
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -18,7 +16,10 @@ snapshot_kind: text
"start": 6, "start": 6,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 10, "end": 10,

View File

@ -60,7 +60,10 @@ expression: actual
"start": 62, "start": 62,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 66, "end": 66,
@ -68,7 +71,10 @@ expression: actual
"start": 65, "start": 65,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 67, "end": 67,
@ -93,7 +99,10 @@ expression: actual
"start": 77, "start": 77,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 22.0 "value": {
"value": 22.0,
"suffix": "None"
}
} }
} }
], ],
@ -127,7 +136,10 @@ expression: actual
"start": 101, "start": 101,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 14.0 "value": {
"value": 14.0,
"suffix": "None"
}
}, },
{ {
"end": 106, "end": 106,

View File

@ -32,7 +32,10 @@ expression: actual
"start": 43, "start": 43,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 360.0 "value": {
"value": 360.0,
"suffix": "None"
}
} }
], ],
"callee": { "callee": {

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