Compare commits

...

96 Commits

Author SHA1 Message Date
9407162543 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation-backup 2024-12-09 14:04:18 -05:00
067b83b468 Manual resolution of snapshot conflicts 2024-12-09 13:56:33 -05:00
ff2103d493 Bump node to v22.12.0 (LTS) (#4706) 2024-12-09 13:37:01 -05:00
2dfa8f2176 Add installation instructions for all platforms (#4592)
* Add installation instructions for all platforms
Fixes #4511

* Typo

* Typo2

* Improve linux instructions, thanks @TomPridham

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
2024-12-09 12:30:24 -05:00
29ed330326 Add some more warnings (#4697) 2024-12-10 06:27:04 +13:00
c5b30341eb Add unit tests for doesSceneHaveExtrudedSketch 2024-12-09 12:26:21 -05:00
ca2cc825a6 Invalidate nightly bucket files after publish (#4627)
* Invalidate nightly bucket files after publish

* Fix conflict resolution
2024-12-09 11:45:32 -05:00
3e6441b563 Fix test annotations 2024-12-09 11:36:46 -05:00
acafcf2d4d Apply suggestions from Frank's review
Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-12-09 11:33:41 -05:00
83fe1b7ce0 Fixup for review comment from #4677 (#4696)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-09 10:42:03 -05:00
157b76cc78 KCL refactor: combine two fields into one struct (#4689) 2024-12-06 19:11:31 -06:00
cf957d880e KCL refactor: Fix argument names (#4690)
Does not change behaviour. It just clarifies whether JS is passing a string containing KCL source code, or containing a JSON-stringified KCL AST.
2024-12-06 17:20:06 -06:00
dfc3d19677 remove clearScene from TS side (#4684)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix lint

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add failing tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* the scene is cleared

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* create new clear scene and bust cache function from rust side

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* pull thru

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* set that we switched files

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix two dirties

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-12-06 22:56:53 +00:00
dd370a9365 AST: Allow unlabeled kw args (#4686)
When declaring a function, its first parameter is allowed to be prefixed with `@`. This means that when users call this function, they don't have to label this argument.

Only the first parameter is allowed this prefix, no others.

Part of https://github.com/KittyCAD/modeling-app/issues/4600
2024-12-06 15:44:39 -06:00
2274d6459c Bump @types/react-dom from 18.3.0 to 18.3.1 (#4411)
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.3.0 to 18.3.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
2024-12-06 15:21:28 -06:00
32ce857119 Update bracket KCL variable syntax in onboarding (#4685) 2024-12-06 16:04:17 -05:00
88b51da417 Run external contributor branch through CI (#4679)
* fix: make variable declaration errors Cut instead of Backtrace

* fix: clippy, move comma to empty case and add test

* fix: add missing TokenType case

* fix: incorrect fn args after merge

* fix: clippy lint

* fix: update error message being looked for in e2e test

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-12-06 14:12:20 -05:00
30d365aeb3 Module/import upgrades (#4677)
* Parse more import syntax

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

* Remove unnecessary Vec from VariableDeclaration

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

* Parse export import

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

* Factor out an execution module

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

* imports: constants, globs, export import

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

* test fixups

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-06 13:16:04 -05:00
7af62399ac Change vite-plugin-eslint to maintained package (#4645)
* Change vite-plugin-eslint to maintained package

* Add .eslintcache to ignores
2024-12-06 12:28:58 -05:00
b9f31d94d5 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 18:43:44 -05:00
9e03b58ae5 Cap and wall pw test 2024-12-05 18:21:20 -05:00
c591f73c70 Working multi-face shell across types 2024-12-05 17:56:18 -05:00
9330aaba13 Trigger CI 2024-12-05 17:27:16 -05:00
5a14f0189e Look at this (photo)Graph *in the voice of Nickelback* 2024-12-05 22:14:20 +00:00
9af001f22e Lint 2024-12-05 17:04:02 -05:00
435d1ea52e WIP circular dep 2024-12-05 16:53:57 -05:00
54847139f2 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 15:00:34 -05:00
9369a17ea7 WIP mutliple faces 2024-12-05 14:59:44 -05:00
9ee2e7c3b0 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-05 19:42:39 +00:00
28ae261e5f Lint fix 2024-12-05 14:38:23 -05:00
e4b0de0ead Add selection guard and clean up 2024-12-05 14:06:09 -05:00
12859598a3 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 13:02:35 -05:00
e9a334f433 Fix lint 2024-12-05 13:02:22 -05:00
e470a7b4af Add shell wall test 2024-12-05 12:33:46 -05:00
602c39f63c Add pw tests for cap shell 2024-12-05 12:03:45 -05:00
4994aa6f61 Handle walls 2024-12-05 11:38:03 -05:00
4aa07b81db Add extrude lookup for more generic shell 2024-12-05 10:26:58 -05:00
89ef4b3243 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-05 14:55:04 +00:00
001c9a8219 Update from main 2024-12-05 09:49:14 -05:00
4dde3f60e0 WIP: first time working shell mod 2024-12-04 18:41:45 -05:00
f407c53032 WIP: closer 2024-12-04 17:53:04 -05:00
9ba584487a WIP: more additions 2024-12-04 17:01:11 -05:00
96b66d6bca Merge branch 'pierremtb/issue4470-loft-ui' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-04 16:44:40 -05:00
8d66f3ffad Rollback pw values to pre cam change 2024-12-04 16:43:29 -05:00
f4c54cbbe4 WIP: initial shell code addition 2024-12-04 16:38:44 -05:00
5156b847f3 Trigger CI 2024-12-04 15:56:48 -05:00
ded9f2c56b A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:54:59 +00:00
5db5f79f9a Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-04 15:46:18 -05:00
6a883f4a8d A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 20:43:39 +00:00
07b91f0fb1 Trigger CI 2024-12-04 15:39:26 -05:00
1b2e213afe A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:37:28 +00:00
f48a23c35e A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 20:36:20 +00:00
7e1d102496 Revert snapshots 2024-12-04 15:32:14 -05:00
94cb2535c0 Fix typo 2024-12-04 15:21:03 -05:00
9e08ec9096 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:00:36 +00:00
17bd8ec32a A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:58:28 +00:00
d0f12e85e5 Trigger CI 2024-12-04 14:54:05 -05:00
94d185944e A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 19:52:22 +00:00
c38b2270c3 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 19:51:34 +00:00
967ad66c98 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:47:04 +00:00
afeca9ca39 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:46:25 +00:00
61242282f0 Trigger CI 2024-12-04 14:42:00 -05:00
0065df13ce A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 16:55:39 +00:00
01c8d45c13 Remove comments 2024-12-04 11:44:49 -05:00
8b25527f21 Move error logic out of loftSketches, fix pw tests 2024-12-04 11:39:35 -05:00
2abd980de9 Move to fromPromise-based Actor 2024-12-04 11:02:51 -05:00
f783deb706 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 15:25:38 +00:00
f4dd295ca1 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 15:17:19 +00:00
ceaa85fe3f A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 15:16:16 +00:00
3991bd9173 Trigger CI 2024-12-04 10:11:47 -05:00
b8f9da36c0 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 10:54:14 +00:00
283315b5d2 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 10:44:01 +00:00
e204dfe564 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 10:43:15 +00:00
208a36196b Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-04 05:38:43 -05:00
660a349588 Add pw test for preselected sketches 2024-12-03 15:17:21 -05:00
56c37da317 Clean up loftSketches function 2024-12-03 15:02:56 -05:00
a46734b76d Add test for doesSceneHaveSweepableSketch with count = 2 2024-12-03 14:39:06 -05:00
4347e0cf84 Clean up and working pw test 2024-12-03 13:49:26 -05:00
df3e541cdf A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-03 17:51:57 +00:00
b1cec443b9 Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-03 12:48:32 -05:00
967f49055d Lint 2024-12-02 14:30:43 -05:00
fe977524b5 First point-click loft test (not working locally, loft gets inserted at the wrong place) 2024-12-02 14:19:32 -05:00
d6f271fb0f Enable multiple selections after the button click 2024-12-01 07:00:13 -05:00
e851b2bcc4 Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-11-29 20:01:32 -05:00
be569c91de Clean up 2024-11-29 19:51:56 -05:00
5080e304b9 Appends the loft line after the 'last' sketch in the code 2024-11-29 18:30:15 -05:00
f4e75b7b4f More checks 2024-11-29 13:38:52 -05:00
31cbc90f56 WIP selections 2024-11-29 12:37:53 -05:00
a7d3552472 WIP selections 2024-11-29 11:17:31 -05:00
e984b20664 First pass at handling more than 2 sketches 2024-11-29 10:09:10 -05:00
0c2cd24bda Working loft for two sketches in the right hardcoded order 2024-11-28 20:28:37 -05:00
9b2de237b8 Add selection guard 2024-11-28 16:45:41 -05:00
c79c02f18e A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-11-28 20:25:44 +00:00
4851aa2d71 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-11-28 20:17:17 +00:00
76fafa6fd0 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-11-28 20:11:35 +00:00
647ca11e08 WIP: experimenting with Loft UI
Relates to #4470
2024-11-27 15:55:01 -05:00
277 changed files with 105407 additions and 104028 deletions

View File

@ -365,7 +365,7 @@ jobs:
- name: Set more complete nightly release notes - name: Set more complete nightly release notes
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
run: | run: |
# Note: prefered going this way instead of a full clone in the checkout step, # Note: preferred going this way instead of a full clone in the checkout step,
# see https://github.com/actions/checkout/issues/1471 # see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}" export TAG="nightly-${VERSION}"
@ -394,6 +394,10 @@ jobs:
parent: false parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly' destination: 'dl.kittycad.io/releases/modeling-app/nightly'
- name: Invalidate bucket cache on latest*.yml and last_download.json files
if: ${{ env.IS_NIGHTLY == 'true' }}
run: yarn files:invalidate-bucket:nightly
- name: Tag nightly commit - name: Tag nightly commit
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: actions/github-script@v7 uses: actions/github-script@v7

View File

@ -126,11 +126,7 @@ jobs:
destination: 'dl.kittycad.io/releases/modeling-app' destination: 'dl.kittycad.io/releases/modeling-app'
- name: Invalidate bucket cache on latest*.yml and last_download.json files - name: Invalidate bucket cache on latest*.yml and last_download.json files
run: | run: yarn files:invalidate-bucket
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
- name: Upload release files to Github - name: Upload release files to Github
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}

1
.gitignore vendored
View File

@ -61,6 +61,7 @@ Mac_App_Distribution.provisionprofile
*.tsbuildinfo *.tsbuildinfo
src/wasm-lib/pkg src/wasm-lib/pkg
.eslintcache
venv venv
.vite/ .vite/

2
.nvmrc
View File

@ -1 +1 @@
v21.7.3 v22.12.0

43
INSTALL.md Normal file
View File

@ -0,0 +1,43 @@
# Setting Up Zoo Modeling App
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
## Windows
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
## macOS
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
## Linux
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
- On Ubuntu, install the FUSE library with these commands in a terminal.
```bash
sudo apt update
sudo apt install libfuse2
```
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
- `appimaged` should automatically find it and make it executable. If not, run:
```bash
chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
```
3. You can double-click on the AppImage to run it, or in a terminal with this command:
```bash
~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
```

File diff suppressed because one or more lines are too long

View File

@ -78970,7 +78970,7 @@
"model = import(\"tests/inputs/cube.gltf\")", "model = import(\"tests/inputs/cube.gltf\")",
"model = import(\"tests/inputs/cube.sldprt\")", "model = import(\"tests/inputs/cube.sldprt\")",
"model = import(\"tests/inputs/cube.step\")", "model = import(\"tests/inputs/cube.step\")",
"import height, buildSketch from 'common.kcl'\n\nplane = 'XZ'\nmargin = 2\ns1 = buildSketch(plane, [0, 0])\ns2 = buildSketch(plane, [0, height() + margin])" "import height, buildSketch from \"common.kcl\"\n\nplane = 'XZ'\nmargin = 2\ns1 = buildSketch(plane, [0, 0])\ns2 = buildSketch(plane, [0, height() + margin])"
] ]
}, },
{ {

View File

@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
/* add the following code to the editor ($ error is not a valid line) /* add the following code to the editor ($ error is not a valid line)
$ error $ error
const topAng = 30 topAng = 30
const bottomAng = 25 bottomAng = 25
*/ */
await u.codeLocator.click() await u.codeLocator.click()
await page.keyboard.type('$ error') await page.keyboard.type('$ error')
@ -474,12 +474,14 @@ test.describe('Editor tests', () => {
await page.keyboard.type('bottomAng = 25') await page.keyboard.type('bottomAng = 25')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// error in guter // error in gutter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible() await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: $').first()).toBeVisible() await expect(
page.getByText('Tag names must not be empty').first()
).toBeVisible()
// select the line that's causing the error and delete it // select the line that's causing the error and delete it
await page.getByText('$ error').click() await page.getByText('$ error').click()

View File

@ -7,6 +7,7 @@ export class ToolbarFixture {
extrudeButton!: Locator extrudeButton!: Locator
loftButton!: Locator loftButton!: Locator
shellButton!: Locator
offsetPlaneButton!: Locator offsetPlaneButton!: Locator
startSketchBtn!: Locator startSketchBtn!: Locator
lineBtn!: Locator lineBtn!: Locator
@ -28,6 +29,7 @@ export class ToolbarFixture {
this.page = page this.page = page
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft') this.loftButton = page.getByTestId('loft')
this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset') this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch') this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line') this.lineBtn = page.getByTestId('line')

View File

@ -768,3 +768,166 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
}) })
}) })
}) })
const shellPointAndClickCapCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },
]
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
app,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
extrude001 = extrude(30, sketch001)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const shellDeclaration =
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
await test.step(`Look for the grey of the shape`, async () => {
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
})
if (!shouldPreselect) {
await test.step(`Go through the command bar flow without preselected faces`, async () => {
await toolbar.shellButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnCap()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
} else {
await test.step(`Preselect the cap`, async () => {
await clickOnCap()
})
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
await toolbar.shellButton.click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
}
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(shellDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [shellDeclaration],
highlightedCode: '',
})
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
})
})
})
test('Shell point-and-click wall', async ({
app,
page,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-20, 20], %)
|> xLine(40, %)
|> yLine(-60, %)
|> xLine(-40, %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(40, sketch001)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 580, y: 180 }
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
const mutatedCode = 'xLine(-40, %, $seg01)'
const shellDeclaration =
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
const formattedOutLastLine = '}, extrude001)'
await test.step(`Look for the grey of the shape`, async () => {
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
})
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
await toolbar.shellButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnCap()
await page.keyboard.down('Shift')
await clickOnWall()
await app.page.waitForTimeout(500)
await page.keyboard.up('Shift')
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap, 1 face',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(mutatedCode)
await editor.expectEditor.toContain(shellDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [formattedOutLastLine],
highlightedCode: '',
})
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
})
})

View File

@ -136,6 +136,335 @@ test(
} }
) )
test(
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
const errorDir = join(dir, 'broken-code')
await fsp.mkdir(errorDir, { recursive: true })
await fsp.copyFile(
executorInputPath('broken-code-test.kcl'),
join(errorDir, 'main.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('broken-code')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('opening broken code project should clear the scene and show the error', async () => {
// Go back home.
await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
const emptyDir = join(dir, 'empty')
await fsp.mkdir(emptyDir, { recursive: true })
await fsp.writeFile(join(emptyDir, 'main.kcl'), '')
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('empty')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('opening empty code project should clear the scene', async () => {
// Go back home.
await expect(page.getByText('empty')).toBeVisible()
await page.getByText('empty').click()
// Ensure the code is empty.
await expect(u.codeLocator).toContainText('')
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
// planes colors means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open empty file, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
await fsp.writeFile(join(bracketDir, 'empty.kcl'), '')
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('creating a empty file should clear the scene', async () => {
// open the file pane.
await page.getByTestId('files-pane-button').click()
// OPen the other file.
const file = page.getByRole('button', { name: 'empty.kcl' })
await expect(file).toBeVisible()
await file.click()
// planes colors means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
timeout: 10_000,
})
.toBeLessThan(15)
// Ensure the code is empty.
await expect(u.codeLocator).toContainText('')
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('broken-code-test.kcl'),
join(bracketDir, 'broken-code-test.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('opening broken code file should clear the scene and show the error', async () => {
// open the file pane.
await page.getByTestId('files-pane-button').click()
// OPen the other file.
const file = page.getByRole('button', { name: 'broken-code-test.kcl' })
await expect(file).toBeVisible()
await file.click()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test( test(
'when code with error first loads you get errors in console', 'when code with error first loads you get errors in console',
{ tag: '@electron' }, { tag: '@electron' },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -81,6 +81,7 @@
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000", "simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &", "simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:stop": "kill-port 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh", "fetch:wasm": "./get-latest-wasm-bundle.sh",
@ -95,6 +96,8 @@
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild", "postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"", "xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
"make:dev": "make dev", "make:dev": "make dev",
@ -158,6 +161,7 @@
"@electron/rebuild": "^3.6.0", "@electron/rebuild": "^3.6.0",
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@nabla/vite-plugin-eslint": "^2.0.5",
"@playwright/test": "^1.46.1", "@playwright/test": "^1.46.1",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2", "@testing-library/react": "^15.0.2",
@ -170,7 +174,7 @@
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4", "@types/react": "^18.3.4",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.3.1",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0", "@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
@ -207,7 +211,6 @@
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vite": "^5.4.6", "vite": "^5.4.6",
"vite-plugin-eslint": "^1.8.1",
"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

@ -0,0 +1,11 @@
#!/bin/bash
base_dir="/releases/modeling-app"
if [[ $1 = "--nightly" ]]; then
base_dir="/releases/modeling-app/nightly"
fi
echo "Invalidating json and yml files at $base_dir in the download bucket"
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/last_download.json" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-linux-arm64.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-mac.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest.yml" --async

View File

@ -701,8 +701,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = const variableDeclarationName = _node1.node?.declaration.id?.name || ''
_node1.node?.declarations?.[0]?.id?.name || ''
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
kclManager.programMemory.get(variableDeclarationName), kclManager.programMemory.get(variableDeclarationName),
@ -902,10 +901,9 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = const variableDeclarationName = _node1.node?.declaration.id?.name || ''
_node1.node?.declarations?.[0]?.id?.name || '' const startSketchOn = _node1.node?.declaration
const startSketchOn = _node1.node?.declarations const startSketchOnInit = startSketchOn?.init
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [ const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentA'),
@ -913,7 +911,7 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'), findUniqueName(_ast, 'rectangleSegmentC'),
] ]
startSketchOn[0].init = createPipeExpression([ startSketchOn.init = createPipeExpression([
startSketchOnInit, startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags), ...getRectangleCallExpressions(rectangleOrigin, tags),
]) ])
@ -943,7 +941,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return Promise.reject(_node) if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node?.declaration.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -992,7 +990,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node?.declaration.init
if (sketchInit.type !== 'PipeExpression') { if (sketchInit.type !== 'PipeExpression') {
return return
@ -1058,10 +1056,9 @@ export class SceneEntities {
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
// startSketchOn already exists // startSketchOn already exists
const variableDeclarationName = const variableDeclarationName = _node1.node?.declaration.id?.name || ''
_node1.node?.declarations?.[0]?.id?.name || '' const startSketchOn = _node1.node?.declaration
const startSketchOn = _node1.node?.declarations const startSketchOnInit = startSketchOn?.init
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [ const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentA'),
@ -1069,7 +1066,7 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'), findUniqueName(_ast, 'rectangleSegmentC'),
] ]
startSketchOn[0].init = createPipeExpression([ startSketchOn.init = createPipeExpression([
startSketchOnInit, startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags), ...getRectangleCallExpressions(rectangleOrigin, tags),
]) ])
@ -1099,7 +1096,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return Promise.reject(_node) if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node?.declaration.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -1155,7 +1152,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node?.declaration.init
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch( updateCenterRectangleSketch(
@ -1224,12 +1221,11 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = const variableDeclarationName = _node1.node?.declaration.id?.name || ''
_node1.node?.declarations?.[0]?.id?.name || '' const startSketchOn = _node1.node?.declaration
const startSketchOn = _node1.node?.declarations const startSketchOnInit = startSketchOn?.init
const startSketchOnInit = startSketchOn?.[0]?.init
startSketchOn[0].init = createPipeExpression([ startSketchOn.init = createPipeExpression([
startSketchOnInit, startSketchOnInit,
createCallExpressionStdLib('circle', [ createCallExpressionStdLib('circle', [
createObjectExpression({ createObjectExpression({
@ -1271,7 +1267,7 @@ export class SceneEntities {
) )
let modded = structuredClone(truncatedAst) let modded = structuredClone(truncatedAst)
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node.declaration.init
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0] const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1] const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
@ -1339,7 +1335,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node?.declaration.init
let modded = structuredClone(_ast) let modded = structuredClone(_ast)
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
@ -2060,7 +2056,7 @@ function prepareTruncatedMemoryAndAst(
'VariableDeclaration' 'VariableDeclaration'
) )
if (err(_node)) return _node if (err(_node)) return _node
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || '' const variableDeclarationName = _node.node?.declaration.id?.name || ''
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
@ -2085,7 +2081,7 @@ function prepareTruncatedMemoryAndAst(
]) ])
} }
;( ;(
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0] (_ast.body[bodyIndex] as VariableDeclaration).declaration
.init as PipeExpression .init as PipeExpression
).body.push(newSegment) ).body.push(newSegment)
// update source ranges to section we just added. // update source ranges to section we just added.
@ -2096,19 +2092,19 @@ function prepareTruncatedMemoryAndAst(
const updatedSrcRangeAst = pResult.program const updatedSrcRangeAst = pResult.program
const lastPipeItem = ( const lastPipeItem = (
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration) (updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
.declarations[0].init as PipeExpression .init as PipeExpression
).body.slice(-1)[0] ).body.slice(-1)[0]
;( ;(
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0] (_ast.body[bodyIndex] as VariableDeclaration).declaration
.init as PipeExpression .init as PipeExpression
).body.slice(-1)[0].start = lastPipeItem.start ).body.slice(-1)[0].start = lastPipeItem.start
_ast.end = lastPipeItem.end _ast.end = lastPipeItem.end
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration> const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
varDec.end = lastPipeItem.end varDec.end = lastPipeItem.end
const declarator = varDec.declarations[0] const declarator = varDec.declaration
declarator.end = lastPipeItem.end declarator.end = lastPipeItem.end
const init = declarator.init as Node<PipeExpression> const init = declarator.init as Node<PipeExpression>
init.end = lastPipeItem.end init.end = lastPipeItem.end
@ -2145,7 +2141,7 @@ function prepareTruncatedMemoryAndAst(
if (node.type !== 'VariableDeclaration') { if (node.type !== 'VariableDeclaration') {
continue continue
} }
const name = node.declarations[0].id.name const name = node.declaration.id.name
const memoryItem = programMemory.get(name) const memoryItem = programMemory.get(name)
if (!memoryItem) { if (!memoryItem) {
continue continue

View File

@ -169,11 +169,11 @@ export function useCalc({
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
a.type === 'VariableDeclaration' && a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__' a.declaration.id?.name === '__result__'
) )
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init resultDeclaration?.declaration.init
const result = execState.memory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)

View File

@ -266,6 +266,7 @@ const FileTreeItem = ({
// Let the lsp servers know we closed a file. // Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null) onFileClose(currentFile?.path || null, project?.path || null)
onFileOpen(fileOrDir.path, project?.path || null) onFileOpen(fileOrDir.path, project?.path || null)
kclManager.switchedFiles = true
// Open kcl files // Open kcl files
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`) navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)

View File

@ -51,6 +51,7 @@ import {
Selections, Selections,
updateSelections, updateSelections,
canLoftSelection, canLoftSelection,
canShellSelection,
} from 'lib/selections' } from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect' import { applyConstraintIntersect } from './Toolbar/Intersect'
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
@ -69,6 +70,7 @@ import {
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { Program, parse, recast, resultIsOk } from 'lang/wasm' import { Program, parse, recast, resultIsOk } from 'lang/wasm'
import { import {
doesSceneHaveExtrudedSketch,
doesSceneHaveSweepableSketch, doesSceneHaveSweepableSketch,
getNodePathFromSourceRange, getNodePathFromSourceRange,
isSingleCursorInPipe, isSingleCursorInPipe,
@ -585,6 +587,24 @@ export const ModelingMachineProvider = ({
if (err(canLoft)) return false if (err(canLoft)) return false
return canLoft return canLoft
}, },
'has valid shell selection': ({
context: { selectionRanges },
event,
}) => {
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
return doesSceneHaveExtrudedSketch(kclManager.ast)
}
const canShell = canShellSelection(selectionRanges)
console.log('canShellSelection', canShellSelection(selectionRanges))
if (err(canShell)) return false
return canShell
},
'has valid selection for deletion': ({ 'has valid selection for deletion': ({
context: { selectionRanges }, context: { selectionRanges },
}) => { }) => {

View File

@ -40,7 +40,9 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50"> <Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
<Menu.Item> <Menu.Item>
<button <button
onClick={() => kclManager.format()} onClick={() => {
kclManager.format().catch(reportRejection)
}}
className={styles.button} className={styles.button}
> >
<span>Format code</span> <span>Format code</span>

View File

@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { engineCommandManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { MachineManagerContext } from 'components/MachineManagerProvider'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
@ -68,8 +68,7 @@ function AppLogoLink({
data-testid="app-logo" data-testid="app-logo"
onClick={() => { onClick={() => {
onProjectClose(file || null, project?.path || null, false) onProjectClose(file || null, project?.path || null, false)
// Clear the scene. kclManager.switchedFiles = true
engineCommandManager.clearScene()
}} }}
to={PATHS.HOME} to={PATHS.HOME}
className={wrapperClassName + ' hover:before:brightness-110'} className={wrapperClassName + ' hover:before:brightness-110'}
@ -190,8 +189,7 @@ function ProjectMenuPopover({
className: !isDesktop() ? 'hidden' : '', className: !isDesktop() ? 'hidden' : '',
onClick: () => { onClick: () => {
onProjectClose(file || null, project?.path || null, true) onProjectClose(file || null, project?.path || null, true)
// Clear the scene. kclManager.switchedFiles = true
engineCommandManager.clearScene()
}, },
}, },
].filter( ].filter(

View File

@ -12,6 +12,7 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { import {
CallExpression, CallExpression,
clearSceneAndBustCache,
emptyExecState, emptyExecState,
ExecState, ExecState,
initPromise, initPromise,
@ -60,6 +61,7 @@ export class KclManager {
private _executeIsStale: ExecuteArgs | null = null private _executeIsStale: ExecuteArgs | null = null
private _wasmInitFailed = true private _wasmInitFailed = true
private _hasErrors = false private _hasErrors = false
private _switchedFiles = false
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
@ -79,6 +81,10 @@ export class KclManager {
this._astCallBack(ast) this._astCallBack(ast)
} }
set switchedFiles(switchedFiles: boolean) {
this._switchedFiles = switchedFiles
}
get programMemory() { get programMemory() {
return this._programMemory return this._programMemory
} }
@ -166,8 +172,12 @@ export class KclManager {
this.engineCommandManager = engineCommandManager this.engineCommandManager = engineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.ensureWasmInit().then(() => { this.ensureWasmInit().then(async () => {
this.ast = this.safeParse(codeManager.code) || this.ast await this.safeParse(codeManager.code).then((ast) => {
if (ast) {
this.ast = ast
}
})
}) })
} }
@ -211,7 +221,25 @@ export class KclManager {
} }
} }
safeParse(code: string): Node<Program> | null { // (jess) I'm not in love with this, but it ensures we clear the scene and
// bust the cache on
// errors from parsing when opening new files.
// Why not just clear the cache on all parse errors, you ask? well its actually
// really nice to keep the cache on parse errors within the same file, and
// only bust on engine errors esp if they take a long time to execute and
// you hit the wrong key!
private async checkIfSwitchedFilesShouldClear() {
// If we were switching files and we hit an error on parse we need to bust
// the cache and clear the scene.
if (this._hasErrors && this._switchedFiles) {
await clearSceneAndBustCache(this.engineCommandManager)
} else if (this._switchedFiles) {
// Reset the switched files boolean.
this._switchedFiles = false
}
}
async safeParse(code: string): Promise<Node<Program> | null> {
const result = parse(code) const result = parse(code)
this.diagnostics = [] this.diagnostics = []
this._hasErrors = false this._hasErrors = false
@ -220,6 +248,8 @@ export class KclManager {
const kclerror: KCLError = result as KCLError const kclerror: KCLError = result as KCLError
this.diagnostics = kclErrorsToDiagnostics([kclerror]) this.diagnostics = kclErrorsToDiagnostics([kclerror])
this._hasErrors = true this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null return null
} }
@ -228,6 +258,7 @@ export class KclManager {
if (result.errors.length > 0) { if (result.errors.length > 0) {
this._hasErrors = true this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null return null
} }
@ -353,7 +384,7 @@ export class KclManager {
console.error(newCode) console.error(newCode)
return return
} }
const newAst = this.safeParse(newCode) const newAst = await this.safeParse(newCode)
if (!newAst) { if (!newAst) {
this.clearAst() this.clearAst()
return return
@ -408,7 +439,7 @@ export class KclManager {
}) })
} }
async executeCode(zoomToFit?: boolean): Promise<void> { async executeCode(zoomToFit?: boolean): Promise<void> {
const ast = this.safeParse(codeManager.code) const ast = await this.safeParse(codeManager.code)
if (!ast) { if (!ast) {
this.clearAst() this.clearAst()
return return
@ -416,9 +447,9 @@ export class KclManager {
this.ast = { ...ast } this.ast = { ...ast }
return this.executeAst({ zoomToFit }) return this.executeAst({ zoomToFit })
} }
format() { async format() {
const originalCode = codeManager.code const originalCode = codeManager.code
const ast = this.safeParse(originalCode) const ast = await this.safeParse(originalCode)
if (!ast) { if (!ast) {
this.clearAst() this.clearAst()
return return
@ -458,7 +489,7 @@ export class KclManager {
const newCode = recast(ast) const newCode = recast(ast)
if (err(newCode)) return Promise.reject(newCode) if (err(newCode)) return Promise.reject(newCode)
const astWithUpdatedSource = this.safeParse(newCode) const astWithUpdatedSource = await this.safeParse(newCode)
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast')) if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
let returnVal: Selections | undefined = undefined let returnVal: Selections | undefined = undefined

View File

@ -60,8 +60,7 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([ expect(nodePath).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['params', 'FunctionExpression'], ['params', 'FunctionExpression'],
[0, 'index'], [0, 'index'],
@ -96,14 +95,12 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([ expect(nodePath).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'FunctionExpression'], ['body', 'FunctionExpression'],
['body', 'FunctionExpression'], ['body', 'FunctionExpression'],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'PipeExpression'], ['body', 'PipeExpression'],
[2, 'index'], [2, 'index'],

View File

@ -82,11 +82,11 @@ describe('Testing createVariableDeclaration', () => {
it('should create a variable declaration', () => { it('should create a variable declaration', () => {
const result = createVariableDeclaration('myVar', createLiteral(5)) const result = createVariableDeclaration('myVar', createLiteral(5))
expect(result.type).toBe('VariableDeclaration') expect(result.type).toBe('VariableDeclaration')
expect(result.declarations[0].type).toBe('VariableDeclarator') expect(result.declaration.type).toBe('VariableDeclarator')
expect(result.declarations[0].id.type).toBe('Identifier') expect(result.declaration.id.type).toBe('Identifier')
expect(result.declarations[0].id.name).toBe('myVar') expect(result.declaration.id.name).toBe('myVar')
expect(result.declarations[0].init.type).toBe('Literal') expect(result.declaration.init.type).toBe('Literal')
expect((result.declarations[0].init as any).value).toBe(5) expect((result.declaration.init as any).value).toBe(5)
}) })
}) })
describe('Testing createPipeExpression', () => { describe('Testing createPipeExpression', () => {

View File

@ -66,8 +66,7 @@ export function startSketchOnDefault(
let pathToNode: PathToNode = [ let pathToNode: PathToNode = [
['body', ''], ['body', ''],
[sketchIndex, 'index'], [sketchIndex, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
] ]
@ -94,7 +93,7 @@ export function addStartProfileAt(
return new Error('variableDeclaration.init.type !== PipeExpression') return new Error('variableDeclaration.init.type !== PipeExpression')
} }
const _node = { ...node } const _node = { ...node }
const init = variableDeclaration.declarations[0].init const init = variableDeclaration.declaration.init
const startProfileAt = createCallExpressionStdLib('startProfileAt', [ const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([ createArrayExpression([
createLiteral(roundOff(at[0])), createLiteral(roundOff(at[0])),
@ -105,7 +104,7 @@ export function addStartProfileAt(
if (init.type === 'PipeExpression') { if (init.type === 'PipeExpression') {
init.body.splice(1, 0, startProfileAt) init.body.splice(1, 0, startProfileAt)
} else { } else {
variableDeclaration.declarations[0].init = createPipeExpression([ variableDeclaration.declaration.init = createPipeExpression([
init, init,
startProfileAt, startProfileAt,
]) ])
@ -149,8 +148,7 @@ export function addSketchTo(
let pathToNode: PathToNode = [ let pathToNode: PathToNode = [
['body', ''], ['body', ''],
[sketchIndex, 'index'], [sketchIndex, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
] ]
if (axis !== 'xy') { if (axis !== 'xy') {
@ -333,8 +331,7 @@ export function extrudeSketch(
const pathToExtrudeArg: PathToNode = [ const pathToExtrudeArg: PathToNode = [
['body', ''], ['body', ''],
[sketchIndexInBody + 1, 'index'], [sketchIndexInBody + 1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -364,8 +361,7 @@ export function loftSketches(
const pathToNode: PathToNode = [ const pathToNode: PathToNode = [
['body', ''], ['body', ''],
[modifiedAst.body.length - 1, 'index'], [modifiedAst.body.length - 1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -460,8 +456,7 @@ export function revolveSketch(
const pathToRevolveArg: PathToNode = [ const pathToRevolveArg: PathToNode = [
['body', ''], ['body', ''],
[sketchIndexInBody + 1, 'index'], [sketchIndexInBody + 1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -547,8 +542,7 @@ export function sketchOnExtrudedFace(
const newpathToNode: PathToNode = [ const newpathToNode: PathToNode = [
['body', ''], ['body', ''],
[expressionIndex + 1, 'index'], [expressionIndex + 1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
] ]
@ -585,8 +579,7 @@ export function addOffsetPlane({
const pathToNode: PathToNode = [ const pathToNode: PathToNode = [
['body', ''], ['body', ''],
[modifiedAst.body.length - 1, 'index'], [modifiedAst.body.length - 1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -823,8 +816,7 @@ export function createVariableDeclaration(
end: 0, end: 0,
moduleId: 0, moduleId: 0,
declarations: [ declaration: {
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
start: 0, start: 0,
end: 0, end: 0,
@ -833,7 +825,6 @@ export function createVariableDeclaration(
id: createIdentifier(varName), id: createIdentifier(varName),
init, init,
}, },
],
visibility, visibility,
kind, kind,
} }
@ -1120,7 +1111,7 @@ export async function deleteFromSelection(
traverse(astClone, { traverse(astClone, {
enter: (node, path) => { enter: (node, path) => {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const dec = node.declarations[0] const dec = node.declaration
if ( if (
dec.init.type === 'CallExpression' && dec.init.type === 'CallExpression' &&
(dec.init.callee.name === 'extrude' || (dec.init.callee.name === 'extrude' ||
@ -1155,7 +1146,7 @@ export async function deleteFromSelection(
enter: (node, path) => { enter: (node, path) => {
;(async () => { ;(async () => {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
currentVariableName = node.declarations[0].id.name currentVariableName = node.declaration.id.name
} }
if ( if (
// match startSketchOn(${extrudeNameToDelete}) // match startSketchOn(${extrudeNameToDelete})

View File

@ -22,7 +22,7 @@ import {
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst' import { createLiteral } from 'lang/modifyAst'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Selections } from 'lib/selections' import { Selection, Selections } from 'lib/selections'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_DEV_TOKEN } from 'env'
import { isOverlap } from 'lib/utils' import { isOverlap } from 'lib/utils'
@ -118,13 +118,8 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length, code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
true, true,
] ]
const selection: Selections = { const selection: Selection = {
graphSelections: [
{
codeRef: codeRefFromRange(segmentRange, ast), codeRef: codeRefFromRange(segmentRange, ast),
},
],
otherSelections: [],
} }
// executeAst and artifactGraph // executeAst and artifactGraph

View File

@ -29,7 +29,7 @@ import {
sketchLineHelperMap, sketchLineHelperMap,
} from '../std/sketch' } from '../std/sketch'
import { err, trap } from 'lib/trap' import { err, trap } from 'lib/trap'
import { Selections } from 'lib/selections' import { Selection, Selections } from 'lib/selections'
import { KclCommandValue } from 'lib/commandTypes' import { KclCommandValue } from 'lib/commandTypes'
import { import {
Artifact, Artifact,
@ -99,14 +99,9 @@ export function modifyAstWithEdgeTreatmentAndTag(
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
for (const selection of selections.graphSelections) { for (const selection of selections.graphSelections) {
const singleSelection = {
graphSelections: [selection],
otherSelections: [],
}
const result = getPathToExtrudeForSegmentSelection( const result = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude, clonedAstForGetExtrude,
singleSelection, selection,
artifactGraph artifactGraph
) )
if (err(result)) return result if (err(result)) return result
@ -259,12 +254,12 @@ function insertParametersIntoAst(
export function getPathToExtrudeForSegmentSelection( export function getPathToExtrudeForSegmentSelection(
ast: Program, ast: Program,
selection: Selections, selection: Selection,
artifactGraph: ArtifactGraph artifactGraph: ArtifactGraph
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error { ): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
const pathToSegmentNode = getNodePathFromSourceRange( const pathToSegmentNode = getNodePathFromSourceRange(
ast, ast,
selection.graphSelections[0]?.codeRef?.range selection.codeRef?.range
) )
const varDecNode = getNodeFromPath<VariableDeclaration>( const varDecNode = getNodeFromPath<VariableDeclaration>(
@ -273,7 +268,7 @@ export function getPathToExtrudeForSegmentSelection(
'VariableDeclaration' 'VariableDeclaration'
) )
if (err(varDecNode)) return varDecNode if (err(varDecNode)) return varDecNode
const sketchVar = varDecNode.node.declarations[0].id.name const sketchVar = varDecNode.node.declaration.id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
kclManager.programMemory.get(sketchVar), kclManager.programMemory.get(sketchVar),
@ -308,7 +303,7 @@ async function updateAstAndFocus(
} }
} }
function mutateAstWithTagForSketchSegment( export function mutateAstWithTagForSketchSegment(
astClone: Node<Program>, astClone: Node<Program>,
pathToSegmentNode: PathToNode pathToSegmentNode: PathToNode
): { modifiedAst: Program; tag: string } | Error { ): { modifiedAst: Program; tag: string } | Error {
@ -367,7 +362,7 @@ function locateExtrudeDeclarator(
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
const { node: extrudeVarDecl } = nodeOfExtrudeCall const { node: extrudeVarDecl } = nodeOfExtrudeCall
const extrudeDeclarator = extrudeVarDecl.declarations[0] const extrudeDeclarator = extrudeVarDecl.declaration
if (!extrudeDeclarator) { if (!extrudeDeclarator) {
return new Error('Extrude Declarator not found.') return new Error('Extrude Declarator not found.')
} }

View File

@ -0,0 +1,124 @@
import { ArtifactGraph } from 'lang/std/artifactGraph'
import { Selections } from 'lib/selections'
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
import { Program } from 'wasm-lib/kcl/bindings/Program'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { PathToNode, VariableDeclarator } from 'lang/wasm'
import {
getPathToExtrudeForSegmentSelection,
mutateAstWithTagForSketchSegment,
} from './addEdgeTreatment'
import { getNodeFromPath } from 'lang/queryAst'
import { err } from 'lib/trap'
import {
createLiteral,
createIdentifier,
findUniqueName,
createCallExpressionStdLib,
createObjectExpression,
createArrayExpression,
createVariableDeclaration,
} from 'lang/modifyAst'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
export function addShell({
node,
selection,
artifactGraph,
thickness,
}: {
node: Node<Program>
selection: Selections
artifactGraph: ArtifactGraph
thickness: Expr
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
const modifiedAst = structuredClone(node)
// Look up the corresponding extrude
const clonedAstForGetExtrude = structuredClone(modifiedAst)
const expressions: Expr[] = []
let pathToExtrudeNode: PathToNode | undefined = undefined
for (const graphSelection of selection.graphSelections) {
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
graphSelection,
artifactGraph
)
if (err(extrudeLookupResult)) {
return new Error("Couldn't find extrude")
}
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
// Get the sketch ref from the selection
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
// We must find a technique for these situations that is robust to intermediate declarations
const sketchNode = getNodeFromPath<VariableDeclarator>(
modifiedAst,
graphSelection.codeRef.pathToNode,
'VariableDeclarator'
)
if (err(sketchNode)) {
return sketchNode
}
const selectedArtifact = graphSelection.artifact
if (!selectedArtifact) {
return new Error('Bad artifact')
}
// Check on the selection, and handle the wall vs cap casees
let expr: Expr
if (selectedArtifact.type === 'cap') {
expr = createLiteral(selectedArtifact.subType)
} else if (selectedArtifact.type === 'wall') {
const tagResult = mutateAstWithTagForSketchSegment(
modifiedAst,
extrudeLookupResult.pathToSegmentNode
)
if (err(tagResult)) return tagResult
const { tag } = tagResult
expr = createIdentifier(tag)
} else {
continue
}
expressions.push(expr)
}
if (!pathToExtrudeNode) return new Error('No extrude found')
const extrudeNode = getNodeFromPath<VariableDeclarator>(
modifiedAst,
pathToExtrudeNode,
'VariableDeclarator'
)
if (err(extrudeNode)) {
return extrudeNode
}
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
const shell = createCallExpressionStdLib('shell', [
createObjectExpression({
faces: createArrayExpression(expressions),
thickness,
}),
createIdentifier(extrudeNode.node.id.name),
])
const declaration = createVariableDeclaration(name, shell)
// TODO: check if we should append at the end like here or right after the extrude
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}

View File

@ -17,6 +17,7 @@ import {
doesSceneHaveSweepableSketch, doesSceneHaveSweepableSketch,
traverse, traverse,
getNodeFromPath, getNodeFromPath,
doesSceneHaveExtrudedSketch,
} from './queryAst' } from './queryAst'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { import {
@ -230,8 +231,7 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'PipeExpression'], ['body', 'PipeExpression'],
[2, 'index'], [2, 'index'],
@ -250,8 +250,7 @@ describe('testing getNodePathFromSourceRange', () => {
const expected = [ const expected = [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'PipeExpression'], ['body', 'PipeExpression'],
[3, 'index'], [3, 'index'],
@ -293,8 +292,7 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[1, 'index'], [1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['cond', 'IfExpression'], ['cond', 'IfExpression'],
['left', 'BinaryExpression'], ['left', 'BinaryExpression'],
@ -324,8 +322,7 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[1, 'index'], [1, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['then_val', 'IfExpression'], ['then_val', 'IfExpression'],
['body', 'IfExpression'], ['body', 'IfExpression'],
@ -353,7 +350,8 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['items', 'ImportStatement'], ['selector', 'ImportStatement'],
['items', 'ImportSelector'],
[1, 'index'], [1, 'index'],
['name', 'ImportItem'], ['name', 'ImportItem'],
]) ])
@ -657,6 +655,38 @@ extrude001 = extrude(10, sketch001)
}) })
}) })
describe('Testing doesSceneHaveExtrudedSketch', () => {
it('finds extruded sketch as variable', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
extrude001 = extrude(1, sketch001)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeTruthy()
})
it('finds extruded sketch in pipe', async () => {
const exampleCode = `extrude001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
|> extrude(1, %)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeTruthy()
})
it('finds no extrusion with sketch only', async () => {
const exampleCode = `extrude001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeFalsy()
})
})
describe('Testing traverse and pathToNode', () => { describe('Testing traverse and pathToNode', () => {
it.each([ it.each([
['basic', '2.73'], ['basic', '2.73'],

View File

@ -259,13 +259,10 @@ function moreNodePathFromSourceRange(
return moreNodePathFromSourceRange(expression, sourceRange, path) return moreNodePathFromSourceRange(expression, sourceRange, path)
} }
if (_node.type === 'VariableDeclaration' && isInRange) { if (_node.type === 'VariableDeclaration' && isInRange) {
const declarations = _node.declarations const declaration = _node.declaration
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) { if (declaration.start <= start && declaration.end >= end) {
path.push(['declarations', 'VariableDeclaration']) path.push(['declaration', 'VariableDeclaration'])
path.push([decIndex, 'index'])
const init = declaration.init const init = declaration.init
if (init.start <= start && init.end >= end) { if (init.start <= start && init.end >= end) {
path.push(['init', '']) path.push(['init', ''])
@ -273,22 +270,17 @@ function moreNodePathFromSourceRange(
} }
} }
} }
}
if (_node.type === 'VariableDeclaration' && isInRange) { if (_node.type === 'VariableDeclaration' && isInRange) {
const declarations = _node.declarations const declaration = _node.declaration
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) { if (declaration.start <= start && declaration.end >= end) {
const init = declaration.init const init = declaration.init
if (init.start <= start && init.end >= end) { if (init.start <= start && init.end >= end) {
path.push(['declarations', 'VariableDeclaration']) path.push(['declaration', 'VariableDeclaration'])
path.push([decIndex, 'index'])
path.push(['init', '']) path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path) return moreNodePathFromSourceRange(init, sourceRange, path)
} }
} }
}
return path return path
} }
if (_node.type === 'UnaryExpression' && isInRange) { if (_node.type === 'UnaryExpression' && isInRange) {
@ -380,17 +372,23 @@ function moreNodePathFromSourceRange(
} }
if (_node.type === 'ImportStatement' && isInRange) { if (_node.type === 'ImportStatement' && isInRange) {
const { items } = _node if (_node.selector && _node.selector.type === 'List') {
path.push(['selector', 'ImportStatement'])
const { items } = _node.selector
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const item = items[i] const item = items[i]
if (item.start <= start && item.end >= end) { if (item.start <= start && item.end >= end) {
path.push(['items', 'ImportStatement']) path.push(['items', 'ImportSelector'])
path.push([i, 'index']) path.push([i, 'index'])
if (item.name.start <= start && item.name.end >= end) { if (item.name.start <= start && item.name.end >= end) {
path.push(['name', 'ImportItem']) path.push(['name', 'ImportItem'])
return path return path
} }
if (item.alias && item.alias.start <= start && item.alias.end >= end) { if (
item.alias &&
item.alias.start <= start &&
item.alias.end >= end
) {
path.push(['alias', 'ImportItem']) path.push(['alias', 'ImportItem'])
return path return path
} }
@ -399,6 +397,7 @@ function moreNodePathFromSourceRange(
} }
return path return path
} }
}
console.error('not implemented: ' + node.type) console.error('not implemented: ' + node.type)
@ -451,13 +450,10 @@ export function traverse(
traverse(node, option, pathToNode) traverse(node, option, pathToNode)
if (_node.type === 'VariableDeclaration') { if (_node.type === 'VariableDeclaration') {
_node.declarations.forEach((declaration, index) => _traverse(_node.declaration, [
_traverse(declaration, [
...pathToNode, ...pathToNode,
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[index, 'index'],
]) ])
)
} else if (_node.type === 'VariableDeclarator') { } else if (_node.type === 'VariableDeclarator') {
_traverse(_node.init, [...pathToNode, ['init', '']]) _traverse(_node.init, [...pathToNode, ['init', '']])
} else if (_node.type === 'PipeExpression') { } else if (_node.type === 'PipeExpression') {
@ -567,7 +563,7 @@ export function findAllPreviousVariablesPath(
const variables: PrevVariable<any>[] = [] const variables: PrevVariable<any>[] = []
bodyItems?.forEach?.((item) => { bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return if (item.type !== 'VariableDeclaration' || item.end > startRange) return
const varName = item.declarations[0].id.name const varName = item.declaration.id.name
const varValue = programMemory?.get(varName) const varValue = programMemory?.get(varName)
if (!varValue || typeof varValue?.value !== type) return if (!varValue || typeof varValue?.value !== type) return
variables.push({ variables.push({
@ -761,7 +757,7 @@ export function isLinesParallelAndConstrained(
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration') const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
if (err(_varDec)) return _varDec if (err(_varDec)) return _varDec
const varDec = _varDec.node const varDec = _varDec.node
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name const varName = (varDec as VariableDeclaration)?.declaration.id?.name
const sg = sketchFromKclValue(programMemory?.get(varName), varName) const sg = sketchFromKclValue(programMemory?.get(varName), varName)
if (err(sg)) return sg if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange( const _primarySegment = getSketchSegmentFromSourceRange(
@ -881,7 +877,7 @@ export function hasExtrudeSketch({
} }
const varDec = varDecMeta.node const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declarations[0].id.name const varName = varDec.declaration.id.name
const varValue = programMemory?.get(varName) const varValue = programMemory?.get(varName)
return ( return (
varValue?.type === 'Solid' || varValue?.type === 'Solid' ||
@ -1068,6 +1064,35 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
return Object.keys(theMap).length >= count return Object.keys(theMap).length >= count
} }
export function doesSceneHaveExtrudedSketch(ast: Node<Program>) {
const theMap: any = {}
traverse(ast as any, {
enter(node) {
if (
node.type === 'VariableDeclarator' &&
node.init?.type === 'PipeExpression'
) {
for (const pipe of node.init.body) {
if (
pipe.type === 'CallExpression' &&
pipe.callee.name === 'extrude'
) {
theMap[node.id.name] = true
break
}
}
} else if (
node.type === 'CallExpression' &&
node.callee.name === 'extrude' &&
node.arguments[1]?.type === 'Identifier'
) {
theMap[node.moduleId] = true
}
},
})
return Object.keys(theMap).length > 0
}
export function getObjExprProperty( export function getObjExprProperty(
node: ObjectExpression, node: ObjectExpression,
propName: string propName: string

View File

@ -1879,17 +1879,6 @@ export class EngineCommandManager extends EventTarget {
} }
return JSON.stringify(this.defaultPlanes) return JSON.stringify(this.defaultPlanes)
} }
clearScene(): void {
const deleteCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'scene_clear_all',
},
}
this.clearDefaultPlanes()
this.engineConnection?.send(deleteCmd)
}
addCommandLog(message: CommandLog) { addCommandLog(message: CommandLog) {
if (this.commandLogs.length > 500) { if (this.commandLogs.length > 500) {
this.commandLogs.shift() this.commandLogs.shift()

View File

@ -164,8 +164,7 @@ mySketch001 = startSketchOn('XY')
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
], ],
}) })
@ -189,8 +188,7 @@ mySketch001 = startSketchOn('XY')
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declarations', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
], ],
}) })

View File

@ -1701,7 +1701,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2 if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2 const { node: varDec } = nodeMeta2
const varName = varDec.declarations[0].id.name const varName = varDec.declaration.id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
previousProgramMemory.get(varName), previousProgramMemory.get(varName),
varName varName

View File

@ -111,13 +111,11 @@ export function isSketchVariablesLinked(
let nextVarDec: VariableDeclarator | undefined let nextVarDec: VariableDeclarator | undefined
for (const node of ast.body) { for (const node of ast.body) {
if (node.type !== 'VariableDeclaration') continue if (node.type !== 'VariableDeclaration') continue
const found = node.declarations.find( if (node.declaration.id.name === secondArg.name) {
({ id }) => id?.name === secondArg.name nextVarDec = node.declaration
)
if (!found) continue
nextVarDec = found
break break
} }
}
if (!nextVarDec) return false if (!nextVarDec) return false
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast) return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)
} }

View File

@ -16,6 +16,7 @@ import init, {
parse_project_settings, parse_project_settings,
default_project_settings, default_project_settings,
base64_decode, base64_decode,
clear_scene_and_bust_cache,
} from '../wasm-lib/pkg/wasm_lib' } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
@ -698,6 +699,21 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
return default_app_settings() return default_app_settings()
} }
export async function clearSceneAndBustCache(
engineCommandManager: EngineCommandManager
): Promise<null | Error> {
try {
await clear_scene_and_bust_cache(engineCommandManager)
} catch (e: any) {
console.error('clear_scene_and_bust_cache: error', e)
return Promise.reject(
new Error(`Error on clear_scene_and_bust_cache: ${e}`)
)
}
return null
}
export function parseAppSettings( export function parseAppSettings(
toml: string toml: string
): DeepPartial<Configuration> | Error { ): DeepPartial<Configuration> | Error {

View File

@ -34,6 +34,10 @@ export type ModelingCommandSchema = {
Loft: { Loft: {
selection: Selections selection: Selections
} }
Shell: {
selection: Selections
thickness: KclCommandValue
}
Revolve: { Revolve: {
selection: Selections selection: Selections
angle: KclCommandValue angle: KclCommandValue
@ -277,6 +281,25 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
Shell: {
description: 'Hollow out a 3D solid.',
icon: 'shell',
needsReview: true,
args: {
selection: {
inputType: 'selection',
selectionTypes: ['cap', 'wall'],
multiple: true,
required: true,
skip: false,
},
thickness: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: true,
},
},
},
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection // TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
Revolve: { Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.', description: 'Create a 3D body by rotating a sketch region about an axis.',

View File

@ -53,6 +53,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch', SKETCH: 'sketch',
EXTRUDE: 'extrude', EXTRUDE: 'extrude',
LOFT: 'loft', LOFT: 'loft',
SHELL: 'shell',
SEGMENT: 'seg', SEGMENT: 'seg',
REVOLVE: 'revolve', REVOLVE: 'revolve',
PLANE: 'plane', PLANE: 'plane',

View File

@ -13,7 +13,6 @@ import {
listProjects, listProjects,
readAppSettingsFile, readAppSettingsFile,
} from './desktop' } from './desktop'
import { engineCommandManager } from './singletons'
export const isHidden = (fileOrDir: FileEntry) => export const isHidden = (fileOrDir: FileEntry) =>
!!fileOrDir.name?.startsWith('.') !!fileOrDir.name?.startsWith('.')
@ -116,9 +115,6 @@ export async function createAndOpenNewTutorialProject({
) => void ) => void
navigate: (path: string) => void navigate: (path: string) => void
}) { }) {
// Clear the scene.
engineCommandManager.clearScene()
// Create a new project with the onboarding project name // Create a new project with the onboarding project name
const configuration = await readAppSettingsFile() const configuration = await readAppSettingsFile()
const projects = await listProjects(configuration) const projects = await listProjects(configuration)

View File

@ -3,27 +3,27 @@ export const bracket = `// Shelf Bracket
// Define constants // Define constants
const sigmaAllow = 35000 // psi (6061-T6 aluminum) sigmaAllow = 35000 // psi (6061-T6 aluminum)
const width = 6 // inch width = 6 // inch
const p = 300 // Force on shelf - lbs p = 300 // Force on shelf - lbs
const factorOfSafety = 1.2 // FOS of 1.2 factorOfSafety = 1.2 // FOS of 1.2
const shelfMountL = 5 // inches shelfMountL = 5 // inches
const wallMountL = 2 // inches wallMountL = 2 // inches
const shelfDepth = 12 // Shelf is 12 inches in depth from the wall shelfDepth = 12 // Shelf is 12 inches in depth from the wall
const moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in) moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
const filletRadius = .375 // inches filletRadius = .375 // inches
const extFilletRadius = .25 // inches extFilletRadius = .25 // inches
const mountingHoleDiameter = 0.5 // inches mountingHoleDiameter = 0.5 // inches
// Calculate required thickness of bracket // Calculate required thickness of bracket
const thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches) thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
// Sketch the bracket body and fillet the inner and outer edges of the bend // Sketch the bracket body and fillet the inner and outer edges of the bend
const bracketLeg1Sketch = startSketchOn('XY') bracketLeg1Sketch = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([shelfMountL - filletRadius, 0], %, $fillet1) |> line([shelfMountL - filletRadius, 0], %, $fillet1)
|> line([0, width], %, $fillet2) |> line([0, width], %, $fillet2)
@ -47,7 +47,7 @@ const bracketLeg1Sketch = startSketchOn('XY')
}, %), %) }, %), %)
// Extrude the leg 2 bracket sketch // Extrude the leg 2 bracket sketch
const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch) bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|> fillet({ |> fillet({
radius = extFilletRadius, radius = extFilletRadius,
tags = [ tags = [
@ -57,7 +57,7 @@ const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
}, %) }, %)
// Sketch the fillet arc // Sketch the fillet arc
const filletSketch = startSketchOn('XZ') filletSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([0, thickness], %) |> line([0, thickness], %)
|> arc({ |> arc({
@ -73,10 +73,10 @@ const filletSketch = startSketchOn('XZ')
}, %) }, %)
// Sketch the bend // Sketch the bend
const filletExtrude = extrude(-width, filletSketch) filletExtrude = extrude(-width, filletSketch)
// Create a custom plane for the leg that sits on the wall // Create a custom plane for the leg that sits on the wall
const customPlane = { customPlane = {
plane = { plane = {
origin = { x = -filletRadius, y = 0, z = 0 }, origin = { x = -filletRadius, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 }, xAxis = { x = 0, y = 1, z = 0 },
@ -86,7 +86,7 @@ const customPlane = {
} }
// Create a sketch for the second leg // Create a sketch for the second leg
const bracketLeg2Sketch = startSketchOn(customPlane) bracketLeg2Sketch = startSketchOn(customPlane)
|> startProfileAt([0, -filletRadius], %) |> startProfileAt([0, -filletRadius], %)
|> line([width, 0], %) |> line([width, 0], %)
|> line([0, -wallMountL], %, $fillet3) |> line([0, -wallMountL], %, $fillet3)
@ -102,7 +102,7 @@ const bracketLeg2Sketch = startSketchOn(customPlane)
}, %), %) }, %), %)
// Extrude the second leg // Extrude the second leg
const bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch) bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|> fillet({ |> fillet({
radius = extFilletRadius, radius = extFilletRadius,
tags = [ tags = [
@ -135,8 +135,8 @@ function findLineInExampleCode({
} }
export const bracketWidthConstantLine = findLineInExampleCode({ export const bracketWidthConstantLine = findLineInExampleCode({
searchText: 'const width', searchText: 'width =',
}) })
export const bracketThicknessCalculationLine = findLineInExampleCode({ export const bracketThicknessCalculationLine = findLineInExampleCode({
searchText: 'const thickness', searchText: 'thickness =',
}) })

View File

@ -5,7 +5,7 @@ import { isDesktop } from './isDesktop'
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants' import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { parseProjectSettings } from 'lang/wasm' import { parseProjectSettings } from 'lang/wasm'
import { err } from './trap' import { err, reportRejection } from './trap'
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils' import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
interface OnSubmitProps { interface OnSubmitProps {
@ -28,7 +28,7 @@ export function kclCommands(
groupId: 'code', groupId: 'code',
icon: 'code', icon: 'code',
onSubmit: () => { onSubmit: () => {
kclManager.format() kclManager.format().catch(reportRejection)
}, },
}, },
{ {

View File

@ -585,6 +585,17 @@ export function canLoftSelection(selection: Selections) {
) )
} }
export function canShellSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
return commonNodes.every(
(n) =>
n.selection.artifact?.type === 'cap' ||
n.selection.artifact?.type === 'wall'
)
}
// This accounts for non-geometry selections under "other" // This accounts for non-geometry selections under "other"
export type ResolvedSelectionType = Artifact['type'] | 'other' export type ResolvedSelectionType = Artifact['type'] | 'other'
export type SelectionCountsByType = Map<ResolvedSelectionType, number> export type SelectionCountsByType = Map<ResolvedSelectionType, number>

View File

@ -190,9 +190,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'shell', id: 'shell',
onClick: () => console.error('Shell not yet implemented'), onClick: ({ commandBarSend }) => {
commandBarSend({
type: 'Find and select command',
data: { name: 'Shell', groupId: 'modeling' },
})
},
disabled: (state) => !state.can({ type: 'Shell' }),
icon: 'shell', icon: 'shell',
status: 'kcl-only', status: 'available',
title: 'Shell', title: 'Shell',
description: 'Hollow out a 3D solid.', description: 'Hollow out a 3D solid.',
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/shell' }], links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/shell' }],

View File

@ -109,11 +109,11 @@ export function useCalculateKclExpression({
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
a.type === 'VariableDeclaration' && a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__' a.declaration.id?.name === '__result__'
) )
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init resultDeclaration?.declaration.init
const result = execState.memory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)

View File

@ -80,6 +80,7 @@ import { ToolbarModeName } from 'lib/toolbar'
import { quaternionFromUpNForward } from 'clientSideScene/helpers' import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { Vector3 } from 'three' import { Vector3 } from 'three'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { addShell } from 'lang/modifyAst/addShell'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
@ -260,6 +261,7 @@ export type ModelingMachineEvent =
| { type: 'Make'; data: ModelingCommandSchema['Make'] } | { type: 'Make'; data: ModelingCommandSchema['Make'] }
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] } | { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] } | { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] } | { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] } | { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] } | { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
@ -392,6 +394,7 @@ export const modelingMachine = setup({
'Selection is on face': () => false, 'Selection is on face': () => false,
'has valid sweep selection': () => false, 'has valid sweep selection': () => false,
'has valid loft selection': () => false, 'has valid loft selection': () => false,
'has valid shell selection': () => false,
'has valid edge treatment selection': () => false, 'has valid edge treatment selection': () => false,
'Has exportable geometry': () => false, 'Has exportable geometry': () => false,
'has valid selection for deletion': () => false, 'has valid selection for deletion': () => false,
@ -1584,6 +1587,66 @@ export const modelingMachine = setup({
updateAstResult.newAst updateAstResult.newAst
) )
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
shellAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Shell'] | undefined
}) => {
if (!input) {
return new Error('No input provided')
}
// Extract inputs
const ast = kclManager.ast
const { selection, thickness } = input
// Insert the thickness variable if it exists
if (
'variableName' in thickness &&
thickness.variableName &&
thickness.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(
thickness.insertIndex,
0,
thickness.variableDeclarationAst
)
ast.body = newBody
}
// Perform the shell op
const shellResult = addShell({
node: ast,
selection,
artifactGraph: engineCommandManager.artifactGraph,
thickness:
'variableName' in thickness
? thickness.variableIdentifierAst
: thickness.valueAst,
})
if (err(shellResult)) {
return err(shellResult)
}
const updateAstResult = await kclManager.updateAst(
shellResult.modifiedAst,
true,
{
focusPath: [shellResult.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) { if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections) editorManager.selectRange(updateAstResult?.selections)
} }
@ -1627,6 +1690,13 @@ export const modelingMachine = setup({
Loft: { Loft: {
target: 'Applying loft', target: 'Applying loft',
guard: 'has valid loft selection',
reenter: true,
},
Shell: {
target: 'Applying shell',
guard: 'has valid shell selection',
reenter: true, reenter: true,
}, },
@ -2391,6 +2461,19 @@ export const modelingMachine = setup({
onError: ['idle'], onError: ['idle'],
}, },
}, },
'Applying shell': {
invoke: {
src: 'shellAstMod',
id: 'shellAstMod',
input: ({ event }) => {
if (event.type !== 'Shell') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
}, },
initial: 'idle', initial: 'idle',
@ -2491,7 +2574,7 @@ export function canRectangleOrCircleTool({
// This should not be returning false, and it should be caught // This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on. // but we need to simulate old behavior to move on.
if (err(node)) return false if (err(node)) return false
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression' return node.node?.declaration.init.type !== 'PipeExpression'
} }
/** If the sketch contains `close` or `circle` stdlib functions it must be closed */ /** If the sketch contains `close` or `circle` stdlib functions it must be closed */
@ -2508,8 +2591,8 @@ export function isClosedSketch({
// This should not be returning false, and it should be caught // This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on. // but we need to simulate old behavior to move on.
if (err(node)) return false if (err(node)) return false
if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false if (node.node?.declaration.init.type !== 'PipeExpression') return false
return node.node.declarations[0].init.body.some( return node.node.declaration.init.body.some(
(node) => (node) =>
node.type === 'CallExpression' && node.type === 'CallExpression' &&
(node.callee.name === 'close' || node.callee.name === 'circle') (node.callee.name === 'close' || node.callee.name === 'circle')

View File

@ -395,10 +395,10 @@ fn do_stdlib_inner(
#const_struct #const_struct
fn #boxed_fn_name_ident( fn #boxed_fn_name_ident(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box<dyn std::future::Future<Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>> + Send + '_>, Box<dyn std::future::Future<Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>> + Send + '_>,
> { > {
Box::pin(#fn_name_ident(exec_state, args)) Box::pin(#fn_name_ident(exec_state, args))
} }
@ -770,12 +770,12 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() { async fn #test_name_mock() {
let program = crate::Program::parse_no_errs(#code_block).unwrap(); let program = crate::Program::parse_no_errs(#code_block).unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())), engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap(); ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
@ -785,7 +785,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
async fn #test_name() { async fn #test_name() {
let code = #code_block; let code = #code_block;
// Note, `crate` must be kcl_lib // Note, `crate` must be kcl_lib
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm).await.unwrap(); let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await.unwrap();
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99); twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99);
} }
} }

View File

@ -3,7 +3,7 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() { async fn test_mock_example_someFn0() {
let program = crate::Program::parse_no_errs("someFn()").unwrap(); let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -12,7 +12,7 @@ mod test_examples_someFn {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -22,8 +22,11 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() { async fn kcl_test_example_someFn0() {
let code = "someFn()"; let code = "someFn()";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -44,12 +47,12 @@ pub(crate) struct SomeFn {}
#[doc = "Std lib function: someFn\nDocs"] #[doc = "Std lib function: someFn\nDocs"]
pub(crate) const SomeFn: SomeFn = SomeFn {}; pub(crate) const SomeFn: SomeFn = SomeFn {};
fn boxed_someFn( fn boxed_someFn(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -3,7 +3,7 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() { async fn test_mock_example_someFn0() {
let program = crate::Program::parse_no_errs("someFn()").unwrap(); let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -12,7 +12,7 @@ mod test_examples_someFn {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -22,8 +22,11 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() { async fn kcl_test_example_someFn0() {
let code = "someFn()"; let code = "someFn()";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -44,12 +47,12 @@ pub(crate) struct SomeFn {}
#[doc = "Std lib function: someFn\nDocs"] #[doc = "Std lib function: someFn\nDocs"]
pub(crate) const SomeFn: SomeFn = SomeFn {}; pub(crate) const SomeFn: SomeFn = SomeFn {};
fn boxed_someFn( fn boxed_someFn(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap(); crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() { async fn kcl_test_example_show0() {
let code = "This is another code block.\nyes sirrr.\nshow"; let code = "This is another code block.\nyes sirrr.\nshow";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -38,7 +41,7 @@ mod test_examples_show {
async fn test_mock_example_show1() { async fn test_mock_example_show1() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -47,7 +50,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -57,8 +60,11 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show1() { async fn kcl_test_example_show1() {
let code = "This is code.\nIt does other shit.\nshow"; let code = "This is code.\nIt does other shit.\nshow";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -79,12 +85,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."] #[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {}; pub(crate) const Show: Show = Show {};
fn boxed_show( fn boxed_show(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() { async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow"; let code = "This is code.\nIt does other shit.\nshow";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -45,12 +48,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."] #[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {}; pub(crate) const Show: Show = Show {};
fn boxed_show( fn boxed_show(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -5,7 +5,7 @@ mod test_examples_my_func {
let program = let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc") crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc")
.unwrap(); .unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -14,7 +14,7 @@ mod test_examples_my_func {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -24,8 +24,11 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func0() { async fn kcl_test_example_my_func0() {
let code = "This is another code block.\nyes sirrr.\nmyFunc"; let code = "This is another code block.\nyes sirrr.\nmyFunc";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -39,7 +42,7 @@ mod test_examples_my_func {
async fn test_mock_example_my_func1() { async fn test_mock_example_my_func1() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -48,7 +51,7 @@ mod test_examples_my_func {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -58,8 +61,11 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func1() { async fn kcl_test_example_my_func1() {
let code = "This is code.\nIt does other shit.\nmyFunc"; let code = "This is code.\nIt does other shit.\nmyFunc";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -80,12 +86,12 @@ pub(crate) struct MyFunc {}
#[doc = "Std lib function: myFunc\nThis is some function.\nIt does shit."] #[doc = "Std lib function: myFunc\nThis is some function.\nIt does shit."]
pub(crate) const MyFunc: MyFunc = MyFunc {}; pub(crate) const MyFunc: MyFunc = MyFunc {};
fn boxed_my_func( fn boxed_my_func(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -5,7 +5,7 @@ mod test_examples_line_to {
let program = let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nlineTo") crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nlineTo")
.unwrap(); .unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -14,7 +14,7 @@ mod test_examples_line_to {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -24,8 +24,11 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_line_to0() { async fn kcl_test_example_line_to0() {
let code = "This is another code block.\nyes sirrr.\nlineTo"; let code = "This is another code block.\nyes sirrr.\nlineTo";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -39,7 +42,7 @@ mod test_examples_line_to {
async fn test_mock_example_line_to1() { async fn test_mock_example_line_to1() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nlineTo").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nlineTo").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -48,7 +51,7 @@ mod test_examples_line_to {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -58,8 +61,11 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_line_to1() { async fn kcl_test_example_line_to1() {
let code = "This is code.\nIt does other shit.\nlineTo"; let code = "This is code.\nIt does other shit.\nlineTo";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -80,12 +86,12 @@ pub(crate) struct LineTo {}
#[doc = "Std lib function: lineTo\nThis is some function.\nIt does shit."] #[doc = "Std lib function: lineTo\nThis is some function.\nIt does shit."]
pub(crate) const LineTo: LineTo = LineTo {}; pub(crate) const LineTo: LineTo = LineTo {};
fn boxed_line_to( fn boxed_line_to(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_min {
async fn test_mock_example_min0() { async fn test_mock_example_min0() {
let program = let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmin").unwrap(); crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmin").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_min {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_min0() { async fn kcl_test_example_min0() {
let code = "This is another code block.\nyes sirrr.\nmin"; let code = "This is another code block.\nyes sirrr.\nmin";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -38,7 +41,7 @@ mod test_examples_min {
async fn test_mock_example_min1() { async fn test_mock_example_min1() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmin").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmin").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -47,7 +50,7 @@ mod test_examples_min {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -57,8 +60,11 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_min1() { async fn kcl_test_example_min1() {
let code = "This is code.\nIt does other shit.\nmin"; let code = "This is code.\nIt does other shit.\nmin";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -79,12 +85,12 @@ pub(crate) struct Min {}
#[doc = "Std lib function: min\nThis is some function.\nIt does shit."] #[doc = "Std lib function: min\nThis is some function.\nIt does shit."]
pub(crate) const Min: Min = Min {}; pub(crate) const Min: Min = Min {};
fn boxed_min( fn boxed_min(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() { async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow"; let code = "This is code.\nIt does other shit.\nshow";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -45,12 +48,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."] #[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {}; pub(crate) const Show: Show = Show {};
fn boxed_show( fn boxed_show(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_import {
async fn test_mock_example_import0() { async fn test_mock_example_import0() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_import {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_import {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() { async fn kcl_test_example_import0() {
let code = "This is code.\nIt does other shit.\nimport"; let code = "This is code.\nIt does other shit.\nimport";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -45,12 +48,12 @@ pub(crate) struct Import {}
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."] #[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
pub(crate) const Import: Import = Import {}; pub(crate) const Import: Import = Import {};
fn boxed_import( fn boxed_import(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_import {
async fn test_mock_example_import0() { async fn test_mock_example_import0() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_import {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_import {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() { async fn kcl_test_example_import0() {
let code = "This is code.\nIt does other shit.\nimport"; let code = "This is code.\nIt does other shit.\nimport";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -45,12 +48,12 @@ pub(crate) struct Import {}
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."] #[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
pub(crate) const Import: Import = Import {}; pub(crate) const Import: Import = Import {};
fn boxed_import( fn boxed_import(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_import {
async fn test_mock_example_import0() { async fn test_mock_example_import0() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_import {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_import {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() { async fn kcl_test_example_import0() {
let code = "This is code.\nIt does other shit.\nimport"; let code = "This is code.\nIt does other shit.\nimport";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -45,12 +48,12 @@ pub(crate) struct Import {}
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."] #[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
pub(crate) const Import: Import = Import {}; pub(crate) const Import: Import = Import {};
fn boxed_import( fn boxed_import(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap(); crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -23,8 +23,11 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() { async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow"; let code = "This is code.\nIt does other shit.\nshow";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -45,12 +48,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."] #[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {}; pub(crate) const Show: Show = Show {};
fn boxed_show( fn boxed_show(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -3,7 +3,7 @@ mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_some_function0() { async fn test_mock_example_some_function0() {
let program = crate::Program::parse_no_errs("someFunction()").unwrap(); let program = crate::Program::parse_no_errs("someFunction()").unwrap();
let ctx = crate::executor::ExecutorContext { let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
@ -12,7 +12,7 @@ mod test_examples_some_function {
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()), stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::default())
.await .await
@ -22,8 +22,11 @@ mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_some_function0() { async fn kcl_test_example_some_function0() {
let code = "someFunction()"; let code = "someFunction()";
let result = let result = crate::test_server::execute_and_snapshot(
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm) code,
crate::settings::types::UnitLength::Mm,
None,
)
.await .await
.unwrap(); .unwrap();
twenty_twenty::assert_image( twenty_twenty::assert_image(
@ -44,12 +47,12 @@ pub(crate) struct SomeFunction {}
#[doc = "Std lib function: someFunction\nDocs"] #[doc = "Std lib function: someFunction\nDocs"]
pub(crate) const SomeFunction: SomeFunction = SomeFunction {}; pub(crate) const SomeFunction: SomeFunction = SomeFunction {};
fn boxed_some_function( fn boxed_some_function(
exec_state: &mut crate::executor::ExecState, exec_state: &mut crate::ExecState,
args: crate::std::Args, args: crate::std::Args,
) -> std::pin::Pin< ) -> std::pin::Pin<
Box< Box<
dyn std::future::Future< dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>, Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send > + Send
+ '_, + '_,
>, >,

View File

@ -15,7 +15,6 @@ redo-kcl-stdlib-docs:
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
# Copy a test KCL file from executor tests into a new simulation test. # Copy a test KCL file from executor tests into a new simulation test.
copy-exec-test-into-sim-test test_name: copy-exec-test-into-sim-test test_name:
mkdir -p kcl/tests/{{test_name}} mkdir -p kcl/tests/{{test_name}}

View File

@ -18,7 +18,7 @@ pub fn bench_execute(c: &mut Criterion) {
let rt = Runtime::new().unwrap(); let rt = Runtime::new().unwrap();
// Spawn a future onto the runtime // Spawn a future onto the runtime
b.iter(|| { b.iter(|| {
rt.block_on(test_server::execute_and_snapshot(s, Mm)).unwrap(); rt.block_on(test_server::execute_and_snapshot(s, Mm, None)).unwrap();
}); });
}); });
group.finish(); group.finish();
@ -38,7 +38,7 @@ pub fn bench_lego(c: &mut Criterion) {
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string()); let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
// Spawn a future onto the runtime // Spawn a future onto the runtime
b.iter(|| { b.iter(|| {
rt.block_on(test_server::execute_and_snapshot(&code, Mm)).unwrap(); rt.block_on(test_server::execute_and_snapshot(&code, Mm, None)).unwrap();
}); });
}); });
} }

View File

@ -3,7 +3,7 @@ use iai::black_box;
async fn execute_server_rack_heavy() { async fn execute_server_rack_heavy() {
let code = SERVER_RACK_HEAVY_PROGRAM; let code = SERVER_RACK_HEAVY_PROGRAM;
black_box( black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm) kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
.await .await
.unwrap(), .unwrap(),
); );
@ -12,7 +12,7 @@ async fn execute_server_rack_heavy() {
async fn execute_server_rack_lite() { async fn execute_server_rack_lite() {
let code = SERVER_RACK_LITE_PROGRAM; let code = SERVER_RACK_LITE_PROGRAM;
black_box( black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm) kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
.await .await
.unwrap(), .unwrap(),
); );

View File

@ -1,3 +0,0 @@
pub mod cache;
pub mod modify;
pub mod types;

View File

@ -22,7 +22,7 @@ use super::ExecutionKind;
use crate::{ use crate::{
engine::EngineManager, engine::EngineManager,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator}, execution::{DefaultPlanes, IdGenerator},
SourceRange, SourceRange,
}; };

View File

@ -20,7 +20,7 @@ use kittycad_modeling_cmds::{self as kcmc};
use super::ExecutionKind; use super::ExecutionKind;
use crate::{ use crate::{
errors::KclError, errors::KclError,
executor::{DefaultPlanes, IdGenerator}, execution::{DefaultPlanes, IdGenerator},
SourceRange, SourceRange,
}; };

View File

@ -11,7 +11,7 @@ use wasm_bindgen::prelude::*;
use crate::{ use crate::{
engine::ExecutionKind, engine::ExecutionKind,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator}, execution::{DefaultPlanes, IdGenerator},
SourceRange, SourceRange,
}; };

View File

@ -32,7 +32,7 @@ use uuid::Uuid;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator, Point3d}, execution::{DefaultPlanes, IdGenerator, Point3d},
SourceRange, SourceRange,
}; };

View File

@ -4,14 +4,19 @@ use async_recursion::async_recursion;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier}, execution::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
},
parsing::ast::types::{ parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
CallExpressionKw, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, CallExpressionKw, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator, ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
}, },
source_range::SourceRange, source_range::SourceRange,
std::{args::Arg, FunctionKind}, std::{
args::{Arg, KwArgs},
FunctionKind,
},
}; };
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01; const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
@ -386,7 +391,14 @@ impl Node<CallExpressionKw> {
None None
}; };
let args = crate::std::Args::new_kw(fn_args, unlabeled, self.into(), ctx.clone()); let args = crate::std::Args::new_kw(
KwArgs {
unlabeled,
labeled: fn_args,
},
self.into(),
ctx.clone(),
);
match ctx.stdlib.get_either(fn_name) { match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => { FunctionKind::Core(func) => {
// Attempt to call the function. // Attempt to call the function.
@ -807,3 +819,10 @@ impl Property {
} }
} }
} }
impl Node<PipeExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
}
}

View File

@ -2,7 +2,7 @@ use schemars::JsonSchema;
use crate::{ use crate::{
errors::KclError, errors::KclError,
executor::{ execution::{
call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
}, },
parsing::ast::types::FunctionExpression, parsing::ast::types::FunctionExpression,

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::KclErrorDetails, errors::KclErrorDetails,
exec::{ProgramMemory, Sketch}, exec::{ProgramMemory, Sketch},
executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier}, execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode}, parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
std::{args::Arg, FnAsArg}, std::{args::Arg, FnAsArg},
ExecState, ExecutorContext, KclError, SourceRange, ExecState, ExecutorContext, KclError, SourceRange,
@ -262,9 +262,6 @@ impl KclValue {
} }
} }
pub(crate) fn is_function(&self) -> bool {
matches!(self, KclValue::Function { .. })
}
/// Put the number into a KCL value. /// Put the number into a KCL value.
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self { pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Self::Number { value: f, meta } Self::Number { value: f, meta }
@ -496,7 +493,7 @@ impl KclValue {
) )
.await .await
} else { } else {
crate::executor::call_user_defined_function( crate::execution::call_user_defined_function(
args, args,
closure_memory.as_ref(), closure_memory.as_ref(),
expression.as_ref(), expression.as_ref(),

View File

@ -1,6 +1,6 @@
//! The executor for the AST. //! The executor for the AST.
use std::{collections::HashSet, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use anyhow::Result; use anyhow::Result;
use async_recursion::async_recursion; use async_recursion::async_recursion;
@ -20,14 +20,18 @@ use serde::{Deserialize, Serialize};
type Point2D = kcmc::shared::Point2d<f64>; type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>; type Point3D = kcmc::shared::Point3d<f64>;
pub use crate::kcl_value::KclValue; pub use function_param::FunctionParam;
pub use kcl_value::{KclObjectFields, KclValue};
use crate::{ use crate::{
engine::{EngineManager, ExecutionKind}, engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem}, fs::{FileManager, FileSystem},
parsing::ast::{ parsing::ast::{
cache::{get_changed_program, CacheInformation}, cache::{get_changed_program, CacheInformation},
types::{BodyItem, Expr, FunctionExpression, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode}, types::{
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode,
},
}, },
settings::types::UnitLength, settings::types::UnitLength,
source_range::{ModuleId, SourceRange}, source_range::{ModuleId, SourceRange},
@ -35,6 +39,10 @@ use crate::{
ExecError, Program, ExecError, Program,
}; };
mod exec_ast;
mod function_param;
mod kcl_value;
/// State for executing a program. /// State for executing a program.
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
@ -50,7 +58,7 @@ pub struct ExecState {
/// expression. If we're not currently in a pipeline, this will be None. /// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>, pub pipe_value: Option<KclValue>,
/// Identifiers that have been exported from the current module. /// Identifiers that have been exported from the current module.
pub module_exports: HashSet<String>, pub module_exports: Vec<String>,
/// The stack of import statements for detecting circular module imports. /// The stack of import statements for detecting circular module imports.
/// If this is empty, we're not currently executing an import statement. /// If this is empty, we're not currently executing an import statement.
pub import_stack: Vec<std::path::PathBuf>, pub import_stack: Vec<std::path::PathBuf>,
@ -61,7 +69,7 @@ pub struct ExecState {
} }
impl ExecState { impl ExecState {
pub fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId { fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId {
// Need to avoid borrowing self in the closure. // Need to avoid borrowing self in the closure.
let new_module_id = ModuleId::from_usize(self.path_to_source_id.len()); let new_module_id = ModuleId::from_usize(self.path_to_source_id.len());
let mut is_new = false; let mut is_new = false;
@ -1498,6 +1506,9 @@ pub struct ExecutorSettings {
/// Should engine store this for replay? /// Should engine store this for replay?
/// If so, under what name? /// If so, under what name?
pub replay: Option<String>, pub replay: Option<String>,
/// The directory of the current project. This is used for resolving import
/// paths. If None is given, the current working directory is used.
pub project_directory: Option<PathBuf>,
} }
impl Default for ExecutorSettings { impl Default for ExecutorSettings {
@ -1508,6 +1519,7 @@ impl Default for ExecutorSettings {
enable_ssao: false, enable_ssao: false,
show_grid: false, show_grid: false,
replay: None, replay: None,
project_directory: None,
} }
} }
} }
@ -1520,6 +1532,7 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
enable_ssao: config.settings.modeling.enable_ssao.into(), enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid, show_grid: config.settings.modeling.show_scale_grid,
replay: None, replay: None,
project_directory: None,
} }
} }
} }
@ -1532,6 +1545,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
enable_ssao: config.settings.modeling.enable_ssao.into(), enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid, show_grid: config.settings.modeling.show_scale_grid,
replay: None, replay: None,
project_directory: None,
} }
} }
} }
@ -1544,6 +1558,7 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
enable_ssao: modeling.enable_ssao.into(), enable_ssao: modeling.enable_ssao.into(),
show_grid: modeling.show_scale_grid, show_grid: modeling.show_scale_grid,
replay: None, replay: None,
project_directory: None,
} }
} }
} }
@ -1774,6 +1789,7 @@ impl ExecutorContext {
enable_ssao: false, enable_ssao: false,
show_grid: false, show_grid: false,
replay: None, replay: None,
project_directory: None,
}, },
None, None,
engine_addr, engine_addr,
@ -1785,7 +1801,7 @@ impl ExecutorContext {
pub async fn reset_scene( pub async fn reset_scene(
&self, &self,
exec_state: &mut ExecState, exec_state: &mut ExecState,
source_range: crate::executor::SourceRange, source_range: crate::execution::SourceRange,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
self.engine self.engine
.clear_scene(&mut exec_state.id_generator, source_range) .clear_scene(&mut exec_state.id_generator, source_range)
@ -1850,7 +1866,7 @@ impl ExecutorContext {
) )
.await?; .await?;
self.inner_execute(&cache_result.program, exec_state, crate::executor::BodyType::Root) self.inner_execute(&cache_result.program, exec_state, crate::execution::BodyType::Root)
.await?; .await?;
let session_data = self.engine.get_session_data(); let session_data = self.engine.get_session_data();
Ok(session_data) Ok(session_data)
@ -1870,67 +1886,15 @@ impl ExecutorContext {
match statement { match statement {
BodyItem::ImportStatement(import_stmt) => { BodyItem::ImportStatement(import_stmt) => {
let source_range = SourceRange::from(import_stmt); let source_range = SourceRange::from(import_stmt);
let path = import_stmt.path.clone(); let (module_memory, module_exports) =
// Empty path is used by the top-level module. self.open_module(&import_stmt.path, exec_state, source_range).await?;
if path.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
message: "import path cannot be empty".to_owned(),
source_ranges: vec![source_range],
}));
}
let resolved_path = std::path::PathBuf::from(&path);
if exec_state.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
resolved_path.to_string_lossy()
),
source_ranges: vec![import_stmt.into()],
}));
}
let module_id = exec_state.add_module(resolved_path.clone());
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
// TODO handle parsing errors properly
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
let (module_memory, module_exports) = {
exec_state.import_stack.push(resolved_path.clone());
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
let original_memory = std::mem::take(&mut exec_state.memory);
let original_exports = std::mem::take(&mut exec_state.module_exports);
let result = self
.inner_execute(&program, exec_state, crate::executor::BodyType::Root)
.await;
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
self.engine.replace_execution_kind(original_execution);
exec_state.import_stack.pop();
result.map_err(|err| { match &import_stmt.selector {
if let KclError::ImportCycle(_) = err { ImportSelector::List { items } => {
// It was an import cycle. Keep the original message. for import_item in items {
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
message: format!(
"Error loading imported file. Open it to view more details. {path}: {}",
err.message()
),
source_ranges: vec![source_range],
})
}
})?;
(module_memory, module_exports)
};
for import_item in &import_stmt.items {
// Extract the item from the module. // Extract the item from the module.
let item = module_memory let item =
module_memory
.get(&import_item.name.name, import_item.into()) .get(&import_item.name.name, import_item.into())
.map_err(|_err| { .map_err(|_err| {
KclError::UndefinedValue(KclErrorDetails { KclError::UndefinedValue(KclErrorDetails {
@ -1955,6 +1919,33 @@ impl ExecutorContext {
item.clone(), item.clone(),
SourceRange::from(&import_item.name), SourceRange::from(&import_item.name),
)?; )?;
if let ItemVisibility::Export = import_stmt.visibility {
exec_state.module_exports.push(import_item.identifier().to_owned());
}
}
}
ImportSelector::Glob(_) => {
for name in module_exports.iter() {
let item = module_memory.get(name, source_range).map_err(|_err| {
KclError::Internal(KclErrorDetails {
message: format!("{} is not defined in module (but was exported?)", name),
source_ranges: vec![source_range],
})
})?;
exec_state.memory.add(name, item.clone(), source_range)?;
if let ItemVisibility::Export = import_stmt.visibility {
exec_state.module_exports.push(name.clone());
}
}
}
ImportSelector::None(_) => {
return Err(KclError::Semantic(KclErrorDetails {
message: "Importing whole module is not yet implemented, sorry.".to_owned(),
source_ranges: vec![source_range],
}));
}
} }
last_expr = None; last_expr = None;
} }
@ -1971,34 +1962,23 @@ impl ExecutorContext {
); );
} }
BodyItem::VariableDeclaration(variable_declaration) => { BodyItem::VariableDeclaration(variable_declaration) => {
for declaration in &variable_declaration.declarations { let var_name = variable_declaration.declaration.id.name.to_string();
let var_name = declaration.id.name.to_string(); let source_range = SourceRange::from(&variable_declaration.declaration.init);
let source_range = SourceRange::from(&declaration.init);
let metadata = Metadata { source_range }; let metadata = Metadata { source_range };
let memory_item = self let memory_item = self
.execute_expr( .execute_expr(
&declaration.init, &variable_declaration.declaration.init,
exec_state, exec_state,
&metadata, &metadata,
StatementKind::Declaration { name: &var_name }, StatementKind::Declaration { name: &var_name },
) )
.await?; .await?;
let is_function = memory_item.is_function();
exec_state.memory.add(&var_name, memory_item, source_range)?; exec_state.memory.add(&var_name, memory_item, source_range)?;
// Track exports. // Track exports.
match variable_declaration.visibility { if let ItemVisibility::Export = variable_declaration.visibility {
ItemVisibility::Export => { exec_state.module_exports.push(var_name);
if !is_function {
return Err(KclError::Semantic(KclErrorDetails {
message: "Only functions can be exported".to_owned(),
source_ranges: vec![source_range],
}));
}
exec_state.module_exports.insert(var_name);
}
ItemVisibility::Default => {}
}
} }
last_expr = None; last_expr = None;
} }
@ -2033,6 +2013,68 @@ impl ExecutorContext {
Ok(last_expr) Ok(last_expr)
} }
async fn open_module(
&self,
path: &str,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<(ProgramMemory, Vec<String>), KclError> {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(path)
} else {
std::path::PathBuf::from(&path)
};
if exec_state.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
resolved_path.to_string_lossy()
),
source_ranges: vec![source_range],
}));
}
let module_id = exec_state.add_module(resolved_path.clone());
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
// TODO handle parsing errors properly
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
exec_state.import_stack.push(resolved_path.clone());
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
let original_memory = std::mem::take(&mut exec_state.memory);
let original_exports = std::mem::take(&mut exec_state.module_exports);
let result = self
.inner_execute(&program, exec_state, crate::execution::BodyType::Root)
.await;
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
self.engine.replace_execution_kind(original_execution);
exec_state.import_stack.pop();
result.map_err(|err| {
if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
message: format!(
"Error loading imported file. Open it to view more details. {path}: {}",
err.message()
),
source_ranges: vec![source_range],
})
}
})?;
Ok((module_memory, module_exports))
}
pub async fn execute_expr<'a>( pub async fn execute_expr<'a>(
&self, &self,
init: &Expr, init: &Expr,
@ -2120,7 +2162,7 @@ impl ExecutorContext {
self.engine self.engine
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(), crate::execution::SourceRange::default(),
ModelingCmd::from(mcmd::ZoomToFit { ModelingCmd::from(mcmd::ZoomToFit {
object_ids: Default::default(), object_ids: Default::default(),
animated: false, animated: false,
@ -2134,7 +2176,7 @@ impl ExecutorContext {
.engine .engine
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(), crate::execution::SourceRange::default(),
ModelingCmd::from(mcmd::TakeSnapshot { ModelingCmd::from(mcmd::TakeSnapshot {
format: ImageFormat::Png, format: ImageFormat::Png,
}), }),

View File

@ -60,10 +60,8 @@ mod coredump;
mod docs; mod docs;
mod engine; mod engine;
mod errors; mod errors;
mod executor; mod execution;
mod fs; mod fs;
mod function_param;
mod kcl_value;
pub mod lint; pub mod lint;
mod log; mod log;
mod lsp; mod lsp;
@ -84,7 +82,7 @@ mod wasm;
pub use coredump::CoreDump; pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind}; pub use engine::{EngineManager, ExecutionKind};
pub use errors::{CompilationError, ConnectionError, ExecError, KclError}; pub use errors::{CompilationError, ConnectionError, ExecError, KclError};
pub use executor::{ExecState, ExecutorContext, ExecutorSettings}; pub use execution::{ExecState, ExecutorContext, ExecutorSettings};
pub use lsp::{ pub use lsp::{
copilot::Backend as CopilotLspBackend, copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand}, kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
@ -100,7 +98,7 @@ pub use source_range::{ModuleId, SourceRange};
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module. // Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
// Ideally we wouldn't export these things at all, they should only be used for testing. // Ideally we wouldn't export these things at all, they should only be used for testing.
pub mod exec { pub mod exec {
pub use crate::executor::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch}; pub use crate::execution::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch};
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -60,11 +60,7 @@ pub fn lint_variables(decl: Node) -> Result<Vec<Discovered>> {
return Ok(vec![]); return Ok(vec![]);
}; };
Ok(decl lint_lower_camel_case_var(&decl.declaration)
.declarations
.iter()
.flat_map(|v| lint_lower_camel_case_var(v).unwrap_or_default())
.collect())
} }
pub fn lint_object_properties(decl: Node) -> Result<Vec<Discovered>> { pub fn lint_object_properties(decl: Node) -> Result<Vec<Discovered>> {

View File

@ -19,7 +19,7 @@ impl Notification for AstUpdated {
pub enum MemoryUpdated {} pub enum MemoryUpdated {}
impl Notification for MemoryUpdated { impl Notification for MemoryUpdated {
type Params = crate::executor::ProgramMemory; type Params = crate::execution::ProgramMemory;
const METHOD: &'static str = "kcl/memoryUpdated"; const METHOD: &'static str = "kcl/memoryUpdated";
} }

View File

@ -115,7 +115,7 @@ pub struct Backend {
/// information. /// information.
pub last_successful_ast_state: Arc<RwLock<Option<OldAstState>>>, pub last_successful_ast_state: Arc<RwLock<Option<OldAstState>>>,
/// Memory maps. /// Memory maps.
pub memory_map: DashMap<String, crate::executor::ProgramMemory>, pub memory_map: DashMap<String, crate::execution::ProgramMemory>,
/// Current code. /// Current code.
pub code_map: DashMap<String, Vec<u8>>, pub code_map: DashMap<String, Vec<u8>>,
/// Diagnostics. /// Diagnostics.
@ -129,7 +129,7 @@ pub struct Backend {
/// If we can send telemetry for this user. /// If we can send telemetry for this user.
pub can_send_telemetry: bool, pub can_send_telemetry: bool,
/// Optional executor context to use if we want to execute the code. /// Optional executor context to use if we want to execute the code.
pub executor_ctx: Arc<RwLock<Option<crate::executor::ExecutorContext>>>, pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
/// If we are currently allowed to execute the ast. /// If we are currently allowed to execute the ast.
pub can_execute: Arc<RwLock<bool>>, pub can_execute: Arc<RwLock<bool>>,
@ -140,7 +140,7 @@ impl Backend {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn new_wasm( pub fn new_wasm(
client: Client, client: Client,
executor_ctx: Option<crate::executor::ExecutorContext>, executor_ctx: Option<crate::execution::ExecutorContext>,
fs: crate::fs::wasm::FileSystemManager, fs: crate::fs::wasm::FileSystemManager,
zoo_client: kittycad::Client, zoo_client: kittycad::Client,
can_send_telemetry: bool, can_send_telemetry: bool,
@ -157,7 +157,7 @@ impl Backend {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn new( pub fn new(
client: Client, client: Client,
executor_ctx: Option<crate::executor::ExecutorContext>, executor_ctx: Option<crate::execution::ExecutorContext>,
zoo_client: kittycad::Client, zoo_client: kittycad::Client,
can_send_telemetry: bool, can_send_telemetry: bool,
) -> Result<Self, String> { ) -> Result<Self, String> {
@ -172,7 +172,7 @@ impl Backend {
fn with_file_manager( fn with_file_manager(
client: Client, client: Client,
executor_ctx: Option<crate::executor::ExecutorContext>, executor_ctx: Option<crate::execution::ExecutorContext>,
fs: crate::fs::FileManager, fs: crate::fs::FileManager,
zoo_client: kittycad::Client, zoo_client: kittycad::Client,
can_send_telemetry: bool, can_send_telemetry: bool,
@ -297,7 +297,7 @@ impl crate::lsp::backend::Backend for Backend {
// Try to get the memory for the current code. // Try to get the memory for the current code.
let has_memory = if let Some(memory) = self.memory_map.get(&filename) { let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
*memory != crate::executor::ProgramMemory::default() *memory != crate::execution::ProgramMemory::default()
} else { } else {
false false
}; };
@ -406,7 +406,7 @@ impl Backend {
*self.can_execute.read().await *self.can_execute.read().await
} }
pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::executor::ExecutorContext>> { pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
self.executor_ctx.read().await self.executor_ctx.read().await
} }
@ -871,7 +871,7 @@ impl Backend {
// Try to get the memory for the current code. // Try to get the memory for the current code.
let has_memory = if let Some(memory) = self.memory_map.get(&filename) { let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
*memory != crate::executor::ProgramMemory::default() *memory != crate::execution::ProgramMemory::default()
} else { } else {
false false
}; };

View File

@ -9,10 +9,10 @@ pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?; let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?;
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?; let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?;
let zoo_client = crate::executor::new_zoo_client(None, None)?; let zoo_client = crate::execution::new_zoo_client(None, None)?;
let executor_ctx = if execute { let executor_ctx = if execute {
Some(crate::executor::ExecutorContext::new(&zoo_client, Default::default()).await?) Some(crate::execution::ExecutorContext::new(&zoo_client, Default::default()).await?)
} else { } else {
None None
}; };

View File

@ -7,7 +7,7 @@ use tower_lsp::{
}; };
use crate::{ use crate::{
executor::ProgramMemory, execution::ProgramMemory,
lsp::test_util::{copilot_lsp_server, kcl_lsp_server}, lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
parsing::ast::types::{Node, Program}, parsing::ast::types::{Node, Program},
}; };
@ -645,7 +645,7 @@ async fn test_kcl_lsp_completions() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const thing= 1 text: r#"thing= 1
st"# st"#
.to_string(), .to_string(),
}, },
@ -688,7 +688,7 @@ async fn test_kcl_lsp_completions_empty_in_comment() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const thing= 1 // st"#.to_string(), text: r#"thing= 1 // st"#.to_string(),
}, },
}) })
.await; .await;
@ -700,7 +700,7 @@ async fn test_kcl_lsp_completions_empty_in_comment() {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier { text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
}, },
position: tower_lsp::lsp_types::Position { line: 0, character: 19 }, position: tower_lsp::lsp_types::Position { line: 0, character: 13 },
}, },
context: None, context: None,
partial_result_params: Default::default(), partial_result_params: Default::default(),
@ -723,7 +723,7 @@ async fn test_kcl_lsp_completions_tags() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> startProfileAt([11.19, 28.35], %) |> startProfileAt([11.19, 28.35], %)
|> line([28.67, -13.25], %, $here) |> line([28.67, -13.25], %, $here)
|> line([-4.12, -22.81], %) |> line([-4.12, -22.81], %)
@ -1058,7 +1058,7 @@ async fn test_kcl_lsp_semantic_tokens_with_modifiers() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
@ -1066,8 +1066,8 @@ async fn test_kcl_lsp_semantic_tokens_with_modifiers() {
|> close(%) |> close(%)
|> extrude(3.14, %) |> extrude(3.14, %)
const thing = {blah: "foo"} thing = {blah: "foo"}
const bar = thing.blah bar = thing.blah
fn myFn = (param1) => { fn myFn = (param1) => {
return param1 return param1
@ -1230,7 +1230,7 @@ async fn test_kcl_lsp_semantic_tokens_multiple_comments() {
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads. // A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
// Define constants like ball diameter, inside diameter, overhange length, and thickness // Define constants like ball diameter, inside diameter, overhange length, and thickness
const sphereDia = 0.5"# sphereDia = 0.5"#
.to_string(), .to_string(),
}, },
}) })
@ -1251,7 +1251,7 @@ const sphereDia = 0.5"#
// Check the semantic tokens. // Check the semantic tokens.
if let tower_lsp::lsp_types::SemanticTokensResult::Tokens(semantic_tokens) = semantic_tokens { if let tower_lsp::lsp_types::SemanticTokensResult::Tokens(semantic_tokens) = semantic_tokens {
assert_eq!(semantic_tokens.data.len(), 7); assert_eq!(semantic_tokens.data.len(), 6);
assert_eq!(semantic_tokens.data[0].length, 15); assert_eq!(semantic_tokens.data[0].length, 15);
assert_eq!(semantic_tokens.data[0].delta_start, 0); assert_eq!(semantic_tokens.data[0].delta_start, 0);
assert_eq!(semantic_tokens.data[0].delta_line, 0); assert_eq!(semantic_tokens.data[0].delta_line, 0);
@ -1279,36 +1279,27 @@ const sphereDia = 0.5"#
.get_semantic_token_type_index(&SemanticTokenType::COMMENT) .get_semantic_token_type_index(&SemanticTokenType::COMMENT)
.unwrap() .unwrap()
); );
assert_eq!(semantic_tokens.data[3].length, 5); assert_eq!(semantic_tokens.data[3].length, 9);
assert_eq!(semantic_tokens.data[3].delta_start, 0); assert_eq!(semantic_tokens.data[3].delta_start, 0);
assert_eq!(semantic_tokens.data[3].delta_line, 1); assert_eq!(semantic_tokens.data[3].delta_line, 1);
assert_eq!( assert_eq!(
semantic_tokens.data[3].token_type, semantic_tokens.data[3].token_type,
server
.get_semantic_token_type_index(&SemanticTokenType::KEYWORD)
.unwrap()
);
assert_eq!(semantic_tokens.data[4].length, 9);
assert_eq!(semantic_tokens.data[4].delta_start, 6);
assert_eq!(semantic_tokens.data[4].delta_line, 0);
assert_eq!(
semantic_tokens.data[4].token_type,
server server
.get_semantic_token_type_index(&SemanticTokenType::VARIABLE) .get_semantic_token_type_index(&SemanticTokenType::VARIABLE)
.unwrap() .unwrap()
); );
assert_eq!(semantic_tokens.data[5].length, 1); assert_eq!(semantic_tokens.data[4].length, 1);
assert_eq!(semantic_tokens.data[5].delta_start, 10); assert_eq!(semantic_tokens.data[4].delta_start, 10);
assert_eq!( assert_eq!(
semantic_tokens.data[5].token_type, semantic_tokens.data[4].token_type,
server server
.get_semantic_token_type_index(&SemanticTokenType::OPERATOR) .get_semantic_token_type_index(&SemanticTokenType::OPERATOR)
.unwrap() .unwrap()
); );
assert_eq!(semantic_tokens.data[6].length, 3); assert_eq!(semantic_tokens.data[5].length, 3);
assert_eq!(semantic_tokens.data[6].delta_start, 2); assert_eq!(semantic_tokens.data[5].delta_start, 2);
assert_eq!( assert_eq!(
semantic_tokens.data[6].token_type, semantic_tokens.data[5].token_type,
server server
.get_semantic_token_type_index(&SemanticTokenType::NUMBER) .get_semantic_token_type_index(&SemanticTokenType::NUMBER)
.unwrap() .unwrap()
@ -1329,7 +1320,7 @@ async fn test_kcl_lsp_document_symbol() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const myVar = 1 text: r#"myVar = 1
startSketchOn('XY')"# startSketchOn('XY')"#
.to_string(), .to_string(),
}, },
@ -1369,7 +1360,7 @@ async fn test_kcl_lsp_document_symbol_tag() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> startProfileAt([11.19, 28.35], %) |> startProfileAt([11.19, 28.35], %)
|> line([28.67, -13.25], %, $here) |> line([28.67, -13.25], %, $here)
|> line([-4.12, -22.81], %) |> line([-4.12, -22.81], %)
@ -1466,13 +1457,13 @@ async fn test_kcl_lsp_formatting_extra_parens() {
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads. // A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
// Define constants like ball diameter, inside diameter, overhange length, and thickness // Define constants like ball diameter, inside diameter, overhange length, and thickness
const sphereDia = 0.5 sphereDia = 0.5
const insideDia = 1 insideDia = 1
const thickness = 0.25 thickness = 0.25
const overHangLength = .4 overHangLength = .4
// Sketch and revolve the inside bearing piece // Sketch and revolve the inside bearing piece
const insideRevolve = startSketchOn('XZ') insideRevolve = startSketchOn('XZ')
|> startProfileAt([insideDia / 2, 0], %) |> startProfileAt([insideDia / 2, 0], %)
|> line([0, thickness + sphereDia / 2], %) |> line([0, thickness + sphereDia / 2], %)
|> line([overHangLength, 0], %) |> line([overHangLength, 0], %)
@ -1486,7 +1477,7 @@ const insideRevolve = startSketchOn('XZ')
|> revolve({ axis: 'y' }, %) |> revolve({ axis: 'y' }, %)
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis) // Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
const sphere = startSketchOn('XZ') sphere = startSketchOn('XZ')
|> startProfileAt([ |> startProfileAt([
0.05 + insideDia / 2 + thickness, 0.05 + insideDia / 2 + thickness,
0 - 0.05 0 - 0.05
@ -1508,7 +1499,7 @@ const sphere = startSketchOn('XZ')
}, %) }, %)
// Sketch and revolve the outside bearing // Sketch and revolve the outside bearing
const outsideRevolve = startSketchOn('XZ') outsideRevolve = startSketchOn('XZ')
|> startProfileAt([ |> startProfileAt([
insideDia / 2 + thickness + sphereDia, insideDia / 2 + thickness + sphereDia,
0 0
@ -1638,7 +1629,7 @@ async fn test_kcl_lsp_rename() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const thing= 1"#.to_string(), text: r#"thing= 1"#.to_string(),
}, },
}) })
.await; .await;
@ -1650,7 +1641,7 @@ async fn test_kcl_lsp_rename() {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier { text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
}, },
position: tower_lsp::lsp_types::Position { line: 0, character: 8 }, position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
}, },
new_name: "newName".to_string(), new_name: "newName".to_string(),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
@ -1667,7 +1658,7 @@ async fn test_kcl_lsp_rename() {
vec![tower_lsp::lsp_types::TextEdit { vec![tower_lsp::lsp_types::TextEdit {
range: tower_lsp::lsp_types::Range { range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 0, character: 0 }, start: tower_lsp::lsp_types::Position { line: 0, character: 0 },
end: tower_lsp::lsp_types::Position { line: 0, character: 13 } end: tower_lsp::lsp_types::Position { line: 0, character: 7 }
}, },
new_text: "newName = 1\n".to_string() new_text: "newName = 1\n".to_string()
}] }]
@ -1773,7 +1764,7 @@ async fn test_kcl_lsp_diagnostic_has_lints() {
uri: "file:///testlint.kcl".try_into().unwrap(), uri: "file:///testlint.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"let THING = 10"#.to_string(), text: r#"THING = 10"#.to_string(),
}, },
}) })
.await; .await;
@ -1859,7 +1850,7 @@ async fn test_copilot_lsp_completions_raw() {
let completions = server let completions = server
.get_completions( .get_completions(
"kcl".to_string(), "kcl".to_string(),
r#"const bracket = startSketchOn('XY') r#"bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
"# "#
.to_string(), .to_string(),
@ -1878,7 +1869,7 @@ async fn test_copilot_lsp_completions_raw() {
let completions_hit_cache = server let completions_hit_cache = server
.get_completions( .get_completions(
"kcl".to_string(), "kcl".to_string(),
r#"const bracket = startSketchOn('XY') r#"bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
"# "#
.to_string(), .to_string(),
@ -1918,7 +1909,7 @@ async fn test_copilot_lsp_completions() {
path: "file:///test.copilot".to_string(), path: "file:///test.copilot".to_string(),
position: crate::lsp::copilot::types::CopilotPosition { line: 3, character: 3 }, position: crate::lsp::copilot::types::CopilotPosition { line: 3, character: 3 },
relative_path: "test.copilot".to_string(), relative_path: "test.copilot".to_string(),
source: r#"const bracket = startSketchOn('XY') source: r#"bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> close(%) |> close(%)
@ -2066,7 +2057,7 @@ async fn test_lsp_initialized() {
async fn test_kcl_lsp_on_change_update_ast() { async fn test_kcl_lsp_on_change_update_ast() {
let server = kcl_lsp_server(false).await.unwrap(); let server = kcl_lsp_server(false).await.unwrap();
let same_text = r#"const thing = 1"#.to_string(); let same_text = r#"thing = 1"#.to_string();
// Send open file. // Send open file.
server server
@ -2102,7 +2093,7 @@ async fn test_kcl_lsp_on_change_update_ast() {
assert_eq!(ast, server.ast_map.get("file:///test.kcl").unwrap().clone()); assert_eq!(ast, server.ast_map.get("file:///test.kcl").unwrap().clone());
// Update the text. // Update the text.
let new_text = r#"const thing = 2"#.to_string(); let new_text = r#"thing = 2"#.to_string();
// Send change file. // Send change file.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2128,7 +2119,7 @@ async fn test_kcl_lsp_on_change_update_ast() {
async fn kcl_test_kcl_lsp_on_change_update_memory() { async fn kcl_test_kcl_lsp_on_change_update_memory() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let same_text = r#"const thing = 1"#.to_string(); let same_text = r#"thing = 1"#.to_string();
// Send open file. // Send open file.
server server
@ -2164,7 +2155,7 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone()); assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone());
// Update the text. // Update the text.
let new_text = r#"const thing = 2"#.to_string(); let new_text = r#"thing = 2"#.to_string();
// Send change file. // Send change file.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2188,7 +2179,7 @@ async fn kcl_test_kcl_lsp_update_units() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let same_text = r#"fn cube = (pos, scale) => { let same_text = r#"fn cube = (pos, scale) => {
const sg = startSketchOn('XY') sg = startSketchOn('XY')
|> startProfileAt(pos, %) |> startProfileAt(pos, %)
|> line([0, scale], %) |> line([0, scale], %)
|> line([scale, 0], %) |> line([scale, 0], %)
@ -2196,7 +2187,7 @@ async fn kcl_test_kcl_lsp_update_units() {
return sg return sg
} }
const part001 = cube([0,0], 20) part001 = cube([0,0], 20)
|> close(%) |> close(%)
|> extrude(20, %)"# |> extrude(20, %)"#
.to_string(); .to_string();
@ -2215,7 +2206,7 @@ const part001 = cube([0,0], 20)
// Get the tokens. // Get the tokens.
let tokens = server.token_map.get("file:///test.kcl").unwrap().clone(); let tokens = server.token_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(tokens.len(), 124); assert_eq!(tokens.len(), 120);
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
@ -2305,7 +2296,7 @@ async fn test_kcl_lsp_diagnostics_on_parse_error() {
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
// Update the text. // Update the text.
let new_text = r#"const thing = 2"#.to_string(); let new_text = r#"thing = 2"#.to_string();
// Send change file. // Send change file.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2336,7 +2327,7 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2356,7 +2347,7 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
// Update the text. // Update the text.
let new_text = r#"const part001 = startSketchOn('XY') let new_text = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2394,7 +2385,7 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2443,7 +2434,7 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() { async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const part001 = startSketchOn('XY') let code = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2536,7 +2527,7 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute() { async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const part001 = startSketchOn('XY') let code = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2624,7 +2615,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexecute_on_unit_change() { async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexecute_on_unit_change() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const part001 = startSketchOn('XY') let code = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2715,7 +2706,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_on_unit_change() { async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_on_unit_change() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const part001 = startSketchOn('XY') let code = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2785,7 +2776,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
async fn kcl_test_kcl_lsp_cant_execute_set() { async fn kcl_test_kcl_lsp_cant_execute_set() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const part001 = startSketchOn('XY') let code = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2982,7 +2973,7 @@ async fn test_kcl_lsp_folding() {
async fn kcl_test_kcl_lsp_code_with_parse_error_and_ast_unchanged_but_has_diagnostics_reparse() { async fn kcl_test_kcl_lsp_code_with_parse_error_and_ast_unchanged_but_has_diagnostics_reparse() {
let server = kcl_lsp_server(false).await.unwrap(); let server = kcl_lsp_server(false).await.unwrap();
let code = r#"const part001 = startSketchOn('XY') let code = r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -3036,8 +3027,8 @@ async fn kcl_test_kcl_lsp_code_with_parse_error_and_ast_unchanged_but_has_diagno
async fn kcl_test_kcl_lsp_code_with_lint_and_ast_unchanged_but_has_diagnostics_reparse() { async fn kcl_test_kcl_lsp_code_with_lint_and_ast_unchanged_but_has_diagnostics_reparse() {
let server = kcl_lsp_server(false).await.unwrap(); let server = kcl_lsp_server(false).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -3090,8 +3081,8 @@ const part001 = startSketchOn('XY')
async fn kcl_test_kcl_lsp_code_with_lint_and_parse_error_and_ast_unchanged_but_has_diagnostics_reparse() { async fn kcl_test_kcl_lsp_code_with_lint_and_parse_error_and_ast_unchanged_but_has_diagnostics_reparse() {
let server = kcl_lsp_server(false).await.unwrap(); let server = kcl_lsp_server(false).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -3145,8 +3136,8 @@ const part001 = startSketchOn('XY')
async fn kcl_test_kcl_lsp_code_lint_and_ast_unchanged_but_has_diagnostics_reexecute() { async fn kcl_test_kcl_lsp_code_lint_and_ast_unchanged_but_has_diagnostics_reexecute() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
@ -3210,8 +3201,8 @@ const part001 = startSketchOn('XY')
async fn kcl_test_kcl_lsp_code_lint_reexecute_new_lint() { async fn kcl_test_kcl_lsp_code_lint_reexecute_new_lint() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
@ -3253,14 +3244,14 @@ const part001 = startSketchOn('XY')
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
range: None, range: None,
range_length: None, range_length: None,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
|> line([-20, 0], %, $seg01) |> line([-20, 0], %, $seg01)
|> close(%) |> close(%)
|> extrude(3.14, %) |> extrude(3.14, %)
const NEW_LINT = 1"# NEW_LINT = 1"#
.to_string(), .to_string(),
}], }],
}) })
@ -3283,8 +3274,8 @@ const NEW_LINT = 1"#
async fn kcl_test_kcl_lsp_code_lint_reexecute_new_ast_error() { async fn kcl_test_kcl_lsp_code_lint_reexecute_new_ast_error() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
@ -3326,14 +3317,14 @@ const part001 = startSketchOn('XY')
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
range: None, range: None,
range_length: None, range_length: None,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> ^^^^startProfileAt([-10, -10], %) |> ^^^^startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
|> line([-20, 0], %, $seg01) |> line([-20, 0], %, $seg01)
|> close(%) |> close(%)
|> extrude(3.14, %) |> extrude(3.14, %)
const NEW_LINT = 1"# NEW_LINT = 1"#
.to_string(), .to_string(),
}], }],
}) })
@ -3356,8 +3347,8 @@ const NEW_LINT = 1"#
async fn kcl_test_kcl_lsp_code_lint_reexecute_had_lint_new_parse_error() { async fn kcl_test_kcl_lsp_code_lint_reexecute_had_lint_new_parse_error() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -3408,14 +3399,14 @@ const part001 = startSketchOn('XY')
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
range: None, range: None,
range_length: None, range_length: None,
text: r#"const part001 = startSketchOn('XY') text: r#"part001 = startSketchOn('XY')
|> ^^^^startProfileAt([-10, -10], %) |> ^^^^startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
|> line([-20, 0], %) |> line([-20, 0], %)
|> close(%) |> close(%)
|> extrude(3.14, %) |> extrude(3.14, %)
const NEW_LINT = 1"# NEW_LINT = 1"#
.to_string(), .to_string(),
}], }],
}) })
@ -3447,8 +3438,8 @@ const NEW_LINT = 1"#
async fn kcl_test_kcl_lsp_code_lint_reexecute_had_lint_new_execution_error() { async fn kcl_test_kcl_lsp_code_lint_reexecute_had_lint_new_execution_error() {
let server = kcl_lsp_server(true).await.unwrap(); let server = kcl_lsp_server(true).await.unwrap();
let code = r#"const LINT = 1 let code = r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -3503,8 +3494,8 @@ const part001 = startSketchOn('XY')
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
range: None, range: None,
range_length: None, range_length: None,
text: r#"const LINT = 1 text: r#"LINT = 1
const part001 = startSketchOn('XY') part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, 20], %, $seg01) |> line([0, 20], %, $seg01)
@ -3552,7 +3543,7 @@ async fn kcl_test_kcl_lsp_completions_number_literal() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: "const thing = 10".to_string(), text: "thing = 10".to_string(),
}, },
}) })
.await; .await;
@ -3563,7 +3554,7 @@ async fn kcl_test_kcl_lsp_completions_number_literal() {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier { text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
}, },
position: tower_lsp::lsp_types::Position { line: 0, character: 15 }, position: tower_lsp::lsp_types::Position { line: 0, character: 10 },
}, },
context: None, context: None,
partial_result_params: Default::default(), partial_result_params: Default::default(),

View File

@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
executor::ExecState, execution::ExecState,
parsing::ast::types::{Node, Program}, parsing::ast::types::{Node, Program},
}; };
@ -27,7 +27,7 @@ pub struct OldAstState {
/// The exec state. /// The exec state.
pub exec_state: ExecState, pub exec_state: ExecState,
/// The last settings used for execution. /// The last settings used for execution.
pub settings: crate::executor::ExecutorSettings, pub settings: crate::execution::ExecutorSettings,
} }
impl From<crate::Program> for CacheInformation { impl From<crate::Program> for CacheInformation {
@ -55,7 +55,7 @@ pub struct CacheResult {
// the cache. // the cache.
pub fn get_changed_program( pub fn get_changed_program(
info: CacheInformation, info: CacheInformation,
new_settings: &crate::executor::ExecutorSettings, new_settings: &crate::execution::ExecutorSettings,
) -> Option<CacheResult> { ) -> Option<CacheResult> {
let Some(old) = info.old else { let Some(old) = info.old else {
// We have no old info, we need to re-execute the whole thing. // We have no old info, we need to re-execute the whole thing.
@ -109,14 +109,14 @@ mod tests {
use super::*; use super::*;
async fn execute(program: &crate::Program) -> Result<ExecState> { async fn execute(program: &crate::Program) -> Result<ExecState> {
let ctx = crate::executor::ExecutorContext { let ctx = crate::execution::ExecutorContext {
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)), engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
fs: Arc::new(crate::fs::FileManager::new()), fs: Arc::new(crate::fs::FileManager::new()),
stdlib: Arc::new(crate::std::StdLib::new()), stdlib: Arc::new(crate::std::StdLib::new()),
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
let mut exec_state = crate::executor::ExecState::default(); let mut exec_state = crate::execution::ExecState::default();
ctx.run(program.clone().into(), &mut exec_state).await?; ctx.run(program.clone().into(), &mut exec_state).await?;
Ok(exec_state) Ok(exec_state)

View File

@ -4,9 +4,10 @@ use super::types::{DefaultParamVal, ItemVisibility, 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,
CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression,
ImportItem, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, ImportItem, ImportSelector, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject,
NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator, PipeSubstitution, Program, ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration,
VariableDeclarator,
}; };
/// Position-independent digest of the AST node. /// Position-independent digest of the AST node.
@ -52,9 +53,20 @@ impl ImportItem {
impl ImportStatement { impl ImportStatement {
compute_digest!(|slf, hasher| { compute_digest!(|slf, hasher| {
for item in &mut slf.items { match &mut slf.selector {
ImportSelector::List { items } => {
for item in items {
hasher.update(item.compute_digest()); hasher.update(item.compute_digest());
} }
}
ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"),
ImportSelector::None(None) => hasher.update(b"ImportSelector::None"),
ImportSelector::None(Some(alias)) => {
hasher.update(b"ImportSelector::None");
hasher.update(alias.compute_digest());
}
}
hasher.update(slf.visibility.digestable_id());
let path = slf.path.as_bytes(); let path = slf.path.as_bytes();
hasher.update(path.len().to_ne_bytes()); hasher.update(path.len().to_ne_bytes());
hasher.update(path); hasher.update(path);
@ -270,10 +282,7 @@ impl ExpressionStatement {
impl VariableDeclaration { impl VariableDeclaration {
compute_digest!(|slf, hasher| { compute_digest!(|slf, hasher| {
hasher.update(slf.declarations.len().to_ne_bytes()); hasher.update(slf.declaration.compute_digest());
for declarator in &mut slf.declarations {
hasher.update(declarator.compute_digest());
}
hasher.update(slf.visibility.digestable_id()); hasher.update(slf.visibility.digestable_id());
hasher.update(slf.kind.digestable_id()); hasher.update(slf.kind.digestable_id());
}); });

View File

@ -1,6 +1,76 @@
pub(crate) mod cache; pub(crate) mod cache;
pub(crate) mod digest; pub(crate) mod digest;
pub(crate) mod execute;
pub mod modify; pub mod modify;
pub(crate) mod source_range;
pub mod types; pub mod types;
use crate::{
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
source_range::ModuleId,
};
impl BodyItem {
pub fn module_id(&self) -> ModuleId {
match self {
BodyItem::ImportStatement(stmt) => stmt.module_id,
BodyItem::ExpressionStatement(expression_statement) => expression_statement.module_id,
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.module_id,
BodyItem::ReturnStatement(return_statement) => return_statement.module_id,
}
}
}
impl Expr {
pub fn module_id(&self) -> ModuleId {
match self {
Expr::Literal(literal) => literal.module_id,
Expr::Identifier(identifier) => identifier.module_id,
Expr::TagDeclarator(tag) => tag.module_id,
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
Expr::FunctionExpression(function_expression) => function_expression.module_id,
Expr::CallExpression(call_expression) => call_expression.module_id,
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
Expr::ArrayExpression(array_expression) => array_expression.module_id,
Expr::ArrayRangeExpression(array_range) => array_range.module_id,
Expr::ObjectExpression(object_expression) => object_expression.module_id,
Expr::MemberExpression(member_expression) => member_expression.module_id,
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
Expr::IfExpression(expr) => expr.module_id,
Expr::None(none) => none.module_id,
}
}
}
impl BinaryPart {
pub fn module_id(&self) -> ModuleId {
match self {
BinaryPart::Literal(literal) => literal.module_id,
BinaryPart::Identifier(identifier) => identifier.module_id,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::IfExpression(e) => e.module_id,
}
}
}
impl MemberObject {
pub fn module_id(&self) -> ModuleId {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
MemberObject::Identifier(identifier) => identifier.module_id,
}
}
}
impl LiteralIdentifier {
pub fn module_id(&self) -> ModuleId {
match self {
LiteralIdentifier::Identifier(identifier) => identifier.module_id,
LiteralIdentifier::Literal(literal) => literal.module_id,
}
}
}

View File

@ -9,7 +9,7 @@ use kittycad_modeling_cmds as kcmc;
use crate::{ use crate::{
engine::EngineManager, engine::EngineManager,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::Point2d, execution::Point2d,
parsing::ast::types::{ parsing::ast::types::{
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression, ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
PipeSubstitution, VariableDeclarator, PipeSubstitution, VariableDeclarator,
@ -42,7 +42,7 @@ pub async fn modify_ast_for_sketch(
// The name of the sketch. // The name of the sketch.
sketch_name: &str, sketch_name: &str,
// The type of plane the sketch is on. `XY` or `XZ`, etc // The type of plane the sketch is on. `XY` or `XZ`, etc
plane: crate::executor::PlaneType, plane: crate::execution::PlaneType,
// The ID of the parent sketch. // The ID of the parent sketch.
sketch_id: uuid::Uuid, sketch_id: uuid::Uuid,
) -> Result<String, KclError> { ) -> Result<String, KclError> {
@ -196,7 +196,7 @@ fn create_start_sketch_on(
name: &str, name: &str,
start: [f64; 2], start: [f64; 2],
end: [f64; 2], end: [f64; 2],
plane: crate::executor::PlaneType, plane: crate::execution::PlaneType,
additional_lines: Vec<[f64; 2]>, additional_lines: Vec<[f64; 2]>,
) -> Result<Node<VariableDeclarator>, KclError> { ) -> Result<Node<VariableDeclarator>, KclError> {
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?; let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;

View File

@ -1,71 +0,0 @@
use crate::{
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
source_range::ModuleId,
};
impl BodyItem {
pub fn module_id(&self) -> ModuleId {
match self {
BodyItem::ImportStatement(stmt) => stmt.module_id,
BodyItem::ExpressionStatement(expression_statement) => expression_statement.module_id,
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.module_id,
BodyItem::ReturnStatement(return_statement) => return_statement.module_id,
}
}
}
impl Expr {
pub fn module_id(&self) -> ModuleId {
match self {
Expr::Literal(literal) => literal.module_id,
Expr::Identifier(identifier) => identifier.module_id,
Expr::TagDeclarator(tag) => tag.module_id,
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
Expr::FunctionExpression(function_expression) => function_expression.module_id,
Expr::CallExpression(call_expression) => call_expression.module_id,
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
Expr::ArrayExpression(array_expression) => array_expression.module_id,
Expr::ArrayRangeExpression(array_range) => array_range.module_id,
Expr::ObjectExpression(object_expression) => object_expression.module_id,
Expr::MemberExpression(member_expression) => member_expression.module_id,
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
Expr::IfExpression(expr) => expr.module_id,
Expr::None(none) => none.module_id,
}
}
}
impl BinaryPart {
pub fn module_id(&self) -> ModuleId {
match self {
BinaryPart::Literal(literal) => literal.module_id,
BinaryPart::Identifier(identifier) => identifier.module_id,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::IfExpression(e) => e.module_id,
}
}
}
impl MemberObject {
pub fn module_id(&self) -> ModuleId {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
MemberObject::Identifier(identifier) => identifier.module_id,
}
}
}
impl LiteralIdentifier {
pub fn module_id(&self) -> ModuleId {
match self {
LiteralIdentifier::Identifier(identifier) => identifier.module_id,
LiteralIdentifier::Literal(literal) => literal.module_id,
}
}
}

View File

@ -8,7 +8,6 @@ use std::{
}; };
use anyhow::Result; use anyhow::Result;
use async_recursion::async_recursion;
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -17,7 +16,7 @@ use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind, CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
}; };
use super::{digest::Digest, execute::execute_pipe_body}; use super::digest::Digest;
pub use crate::parsing::ast::types::{ pub use crate::parsing::ast::types::{
condition::{ElseIf, IfExpression}, condition::{ElseIf, IfExpression},
literal_value::LiteralValue, literal_value::LiteralValue,
@ -26,7 +25,7 @@ pub use crate::parsing::ast::types::{
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::KclError, errors::KclError,
executor::{ExecState, ExecutorContext, KclValue, Metadata, TagIdentifier}, execution::{KclValue, Metadata, TagIdentifier},
parsing::PIPE_OPERATOR, parsing::PIPE_OPERATOR,
source_range::{ModuleId, SourceRange}, source_range::{ModuleId, SourceRange},
}; };
@ -466,13 +465,11 @@ impl Program {
continue; continue;
} }
BodyItem::VariableDeclaration(ref mut variable_declaration) => { BodyItem::VariableDeclaration(ref mut variable_declaration) => {
for declaration in &mut variable_declaration.declarations { if variable_declaration.declaration.id.name == name {
if declaration.id.name == name { variable_declaration.declaration = declarator;
*declaration = declarator;
return; return;
} }
} }
}
BodyItem::ReturnStatement(_return_statement) => continue, BodyItem::ReturnStatement(_return_statement) => continue,
} }
} }
@ -501,20 +498,16 @@ impl Program {
for item in &self.body { for item in &self.body {
match item { match item {
BodyItem::ImportStatement(stmt) => { BodyItem::ImportStatement(stmt) => {
for import_item in &stmt.items { if stmt.get_variable(name) {
if import_item.identifier() == name { return Some(Definition::Import(stmt));
return Some(Definition::Import(stmt.as_ref()));
}
} }
} }
BodyItem::ExpressionStatement(_expression_statement) => { BodyItem::ExpressionStatement(_expression_statement) => {
continue; continue;
} }
BodyItem::VariableDeclaration(variable_declaration) => { BodyItem::VariableDeclaration(variable_declaration) => {
for declaration in &variable_declaration.declarations { if variable_declaration.declaration.id.name == name {
if declaration.id.name == name { return Some(Definition::Variable(&variable_declaration.declaration));
return Some(Definition::Variable(declaration));
}
} }
} }
BodyItem::ReturnStatement(_return_statement) => continue, BodyItem::ReturnStatement(_return_statement) => continue,
@ -1125,7 +1118,7 @@ impl NonCodeMeta {
pub struct ImportItem { pub struct ImportItem {
/// Name of the item to import. /// Name of the item to import.
pub name: Node<Identifier>, pub name: Node<Identifier>,
/// Rename the item using an identifier after "as". /// Rename the item using an identifier after `as`.
pub alias: Option<Node<Identifier>>, pub alias: Option<Node<Identifier>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@ -1174,25 +1167,24 @@ impl ImportItem {
#[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(tag = "type")] #[serde(tag = "type")]
pub struct ImportStatement { #[allow(clippy::large_enum_variant)]
pub items: NodeList<ImportItem>, pub enum ImportSelector {
pub path: String, /// A comma-separated list of names and possible aliases to import (may be a single item, but never zero).
pub raw_path: String, /// E.g., `import bar as baz from "foo.kcl"`
List { items: NodeList<ImportItem> },
#[serde(default, skip_serializing_if = "Option::is_none")] /// Import all public items from a module.
#[ts(optional)] /// E.g., `import * from "foo.kcl"`
pub digest: Option<Digest>, Glob(Node<()>),
} /// Import the module itself (the param is an optional alias).
/// E.g., `import "foo.kcl" as bar`
impl Node<ImportStatement> { None(Option<Node<Identifier>>),
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
}
} }
impl ImportSelector {
pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> { pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> {
for item in &mut self.items { match self {
ImportSelector::List { items } => {
for item in items {
let source_range = SourceRange::from(&*item); let source_range = SourceRange::from(&*item);
if source_range.contains(pos) { if source_range.contains(pos) {
let old_name = item.rename_symbol(new_name, pos); let old_name = item.rename_symbol(new_name, pos);
@ -1203,13 +1195,97 @@ impl Node<ImportStatement> {
} }
None None
} }
ImportSelector::Glob(_) => None,
ImportSelector::None(None) => None,
ImportSelector::None(Some(alias)) => {
let alias_source_range = SourceRange::from(&*alias);
if !alias_source_range.contains(pos) {
return None;
}
let old_name = std::mem::replace(&mut alias.name, new_name.to_owned());
Some(old_name)
}
}
}
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
match self {
ImportSelector::List { items } => {
for item in items {
item.rename_identifiers(old_name, new_name);
}
}
ImportSelector::Glob(_) => {}
ImportSelector::None(None) => {}
ImportSelector::None(Some(alias)) => alias.rename(old_name, new_name),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ImportStatement {
pub selector: ImportSelector,
pub path: String,
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
pub visibility: ItemVisibility,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<Digest>,
}
impl Node<ImportStatement> {
pub fn get_variable(&self, name: &str) -> bool {
match &self.selector {
ImportSelector::List { items } => {
for import_item in items {
if import_item.identifier() == name {
return true;
}
}
false
}
ImportSelector::Glob(_) => false,
ImportSelector::None(_) => name == self.module_name().unwrap(),
}
}
/// Get the name of the module object for this import.
/// Validated during parsing and guaranteed to return `Some` if the statement imports
/// the module itself (i.e., self.selector is ImportSelector::None).
pub fn module_name(&self) -> Option<String> {
if let ImportSelector::None(Some(alias)) = &self.selector {
return Some(alias.name.clone());
}
let mut parts = self.path.split('.');
let name = parts.next()?;
let ext = parts.next()?;
let rest = parts.next();
if rest.is_some() || ext != "kcl" {
return None;
}
Some(name.to_owned())
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
}
}
pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> {
self.selector.rename_symbol(new_name, pos)
}
} }
impl ImportStatement { impl ImportStatement {
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) { pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
for item in &mut self.items { self.selector.rename_identifiers(old_name, new_name);
item.rename_identifiers(old_name, new_name);
}
} }
} }
@ -1471,7 +1547,7 @@ impl ItemVisibility {
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub struct VariableDeclaration { pub struct VariableDeclaration {
pub declarations: NodeList<VariableDeclarator>, pub declaration: Node<VariableDeclarator>,
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")] #[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
pub visibility: ItemVisibility, pub visibility: ItemVisibility,
pub kind: VariableKind, // Change to enum if there are specific values pub kind: VariableKind, // Change to enum if there are specific values
@ -1483,10 +1559,8 @@ pub struct VariableDeclaration {
impl From<&Node<VariableDeclaration>> for Vec<CompletionItem> { impl From<&Node<VariableDeclaration>> for Vec<CompletionItem> {
fn from(declaration: &Node<VariableDeclaration>) -> Self { fn from(declaration: &Node<VariableDeclaration>) -> Self {
let mut completions = vec![]; vec![CompletionItem {
for variable in &declaration.declarations { label: declaration.declaration.id.name.to_string(),
completions.push(CompletionItem {
label: variable.id.name.to_string(),
label_details: None, label_details: None,
kind: Some(match declaration.inner.kind { kind: Some(match declaration.inner.kind {
VariableKind::Const => CompletionItemKind::CONSTANT, VariableKind::Const => CompletionItemKind::CONSTANT,
@ -1507,9 +1581,7 @@ impl From<&Node<VariableDeclaration>> for Vec<CompletionItem> {
commit_characters: None, commit_characters: None,
data: None, data: None,
tags: None, tags: None,
}) }]
}
completions
} }
} }
@ -1543,23 +1615,21 @@ impl Node<VariableDeclaration> {
return None; return None;
} }
for declaration in &mut self.declarations { let declaration_source_range: SourceRange = self.declaration.id.clone().into();
let declaration_source_range: SourceRange = declaration.id.clone().into();
if declaration_source_range.contains(pos) { if declaration_source_range.contains(pos) {
let old_name = declaration.id.name.clone(); let old_name = self.declaration.id.name.clone();
declaration.id.name = new_name.to_string(); self.declaration.id.name = new_name.to_string();
return Some(old_name); return Some(old_name);
} }
}
None None
} }
} }
impl VariableDeclaration { impl VariableDeclaration {
pub fn new(declarations: NodeList<VariableDeclarator>, visibility: ItemVisibility, kind: VariableKind) -> Self { pub fn new(declaration: Node<VariableDeclarator>, visibility: ItemVisibility, kind: VariableKind) -> Self {
Self { Self {
declarations, declaration,
visibility, visibility,
kind, kind,
digest: None, digest: None,
@ -1567,18 +1637,14 @@ impl VariableDeclaration {
} }
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) { pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
for declaration in &mut self.declarations { self.declaration.init.replace_value(source_range, new_value.clone());
declaration.init.replace_value(source_range, new_value.clone());
}
} }
/// Returns an Expr that includes the given character position. /// Returns an Expr that includes the given character position.
pub fn get_expr_for_position(&self, pos: usize) -> Option<&Expr> { pub fn get_expr_for_position(&self, pos: usize) -> Option<&Expr> {
for declaration in &self.declarations { let source_range: SourceRange = self.declaration.clone().into();
let source_range: SourceRange = declaration.into();
if source_range.contains(pos) { if source_range.contains(pos) {
return Some(&declaration.init); return Some(&self.declaration.init);
}
} }
None None
@ -1586,40 +1652,31 @@ impl VariableDeclaration {
/// Returns an Expr that includes the given character position. /// Returns an Expr that includes the given character position.
pub fn get_mut_expr_for_position(&mut self, pos: usize) -> Option<&mut Expr> { pub fn get_mut_expr_for_position(&mut self, pos: usize) -> Option<&mut Expr> {
for declaration in &mut self.declarations { let source_range: SourceRange = self.declaration.clone().into();
let source_range: SourceRange = declaration.clone().into();
if source_range.contains(pos) { if source_range.contains(pos) {
return Some(&mut declaration.init); return Some(&mut self.declaration.init);
}
} }
None None
} }
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) { pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
for declaration in &mut self.declarations {
// Skip the init for the variable with the new name since it is the one we are renaming. // Skip the init for the variable with the new name since it is the one we are renaming.
if declaration.id.name == new_name { if self.declaration.id.name != new_name {
continue; self.declaration.init.rename_identifiers(old_name, new_name);
}
declaration.init.rename_identifiers(old_name, new_name);
} }
} }
pub fn get_lsp_symbols(&self, code: &str) -> Vec<DocumentSymbol> { pub fn get_lsp_symbols(&self, code: &str) -> Vec<DocumentSymbol> {
let mut symbols = vec![]; let source_range: SourceRange = self.declaration.clone().into();
let inner_source_range: SourceRange = self.declaration.id.clone().into();
for declaration in &self.declarations {
let source_range: SourceRange = declaration.into();
let inner_source_range: SourceRange = declaration.id.clone().into();
let mut symbol_kind = match self.kind { let mut symbol_kind = match self.kind {
VariableKind::Fn => SymbolKind::FUNCTION, VariableKind::Fn => SymbolKind::FUNCTION,
VariableKind::Const => SymbolKind::CONSTANT, VariableKind::Const => SymbolKind::CONSTANT,
}; };
let children = match &declaration.init { let children = match &self.declaration.init {
Expr::FunctionExpression(function_expression) => { Expr::FunctionExpression(function_expression) => {
symbol_kind = SymbolKind::FUNCTION; symbol_kind = SymbolKind::FUNCTION;
let mut children = vec![]; let mut children = vec![];
@ -1654,9 +1711,10 @@ impl VariableDeclaration {
_ => vec![], _ => vec![],
}; };
vec![
#[allow(deprecated)] #[allow(deprecated)]
symbols.push(DocumentSymbol { DocumentSymbol {
name: declaration.id.name.clone(), name: self.declaration.id.name.clone(),
detail: Some(self.kind.to_string()), detail: Some(self.kind.to_string()),
kind: symbol_kind, kind: symbol_kind,
range: source_range.to_lsp_range(code), range: source_range.to_lsp_range(code),
@ -1664,10 +1722,8 @@ impl VariableDeclaration {
children: Some(children), children: Some(children),
tags: None, tags: None,
deprecated: None, deprecated: None,
}); },
} ]
symbols
} }
} }
@ -2629,11 +2685,6 @@ impl Node<PipeExpression> {
constraint_levels.get_constraint_level(self.into()) constraint_levels.get_constraint_level(self.into())
} }
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
}
} }
impl PipeExpression { impl PipeExpression {
@ -2783,6 +2834,18 @@ impl Parameter {
} }
} }
impl From<&Parameter> for SourceRange {
fn from(p: &Parameter) -> Self {
let sr = Self::from(&p.identifier);
// If it's unlabelled, the span should start 1 char earlier than the identifier,
// to include the '@' symbol.
if !p.labeled {
return Self::new(sr.start() - 1, sr.end(), sr.module_id());
}
sr
}
}
fn is_true(b: &bool) -> bool { fn is_true(b: &bool) -> bool {
*b *b
} }
@ -3243,7 +3306,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else { let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration") panic!("expected a variable declaration")
}; };
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else { let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
panic!("expected a function expression") panic!("expected a function expression")
}; };
let params = &func_expr.params; let params = &func_expr.params;
@ -3265,7 +3328,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else { let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration") panic!("expected a variable declaration")
}; };
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else { let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
panic!("expected a function expression") panic!("expected a function expression")
}; };
let params = &func_expr.params; let params = &func_expr.params;
@ -3288,7 +3351,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else { let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration") panic!("expected a variable declaration")
}; };
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else { let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
panic!("expected a function expression") panic!("expected a function expression")
}; };
let params = &func_expr.params; let params = &func_expr.params;
@ -3362,7 +3425,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else { let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration") panic!("expected a variable declaration")
}; };
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else { let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
panic!("expected a function expression") panic!("expected a function expression")
}; };
let params = &func_expr.params; let params = &func_expr.params;

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,16 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3893
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 137,
"end": 143,
"id": { "id": {
"end": 15, "end": 9,
"name": "boxSketch", "name": "boxSketch",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
@ -23,36 +20,36 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 34, "end": 28,
"raw": "0", "raw": "0",
"start": 33, "start": 27,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 37, "end": 31,
"raw": "0", "raw": "0",
"start": 36, "start": 30,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
} }
], ],
"end": 38, "end": 32,
"start": 32, "start": 26,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
} }
], ],
"callee": { "callee": {
"end": 31, "end": 25,
"name": "startSketchAt", "name": "startSketchAt",
"start": 18, "start": 12,
"type": "Identifier" "type": "Identifier"
}, },
"end": 39, "end": 33,
"start": 18, "start": 12,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -61,42 +58,42 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 54, "end": 48,
"raw": "0", "raw": "0",
"start": 53, "start": 47,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 58, "end": 52,
"raw": "10", "raw": "10",
"start": 56, "start": 50,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": 10.0
} }
], ],
"end": 59, "end": 53,
"start": 52, "start": 46,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 62, "end": 56,
"start": 61, "start": 55,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 51, "end": 45,
"name": "line", "name": "line",
"start": 47, "start": 41,
"type": "Identifier" "type": "Identifier"
}, },
"end": 63, "end": 57,
"start": 47, "start": 41,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -106,48 +103,48 @@ snapshot_kind: text
"elements": [ "elements": [
{ {
"argument": { "argument": {
"end": 88, "end": 82,
"raw": "5", "raw": "5",
"start": 87, "start": 81,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": 5.0
}, },
"end": 88, "end": 82,
"operator": "-", "operator": "-",
"start": 86, "start": 80,
"type": "UnaryExpression", "type": "UnaryExpression",
"type": "UnaryExpression" "type": "UnaryExpression"
}, },
{ {
"end": 91, "end": 85,
"raw": "5", "raw": "5",
"start": 90, "start": 84,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": 5.0
} }
], ],
"end": 92, "end": 86,
"start": 85, "start": 79,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 95, "end": 89,
"start": 94, "start": 88,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 84, "end": 78,
"name": "tangentialArc", "name": "tangentialArc",
"start": 71, "start": 65,
"type": "Identifier" "type": "Identifier"
}, },
"end": 96, "end": 90,
"start": 71, "start": 65,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -156,97 +153,96 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 111, "end": 105,
"raw": "5", "raw": "5",
"start": 110, "start": 104,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": 5.0
}, },
{ {
"argument": { "argument": {
"end": 116, "end": 110,
"raw": "15", "raw": "15",
"start": 114, "start": 108,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 15.0 "value": 15.0
}, },
"end": 116, "end": 110,
"operator": "-", "operator": "-",
"start": 113, "start": 107,
"type": "UnaryExpression", "type": "UnaryExpression",
"type": "UnaryExpression" "type": "UnaryExpression"
} }
], ],
"end": 117, "end": 111,
"start": 109, "start": 103,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 120, "end": 114,
"start": 119, "start": 113,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 108, "end": 102,
"name": "line", "name": "line",
"start": 104, "start": 98,
"type": "Identifier" "type": "Identifier"
}, },
"end": 121, "end": 115,
"start": 104, "start": 98,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
{ {
"arguments": [ "arguments": [
{ {
"end": 139, "end": 133,
"raw": "10", "raw": "10",
"start": 137, "start": 131,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": 10.0
}, },
{ {
"end": 142, "end": 136,
"start": 141, "start": 135,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 136, "end": 130,
"name": "extrude", "name": "extrude",
"start": 129, "start": 123,
"type": "Identifier" "type": "Identifier"
}, },
"end": 143, "end": 137,
"start": 129, "start": 123,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
} }
], ],
"end": 143, "end": 137,
"start": 18, "start": 12,
"type": "PipeExpression", "type": "PipeExpression",
"type": "PipeExpression" "type": "PipeExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 137,
"end": 143,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 144, "end": 138,
"start": 0 "start": 0
} }

View File

@ -1,46 +1,42 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3963
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 11,
"end": 17,
"id": { "id": {
"end": 8, "end": 2,
"name": "sg", "name": "sg",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
"argument": { "argument": {
"end": 17, "end": 11,
"name": "scale", "name": "scale",
"start": 12, "start": 6,
"type": "Identifier", "type": "Identifier",
"type": "Identifier" "type": "Identifier"
}, },
"end": 17, "end": 11,
"operator": "-", "operator": "-",
"start": 11, "start": 5,
"type": "UnaryExpression", "type": "UnaryExpression",
"type": "UnaryExpression" "type": "UnaryExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 11,
"end": 17,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 17, "end": 11,
"start": 0 "start": 0
} }

View File

@ -1,37 +1,34 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3965
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 17,
"end": 23,
"id": { "id": {
"end": 13, "end": 7,
"name": "myArray", "name": "myArray",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
"end": 23, "end": 17,
"endElement": { "endElement": {
"end": 22, "end": 16,
"raw": "10", "raw": "10",
"start": 20, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": 10.0
}, },
"endInclusive": true, "endInclusive": true,
"start": 16, "start": 10,
"startElement": { "startElement": {
"end": 18, "end": 12,
"raw": "0", "raw": "0",
"start": 17, "start": 11,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
@ -39,17 +36,16 @@ snapshot_kind: text
"type": "ArrayRangeExpression", "type": "ArrayRangeExpression",
"type": "ArrayRangeExpression" "type": "ArrayRangeExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 17,
"end": 23,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 23, "end": 17,
"start": 0 "start": 0
} }

View File

@ -1,14 +1,11 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3966
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{
"end": 57, "end": 57,
"id": { "id": {
"end": 24, "end": 24,
@ -45,8 +42,7 @@ snapshot_kind: text
}, },
"start": 8, "start": 8,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
],
"end": 57, "end": 57,
"kind": "fn", "kind": "fn",
"start": 5, "start": 5,

View File

@ -1,13 +1,11 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{
"end": 49, "end": 49,
"id": { "id": {
"end": 8, "end": 8,
@ -54,8 +52,7 @@ snapshot_kind: text
}, },
"start": 3, "start": 3,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
],
"end": 49, "end": 49,
"kind": "fn", "kind": "fn",
"start": 0, "start": 0,

View File

@ -1,19 +1,16 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3981
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 159,
"end": 165,
"id": { "id": {
"end": 14, "end": 8,
"name": "mySketch", "name": "mySketch",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
@ -23,36 +20,36 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 33, "end": 27,
"raw": "0", "raw": "0",
"start": 32, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 35, "end": 29,
"raw": "0", "raw": "0",
"start": 34, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
} }
], ],
"end": 36, "end": 30,
"start": 31, "start": 25,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
} }
], ],
"callee": { "callee": {
"end": 30, "end": 24,
"name": "startSketchAt", "name": "startSketchAt",
"start": 17, "start": 11,
"type": "Identifier" "type": "Identifier"
}, },
"end": 37, "end": 31,
"start": 17, "start": 11,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -61,49 +58,49 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 58, "end": 52,
"raw": "0", "raw": "0",
"start": 57, "start": 51,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 61, "end": 55,
"raw": "1", "raw": "1",
"start": 60, "start": 54,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
} }
], ],
"end": 62, "end": 56,
"start": 56, "start": 50,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 65, "end": 59,
"start": 64, "start": 58,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
}, },
{ {
"end": 74, "end": 68,
"start": 67, "start": 61,
"type": "TagDeclarator", "type": "TagDeclarator",
"type": "TagDeclarator", "type": "TagDeclarator",
"value": "myPath" "value": "myPath"
} }
], ],
"callee": { "callee": {
"end": 55, "end": 49,
"name": "lineTo", "name": "lineTo",
"start": 49, "start": 43,
"type": "Identifier" "type": "Identifier"
}, },
"end": 75, "end": 69,
"start": 49, "start": 43,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -112,42 +109,42 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 96, "end": 90,
"raw": "1", "raw": "1",
"start": 95, "start": 89,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
}, },
{ {
"end": 99, "end": 93,
"raw": "1", "raw": "1",
"start": 98, "start": 92,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
} }
], ],
"end": 100, "end": 94,
"start": 94, "start": 88,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 103, "end": 97,
"start": 102, "start": 96,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 93, "end": 87,
"name": "lineTo", "name": "lineTo",
"start": 87, "start": 81,
"type": "Identifier" "type": "Identifier"
}, },
"end": 104, "end": 98,
"start": 87, "start": 81,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -156,89 +153,88 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 125, "end": 119,
"raw": "1", "raw": "1",
"start": 124, "start": 118,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
}, },
{ {
"end": 128, "end": 122,
"raw": "0", "raw": "0",
"start": 127, "start": 121,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
} }
], ],
"end": 129, "end": 123,
"start": 123, "start": 117,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 132, "end": 126,
"start": 131, "start": 125,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
}, },
{ {
"end": 144, "end": 138,
"start": 134, "start": 128,
"type": "TagDeclarator", "type": "TagDeclarator",
"type": "TagDeclarator", "type": "TagDeclarator",
"value": "rightPath" "value": "rightPath"
} }
], ],
"callee": { "callee": {
"end": 122, "end": 116,
"name": "lineTo", "name": "lineTo",
"start": 116, "start": 110,
"type": "Identifier" "type": "Identifier"
}, },
"end": 145, "end": 139,
"start": 116, "start": 110,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
{ {
"arguments": [ "arguments": [
{ {
"end": 164, "end": 158,
"start": 163, "start": 157,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 162, "end": 156,
"name": "close", "name": "close",
"start": 157, "start": 151,
"type": "Identifier" "type": "Identifier"
}, },
"end": 165, "end": 159,
"start": 157, "start": 151,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
} }
], ],
"end": 165, "end": 159,
"start": 17, "start": 11,
"type": "PipeExpression", "type": "PipeExpression",
"type": "PipeExpression" "type": "PipeExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 159,
"end": 165,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 165, "end": 159,
"start": 0 "start": 0
} }

View File

@ -1,19 +1,16 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3989
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 64,
"end": 70,
"id": { "id": {
"end": 14, "end": 8,
"name": "mySketch", "name": "mySketch",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
@ -23,36 +20,36 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 33, "end": 27,
"raw": "0", "raw": "0",
"start": 32, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 35, "end": 29,
"raw": "0", "raw": "0",
"start": 34, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
} }
], ],
"end": 36, "end": 30,
"start": 31, "start": 25,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
} }
], ],
"callee": { "callee": {
"end": 30, "end": 24,
"name": "startSketchAt", "name": "startSketchAt",
"start": 17, "start": 11,
"type": "Identifier" "type": "Identifier"
}, },
"end": 37, "end": 31,
"start": 17, "start": 11,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -61,82 +58,81 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 50, "end": 44,
"raw": "1", "raw": "1",
"start": 49, "start": 43,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
}, },
{ {
"end": 53, "end": 47,
"raw": "1", "raw": "1",
"start": 52, "start": 46,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
} }
], ],
"end": 54, "end": 48,
"start": 48, "start": 42,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 57, "end": 51,
"start": 56, "start": 50,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 47, "end": 41,
"name": "lineTo", "name": "lineTo",
"start": 41, "start": 35,
"type": "Identifier" "type": "Identifier"
}, },
"end": 58, "end": 52,
"start": 41, "start": 35,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
{ {
"arguments": [ "arguments": [
{ {
"end": 69, "end": 63,
"start": 68, "start": 62,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 67, "end": 61,
"name": "close", "name": "close",
"start": 62, "start": 56,
"type": "Identifier" "type": "Identifier"
}, },
"end": 70, "end": 64,
"start": 62, "start": 56,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
} }
], ],
"end": 70, "end": 64,
"start": 17, "start": 11,
"type": "PipeExpression", "type": "PipeExpression",
"type": "PipeExpression" "type": "PipeExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 64,
"end": 70,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 70, "end": 64,
"start": 0 "start": 0
} }

View File

@ -1,53 +1,49 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3993
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 24,
"end": 30,
"id": { "id": {
"end": 11, "end": 5,
"name": "myBox", "name": "myBox",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
"arguments": [ "arguments": [
{ {
"end": 29, "end": 23,
"name": "p", "name": "p",
"start": 28, "start": 22,
"type": "Identifier", "type": "Identifier",
"type": "Identifier" "type": "Identifier"
} }
], ],
"callee": { "callee": {
"end": 27, "end": 21,
"name": "startSketchAt", "name": "startSketchAt",
"start": 14, "start": 8,
"type": "Identifier" "type": "Identifier"
}, },
"end": 30, "end": 24,
"start": 14, "start": 8,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 24,
"end": 30,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 30, "end": 24,
"start": 0 "start": 0
} }

View File

@ -1,19 +1,16 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3994
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 23,
"end": 29,
"id": { "id": {
"end": 11, "end": 5,
"name": "myBox", "name": "myBox",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
@ -21,70 +18,69 @@ snapshot_kind: text
{ {
"arguments": [ "arguments": [
{ {
"end": 17, "end": 11,
"raw": "1", "raw": "1",
"start": 16, "start": 10,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": 1.0
} }
], ],
"callee": { "callee": {
"end": 15, "end": 9,
"name": "f", "name": "f",
"start": 14, "start": 8,
"type": "Identifier" "type": "Identifier"
}, },
"end": 18, "end": 12,
"start": 14, "start": 8,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
{ {
"arguments": [ "arguments": [
{ {
"end": 25, "end": 19,
"raw": "2", "raw": "2",
"start": 24, "start": 18,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0 "value": 2.0
}, },
{ {
"end": 28, "end": 22,
"start": 27, "start": 21,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 23, "end": 17,
"name": "g", "name": "g",
"start": 22, "start": 16,
"type": "Identifier" "type": "Identifier"
}, },
"end": 29, "end": 23,
"start": 22, "start": 16,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
} }
], ],
"end": 29, "end": 23,
"start": 14, "start": 8,
"type": "PipeExpression", "type": "PipeExpression",
"type": "PipeExpression" "type": "PipeExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 23,
"end": 29,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 29, "end": 23,
"start": 0 "start": 0
} }

View File

@ -1,19 +1,16 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3995
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 43,
"end": 49,
"id": { "id": {
"end": 11, "end": 5,
"name": "myBox", "name": "myBox",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
@ -21,21 +18,21 @@ snapshot_kind: text
{ {
"arguments": [ "arguments": [
{ {
"end": 29, "end": 23,
"name": "p", "name": "p",
"start": 28, "start": 22,
"type": "Identifier", "type": "Identifier",
"type": "Identifier" "type": "Identifier"
} }
], ],
"callee": { "callee": {
"end": 27, "end": 21,
"name": "startSketchAt", "name": "startSketchAt",
"start": 14, "start": 8,
"type": "Identifier" "type": "Identifier"
}, },
"end": 30, "end": 24,
"start": 14, "start": 8,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
@ -44,61 +41,60 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 41, "end": 35,
"raw": "0", "raw": "0",
"start": 40, "start": 34,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 44, "end": 38,
"name": "l", "name": "l",
"start": 43, "start": 37,
"type": "Identifier", "type": "Identifier",
"type": "Identifier" "type": "Identifier"
} }
], ],
"end": 45, "end": 39,
"start": 39, "start": 33,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
}, },
{ {
"end": 48, "end": 42,
"start": 47, "start": 41,
"type": "PipeSubstitution", "type": "PipeSubstitution",
"type": "PipeSubstitution" "type": "PipeSubstitution"
} }
], ],
"callee": { "callee": {
"end": 38, "end": 32,
"name": "line", "name": "line",
"start": 34, "start": 28,
"type": "Identifier" "type": "Identifier"
}, },
"end": 49, "end": 43,
"start": 34, "start": 28,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
} }
], ],
"end": 49, "end": 43,
"start": 14, "start": 8,
"type": "PipeExpression", "type": "PipeExpression",
"type": "PipeExpression" "type": "PipeExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 43,
"end": 49,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 49, "end": 43,
"start": 0 "start": 0
} }

View File

@ -1,19 +1,16 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 4001
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{ "end": 31,
"end": 37,
"id": { "id": {
"end": 14, "end": 8,
"name": "mySketch", "name": "mySketch",
"start": 6, "start": 0,
"type": "Identifier" "type": "Identifier"
}, },
"init": { "init": {
@ -21,50 +18,49 @@ snapshot_kind: text
{ {
"elements": [ "elements": [
{ {
"end": 33, "end": 27,
"raw": "0", "raw": "0",
"start": 32, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
}, },
{ {
"end": 35, "end": 29,
"raw": "0", "raw": "0",
"start": 34, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": 0.0
} }
], ],
"end": 36, "end": 30,
"start": 31, "start": 25,
"type": "ArrayExpression", "type": "ArrayExpression",
"type": "ArrayExpression" "type": "ArrayExpression"
} }
], ],
"callee": { "callee": {
"end": 30, "end": 24,
"name": "startSketchAt", "name": "startSketchAt",
"start": 17, "start": 11,
"type": "Identifier" "type": "Identifier"
}, },
"end": 37, "end": 31,
"start": 17, "start": 11,
"type": "CallExpression", "type": "CallExpression",
"type": "CallExpression" "type": "CallExpression"
}, },
"start": 6, "start": 0,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
], "end": 31,
"end": 37,
"kind": "const", "kind": "const",
"start": 0, "start": 0,
"type": "VariableDeclaration", "type": "VariableDeclaration",
"type": "VariableDeclaration" "type": "VariableDeclaration"
} }
], ],
"end": 37, "end": 31,
"start": 0 "start": 0
} }

View File

@ -1,14 +1,11 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 4005
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{
"end": 107, "end": 107,
"id": { "id": {
"end": 14, "end": 14,
@ -158,8 +155,7 @@ snapshot_kind: text
}, },
"start": 6, "start": 6,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
],
"end": 107, "end": 107,
"kind": "const", "kind": "const",
"start": 0, "start": 0,

View File

@ -1,13 +1,11 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
{ {
"declarations": [ "declaration": {
{
"end": 49, "end": 49,
"id": { "id": {
"end": 4, "end": 4,
@ -80,8 +78,7 @@ snapshot_kind: text
}, },
"start": 3, "start": 3,
"type": "VariableDeclarator" "type": "VariableDeclarator"
} },
],
"end": 49, "end": 49,
"kind": "fn", "kind": "fn",
"start": 0, "start": 0,

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