Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation-backup
This commit is contained in:
6
.github/workflows/build-apps.yml
vendored
6
.github/workflows/build-apps.yml
vendored
@ -365,7 +365,7 @@ jobs:
|
||||
- name: Set more complete nightly release notes
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
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
|
||||
git fetch --prune --unshallow --tags
|
||||
export TAG="nightly-${VERSION}"
|
||||
@ -394,6 +394,10 @@ jobs:
|
||||
parent: false
|
||||
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
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
|
||||
6
.github/workflows/publish-apps-release.yml
vendored
6
.github/workflows/publish-apps-release.yml
vendored
@ -126,11 +126,7 @@ jobs:
|
||||
destination: 'dl.kittycad.io/releases/modeling-app'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
run: |
|
||||
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
|
||||
run: yarn files:invalidate-bucket
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -61,6 +61,7 @@ Mac_App_Distribution.provisionprofile
|
||||
*.tsbuildinfo
|
||||
src/wasm-lib/pkg
|
||||
|
||||
.eslintcache
|
||||
venv
|
||||
.vite/
|
||||
|
||||
|
||||
43
INSTALL.md
Normal file
43
INSTALL.md
Normal 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
|
||||
```
|
||||
@ -99,7 +99,7 @@ yarn tron:start
|
||||
|
||||
This will start the application and hot-reload on changes.
|
||||
|
||||
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
|
||||
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
|
||||
|
||||
To build, run `yarn tron:package`.
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -78970,7 +78970,7 @@
|
||||
"model = import(\"tests/inputs/cube.gltf\")",
|
||||
"model = import(\"tests/inputs/cube.sldprt\")",
|
||||
"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])"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
|
||||
|
||||
/* add the following code to the editor ($ error is not a valid line)
|
||||
$ error
|
||||
const topAng = 30
|
||||
const bottomAng = 25
|
||||
topAng = 30
|
||||
bottomAng = 25
|
||||
*/
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('$ error')
|
||||
@ -474,12 +474,14 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.type('bottomAng = 25')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// error in guter
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
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
|
||||
await page.getByText('$ error').click()
|
||||
@ -518,7 +520,10 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
|
||||
// TODO currently multiple source ranges are not supported
|
||||
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
|
||||
@ -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(
|
||||
'when code with error first loads you get errors in console',
|
||||
{ tag: '@electron' },
|
||||
|
||||
@ -943,6 +943,110 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
`.replace(/\s/g, '')
|
||||
)
|
||||
})
|
||||
|
||||
/* TODO: once we fix bug turn on.
|
||||
test('empty-scene default-planes act as expected when spaces in file', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
// Fill with spaces
|
||||
await u.codeLocator.fill(`
|
||||
`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
})
|
||||
|
||||
test('empty-scene default-planes act as expected when only code comments in file', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
// Fill with spaces
|
||||
await u.codeLocator.fill(`// this is a code comments ya nerds
|
||||
`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
})*/
|
||||
|
||||
test('empty-scene default-planes act as expected', async ({
|
||||
page,
|
||||
browserName,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@ -81,6 +81,7 @@
|
||||
"simpleserver": "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:stop": "kill-port 3000",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"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-notes": "./scripts/set-files-notes.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",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev",
|
||||
@ -158,6 +161,7 @@
|
||||
"@electron/rebuild": "^3.6.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
@ -170,7 +174,7 @@
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.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/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
@ -207,7 +211,6 @@
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
|
||||
11
scripts/invalidate-files-bucket.sh
Executable file
11
scripts/invalidate-files-bucket.sh
Executable 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
|
||||
@ -155,7 +155,6 @@ export class CameraControls {
|
||||
this.camera.zoom = camProps.zoom || 1
|
||||
}
|
||||
this.camera.updateProjectionMatrix()
|
||||
console.log('doing this thing', camProps)
|
||||
this.update(true)
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,9 @@ import {
|
||||
Expr,
|
||||
parse,
|
||||
recast,
|
||||
defaultSourceRange,
|
||||
resultIsOk,
|
||||
ProgramMemory,
|
||||
} from 'lang/wasm'
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
import { ConstrainInfo } from 'lang/std/stdTypes'
|
||||
@ -412,14 +415,15 @@ export async function deleteSegment({
|
||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
|
||||
const newCode = recast(modifiedAst)
|
||||
modifiedAst = parse(newCode)
|
||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
const pResult = parse(newCode)
|
||||
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
modifiedAst = pResult.program
|
||||
|
||||
const testExecute = await executeAst({
|
||||
ast: modifiedAst,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
||||
@ -590,7 +594,9 @@ const ConstraintSymbol = ({
|
||||
if (err(_node)) return
|
||||
const node = _node.node
|
||||
|
||||
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
|
||||
const range: SourceRange = node
|
||||
? [node.start, node.end, true]
|
||||
: defaultSourceRange()
|
||||
|
||||
if (_type === 'intersectionTag') return null
|
||||
|
||||
@ -612,7 +618,7 @@ const ConstraintSymbol = ({
|
||||
editorManager.setHighlightRange([range])
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
}}
|
||||
// disabled={isConstrained || !convertToVarEnabled}
|
||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||
@ -627,10 +633,12 @@ const ConstraintSymbol = ({
|
||||
})
|
||||
} else if (isConstrained) {
|
||||
try {
|
||||
const parsed = parse(recast(kclManager.ast))
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
const pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
|
||||
const _node1 = getNodeFromPath<CallExpression>(
|
||||
parsed,
|
||||
pResult.program!,
|
||||
pathToNode,
|
||||
'CallExpression',
|
||||
true
|
||||
|
||||
@ -48,6 +48,9 @@ import {
|
||||
VariableDeclarator,
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
defaultSourceRange,
|
||||
sourceRangeFromRust,
|
||||
resultIsOk,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
engineCommandManager,
|
||||
@ -495,10 +498,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
const sketch = sketchFromPathToNode({
|
||||
@ -530,7 +532,7 @@ export class SceneEntities {
|
||||
|
||||
const segPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
sketch.start.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
||||
)
|
||||
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
||||
const _profileStart = createProfileStartHandle({
|
||||
@ -552,7 +554,7 @@ export class SceneEntities {
|
||||
sketch.paths.forEach((segment, index) => {
|
||||
let segPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
if (
|
||||
draftExpressionsIndices &&
|
||||
@ -561,12 +563,12 @@ export class SceneEntities {
|
||||
const previousSegment = sketch.paths[index - 1] || sketch.start
|
||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
previousSegment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
|
||||
)
|
||||
const bodyIndex = previousSegmentPathToNode[1][0]
|
||||
segPathToNode = getNodePathFromSourceRange(
|
||||
truncatedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
segPathToNode[1][0] = bodyIndex
|
||||
}
|
||||
@ -575,7 +577,10 @@ export class SceneEntities {
|
||||
index <= draftExpressionsIndices.end &&
|
||||
index >= draftExpressionsIndices.start
|
||||
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
||||
isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
|
||||
isOverlap(
|
||||
selection?.codeRef?.range,
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
)
|
||||
|
||||
let seg: Group
|
||||
@ -657,13 +662,11 @@ export class SceneEntities {
|
||||
}
|
||||
updateAstAndRejigSketch = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
modifiedAst: Node<Program> | Error,
|
||||
modifiedAst: Node<Program>,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
origin: [number, number, number]
|
||||
) => {
|
||||
if (err(modifiedAst)) return modifiedAst
|
||||
|
||||
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
sceneInfra.resetMouseListeners()
|
||||
@ -698,8 +701,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
|
||||
const sg = sketchFromKclValue(
|
||||
kclManager.programMemory.get(variableDeclarationName),
|
||||
@ -721,8 +723,9 @@ export class SceneEntities {
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
if (trap(mod)) return Promise.reject(mod)
|
||||
const modifiedAst = parse(recast(mod.modifiedAst))
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
const pResult = parse(recast(mod.modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
const modifiedAst = pResult.program
|
||||
|
||||
const draftExpressionsIndices = { start: index, end: index }
|
||||
|
||||
@ -898,10 +901,9 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -909,14 +911,14 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
@ -939,7 +941,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
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 y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -950,10 +952,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -989,7 +990,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
if (sketchInit.type !== 'PipeExpression') {
|
||||
return
|
||||
@ -998,9 +999,10 @@ export class SceneEntities {
|
||||
updateRectangleSketch(sketchInit, x, y, tags[0])
|
||||
|
||||
const newCode = recast(_ast)
|
||||
let _recastAst = parse(newCode)
|
||||
if (trap(_recastAst)) return
|
||||
_ast = _recastAst
|
||||
const pResult = parse(newCode)
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1013,10 +1015,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: _ast,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
|
||||
@ -1055,10 +1056,9 @@ export class SceneEntities {
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
|
||||
// startSketchOn already exists
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -1066,14 +1066,14 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
@ -1096,7 +1096,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
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 y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -1114,10 +1114,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -1153,7 +1152,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
updateCenterRectangleSketch(
|
||||
@ -1165,9 +1164,10 @@ export class SceneEntities {
|
||||
rectangleOrigin[1]
|
||||
)
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1180,10 +1180,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: _ast,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
|
||||
@ -1222,12 +1221,11 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
createCallExpressionStdLib('circle', [
|
||||
createObjectExpression({
|
||||
@ -1241,9 +1239,9 @@ export class SceneEntities {
|
||||
]),
|
||||
])
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// do a quick mock execution to get the program memory up-to-date
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1269,7 +1267,7 @@ export class SceneEntities {
|
||||
)
|
||||
let modded = structuredClone(truncatedAst)
|
||||
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 y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
||||
@ -1299,10 +1297,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: modded,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -1338,7 +1335,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
let modded = structuredClone(_ast)
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
@ -1365,9 +1362,10 @@ export class SceneEntities {
|
||||
|
||||
const newCode = recast(modded)
|
||||
if (err(newCode)) return
|
||||
let _recastAst = parse(newCode)
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(newCode)
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1660,7 +1658,7 @@ export class SceneEntities {
|
||||
kclManager.programMemory,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: [node.start, node.end],
|
||||
sourceRange: [node.start, node.end, true],
|
||||
},
|
||||
getChangeSketchInput()
|
||||
)
|
||||
@ -1683,10 +1681,9 @@ export class SceneEntities {
|
||||
codeManager.updateCodeEditor(code)
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -1750,7 +1747,7 @@ export class SceneEntities {
|
||||
): (() => SegmentOverlayPayload | null) => {
|
||||
const segPathToNode = getNodePathFromSourceRange(
|
||||
modifiedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
const sgPaths = sketch.paths
|
||||
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
||||
@ -1901,8 +1898,10 @@ export class SceneEntities {
|
||||
SEGMENT_BODIES_PLUS_PROFILE_START
|
||||
)
|
||||
if (parent?.userData?.pathToNode) {
|
||||
const updatedAst = parse(recast(kclManager.ast))
|
||||
if (trap(updatedAst)) return
|
||||
const pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
const updatedAst = pResult.program
|
||||
const _node = getNodeFromPath<Node<CallExpression>>(
|
||||
updatedAst,
|
||||
parent.userData.pathToNode,
|
||||
@ -1910,7 +1909,7 @@ export class SceneEntities {
|
||||
)
|
||||
if (trap(_node, { suppress: true })) return
|
||||
const node = _node.node
|
||||
editorManager.setHighlightRange([[node.start, node.end]])
|
||||
editorManager.setHighlightRange([[node.start, node.end, true]])
|
||||
const yellow = 0xffff00
|
||||
colorSegment(selected, yellow)
|
||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||
@ -1955,10 +1954,10 @@ export class SceneEntities {
|
||||
})
|
||||
return
|
||||
}
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
},
|
||||
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
const parent = getParentGroup(
|
||||
selected,
|
||||
SEGMENT_BODIES_PLUS_PROFILE_START
|
||||
@ -2057,7 +2056,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(_node)) return _node
|
||||
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
||||
const variableDeclarationName = _node.node?.declaration.id?.name || ''
|
||||
const sg = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
@ -2082,28 +2081,30 @@ function prepareTruncatedMemoryAndAst(
|
||||
])
|
||||
}
|
||||
;(
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
).body.push(newSegment)
|
||||
// update source ranges to section we just added.
|
||||
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
|
||||
const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
||||
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
|
||||
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Error('Unexpected compilation error')
|
||||
const updatedSrcRangeAst = pResult.program
|
||||
|
||||
const lastPipeItem = (
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
||||
.declarations[0].init as PipeExpression
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
).body.slice(-1)[0]
|
||||
|
||||
;(
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
).body.slice(-1)[0].start = lastPipeItem.start
|
||||
|
||||
_ast.end = lastPipeItem.end
|
||||
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
|
||||
varDec.end = lastPipeItem.end
|
||||
const declarator = varDec.declarations[0]
|
||||
const declarator = varDec.declaration
|
||||
declarator.end = lastPipeItem.end
|
||||
const init = declarator.init as Node<PipeExpression>
|
||||
init.end = lastPipeItem.end
|
||||
@ -2140,7 +2141,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
if (node.type !== 'VariableDeclaration') {
|
||||
continue
|
||||
}
|
||||
const name = node.declarations[0].id.name
|
||||
const name = node.declaration.id.name
|
||||
const memoryItem = programMemory.get(name)
|
||||
if (!memoryItem) {
|
||||
continue
|
||||
|
||||
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { trap } from 'lib/trap'
|
||||
import { codeToIdSelections } from 'lib/selections'
|
||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||
import { defaultSourceRange } from 'lang/wasm'
|
||||
|
||||
export function AstExplorer() {
|
||||
const { context } = useModelingContext()
|
||||
@ -46,7 +47,7 @@ export function AstExplorer() {
|
||||
<div
|
||||
className="h-full relative"
|
||||
onMouseLeave={(e) => {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
}}
|
||||
>
|
||||
<pre className="text-xs">
|
||||
@ -115,15 +116,19 @@ function DisplayObj({
|
||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||
}`}
|
||||
onMouseEnter={(e) => {
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onMouseMove={(e) => {
|
||||
e.stopPropagation()
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||
}}
|
||||
onClick={(e) => {
|
||||
const range: [number, number] = [obj?.start || 0, obj.end || 0]
|
||||
const range: [number, number, boolean] = [
|
||||
obj?.start || 0,
|
||||
obj.end || 0,
|
||||
true,
|
||||
]
|
||||
const idInfo = codeToIdSelections([
|
||||
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
||||
])[0]
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm'
|
||||
import {
|
||||
parse,
|
||||
BinaryPart,
|
||||
Expr,
|
||||
ProgramMemory,
|
||||
resultIsOk,
|
||||
} from '../lang/wasm'
|
||||
import {
|
||||
createIdentifier,
|
||||
createLiteral,
|
||||
@ -141,8 +147,9 @@ export function useCalc({
|
||||
useEffect(() => {
|
||||
try {
|
||||
const code = `const __result__ = ${value}`
|
||||
const ast = parse(code)
|
||||
if (trap(ast)) return
|
||||
const pResult = parse(code)
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return
|
||||
const ast = pResult.program
|
||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||
for (const { key, value } of availableVarInfo.variables) {
|
||||
const error = _programMem.set(key, {
|
||||
@ -156,18 +163,17 @@ export function useCalc({
|
||||
executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: kclManager.programMemory.clone(),
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
}).then(({ execState }) => {
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declarations?.[0]?.id?.name === '__result__'
|
||||
a.declaration.id?.name === '__result__'
|
||||
)
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declarations?.[0]?.init
|
||||
resultDeclaration?.declaration.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
|
||||
@ -266,6 +266,7 @@ const FileTreeItem = ({
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
onFileOpen(fileOrDir.path, project?.path || null)
|
||||
kclManager.switchedFiles = true
|
||||
|
||||
// Open kcl files
|
||||
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||
|
||||
@ -68,7 +68,7 @@ import {
|
||||
sketchOnOffsetPlane,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { Program, parse, recast } from 'lang/wasm'
|
||||
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import {
|
||||
doesSceneHaveExtrudedSketch,
|
||||
doesSceneHaveSweepableSketch,
|
||||
@ -632,15 +632,11 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
},
|
||||
'Has exportable geometry': () => {
|
||||
if (
|
||||
kclManager.kclErrors.length === 0 &&
|
||||
kclManager.ast.body.length > 0
|
||||
)
|
||||
if (!kclManager.hasErrors() && kclManager.ast.body.length > 0)
|
||||
return true
|
||||
else {
|
||||
let errorMessage = 'Unable to Export '
|
||||
if (kclManager.kclErrors.length > 0)
|
||||
errorMessage += 'due to KCL Errors'
|
||||
if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors'
|
||||
else if (kclManager.ast.body.length === 0)
|
||||
errorMessage += 'due to Empty Scene'
|
||||
console.error(errorMessage)
|
||||
@ -758,7 +754,11 @@ export const ModelingMachineProvider = ({
|
||||
constraint: 'setHorzDistance',
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
@ -799,7 +799,10 @@ export const ModelingMachineProvider = ({
|
||||
constraint: 'setVertDistance',
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
@ -847,7 +850,10 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
|
||||
|
||||
if (!sketchDetails)
|
||||
@ -889,7 +895,10 @@ export const ModelingMachineProvider = ({
|
||||
await applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
@ -929,7 +938,10 @@ export const ModelingMachineProvider = ({
|
||||
await applyConstraintIntersect({
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
@ -970,7 +982,10 @@ export const ModelingMachineProvider = ({
|
||||
constraint: 'xAbs',
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
@ -1011,7 +1026,10 @@ export const ModelingMachineProvider = ({
|
||||
constraint: 'yAbs',
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
@ -1052,9 +1070,10 @@ export const ModelingMachineProvider = ({
|
||||
const { variableName } = await getVarNameModal({
|
||||
valueName: data?.variableName || 'var',
|
||||
})
|
||||
let parsed = parse(recast(kclManager.ast))
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
parsed = parsed as Node<Program>
|
||||
let pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
let parsed = pResult.program
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||
moveValueIntoNewVariablePath(
|
||||
@ -1063,7 +1082,11 @@ export const ModelingMachineProvider = ({
|
||||
data?.pathToNode || [],
|
||||
variableName
|
||||
)
|
||||
parsed = parse(recast(_modifiedAst))
|
||||
pResult = parse(recast(_modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
parsed = pResult.program
|
||||
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
parsed = parsed as Node<Program>
|
||||
if (!pathToReplacedNode)
|
||||
|
||||
@ -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.Item>
|
||||
<button
|
||||
onClick={() => kclManager.format()}
|
||||
onClick={() => {
|
||||
kclManager.format().catch(reportRejection)
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
<span>Format code</span>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { processMemory } from './MemoryPane'
|
||||
import { enginelessExecutor } from '../../../lib/testHelpers'
|
||||
import { initPromise, parse, ProgramMemory } from '../../../lang/wasm'
|
||||
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -28,12 +28,16 @@ describe('processMemory', () => {
|
||||
|> lineTo([0.98, 5.16], %)
|
||||
|> lineTo([2.15, 4.32], %)
|
||||
// |> rx(90, %)`
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
|
||||
const output = processMemory(execState.memory)
|
||||
expect(output.myVar).toEqual(5)
|
||||
expect(output.otherVar).toEqual(3)
|
||||
expect(output).toEqual({
|
||||
HALF_TURN: 180,
|
||||
QUARTER_TURN: 90,
|
||||
THREE_QUARTER_TURN: 270,
|
||||
ZERO: 0,
|
||||
myVar: 5,
|
||||
myFn: '__function(a)__',
|
||||
otherVar: 3,
|
||||
|
||||
@ -90,7 +90,7 @@ export const sidebarPanes: SidebarPane[] = [
|
||||
keybinding: 'Shift + C',
|
||||
showBadge: {
|
||||
value: ({ kclContext }) => {
|
||||
return kclContext.errors.length
|
||||
return kclContext.diagnostics.length
|
||||
},
|
||||
onClick: (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
@ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
settings: settings.context,
|
||||
platform: getPlatformString(),
|
||||
}),
|
||||
[kclContext.errors, settings.context]
|
||||
[kclContext.diagnostics, settings.context]
|
||||
)
|
||||
|
||||
const sidebarActions: SidebarAction[] = [
|
||||
|
||||
@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
@ -68,8 +68,7 @@ function AppLogoLink({
|
||||
data-testid="app-logo"
|
||||
onClick={() => {
|
||||
onProjectClose(file || null, project?.path || null, false)
|
||||
// Clear the scene and end the session.
|
||||
engineCommandManager.endSession()
|
||||
kclManager.switchedFiles = true
|
||||
}}
|
||||
to={PATHS.HOME}
|
||||
className={wrapperClassName + ' hover:before:brightness-110'}
|
||||
@ -190,8 +189,7 @@ function ProjectMenuPopover({
|
||||
className: !isDesktop() ? 'hidden' : '',
|
||||
onClick: () => {
|
||||
onProjectClose(file || null, project?.path || null, true)
|
||||
// Clear the scene and end the session.
|
||||
engineCommandManager.endSession()
|
||||
kclManager.switchedFiles = true
|
||||
},
|
||||
},
|
||||
].filter(
|
||||
|
||||
@ -40,7 +40,10 @@ export function removeConstrainingValuesInfo({
|
||||
otherSelections: [],
|
||||
graphSelections: nodes.map(
|
||||
(node): Selection => ({
|
||||
codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
|
||||
codeRef: codeRefFromRange(
|
||||
[node.start, node.end, true],
|
||||
kclManager.ast
|
||||
),
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
@ -139,7 +139,9 @@ export default class EditorManager {
|
||||
}
|
||||
|
||||
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
||||
this._highlightRange = range
|
||||
this._highlightRange = range.map((s): [number, number] => {
|
||||
return [s[0], s[1]]
|
||||
})
|
||||
|
||||
const selectionsWithSafeEnds = range.map((s): [number, number] => {
|
||||
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { CallExpression } from 'lang/wasm'
|
||||
import { CallExpression, defaultSourceRange } from 'lang/wasm'
|
||||
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||
|
||||
export function useEngineConnectionSubscriptions() {
|
||||
@ -46,7 +46,7 @@ export function useEngineConnectionSubscriptions() {
|
||||
(editorManager.highlightRange[0][0] !== 0 &&
|
||||
editorManager.highlightRange[0][1] !== 0)
|
||||
) {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -201,7 +201,7 @@ export function useEngineConnectionSubscriptions() {
|
||||
const { z_axis, y_axis, origin } = faceInfo
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
err(codeRef) ? [0, 0] : codeRef.range
|
||||
err(codeRef) ? defaultSourceRange() : codeRef.range
|
||||
)
|
||||
|
||||
const getEdgeCutMeta = (): null | EdgeCutInfo => {
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { KCLError } from './errors'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
programMemory: kclManager?.programMemory,
|
||||
ast: kclManager?.ast,
|
||||
isExecuting: kclManager?.isExecuting,
|
||||
errors: kclManager?.kclErrors,
|
||||
diagnostics: kclManager?.diagnostics,
|
||||
logs: kclManager?.logs,
|
||||
wasmInitFailed: kclManager?.wasmInitFailed,
|
||||
})
|
||||
@ -32,7 +32,7 @@ export function KclContextProvider({
|
||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||
const [ast, setAst] = useState(kclManager.ast)
|
||||
const [isExecuting, setIsExecuting] = useState(false)
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [diagnostics, setErrors] = useState<Diagnostic[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
|
||||
@ -57,7 +57,7 @@ export function KclContextProvider({
|
||||
programMemory,
|
||||
ast,
|
||||
isExecuting,
|
||||
errors,
|
||||
diagnostics,
|
||||
logs,
|
||||
wasmInitFailed,
|
||||
}}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { executeAst, lintAst } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||
import {
|
||||
KCLError,
|
||||
complilationErrorsToDiagnostics,
|
||||
kclErrorsToDiagnostics,
|
||||
} from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EngineCommandManager } from './std/engineConnection'
|
||||
import { err } from 'lib/trap'
|
||||
@ -8,6 +12,7 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||
|
||||
import {
|
||||
CallExpression,
|
||||
clearSceneAndBustCache,
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
initPromise,
|
||||
@ -51,11 +56,12 @@ export class KclManager {
|
||||
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
||||
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
|
||||
private _logs: string[] = []
|
||||
private _lints: Diagnostic[] = []
|
||||
private _kclErrors: KCLError[] = []
|
||||
private _diagnostics: Diagnostic[] = []
|
||||
private _isExecuting = false
|
||||
private _executeIsStale: ExecuteArgs | null = null
|
||||
private _wasmInitFailed = true
|
||||
private _hasErrors = false
|
||||
private _switchedFiles = false
|
||||
|
||||
engineCommandManager: EngineCommandManager
|
||||
|
||||
@ -63,7 +69,7 @@ export class KclManager {
|
||||
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
|
||||
private _kclErrorsCallBack: (errors: Diagnostic[]) => void = () => {}
|
||||
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
||||
private _executeCallback: () => void = () => {}
|
||||
|
||||
@ -75,6 +81,10 @@ export class KclManager {
|
||||
this._astCallBack(ast)
|
||||
}
|
||||
|
||||
set switchedFiles(switchedFiles: boolean) {
|
||||
this._switchedFiles = switchedFiles
|
||||
}
|
||||
|
||||
get programMemory() {
|
||||
return this._programMemory
|
||||
}
|
||||
@ -84,7 +94,7 @@ export class KclManager {
|
||||
this._programMemoryCallBack(programMemory)
|
||||
}
|
||||
|
||||
set execState(execState) {
|
||||
private set execState(execState) {
|
||||
this._execState = execState
|
||||
this.programMemory = execState.memory
|
||||
}
|
||||
@ -101,38 +111,28 @@ export class KclManager {
|
||||
this._logsCallBack(logs)
|
||||
}
|
||||
|
||||
get lints() {
|
||||
return this._lints
|
||||
get diagnostics() {
|
||||
return this._diagnostics
|
||||
}
|
||||
|
||||
set lints(lints) {
|
||||
if (lints === this._lints) return
|
||||
this._lints = lints
|
||||
// Run the lints through the diagnostics.
|
||||
this.kclErrors = this._kclErrors
|
||||
}
|
||||
|
||||
get kclErrors() {
|
||||
return this._kclErrors
|
||||
}
|
||||
set kclErrors(kclErrors) {
|
||||
if (kclErrors === this._kclErrors && this.lints.length === 0) return
|
||||
this._kclErrors = kclErrors
|
||||
set diagnostics(ds) {
|
||||
if (ds === this._diagnostics) return
|
||||
this._diagnostics = ds
|
||||
this.setDiagnosticsForCurrentErrors()
|
||||
this._kclErrorsCallBack(kclErrors)
|
||||
}
|
||||
|
||||
addDiagnostics(ds: Diagnostic[]) {
|
||||
if (ds.length === 0) return
|
||||
this.diagnostics = this.diagnostics.concat(ds)
|
||||
}
|
||||
|
||||
hasErrors(): boolean {
|
||||
return this._hasErrors
|
||||
}
|
||||
|
||||
setDiagnosticsForCurrentErrors() {
|
||||
let diagnostics = kclErrorsToDiagnostics(this.kclErrors)
|
||||
if (this.lints.length > 0) {
|
||||
diagnostics = diagnostics.concat(this.lints)
|
||||
}
|
||||
editorManager?.setDiagnostics(diagnostics)
|
||||
}
|
||||
|
||||
addKclErrors(kclErrors: KCLError[]) {
|
||||
if (kclErrors.length === 0) return
|
||||
this.kclErrors = this.kclErrors.concat(kclErrors)
|
||||
editorManager?.setDiagnostics(this.diagnostics)
|
||||
this._kclErrorsCallBack(this.diagnostics)
|
||||
}
|
||||
|
||||
get isExecuting() {
|
||||
@ -172,8 +172,12 @@ export class KclManager {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ensureWasmInit().then(() => {
|
||||
this.ast = this.safeParse(codeManager.code) || this.ast
|
||||
this.ensureWasmInit().then(async () => {
|
||||
await this.safeParse(codeManager.code).then((ast) => {
|
||||
if (ast) {
|
||||
this.ast = ast
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -188,7 +192,7 @@ export class KclManager {
|
||||
setProgramMemory: (arg: ProgramMemory) => void
|
||||
setAst: (arg: Node<Program>) => void
|
||||
setLogs: (arg: string[]) => void
|
||||
setKclErrors: (arg: KCLError[]) => void
|
||||
setKclErrors: (errors: Diagnostic[]) => void
|
||||
setIsExecuting: (arg: boolean) => void
|
||||
setWasmInitFailed: (arg: boolean) => void
|
||||
}) {
|
||||
@ -217,18 +221,48 @@ export class KclManager {
|
||||
}
|
||||
}
|
||||
|
||||
safeParse(code: string): Node<Program> | null {
|
||||
const ast = parse(code)
|
||||
this.lints = []
|
||||
this.kclErrors = []
|
||||
if (!err(ast)) return ast
|
||||
const kclerror: KCLError = ast as KCLError
|
||||
// (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
|
||||
}
|
||||
}
|
||||
|
||||
this.addKclErrors([kclerror])
|
||||
// TODO: re-eval if session should end?
|
||||
if (kclerror.msg === 'file is empty')
|
||||
this.engineCommandManager?.endSession()
|
||||
return null
|
||||
async safeParse(code: string): Promise<Node<Program> | null> {
|
||||
const result = parse(code)
|
||||
this.diagnostics = []
|
||||
this._hasErrors = false
|
||||
|
||||
if (err(result)) {
|
||||
const kclerror: KCLError = result as KCLError
|
||||
this.diagnostics = kclErrorsToDiagnostics([kclerror])
|
||||
this._hasErrors = true
|
||||
|
||||
await this.checkIfSwitchedFilesShouldClear()
|
||||
return null
|
||||
}
|
||||
|
||||
this.addDiagnostics(complilationErrorsToDiagnostics(result.errors))
|
||||
this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings))
|
||||
if (result.errors.length > 0) {
|
||||
this._hasErrors = true
|
||||
|
||||
await this.checkIfSwitchedFilesShouldClear()
|
||||
return null
|
||||
}
|
||||
|
||||
return result.program
|
||||
}
|
||||
|
||||
async ensureWasmInit() {
|
||||
@ -267,19 +301,16 @@ export class KclManager {
|
||||
this._cancelTokens.set(currentExecutionId, false)
|
||||
|
||||
this.isExecuting = true
|
||||
// Make sure we clear before starting again. End session will do this.
|
||||
this.engineCommandManager?.endSession()
|
||||
await this.ensureWasmInit()
|
||||
const { logs, errors, execState, isInterrupted } = await executeAst({
|
||||
ast,
|
||||
idGenerator: this.execState.idGenerator,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
})
|
||||
|
||||
// Program was not interrupted, setup the scene
|
||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||
if (!isInterrupted) {
|
||||
this.lints = await lintAst({ ast: ast })
|
||||
this.addDiagnostics(await lintAst({ ast: ast }))
|
||||
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||
@ -321,9 +352,7 @@ export class KclManager {
|
||||
|
||||
this.logs = logs
|
||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||
this.addKclErrors(isInterrupted ? [] : errors)
|
||||
// Reset the next ID index so that we reuse the previous IDs next time.
|
||||
execState.idGenerator.nextId = 0
|
||||
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
||||
this.execState = execState
|
||||
if (!errors.length) {
|
||||
this.lastSuccessfulProgramMemory = execState.memory
|
||||
@ -355,7 +384,7 @@ export class KclManager {
|
||||
console.error(newCode)
|
||||
return
|
||||
}
|
||||
const newAst = this.safeParse(newCode)
|
||||
const newAst = await this.safeParse(newCode)
|
||||
if (!newAst) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -364,13 +393,13 @@ export class KclManager {
|
||||
|
||||
const { logs, errors, execState } = await executeAst({
|
||||
ast: newAst,
|
||||
idGenerator: this.execState.idGenerator,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
})
|
||||
|
||||
this._logs = logs
|
||||
this._kclErrors = errors
|
||||
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||
this._execState = execState
|
||||
this._programMemory = execState.memory
|
||||
if (!errors.length) {
|
||||
@ -398,7 +427,7 @@ export class KclManager {
|
||||
...artifact,
|
||||
codeRef: {
|
||||
...artifact.codeRef,
|
||||
range: [node.start, node.end],
|
||||
range: [node.start, node.end, true],
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -410,7 +439,7 @@ export class KclManager {
|
||||
})
|
||||
}
|
||||
async executeCode(zoomToFit?: boolean): Promise<void> {
|
||||
const ast = this.safeParse(codeManager.code)
|
||||
const ast = await this.safeParse(codeManager.code)
|
||||
if (!ast) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -418,9 +447,9 @@ export class KclManager {
|
||||
this.ast = { ...ast }
|
||||
return this.executeAst({ zoomToFit })
|
||||
}
|
||||
format() {
|
||||
async format() {
|
||||
const originalCode = codeManager.code
|
||||
const ast = this.safeParse(originalCode)
|
||||
const ast = await this.safeParse(originalCode)
|
||||
if (!ast) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -460,7 +489,7 @@ export class KclManager {
|
||||
const newCode = recast(ast)
|
||||
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'))
|
||||
let returnVal: Selections | undefined = undefined
|
||||
|
||||
@ -490,7 +519,7 @@ export class KclManager {
|
||||
if (start && end) {
|
||||
returnVal.graphSelections.push({
|
||||
codeRef: {
|
||||
range: [start, end],
|
||||
range: [start, end, true],
|
||||
pathToNode: path,
|
||||
},
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
import { parse, initPromise } from './wasm'
|
||||
import { assertParse, initPromise } from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -14,7 +14,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
// |> rx(45, %)`
|
||||
const execState = await enginelessExecutor(parse(code))
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
// @ts-ignore
|
||||
const sketch001 = execState.memory.get('mySketch001')
|
||||
expect(sketch001).toEqual({
|
||||
@ -67,7 +67,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
// |> rx(45, %)
|
||||
|> extrude(2, %)`
|
||||
const execState = await enginelessExecutor(parse(code))
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
// @ts-ignore
|
||||
const sketch001 = execState.memory.get('mySketch001')
|
||||
expect(sketch001).toEqual({
|
||||
@ -147,7 +147,7 @@ const sk2 = startSketchOn('XY')
|
||||
|> extrude(2, %)
|
||||
|
||||
`
|
||||
const execState = await enginelessExecutor(parse(code))
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
const programMemory = execState.memory
|
||||
// @ts-ignore
|
||||
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
|
||||
|
||||
@ -8,20 +8,14 @@ describe('test kclErrToDiagnostic', () => {
|
||||
message: '',
|
||||
kind: 'semantic',
|
||||
msg: 'Semantic error',
|
||||
sourceRanges: [
|
||||
[0, 1, 0],
|
||||
[2, 3, 0],
|
||||
],
|
||||
sourceRange: [0, 1, true],
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
message: '',
|
||||
kind: 'type',
|
||||
msg: 'Type error',
|
||||
sourceRanges: [
|
||||
[4, 5, 0],
|
||||
[6, 7, 0],
|
||||
],
|
||||
sourceRange: [4, 5, true],
|
||||
},
|
||||
]
|
||||
const diagnostics = kclErrorsToDiagnostics(errors)
|
||||
@ -32,24 +26,12 @@ describe('test kclErrToDiagnostic', () => {
|
||||
message: 'Semantic error',
|
||||
severity: 'error',
|
||||
},
|
||||
{
|
||||
from: 2,
|
||||
to: 3,
|
||||
message: 'Semantic error',
|
||||
severity: 'error',
|
||||
},
|
||||
{
|
||||
from: 4,
|
||||
to: 5,
|
||||
message: 'Type error',
|
||||
severity: 'error',
|
||||
},
|
||||
{
|
||||
from: 6,
|
||||
to: 7,
|
||||
message: 'Type error',
|
||||
severity: 'error',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,88 +1,90 @@
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
|
||||
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
|
||||
import { posToOffset } from '@kittycad/codemirror-lsp-client'
|
||||
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
||||
import { Text } from '@codemirror/state'
|
||||
|
||||
const TOP_LEVEL_MODULE_ID = 0
|
||||
import { EditorView } from 'codemirror'
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
|
||||
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
||||
export class KCLError extends Error {
|
||||
kind: ExtractKind<RustKclError> | 'name'
|
||||
sourceRanges: [number, number, number][]
|
||||
sourceRange: SourceRange
|
||||
msg: string
|
||||
|
||||
constructor(
|
||||
kind: ExtractKind<RustKclError> | 'name',
|
||||
msg: string,
|
||||
sourceRanges: [number, number, number][]
|
||||
sourceRange: SourceRange
|
||||
) {
|
||||
super()
|
||||
this.kind = kind
|
||||
this.msg = msg
|
||||
this.sourceRanges = sourceRanges
|
||||
this.sourceRange = sourceRange
|
||||
Object.setPrototypeOf(this, KCLError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLLexicalError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('lexical', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('lexical', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLInternalError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('internal', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('internal', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLSyntaxError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('syntax', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('syntax', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLSemanticError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('semantic', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('semantic', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLTypeError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('type', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('type', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLUnimplementedError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('unimplemented', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('unimplemented', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLUnexpectedError extends KCLError {
|
||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
||||
super('unexpected', msg, sourceRanges)
|
||||
constructor(msg: string, sourceRange: SourceRange) {
|
||||
super('unexpected', msg, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLValueAlreadyDefined extends KCLError {
|
||||
constructor(key: string, sourceRanges: [number, number, number][]) {
|
||||
super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
|
||||
constructor(key: string, sourceRange: SourceRange) {
|
||||
super('name', `Key ${key} was already defined elsewhere`, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class KCLUndefinedValueError extends KCLError {
|
||||
constructor(key: string, sourceRanges: [number, number, number][]) {
|
||||
super('name', `Key ${key} has not been defined`, sourceRanges)
|
||||
constructor(key: string, sourceRange: SourceRange) {
|
||||
super('name', `Key ${key} has not been defined`, sourceRange)
|
||||
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
||||
}
|
||||
}
|
||||
@ -99,27 +101,14 @@ export function lspDiagnosticsToKclErrors(
|
||||
.flatMap(
|
||||
({ range, message }) =>
|
||||
new KCLError('unexpected', message, [
|
||||
[
|
||||
posToOffset(doc, range.start)!,
|
||||
posToOffset(doc, range.end)!,
|
||||
TOP_LEVEL_MODULE_ID,
|
||||
],
|
||||
posToOffset(doc, range.start)!,
|
||||
posToOffset(doc, range.end)!,
|
||||
true,
|
||||
])
|
||||
)
|
||||
.filter(({ sourceRanges }) => {
|
||||
const [from, to, moduleId] = sourceRanges[0]
|
||||
return (
|
||||
from !== null &&
|
||||
to !== null &&
|
||||
from !== undefined &&
|
||||
to !== undefined &&
|
||||
// Filter out errors that are not from the top-level module.
|
||||
moduleId === TOP_LEVEL_MODULE_ID
|
||||
)
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const c = a.sourceRanges[0][0]
|
||||
const d = b.sourceRanges[0][0]
|
||||
const c = a.sourceRange[0]
|
||||
const d = b.sourceRange[0]
|
||||
switch (true) {
|
||||
case c < d:
|
||||
return -1
|
||||
@ -137,17 +126,48 @@ export function lspDiagnosticsToKclErrors(
|
||||
export function kclErrorsToDiagnostics(
|
||||
errors: KCLError[]
|
||||
): CodeMirrorDiagnostic[] {
|
||||
return errors?.flatMap((err) => {
|
||||
const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges
|
||||
// Filter out errors that are not from the top-level module.
|
||||
.filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID)
|
||||
.map(([from, to]) => {
|
||||
return { from, to, message: err.msg, severity: 'error' }
|
||||
})
|
||||
// Make sure we didn't filter out all the source ranges.
|
||||
if (sourceRanges.length === 0) {
|
||||
sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' })
|
||||
}
|
||||
return sourceRanges
|
||||
})
|
||||
return errors
|
||||
?.filter((err) => err.sourceRange[2])
|
||||
.map((err) => {
|
||||
return {
|
||||
from: err.sourceRange[0],
|
||||
to: err.sourceRange[1],
|
||||
message: err.msg,
|
||||
severity: 'error',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function complilationErrorsToDiagnostics(
|
||||
errors: CompilationError[]
|
||||
): CodeMirrorDiagnostic[] {
|
||||
return errors
|
||||
?.filter((err) => err.sourceRange[2] === 0)
|
||||
.map((err) => {
|
||||
let severity: any = 'error'
|
||||
if (err.severity === 'Warning') {
|
||||
severity = 'warning'
|
||||
}
|
||||
let actions
|
||||
const suggestion = err.suggestion
|
||||
if (suggestion) {
|
||||
actions = [
|
||||
{
|
||||
name: suggestion.title,
|
||||
apply: (view: EditorView, from: number, to: number) => {
|
||||
view.dispatch({
|
||||
changes: { from, to, insert: suggestion.insert },
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
return {
|
||||
from: err.sourceRange[0],
|
||||
to: err.sourceRange[1],
|
||||
message: err.message,
|
||||
severity,
|
||||
actions,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
import {
|
||||
parse,
|
||||
assertParse,
|
||||
ProgramMemory,
|
||||
Sketch,
|
||||
initPromise,
|
||||
@ -472,7 +472,7 @@ describe('Testing Errors', () => {
|
||||
const theExtrude = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([-2.4, 5], %)
|
||||
|> line([-0.76], myVarZ, %)
|
||||
|> line(myVarZ, %)
|
||||
|> line([5,5], %)
|
||||
|> close(%)
|
||||
|> extrude(4, %)`
|
||||
@ -480,7 +480,7 @@ const theExtrude = startSketchOn('XY')
|
||||
new KCLError(
|
||||
'undefined_value',
|
||||
'memory item key `myVarZ` is not defined',
|
||||
[[129, 135, 0]]
|
||||
[129, 135, true]
|
||||
)
|
||||
)
|
||||
})
|
||||
@ -492,7 +492,7 @@ async function exe(
|
||||
code: string,
|
||||
programMemory: ProgramMemory = ProgramMemory.empty()
|
||||
) {
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
|
||||
const execState = await enginelessExecutor(ast, programMemory)
|
||||
return execState.memory
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||
import { Identifier, parse, initPromise, Parameter } from './wasm'
|
||||
import { Identifier, assertParse, initPromise, Parameter } from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -17,19 +17,19 @@ const sk3 = startSketchAt([0, 0])
|
||||
`
|
||||
const subStr = 'lineTo([3, 4], %, $yo)'
|
||||
const lineToSubstringIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number] = [
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
lineToSubstringIndex,
|
||||
lineToSubstringIndex + subStr.length,
|
||||
true,
|
||||
]
|
||||
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const _node = getNodeFromPath<any>(ast, nodePath)
|
||||
if (err(_node)) throw _node
|
||||
const { node } = _node
|
||||
|
||||
expect([node.start, node.end]).toEqual(sourceRange)
|
||||
expect([node.start, node.end, true]).toEqual(sourceRange)
|
||||
expect(node.type).toBe('CallExpression')
|
||||
})
|
||||
it('gets path right for function definition params', () => {
|
||||
@ -45,13 +45,13 @@ const sk3 = startSketchAt([0, 0])
|
||||
const b1 = cube([0,0], 10)`
|
||||
const subStr = 'pos, scale'
|
||||
const subStrIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number] = [
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
subStrIndex,
|
||||
subStrIndex + 'pos'.length,
|
||||
true,
|
||||
]
|
||||
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const _node = getNodeFromPath<Parameter>(ast, nodePath)
|
||||
if (err(_node)) throw _node
|
||||
@ -60,8 +60,7 @@ const b1 = cube([0,0], 10)`
|
||||
expect(nodePath).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['params', 'FunctionExpression'],
|
||||
[0, 'index'],
|
||||
@ -82,13 +81,13 @@ const b1 = cube([0,0], 10)`
|
||||
const b1 = cube([0,0], 10)`
|
||||
const subStr = 'scale, 0'
|
||||
const subStrIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number] = [
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
subStrIndex,
|
||||
subStrIndex + 'scale'.length,
|
||||
true,
|
||||
]
|
||||
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const _node = getNodeFromPath<Identifier>(ast, nodePath)
|
||||
if (err(_node)) throw _node
|
||||
@ -96,14 +95,12 @@ const b1 = cube([0,0], 10)`
|
||||
expect(nodePath).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['body', 'FunctionExpression'],
|
||||
['body', 'FunctionExpression'],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['body', 'PipeExpression'],
|
||||
[2, 'index'],
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { parse, initPromise, programMemoryInit } from './wasm'
|
||||
import { assertParse, initPromise, programMemoryInit } from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { assert } from 'vitest'
|
||||
// These unit tests makes web requests to a public github repository.
|
||||
|
||||
interface KclSampleFile {
|
||||
@ -58,8 +57,7 @@ describe('Test KCL Samples from public Github repository', () => {
|
||||
files.forEach((file: KclSampleFile) => {
|
||||
it(`should parse ${file.filename} without errors`, async () => {
|
||||
const code = await getKclSampleCodeFromGithub(file.filename)
|
||||
const parsed = parse(code)
|
||||
assert(!(parsed instanceof Error))
|
||||
assertParse(code)
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
@ -71,9 +69,8 @@ describe('Test KCL Samples from public Github repository', () => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file: KclSampleFile = files[i]
|
||||
const code = await getKclSampleCodeFromGithub(file.filename)
|
||||
const parsed = parse(code)
|
||||
assert(!(parsed instanceof Error))
|
||||
await enginelessExecutor(parsed, programMemoryInit())
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(ast, programMemoryInit())
|
||||
}
|
||||
},
|
||||
files.length * 1000
|
||||
|
||||
@ -2,7 +2,6 @@ import {
|
||||
Program,
|
||||
_executor,
|
||||
ProgramMemory,
|
||||
programMemoryInit,
|
||||
kclLint,
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
@ -11,7 +10,6 @@ import { enginelessExecutor } from 'lib/testHelpers'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { KCLError } from 'lang/errors'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
export type ToolTip =
|
||||
@ -49,15 +47,13 @@ export const toolTips: Array<ToolTip> = [
|
||||
export async function executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor = false,
|
||||
// If you set programMemoryOverride we assume you mean mock mode. Since that
|
||||
// is the only way to go about it.
|
||||
programMemoryOverride,
|
||||
idGenerator,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
engineCommandManager: EngineCommandManager
|
||||
useFakeExecutor?: boolean
|
||||
programMemoryOverride?: ProgramMemory
|
||||
idGenerator?: IdGenerator
|
||||
isInterrupted?: boolean
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
@ -66,22 +62,14 @@ export async function executeAst({
|
||||
isInterrupted: boolean
|
||||
}> {
|
||||
try {
|
||||
if (!useFakeExecutor) {
|
||||
engineCommandManager.endSession()
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.startNewSession()
|
||||
}
|
||||
const execState = await (useFakeExecutor
|
||||
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
|
||||
: _executor(
|
||||
ast,
|
||||
programMemoryInit(),
|
||||
idGenerator,
|
||||
engineCommandManager,
|
||||
false
|
||||
))
|
||||
const execState = await (programMemoryOverride
|
||||
? enginelessExecutor(ast, programMemoryOverride)
|
||||
: _executor(ast, engineCommandManager))
|
||||
|
||||
await engineCommandManager.waitForAllCommands(
|
||||
programMemoryOverride !== undefined
|
||||
)
|
||||
|
||||
await engineCommandManager.waitForAllCommands(useFakeExecutor)
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { parse, recast, initPromise, Identifier } from './wasm'
|
||||
import { assertParse, recast, initPromise, Identifier } from './wasm'
|
||||
import {
|
||||
createLiteral,
|
||||
createIdentifier,
|
||||
@ -82,11 +82,11 @@ describe('Testing createVariableDeclaration', () => {
|
||||
it('should create a variable declaration', () => {
|
||||
const result = createVariableDeclaration('myVar', createLiteral(5))
|
||||
expect(result.type).toBe('VariableDeclaration')
|
||||
expect(result.declarations[0].type).toBe('VariableDeclarator')
|
||||
expect(result.declarations[0].id.type).toBe('Identifier')
|
||||
expect(result.declarations[0].id.name).toBe('myVar')
|
||||
expect(result.declarations[0].init.type).toBe('Literal')
|
||||
expect((result.declarations[0].init as any).value).toBe(5)
|
||||
expect(result.declaration.type).toBe('VariableDeclarator')
|
||||
expect(result.declaration.id.type).toBe('Identifier')
|
||||
expect(result.declaration.id.name).toBe('myVar')
|
||||
expect(result.declaration.init.type).toBe('Literal')
|
||||
expect((result.declaration.init as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createPipeExpression', () => {
|
||||
@ -146,10 +146,13 @@ function giveSketchFnCallTagTestHelper(
|
||||
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
|
||||
// this wrapper changes the input and output to code
|
||||
// making it more of an integration test, but easier to read the test intention is the goal
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const start = code.indexOf(searchStr)
|
||||
const range: [number, number] = [start, start + searchStr.length]
|
||||
const range: [number, number, boolean] = [
|
||||
start,
|
||||
start + searchStr.length,
|
||||
true,
|
||||
]
|
||||
const sketchRes = giveSketchFnCallTag(ast, range)
|
||||
if (err(sketchRes)) throw sketchRes
|
||||
const { modifiedAst, tag, isTagExisting } = sketchRes
|
||||
@ -221,14 +224,13 @@ part001 = startSketchOn('XY')
|
||||
|> angledLine([jkl(yo) + 2, 3.09], %)
|
||||
yo2 = hmm([identifierGuy + 5])`
|
||||
it('should move a binary expression into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('100 + 100') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex],
|
||||
[startIndex, startIndex, true],
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -236,14 +238,13 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||
})
|
||||
it('should move a value into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('2.8') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex],
|
||||
[startIndex, startIndex, true],
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -251,14 +252,13 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(newCode).toContain(`line([newVar, 0], %)`)
|
||||
})
|
||||
it('should move a callExpression into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('def(')
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex],
|
||||
[startIndex, startIndex, true],
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -266,14 +266,13 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||
})
|
||||
it('should move a binary expression with call expression into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('jkl(') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex],
|
||||
[startIndex, startIndex, true],
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -281,14 +280,13 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||
})
|
||||
it('should move a identifier into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('identifierGuy +') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex],
|
||||
[startIndex, startIndex, true],
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -305,19 +303,20 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const segmentSnippet = `line([9.7, 9.19], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||
const extrudeRange: [number, number] = [
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
@ -345,18 +344,19 @@ sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const segmentSnippet = `close(%)`
|
||||
const segmentRange: [number, number] = [
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||
const extrudeRange: [number, number] = [
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
@ -384,18 +384,19 @@ sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
||||
const sketchRange: [number, number] = [
|
||||
const sketchRange: [number, number, boolean] = [
|
||||
code.indexOf(sketchSnippet),
|
||||
code.indexOf(sketchSnippet) + sketchSnippet.length,
|
||||
true,
|
||||
]
|
||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||
const extrudeRange: [number, number] = [
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
@ -432,18 +433,19 @@ sketch001 = startSketchOn(part001, 'END')`)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
part001 = extrude(5 + 7, sketch001)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const segmentSnippet = `line([4.99, -0.46], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, sketch001)`
|
||||
const extrudeRange: [number, number] = [
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
@ -466,13 +468,13 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
||||
|> line([306.21, 198.82], %)
|
||||
|> line([306.21, 198.85], %, $a)
|
||||
|> line([306.21, 198.87], %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = 'line([306.21, 198.85], %, $a)'
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||
@ -544,13 +546,13 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
||||
],
|
||||
])(`%s`, async (_, line, [replace1, replace2]) => {
|
||||
const code = makeCode(line)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = line
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
|
||||
@ -632,14 +634,14 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
],
|
||||
['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1],
|
||||
] as const)('stdlib fn: %s', async (expectedFinish, key, value) => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest) + 1,
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
let argPosition: SimplifiedArgDetails
|
||||
@ -686,14 +688,14 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
['angledLineToX([12.14 + 0, 12], %)', 'arrayIndex', 1],
|
||||
['angledLineToY([30, 10.14 + 0], %)', 'arrayIndex', 0],
|
||||
])('stdlib fn: %s', async (expectedFinish, key, value) => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest) + 1,
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
let argPosition: SimplifiedArgDetails
|
||||
if (key === 'arrayIndex' && typeof value === 'number') {
|
||||
@ -883,14 +885,14 @@ sketch002 = startSketchOn({
|
||||
'%s',
|
||||
async (name, { codeBefore, codeAfter, lineOfInterest, type }) => {
|
||||
// const lineOfInterest = 'line([-2.94, 2.7], %)'
|
||||
const ast = parse(codeBefore)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(codeBefore)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
|
||||
// deleteFromSelection
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
codeBefore.indexOf(lineOfInterest),
|
||||
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const artifact = { type } as Artifact
|
||||
const newAst = await deleteFromSelection(
|
||||
|
||||
@ -66,8 +66,7 @@ export function startSketchOnDefault(
|
||||
let pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndex, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
|
||||
@ -94,7 +93,7 @@ export function addStartProfileAt(
|
||||
return new Error('variableDeclaration.init.type !== PipeExpression')
|
||||
}
|
||||
const _node = { ...node }
|
||||
const init = variableDeclaration.declarations[0].init
|
||||
const init = variableDeclaration.declaration.init
|
||||
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(at[0])),
|
||||
@ -105,7 +104,7 @@ export function addStartProfileAt(
|
||||
if (init.type === 'PipeExpression') {
|
||||
init.body.splice(1, 0, startProfileAt)
|
||||
} else {
|
||||
variableDeclaration.declarations[0].init = createPipeExpression([
|
||||
variableDeclaration.declaration.init = createPipeExpression([
|
||||
init,
|
||||
startProfileAt,
|
||||
])
|
||||
@ -149,8 +148,7 @@ export function addSketchTo(
|
||||
let pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndex, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
if (axis !== 'xy') {
|
||||
@ -333,8 +331,7 @@ export function extrudeSketch(
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndexInBody + 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -364,8 +361,7 @@ export function loftSketches(
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -460,8 +456,7 @@ export function revolveSketch(
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndexInBody + 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -547,8 +542,7 @@ export function sketchOnExtrudedFace(
|
||||
const newpathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[expressionIndex + 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
|
||||
@ -585,8 +579,7 @@ export function addOffsetPlane({
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -823,17 +816,15 @@ export function createVariableDeclaration(
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
declaration: {
|
||||
type: 'VariableDeclarator',
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
|
||||
id: createIdentifier(varName),
|
||||
init,
|
||||
},
|
||||
],
|
||||
id: createIdentifier(varName),
|
||||
init,
|
||||
},
|
||||
visibility,
|
||||
kind,
|
||||
}
|
||||
@ -1120,7 +1111,7 @@ export async function deleteFromSelection(
|
||||
traverse(astClone, {
|
||||
enter: (node, path) => {
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
const dec = node.declarations[0]
|
||||
const dec = node.declaration
|
||||
if (
|
||||
dec.init.type === 'CallExpression' &&
|
||||
(dec.init.callee.name === 'extrude' ||
|
||||
@ -1155,7 +1146,7 @@ export async function deleteFromSelection(
|
||||
enter: (node, path) => {
|
||||
;(async () => {
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
currentVariableName = node.declarations[0].id.name
|
||||
currentVariableName = node.declaration.id.name
|
||||
}
|
||||
if (
|
||||
// match startSketchOn(${extrudeNameToDelete})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {
|
||||
parse,
|
||||
assertParse,
|
||||
recast,
|
||||
initPromise,
|
||||
PathToNode,
|
||||
@ -78,9 +78,10 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
code: string,
|
||||
expectedExtrudeSnippet: string
|
||||
): CallExpression | PipeExpression | Error {
|
||||
const extrudeRange: [number, number] = [
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
code.indexOf(expectedExtrudeSnippet),
|
||||
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
const expectedExtrudeNodeResult = getNodeFromPath<
|
||||
@ -109,14 +110,13 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
}
|
||||
|
||||
// ast
|
||||
const astOrError = parse(code)
|
||||
if (err(astOrError)) return new Error('AST not found')
|
||||
const ast = astOrError
|
||||
const ast = assertParse(code)
|
||||
|
||||
// selection
|
||||
const segmentRange: [number, number] = [
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
code.indexOf(selectedSegmentSnippet),
|
||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
const selection: Selection = {
|
||||
codeRef: codeRefFromRange(segmentRange, ast),
|
||||
@ -258,17 +258,14 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||
expectedCode: string
|
||||
) => {
|
||||
// ast
|
||||
const astOrError = parse(code)
|
||||
if (err(astOrError)) {
|
||||
return new Error('AST not found')
|
||||
}
|
||||
const ast = astOrError
|
||||
const ast = assertParse(code)
|
||||
|
||||
// selection
|
||||
const segmentRanges: Array<[number, number]> = selectionSnippets.map(
|
||||
const segmentRanges: Array<[number, number, boolean]> = selectionSnippets.map(
|
||||
(selectionSnippet) => [
|
||||
code.indexOf(selectionSnippet),
|
||||
code.indexOf(selectionSnippet) + selectionSnippet.length,
|
||||
true,
|
||||
]
|
||||
)
|
||||
|
||||
@ -598,12 +595,12 @@ extrude001 = extrude(-5, sketch001)
|
||||
}, %)
|
||||
`
|
||||
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const ast = assertParse(code)
|
||||
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
@ -617,12 +614,12 @@ extrude001 = extrude(-5, sketch001)
|
||||
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
|
||||
})
|
||||
it('should correctly identify getPreviousAdjacentEdge edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const ast = assertParse(code)
|
||||
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
@ -636,12 +633,12 @@ extrude001 = extrude(-5, sketch001)
|
||||
expect(edges).toEqual(['getPreviousAdjacentEdge'])
|
||||
})
|
||||
it('should correctly identify no edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const ast = assertParse(code)
|
||||
const lineOfInterest = `line([-3.29, -13.85], %)`
|
||||
const range: [number, number] = [
|
||||
const range: [number, number, boolean] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
@ -662,19 +659,15 @@ describe('Testing button states', () => {
|
||||
segmentSnippet: string,
|
||||
expectedState: boolean
|
||||
) => {
|
||||
// ast
|
||||
const astOrError = parse(code)
|
||||
if (err(astOrError)) {
|
||||
return new Error('AST not found')
|
||||
}
|
||||
const ast = astOrError
|
||||
const ast = assertParse(code)
|
||||
|
||||
const range: [number, number] = segmentSnippet
|
||||
const range: [number, number, boolean] = segmentSnippet
|
||||
? [
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
: [ast.end, ast.end] // empty line in the end of the code
|
||||
: [ast.end, ast.end, true] // empty line in the end of the code
|
||||
|
||||
const selectionRanges: Selections = {
|
||||
graphSelections: [
|
||||
|
||||
@ -268,7 +268,7 @@ export function getPathToExtrudeForSegmentSelection(
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDecNode)) return varDecNode
|
||||
const sketchVar = varDecNode.node.declarations[0].id.name
|
||||
const sketchVar = varDecNode.node.declaration.id.name
|
||||
|
||||
const sketch = sketchFromKclValue(
|
||||
kclManager.programMemory.get(sketchVar),
|
||||
@ -362,7 +362,7 @@ function locateExtrudeDeclarator(
|
||||
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
|
||||
|
||||
const { node: extrudeVarDecl } = nodeOfExtrudeCall
|
||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||
const extrudeDeclarator = extrudeVarDecl.declaration
|
||||
if (!extrudeDeclarator) {
|
||||
return new Error('Extrude Declarator not found.')
|
||||
}
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { parse, recast, initPromise, PathToNode, Identifier } from './wasm'
|
||||
import {
|
||||
assertParse,
|
||||
recast,
|
||||
initPromise,
|
||||
PathToNode,
|
||||
Identifier,
|
||||
} from './wasm'
|
||||
import {
|
||||
findAllPreviousVariables,
|
||||
isNodeSafeToReplace,
|
||||
@ -46,14 +52,13 @@ part001 = startSketchOn('XY')
|
||||
variableBelowShouldNotBeIncluded = 3
|
||||
`
|
||||
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
|
||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||
ast,
|
||||
execState.memory,
|
||||
[rangeStart, rangeStart]
|
||||
[rangeStart, rangeStart, true]
|
||||
)
|
||||
expect(variables).toEqual([
|
||||
{ key: 'baseThick', value: 1 },
|
||||
@ -81,10 +86,9 @@ describe('testing argIsNotIdentifier', () => {
|
||||
yo = 5 + 6
|
||||
yo2 = hmm([identifierGuy + 5])`
|
||||
it('find a safe binaryExpression', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('100 + 100') + 2
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
@ -95,20 +99,18 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
})
|
||||
it('find a safe Identifier', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('abc')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('Identifier')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
|
||||
})
|
||||
it('find a safe CallExpression', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('def')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
@ -119,10 +121,9 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
})
|
||||
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('ghi')
|
||||
const range: [number, number] = [rangeStart, rangeStart]
|
||||
const range: [number, number, boolean] = [rangeStart, rangeStart, true]
|
||||
const result = isNodeSafeToReplace(ast, range)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(false)
|
||||
@ -130,10 +131,9 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
|
||||
})
|
||||
it('find an UNsafe Identifier, as it is a callee', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('ine([2.8,')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(false)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
@ -142,10 +142,9 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
)
|
||||
})
|
||||
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('5 + 6') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
@ -156,10 +155,9 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(outCode).toContain(`yo = replaceName`)
|
||||
})
|
||||
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('jkl') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
@ -173,11 +171,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
})
|
||||
it('find a safe BinaryExpression within a CallExpression', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const rangeStart = code.indexOf('identifierGuy') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
if (err(result)) throw result
|
||||
|
||||
expect(result.isSafe).toBe(true)
|
||||
@ -224,15 +221,17 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
it('finds the second line when cursor is put at the end', () => {
|
||||
const searchLn = `line([0.94, 2.61], %)`
|
||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['body', 'PipeExpression'],
|
||||
[2, 'index'],
|
||||
@ -241,15 +240,17 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
it('finds the last line when cursor is put at the end', () => {
|
||||
const searchLn = `line([-0.21, -1.4], %)`
|
||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const expected = [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['body', 'PipeExpression'],
|
||||
[3, 'index'],
|
||||
@ -260,12 +261,14 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const startResult = getNodePathFromSourceRange(ast, [
|
||||
startSourceIndex,
|
||||
startSourceIndex,
|
||||
true,
|
||||
])
|
||||
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
|
||||
// expect similar result when whole line is selected
|
||||
const selectWholeThing = getNodePathFromSourceRange(ast, [
|
||||
startSourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
expect(selectWholeThing).toEqual(expected)
|
||||
})
|
||||
@ -279,15 +282,17 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
}`
|
||||
const searchLn = `x > y`
|
||||
const sourceIndex = code.indexOf(searchLn)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['cond', 'IfExpression'],
|
||||
['left', 'BinaryExpression'],
|
||||
@ -307,15 +312,17 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
}`
|
||||
const searchLn = `x + 1`
|
||||
const sourceIndex = code.indexOf(searchLn)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', ''],
|
||||
['then_val', 'IfExpression'],
|
||||
['body', 'IfExpression'],
|
||||
@ -333,14 +340,18 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const code = `import foo, bar as baz from 'thing.kcl'`
|
||||
const searchLn = `bar`
|
||||
const sourceIndex = code.indexOf(searchLn)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['items', 'ImportStatement'],
|
||||
['selector', 'ImportStatement'],
|
||||
['items', 'ImportSelector'],
|
||||
[1, 'index'],
|
||||
['name', 'ImportItem'],
|
||||
])
|
||||
@ -361,14 +372,13 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
|> angledLine([-175, segLen(seg01)], %)
|
||||
|> close(%)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101], ast),
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
},
|
||||
})
|
||||
expect(result).toEqual(true)
|
||||
@ -383,14 +393,13 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
|> close(%)
|
||||
|> extrude(1, %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'extrude',
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101], ast),
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
},
|
||||
})
|
||||
expect(result).toEqual(true)
|
||||
@ -403,28 +412,26 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
|> line([-3.22, -7.36], %)
|
||||
|> angledLine([-175, segLen(seg01)], %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101], ast),
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
},
|
||||
})
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
it('returns false if not a pipe', () => {
|
||||
const exampleCode = `length001 = 2`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([9, 10], ast),
|
||||
codeRef: codeRefFromRange([9, 10, true], ast),
|
||||
},
|
||||
})
|
||||
expect(result).toEqual(false)
|
||||
@ -439,14 +446,13 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
|> angledLine([-35, length001], %)
|
||||
|> line([-3.22, -7.36], %)
|
||||
|> angledLine([-175, segLen(seg01)], %)`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const result = hasExtrudeSketch({
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101], ast),
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
})
|
||||
@ -460,14 +466,13 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
|> line([-3.22, -7.36], %)
|
||||
|> angledLine([-175, segLen(seg01)], %)
|
||||
|> extrude(1, %)`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const result = hasExtrudeSketch({
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101], ast),
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
})
|
||||
@ -475,14 +480,13 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
})
|
||||
it('finds nothing', async () => {
|
||||
const exampleCode = `length001 = 2`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const result = hasExtrudeSketch({
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([10, 11], ast),
|
||||
codeRef: codeRefFromRange([10, 11, true], ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
})
|
||||
@ -499,8 +503,7 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
|> line([306.21, 198.87], %)
|
||||
|> angledLine([65, segLen(seg01)], %)`
|
||||
it('finds the current segment', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const lineOfInterest = `198.85], %, $seg01`
|
||||
const characterIndex =
|
||||
@ -508,6 +511,7 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [
|
||||
characterIndex,
|
||||
characterIndex,
|
||||
true,
|
||||
])
|
||||
const result = findUsesOfTagInPipe(ast, pathToNode)
|
||||
expect(result).toHaveLength(2)
|
||||
@ -516,8 +520,7 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
})
|
||||
})
|
||||
it('find no tag if line has no tag', () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
|
||||
const lineOfInterest = `line([306.21, 198.82], %)`
|
||||
const characterIndex =
|
||||
@ -525,6 +528,7 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [
|
||||
characterIndex,
|
||||
characterIndex,
|
||||
true,
|
||||
])
|
||||
const result = findUsesOfTagInPipe(ast, pathToNode)
|
||||
expect(result).toHaveLength(0)
|
||||
@ -565,42 +569,39 @@ sketch003 = startSketchOn(extrude001, 'END')
|
||||
|> extrude(3.14, %)
|
||||
`
|
||||
it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const extruded = hasSketchPipeBeenExtruded(
|
||||
{
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
||||
},
|
||||
ast
|
||||
)
|
||||
expect(extruded).toBeTruthy()
|
||||
})
|
||||
it('identifies sketch002 pipe as not extruded', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
const lineOfInterest = `line([2.45, -0.2], %)`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const extruded = hasSketchPipeBeenExtruded(
|
||||
{
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
||||
},
|
||||
ast
|
||||
)
|
||||
expect(extruded).toBeFalsy()
|
||||
})
|
||||
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
const lineOfInterest = `|> line([3.12, 1.74], %)`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const extruded = hasSketchPipeBeenExtruded(
|
||||
{
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
||||
},
|
||||
ast
|
||||
)
|
||||
@ -624,8 +625,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
const extrudable = doesSceneHaveSweepableSketch(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
@ -636,8 +636,7 @@ plane001 = offsetPlane('XZ', 2)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 3 }, %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
const extrudable = doesSceneHaveSweepableSketch(ast, 2)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
@ -650,8 +649,7 @@ sketch002 = startSketchOn(plane001)
|
||||
|> close(%)
|
||||
extrude001 = extrude(10, sketch001)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(exampleCode)
|
||||
const extrudable = doesSceneHaveSweepableSketch(ast)
|
||||
expect(extrudable).toBeFalsy()
|
||||
})
|
||||
@ -711,8 +709,7 @@ myNestedVar = [
|
||||
}
|
||||
]
|
||||
`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
let pathToNode: PathToNode = []
|
||||
traverse(ast, {
|
||||
enter: (node, path) => {
|
||||
@ -734,6 +731,7 @@ myNestedVar = [
|
||||
const pathToNode2 = getNodePathFromSourceRange(ast, [
|
||||
literalIndex + 2,
|
||||
literalIndex + 2,
|
||||
true,
|
||||
])
|
||||
expect(pathToNode).toEqual(pathToNode2)
|
||||
})
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
SourceRange,
|
||||
sourceRangeFromRust,
|
||||
SyntaxType,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
@ -258,34 +259,26 @@ function moreNodePathFromSourceRange(
|
||||
return moreNodePathFromSourceRange(expression, sourceRange, path)
|
||||
}
|
||||
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) {
|
||||
path.push(['declarations', 'VariableDeclaration'])
|
||||
path.push([decIndex, 'index'])
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['declarations', 'VariableDeclaration'])
|
||||
path.push([decIndex, 'index'])
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
@ -379,24 +372,31 @@ function moreNodePathFromSourceRange(
|
||||
}
|
||||
|
||||
if (_node.type === 'ImportStatement' && isInRange) {
|
||||
const { items } = _node
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.start <= start && item.end >= end) {
|
||||
path.push(['items', 'ImportStatement'])
|
||||
path.push([i, 'index'])
|
||||
if (item.name.start <= start && item.name.end >= end) {
|
||||
path.push(['name', 'ImportItem'])
|
||||
if (_node.selector && _node.selector.type === 'List') {
|
||||
path.push(['selector', 'ImportStatement'])
|
||||
const { items } = _node.selector
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.start <= start && item.end >= end) {
|
||||
path.push(['items', 'ImportSelector'])
|
||||
path.push([i, 'index'])
|
||||
if (item.name.start <= start && item.name.end >= end) {
|
||||
path.push(['name', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
if (
|
||||
item.alias &&
|
||||
item.alias.start <= start &&
|
||||
item.alias.end >= end
|
||||
) {
|
||||
path.push(['alias', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (item.alias && item.alias.start <= start && item.alias.end >= end) {
|
||||
path.push(['alias', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
console.error('not implemented: ' + node.type)
|
||||
@ -450,13 +450,10 @@ export function traverse(
|
||||
traverse(node, option, pathToNode)
|
||||
|
||||
if (_node.type === 'VariableDeclaration') {
|
||||
_node.declarations.forEach((declaration, index) =>
|
||||
_traverse(declaration, [
|
||||
...pathToNode,
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[index, 'index'],
|
||||
])
|
||||
)
|
||||
_traverse(_node.declaration, [
|
||||
...pathToNode,
|
||||
['declaration', 'VariableDeclaration'],
|
||||
])
|
||||
} else if (_node.type === 'VariableDeclarator') {
|
||||
_traverse(_node.init, [...pathToNode, ['init', '']])
|
||||
} else if (_node.type === 'PipeExpression') {
|
||||
@ -566,7 +563,7 @@ export function findAllPreviousVariablesPath(
|
||||
const variables: PrevVariable<any>[] = []
|
||||
bodyItems?.forEach?.((item) => {
|
||||
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)
|
||||
if (!varValue || typeof varValue?.value !== type) return
|
||||
variables.push({
|
||||
@ -669,7 +666,7 @@ export function isNodeSafeToReplacePath(
|
||||
|
||||
export function isNodeSafeToReplace(
|
||||
ast: Node<Program>,
|
||||
sourceRange: [number, number]
|
||||
sourceRange: SourceRange
|
||||
):
|
||||
| {
|
||||
isSafe: boolean
|
||||
@ -760,7 +757,7 @@ export function isLinesParallelAndConstrained(
|
||||
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
|
||||
if (err(_varDec)) return _varDec
|
||||
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)
|
||||
if (err(sg)) return sg
|
||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||
@ -821,7 +818,7 @@ export function isLinesParallelAndConstrained(
|
||||
return {
|
||||
isParallelAndConstrained,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange(prevSourceRange, ast),
|
||||
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast),
|
||||
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
|
||||
},
|
||||
}
|
||||
@ -880,7 +877,7 @@ export function hasExtrudeSketch({
|
||||
}
|
||||
const varDec = varDecMeta.node
|
||||
if (varDec.type !== 'VariableDeclaration') return false
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const varName = varDec.declaration.id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
return (
|
||||
varValue?.type === 'Solid' ||
|
||||
@ -957,7 +954,8 @@ export function findUsesOfTagInPipe(
|
||||
return
|
||||
const tagArgValue =
|
||||
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
|
||||
if (tagArgValue === tag) dependentRanges.push([node.start, node.end])
|
||||
if (tagArgValue === tag)
|
||||
dependentRanges.push([node.start, node.end, true])
|
||||
},
|
||||
})
|
||||
return dependentRanges
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { parse, Program, recast, initPromise } from './wasm'
|
||||
import { assertParse, Program, recast, initPromise } from './wasm'
|
||||
import fs from 'node:fs'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
@ -394,8 +394,6 @@ describe('it recasts binary expression using brackets where needed', () => {
|
||||
// helpers
|
||||
|
||||
function code2ast(code: string): { ast: Program } {
|
||||
const ast = parse(code)
|
||||
// eslint-ignore-next-line
|
||||
if (err(ast)) throw ast
|
||||
const ast = assertParse(code)
|
||||
return { ast }
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { makeDefaultPlanes, parse, initPromise, Program } from 'lang/wasm'
|
||||
import { makeDefaultPlanes, assertParse, initPromise, Program } from 'lang/wasm'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import {
|
||||
OrderedCommand,
|
||||
@ -148,11 +148,7 @@ beforeAll(async () => {
|
||||
][]
|
||||
const cacheToWriteToFileTemp: Partial<CacheShape> = {}
|
||||
for (const [codeKey, code] of cacheEntries) {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) {
|
||||
console.error(ast)
|
||||
return Promise.reject(ast)
|
||||
}
|
||||
const ast = assertParse(code)
|
||||
await kclManager.executeAst({ ast })
|
||||
|
||||
cacheToWriteToFileTemp[codeKey] = {
|
||||
@ -403,11 +399,7 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
|
||||
})
|
||||
|
||||
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } {
|
||||
const ast = parse(codeKey)
|
||||
if (err(ast)) {
|
||||
console.error(ast)
|
||||
throw ast
|
||||
}
|
||||
const ast = assertParse(codeKey)
|
||||
const file = fs.readFileSync(fullPath, 'utf-8')
|
||||
const parsed: CacheShape = JSON.parse(file)
|
||||
// these either already exist from the last run, or were created in
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
import { defaultSourceRange, SourceRange } from 'lang/wasm'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { exportSave } from 'lib/exportSave'
|
||||
@ -1879,17 +1879,6 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
return JSON.stringify(this.defaultPlanes)
|
||||
}
|
||||
endSession() {
|
||||
const deleteCmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'scene_clear_all',
|
||||
},
|
||||
}
|
||||
this.clearDefaultPlanes()
|
||||
this.engineConnection?.send(deleteCmd)
|
||||
}
|
||||
addCommandLog(message: CommandLog) {
|
||||
if (this.commandLogs.length > 500) {
|
||||
this.commandLogs.shift()
|
||||
@ -2014,7 +2003,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
{
|
||||
command,
|
||||
idToRangeMap: {},
|
||||
range: [0, 0],
|
||||
range: defaultSourceRange(),
|
||||
},
|
||||
true // isSceneCommand
|
||||
)
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
getConstraintInfo,
|
||||
} from './sketch'
|
||||
import {
|
||||
parse,
|
||||
assertParse,
|
||||
recast,
|
||||
initPromise,
|
||||
SourceRange,
|
||||
@ -115,8 +115,7 @@ describe('testing changeSketchArguments', () => {
|
||||
`
|
||||
const code = genCode(lineToChange)
|
||||
const expectedCode = genCode(lineAfterChange)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
@ -125,7 +124,7 @@ describe('testing changeSketchArguments', () => {
|
||||
execState.memory,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: [sourceStart, sourceStart + lineToChange.length],
|
||||
sourceRange: [sourceStart, sourceStart + lineToChange.length, true],
|
||||
},
|
||||
{
|
||||
type: 'straight-segment',
|
||||
@ -148,8 +147,7 @@ mySketch001 = startSketchOn('XY')
|
||||
// |> rx(45, %)
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
@ -166,8 +164,7 @@ mySketch001 = startSketchOn('XY')
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
@ -191,8 +188,7 @@ mySketch001 = startSketchOn('XY')
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
@ -220,12 +216,13 @@ describe('testing addTagForSketchOnFace', () => {
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
`
|
||||
const code = genCode(originalLine)
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(originalLine)
|
||||
const sourceRange: [number, number] = [
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
sourceStart,
|
||||
sourceStart + originalLine.length,
|
||||
true,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
@ -291,13 +288,14 @@ extrude001 = extrude(100, sketch001)
|
||||
${insertCode}
|
||||
`
|
||||
const code = genCode(originalChamfer)
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(originalChamfer)
|
||||
const extraChars = originalChamfer.indexOf('chamfer')
|
||||
const sourceRange: [number, number] = [
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
sourceStart + extraChars,
|
||||
sourceStart + originalChamfer.length - extraChars,
|
||||
true,
|
||||
]
|
||||
|
||||
if (err(ast)) throw ast
|
||||
@ -335,7 +333,7 @@ describe('testing getConstraintInfo', () => {
|
||||
|> lineTo([6.14, 3.14], %)
|
||||
|> xLineTo(8, %)
|
||||
|> yLineTo(5, %)
|
||||
|> yLine(3.14, %, 'a')
|
||||
|> yLine(3.14, %, $a)
|
||||
|> xLine(3.14, %)
|
||||
|> angledLineOfXLength({
|
||||
angle = 3.14,
|
||||
@ -355,11 +353,11 @@ describe('testing getConstraintInfo', () => {
|
||||
}, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 3.14,
|
||||
intersectTag = 'a',
|
||||
intersectTag = a,
|
||||
offset = 0
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14, 13.14], %)`
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
test.each([
|
||||
[
|
||||
'line',
|
||||
@ -368,7 +366,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3',
|
||||
sourceRange: [78, 79],
|
||||
sourceRange: [78, 79, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -377,7 +375,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '4',
|
||||
sourceRange: [81, 82],
|
||||
sourceRange: [81, 82, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -391,7 +389,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [117, 121],
|
||||
sourceRange: [118, 122, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -400,7 +398,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'length',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [135, 139],
|
||||
sourceRange: [137, 141, true],
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -414,7 +412,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '6.14',
|
||||
sourceRange: [162, 166],
|
||||
sourceRange: [164, 168, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -423,7 +421,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [168, 172],
|
||||
sourceRange: [170, 174, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -437,7 +435,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLineTo',
|
||||
sourceRange: [183, 190],
|
||||
sourceRange: [185, 192, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -446,7 +444,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '8',
|
||||
sourceRange: [191, 192],
|
||||
sourceRange: [193, 194, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -460,7 +458,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLineTo',
|
||||
sourceRange: [202, 209],
|
||||
sourceRange: [204, 211, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -469,7 +467,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '5',
|
||||
sourceRange: [210, 211],
|
||||
sourceRange: [212, 213, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -483,7 +481,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLine',
|
||||
sourceRange: [221, 226],
|
||||
sourceRange: [223, 228, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -492,7 +490,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [227, 231],
|
||||
sourceRange: [229, 233, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -506,7 +504,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLine',
|
||||
sourceRange: [246, 251],
|
||||
sourceRange: [247, 252, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -515,7 +513,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [252, 256],
|
||||
sourceRange: [253, 257, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -529,7 +527,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [299, 303],
|
||||
sourceRange: [301, 305, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -538,7 +536,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [317, 321],
|
||||
sourceRange: [320, 324, true],
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -552,7 +550,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [369, 371],
|
||||
sourceRange: [373, 375, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -561,7 +559,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '3',
|
||||
sourceRange: [385, 386],
|
||||
sourceRange: [390, 391, true],
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -575,7 +573,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '12.14',
|
||||
sourceRange: [428, 433],
|
||||
sourceRange: [434, 439, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -584,7 +582,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '12',
|
||||
sourceRange: [443, 445],
|
||||
sourceRange: [450, 452, true],
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -598,7 +596,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [487, 489],
|
||||
sourceRange: [495, 497, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -607,7 +605,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '10.14',
|
||||
sourceRange: [499, 504],
|
||||
sourceRange: [508, 513, true],
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -621,7 +619,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [557, 561],
|
||||
sourceRange: [567, 571, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -630,7 +628,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'intersectionOffset',
|
||||
isConstrained: false,
|
||||
value: '0',
|
||||
sourceRange: [598, 599],
|
||||
sourceRange: [608, 609, true],
|
||||
argPosition: { type: 'objectProperty', key: 'offset' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -638,8 +636,8 @@ describe('testing getConstraintInfo', () => {
|
||||
{
|
||||
type: 'intersectionTag',
|
||||
isConstrained: false,
|
||||
value: "'a'",
|
||||
sourceRange: [581, 584],
|
||||
value: 'a',
|
||||
sourceRange: [592, 593, true],
|
||||
argPosition: {
|
||||
key: 'intersectTag',
|
||||
type: 'objectProperty',
|
||||
@ -656,7 +654,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'tangentialWithPrevious',
|
||||
isConstrained: true,
|
||||
value: 'tangentialArcTo',
|
||||
sourceRange: [613, 628],
|
||||
sourceRange: [623, 638, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -665,7 +663,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [630, 634],
|
||||
sourceRange: [640, 644, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -674,7 +672,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '13.14',
|
||||
sourceRange: [636, 641],
|
||||
sourceRange: [646, 651, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -685,6 +683,7 @@ describe('testing getConstraintInfo', () => {
|
||||
const sourceRange: SourceRange = [
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
true,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
@ -706,7 +705,7 @@ describe('testing getConstraintInfo', () => {
|
||||
|> lineTo([6.14, 3.14], %)
|
||||
|> xLineTo(8, %)
|
||||
|> yLineTo(5, %)
|
||||
|> yLine(3.14, %, 'a')
|
||||
|> yLine(3.14, %, $a)
|
||||
|> xLine(3.14, %)
|
||||
|> angledLineOfXLength([3.14, 3.14], %)
|
||||
|> angledLineOfYLength([30, 3], %)
|
||||
@ -714,11 +713,11 @@ describe('testing getConstraintInfo', () => {
|
||||
|> angledLineToY([30, 10], %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 3.14,
|
||||
intersectTag = 'a',
|
||||
intersectTag = a,
|
||||
offset = 0
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14, 13.14], %)`
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
test.each([
|
||||
[
|
||||
`angledLine(`,
|
||||
@ -727,7 +726,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [112, 116],
|
||||
sourceRange: [112, 116, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -736,7 +735,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'length',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [118, 122],
|
||||
sourceRange: [118, 122, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -750,7 +749,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [278, 282],
|
||||
sourceRange: [277, 281, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -759,7 +758,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [284, 288],
|
||||
sourceRange: [283, 287, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -773,7 +772,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [322, 324],
|
||||
sourceRange: [321, 323, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -782,7 +781,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '3',
|
||||
sourceRange: [326, 327],
|
||||
sourceRange: [325, 326, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -796,7 +795,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '12',
|
||||
sourceRange: [355, 357],
|
||||
sourceRange: [354, 356, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -805,7 +804,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '12',
|
||||
sourceRange: [359, 361],
|
||||
sourceRange: [358, 360, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -819,7 +818,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [389, 391],
|
||||
sourceRange: [388, 390, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -828,7 +827,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '10',
|
||||
sourceRange: [393, 395],
|
||||
sourceRange: [392, 394, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -839,6 +838,7 @@ describe('testing getConstraintInfo', () => {
|
||||
const sourceRange: SourceRange = [
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
true,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
@ -860,7 +860,7 @@ describe('testing getConstraintInfo', () => {
|
||||
|> lineTo([6.14 + 0, 3.14 + 0], %)
|
||||
|> xLineTo(8 + 0, %)
|
||||
|> yLineTo(5 + 0, %)
|
||||
|> yLine(3.14 + 0, %, 'a')
|
||||
|> yLine(3.14 + 0, %, $a)
|
||||
|> xLine(3.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 3.14 + 0, length = 3.14 + 0 }, %)
|
||||
|> angledLineOfYLength({ angle = 30 + 0, length = 3 + 0 }, %)
|
||||
@ -868,11 +868,11 @@ describe('testing getConstraintInfo', () => {
|
||||
|> angledLineToY({ angle = 30 + 0, to = 10.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 3.14 + 0,
|
||||
intersectTag = 'a',
|
||||
intersectTag = a,
|
||||
offset = 0 + 0
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
test.each([
|
||||
[
|
||||
'line',
|
||||
@ -881,7 +881,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: true,
|
||||
value: '3 + 0',
|
||||
sourceRange: [83, 88],
|
||||
sourceRange: [83, 88, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -890,7 +890,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: true,
|
||||
value: '4 + 0',
|
||||
sourceRange: [90, 95],
|
||||
sourceRange: [90, 95, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -904,7 +904,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [128, 136],
|
||||
sourceRange: [129, 137, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -913,7 +913,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'length',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [146, 154],
|
||||
sourceRange: [148, 156, true],
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -927,7 +927,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '6.14 + 0',
|
||||
sourceRange: [176, 184],
|
||||
sourceRange: [178, 186, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -936,7 +936,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [186, 194],
|
||||
sourceRange: [188, 196, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -950,7 +950,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLineTo',
|
||||
sourceRange: [207, 214],
|
||||
sourceRange: [209, 216, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -959,7 +959,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '8 + 0',
|
||||
sourceRange: [215, 220],
|
||||
sourceRange: [217, 222, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -973,7 +973,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLineTo',
|
||||
sourceRange: [232, 239],
|
||||
sourceRange: [234, 241, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -982,7 +982,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '5 + 0',
|
||||
sourceRange: [240, 245],
|
||||
sourceRange: [242, 247, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -996,7 +996,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLine',
|
||||
sourceRange: [257, 262],
|
||||
sourceRange: [259, 264, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -1005,7 +1005,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [263, 271],
|
||||
sourceRange: [265, 273, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -1019,7 +1019,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLine',
|
||||
sourceRange: [288, 293],
|
||||
sourceRange: [289, 294, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -1028,7 +1028,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [294, 302],
|
||||
sourceRange: [295, 303, true],
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -1042,7 +1042,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [343, 351],
|
||||
sourceRange: [345, 353, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -1051,7 +1051,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [361, 369],
|
||||
sourceRange: [364, 372, true],
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -1065,7 +1065,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '30 + 0',
|
||||
sourceRange: [412, 418],
|
||||
sourceRange: [416, 422, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -1074,7 +1074,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: true,
|
||||
value: '3 + 0',
|
||||
sourceRange: [428, 433],
|
||||
sourceRange: [433, 438, true],
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -1088,7 +1088,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '12.14 + 0',
|
||||
sourceRange: [470, 479],
|
||||
sourceRange: [476, 485, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -1097,7 +1097,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '12 + 0',
|
||||
sourceRange: [485, 491],
|
||||
sourceRange: [492, 498, true],
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -1111,7 +1111,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '30 + 0',
|
||||
sourceRange: [528, 534],
|
||||
sourceRange: [536, 542, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -1120,7 +1120,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '10.14 + 0',
|
||||
sourceRange: [540, 549],
|
||||
sourceRange: [549, 558, true],
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -1134,7 +1134,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [606, 614],
|
||||
sourceRange: [616, 624, true],
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -1143,7 +1143,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'intersectionOffset',
|
||||
isConstrained: true,
|
||||
value: '0 + 0',
|
||||
sourceRange: [661, 666],
|
||||
sourceRange: [671, 676, true],
|
||||
argPosition: { type: 'objectProperty', key: 'offset' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -1151,8 +1151,8 @@ describe('testing getConstraintInfo', () => {
|
||||
{
|
||||
type: 'intersectionTag',
|
||||
isConstrained: false,
|
||||
value: "'a'",
|
||||
sourceRange: [639, 642],
|
||||
value: 'a',
|
||||
sourceRange: [650, 651, true],
|
||||
argPosition: { key: 'intersectTag', type: 'objectProperty' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -1166,7 +1166,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'tangentialWithPrevious',
|
||||
isConstrained: true,
|
||||
value: 'tangentialArcTo',
|
||||
sourceRange: [687, 702],
|
||||
sourceRange: [697, 712, true],
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -1175,7 +1175,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [704, 712],
|
||||
sourceRange: [714, 722, true],
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -1184,7 +1184,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '13.14 + 0',
|
||||
sourceRange: [714, 723],
|
||||
sourceRange: [724, 733, true],
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -1195,6 +1195,7 @@ describe('testing getConstraintInfo', () => {
|
||||
const sourceRange: SourceRange = [
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
true,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
|
||||
@ -222,7 +222,7 @@ const commonConstraintInfoHelper = (
|
||||
code.slice(input1.start, input1.end),
|
||||
stdLibFnName,
|
||||
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
|
||||
[input1.start, input1.end],
|
||||
[input1.start, input1.end, true],
|
||||
pathToFirstArg
|
||||
)
|
||||
)
|
||||
@ -234,7 +234,7 @@ const commonConstraintInfoHelper = (
|
||||
code.slice(input2.start, input2.end),
|
||||
stdLibFnName,
|
||||
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
|
||||
[input2.start, input2.end],
|
||||
[input2.start, input2.end, true],
|
||||
pathToSecondArg
|
||||
)
|
||||
)
|
||||
@ -266,7 +266,7 @@ const horzVertConstraintInfoHelper = (
|
||||
callee.name,
|
||||
stdLibFnName,
|
||||
undefined,
|
||||
[callee.start, callee.end],
|
||||
[callee.start, callee.end, true],
|
||||
pathToCallee
|
||||
),
|
||||
constrainInfo(
|
||||
@ -275,7 +275,7 @@ const horzVertConstraintInfoHelper = (
|
||||
code.slice(firstArg.start, firstArg.end),
|
||||
stdLibFnName,
|
||||
abbreviatedInput,
|
||||
[firstArg.start, firstArg.end],
|
||||
[firstArg.start, firstArg.end, true],
|
||||
pathToFirstArg
|
||||
),
|
||||
]
|
||||
@ -905,7 +905,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
callee.name,
|
||||
'tangentialArcTo',
|
||||
undefined,
|
||||
[callee.start, callee.end],
|
||||
[callee.start, callee.end, true],
|
||||
pathToCallee
|
||||
),
|
||||
constrainInfo(
|
||||
@ -914,7 +914,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
|
||||
'tangentialArcTo',
|
||||
0,
|
||||
[firstArg.elements[0].start, firstArg.elements[0].end],
|
||||
[firstArg.elements[0].start, firstArg.elements[0].end, true],
|
||||
pathToFirstArg
|
||||
),
|
||||
constrainInfo(
|
||||
@ -923,7 +923,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
|
||||
'tangentialArcTo',
|
||||
1,
|
||||
[firstArg.elements[1].start, firstArg.elements[1].end],
|
||||
[firstArg.elements[1].start, firstArg.elements[1].end, true],
|
||||
pathToSecondArg
|
||||
),
|
||||
]
|
||||
@ -1052,7 +1052,7 @@ export const circle: SketchLineHelper = {
|
||||
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
|
||||
'circle',
|
||||
'radius',
|
||||
[radiusDetails.expr.start, radiusDetails.expr.end],
|
||||
[radiusDetails.expr.start, radiusDetails.expr.end, true],
|
||||
pathToRadiusLiteral
|
||||
),
|
||||
{
|
||||
@ -1064,6 +1064,7 @@ export const circle: SketchLineHelper = {
|
||||
sourceRange: [
|
||||
centerDetails.expr.elements[0].start,
|
||||
centerDetails.expr.elements[0].end,
|
||||
true,
|
||||
],
|
||||
pathToNode: pathToXArg,
|
||||
value: code.slice(
|
||||
@ -1085,6 +1086,7 @@ export const circle: SketchLineHelper = {
|
||||
sourceRange: [
|
||||
centerDetails.expr.elements[1].start,
|
||||
centerDetails.expr.elements[1].end,
|
||||
true,
|
||||
],
|
||||
pathToNode: pathToYArg,
|
||||
value: code.slice(
|
||||
@ -1699,7 +1701,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
|
||||
const { node: varDec } = nodeMeta2
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const varName = varDec.declaration.id.name
|
||||
const sketch = sketchFromKclValue(
|
||||
previousProgramMemory.get(varName),
|
||||
varName
|
||||
@ -1761,7 +1763,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
code.slice(angle.start, angle.end),
|
||||
'angledLineThatIntersects',
|
||||
'angle',
|
||||
[angle.start, angle.end],
|
||||
[angle.start, angle.end, true],
|
||||
pathToAngleProp
|
||||
)
|
||||
)
|
||||
@ -1780,7 +1782,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
code.slice(offset.start, offset.end),
|
||||
'angledLineThatIntersects',
|
||||
'offset',
|
||||
[offset.start, offset.end],
|
||||
[offset.start, offset.end, true],
|
||||
pathToOffsetProp
|
||||
)
|
||||
)
|
||||
@ -1799,7 +1801,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
code.slice(tag.start, tag.end),
|
||||
'angledLineThatIntersects',
|
||||
'intersectTag',
|
||||
[tag.start, tag.end],
|
||||
[tag.start, tag.end, true],
|
||||
pathToTagProp
|
||||
)
|
||||
returnVal.push(info)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {
|
||||
parse,
|
||||
assertParse,
|
||||
Sketch,
|
||||
recast,
|
||||
initPromise,
|
||||
@ -31,12 +31,11 @@ async function testingSwapSketchFnCall({
|
||||
constraintType: ConstraintType
|
||||
}): Promise<{
|
||||
newCode: string
|
||||
originalRange: [number, number]
|
||||
originalRange: [number, number, boolean]
|
||||
}> {
|
||||
const startIndex = inputCode.indexOf(callToSwap)
|
||||
const range: SourceRange = [startIndex, startIndex + callToSwap.length]
|
||||
const ast = parse(inputCode)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const range: SourceRange = [startIndex, startIndex + callToSwap.length, true]
|
||||
const ast = assertParse(inputCode)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const selections = {
|
||||
@ -370,13 +369,13 @@ part001 = startSketchOn('XY')
|
||||
|> line([2.14, 1.35], %) // normal-segment
|
||||
|> xLine(3.54, %)`
|
||||
it('normal case works', async () => {
|
||||
const execState = await enginelessExecutor(parse(code))
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
const index = code.indexOf('// normal-segment') - 7
|
||||
const sg = sketchFromKclValue(
|
||||
execState.memory.get('part001'),
|
||||
'part001'
|
||||
) as Sketch
|
||||
const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
|
||||
const _segment = getSketchSegmentFromSourceRange(sg, [index, index, true])
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
expect(segment).toEqual({
|
||||
@ -387,11 +386,11 @@ part001 = startSketchOn('XY')
|
||||
})
|
||||
})
|
||||
it('verify it works when the segment is in the `start` property', async () => {
|
||||
const execState = await enginelessExecutor(parse(code))
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
const index = code.indexOf('// segment-in-start') - 7
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
|
||||
[index, index]
|
||||
[index, index, true]
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
|
||||
@ -31,7 +31,7 @@ export function getSketchSegmentFromPathToNode(
|
||||
const node = nodeMeta.node
|
||||
if (!node || typeof node.start !== 'number' || !node.end)
|
||||
return new Error('no node found')
|
||||
const sourceRange: SourceRange = [node.start, node.end]
|
||||
const sourceRange: SourceRange = [node.start, node.end, true]
|
||||
return getSketchSegmentFromSourceRange(sketch, sourceRange)
|
||||
}
|
||||
export function getSketchSegmentFromSourceRange(
|
||||
@ -111,12 +111,10 @@ export function isSketchVariablesLinked(
|
||||
let nextVarDec: VariableDeclarator | undefined
|
||||
for (const node of ast.body) {
|
||||
if (node.type !== 'VariableDeclaration') continue
|
||||
const found = node.declarations.find(
|
||||
({ id }) => id?.name === secondArg.name
|
||||
)
|
||||
if (!found) continue
|
||||
nextVarDec = found
|
||||
break
|
||||
if (node.declaration.id.name === secondArg.name) {
|
||||
nextVarDec = node.declaration
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!nextVarDec) return false
|
||||
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { parse, Expr, recast, initPromise, Program } from '../wasm'
|
||||
import { assertParse, Expr, recast, initPromise, Program } from '../wasm'
|
||||
import {
|
||||
getConstraintType,
|
||||
getTransformInfos,
|
||||
@ -66,8 +66,7 @@ describe('testing getConstraintType', () => {
|
||||
function getConstraintTypeFromSourceHelper(
|
||||
code: string
|
||||
): ReturnType<typeof getConstraintType> | Error {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
||||
Expr,
|
||||
@ -79,8 +78,7 @@ function getConstraintTypeFromSourceHelper(
|
||||
function getConstraintTypeFromSourceHelper2(
|
||||
code: string
|
||||
): ReturnType<typeof getConstraintType> | Error {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
const ast = assertParse(code)
|
||||
|
||||
const arg = (ast.body[0] as any).expression.arguments[0] as Expr
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||
@ -127,7 +125,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
||||
)
|
||||
}
|
||||
const start = codeBeforeLine + line.indexOf('|> ' + 5)
|
||||
const range: [number, number] = [start, start]
|
||||
const range: [number, number, boolean] = [start, start, true]
|
||||
return {
|
||||
codeRef: codeRefFromRange(range, ast),
|
||||
}
|
||||
@ -137,8 +135,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
||||
inputCode: string,
|
||||
selectionRanges: Selections['graphSelections']
|
||||
) {
|
||||
const ast = parse(inputCode)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputCode)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const transformInfos = getTransformInfos(
|
||||
makeSelections(selectionRanges.slice(1)),
|
||||
@ -161,8 +158,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
||||
}
|
||||
|
||||
it(`Should reorder when user selects first-to-last`, async () => {
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputScript)
|
||||
const selectionRanges: Selections['graphSelections'] = [
|
||||
selectLine(inputScript, 3, ast),
|
||||
selectLine(inputScript, 4, ast),
|
||||
@ -173,8 +169,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
||||
})
|
||||
|
||||
it(`Should reorder when user selects last-to-first`, async () => {
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputScript)
|
||||
const selectionRanges: Selections['graphSelections'] = [
|
||||
selectLine(inputScript, 4, ast),
|
||||
selectLine(inputScript, 3, ast),
|
||||
@ -293,8 +288,7 @@ part001 = startSketchOn('XY')
|
||||
|> yLine(segLen(seg01), %) // ln-yLineTo-free should convert to yLine
|
||||
`
|
||||
it('should transform the ast', async () => {
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputScript)
|
||||
|
||||
const selectionRanges: Selections['graphSelections'] = inputScript
|
||||
.split('\n')
|
||||
@ -303,7 +297,7 @@ part001 = startSketchOn('XY')
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start], ast),
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -383,8 +377,7 @@ part001 = startSketchOn('XY')
|
||||
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||
`
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputScript)
|
||||
|
||||
const selectionRanges: Selections['graphSelections'] = inputScript
|
||||
.split('\n')
|
||||
@ -393,7 +386,7 @@ part001 = startSketchOn('XY')
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start], ast),
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -444,8 +437,7 @@ part001 = startSketchOn('XY')
|
||||
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||
`
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputScript)
|
||||
|
||||
const selectionRanges: Selections['graphSelections'] = inputScript
|
||||
.split('\n')
|
||||
@ -454,7 +446,7 @@ part001 = startSketchOn('XY')
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start], ast),
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -538,8 +530,7 @@ async function helperThing(
|
||||
linesOfInterest: string[],
|
||||
constraint: ConstraintType
|
||||
): Promise<string> {
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
const ast = assertParse(inputScript)
|
||||
|
||||
const selectionRanges: Selections['graphSelections'] = inputScript
|
||||
.split('\n')
|
||||
@ -550,7 +541,7 @@ async function helperThing(
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start], ast),
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -606,7 +597,7 @@ part001 = startSketchOn('XY')
|
||||
|> line([-1.49, 1.06], %) // free
|
||||
|> xLine(-3.43 + 0, %) // full
|
||||
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
|
||||
const ast = parse(code)
|
||||
const ast = assertParse(code)
|
||||
const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
|
||||
constraintLevels.forEach((constraintLevel) => {
|
||||
const recursivelySearchCommentsAndCheckConstraintLevel = (
|
||||
@ -619,7 +610,7 @@ part001 = startSketchOn('XY')
|
||||
}
|
||||
const offsetIndex = index - 7
|
||||
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
||||
[offsetIndex, offsetIndex],
|
||||
[offsetIndex, offsetIndex, true],
|
||||
ast
|
||||
)
|
||||
if (err(expectedConstraintLevel)) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { parse, initPromise } from '../wasm'
|
||||
import { assertParse, initPromise } from '../wasm'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
|
||||
offset: ${offset},
|
||||
}, %, $yo2)
|
||||
intersect = segEndX(yo2)`
|
||||
const execState = await enginelessExecutor(parse(code('-1')))
|
||||
const execState = await enginelessExecutor(assertParse(code('-1')))
|
||||
expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
|
||||
const noOffset = await enginelessExecutor(parse(code('0')))
|
||||
const noOffset = await enginelessExecutor(assertParse(code('0')))
|
||||
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { err } from 'lib/trap'
|
||||
import { parse } from './wasm'
|
||||
import { parse, ParseResult } from './wasm'
|
||||
import { enginelessExecutor } from 'lib/testHelpers'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
|
||||
it('can execute parsed AST', async () => {
|
||||
const code = `x = 1
|
||||
// A comment.`
|
||||
const ast = parse(code)
|
||||
expect(err(ast)).toEqual(false)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
expect(err(ast)).toEqual(false)
|
||||
const result = parse(code)
|
||||
expect(err(result)).toEqual(false)
|
||||
const pResult = result as ParseResult
|
||||
expect(pResult.errors.length).toEqual(0)
|
||||
expect(pResult.program).not.toEqual(null)
|
||||
const execState = await enginelessExecutor(pResult.program as Node<Program>)
|
||||
expect(err(execState)).toEqual(false)
|
||||
expect(execState.memory.get('x')?.value).toEqual(1)
|
||||
})
|
||||
|
||||
154
src/lang/wasm.ts
154
src/lang/wasm.ts
@ -16,6 +16,7 @@ import init, {
|
||||
parse_project_settings,
|
||||
default_project_settings,
|
||||
base64_decode,
|
||||
clear_scene_and_bust_cache,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
@ -35,12 +36,13 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
|
||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
|
||||
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
|
||||
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
|
||||
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
|
||||
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
|
||||
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
||||
@ -84,13 +86,22 @@ export type SyntaxType =
|
||||
| 'NonCodeNode'
|
||||
| 'UnaryExpression'
|
||||
|
||||
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
|
||||
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
||||
export type { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
|
||||
export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
|
||||
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
|
||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||
|
||||
export type SourceRange = [number, number, boolean]
|
||||
|
||||
export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
|
||||
return [s[0], s[1], s[2] === 0]
|
||||
}
|
||||
|
||||
export function defaultSourceRange(): SourceRange {
|
||||
return [0, 0, true]
|
||||
}
|
||||
|
||||
export const wasmUrl = () => {
|
||||
// For when we're in electron (file based) or web server (network based)
|
||||
// For some reason relative paths don't work as expected. Otherwise we would
|
||||
@ -120,26 +131,81 @@ const initialise = async () => {
|
||||
|
||||
export const initPromise = initialise()
|
||||
|
||||
export const rangeTypeFix = (ranges: number[][]): [number, number, number][] =>
|
||||
ranges.map(([start, end, moduleId]) => [start, end, moduleId])
|
||||
const splitErrors = (
|
||||
input: CompilationError[]
|
||||
): { errors: CompilationError[]; warnings: CompilationError[] } => {
|
||||
let errors = []
|
||||
let warnings = []
|
||||
for (const i of input) {
|
||||
if (i.severity === 'Warning') {
|
||||
warnings.push(i)
|
||||
} else {
|
||||
errors.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
export const parse = (code: string | Error): Node<Program> | Error => {
|
||||
return { errors, warnings }
|
||||
}
|
||||
|
||||
export class ParseResult {
|
||||
program: Node<Program> | null
|
||||
errors: CompilationError[]
|
||||
warnings: CompilationError[]
|
||||
|
||||
constructor(
|
||||
program: Node<Program> | null,
|
||||
errors: CompilationError[],
|
||||
warnings: CompilationError[]
|
||||
) {
|
||||
this.program = program
|
||||
this.errors = errors
|
||||
this.warnings = warnings
|
||||
}
|
||||
}
|
||||
|
||||
class SuccessParseResult extends ParseResult {
|
||||
program: Node<Program>
|
||||
|
||||
constructor(
|
||||
program: Node<Program>,
|
||||
errors: CompilationError[],
|
||||
warnings: CompilationError[]
|
||||
) {
|
||||
super(program, errors, warnings)
|
||||
this.program = program
|
||||
}
|
||||
}
|
||||
|
||||
export function resultIsOk(result: ParseResult): result is SuccessParseResult {
|
||||
return !!result.program && result.errors.length === 0
|
||||
}
|
||||
|
||||
export const parse = (code: string | Error): ParseResult | Error => {
|
||||
if (err(code)) return code
|
||||
|
||||
try {
|
||||
const program: Node<Program> = parse_wasm(code)
|
||||
return program
|
||||
const parsed: [Node<Program>, CompilationError[]] = parse_wasm(code)
|
||||
let errs = splitErrors(parsed[1])
|
||||
return new ParseResult(parsed[0], errs.errors, errs.warnings)
|
||||
} catch (e: any) {
|
||||
// throw e
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
return new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
sourceRangeFromRust(parsed.sourceRanges[0])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and throw an exception if there are any errors (probably not suitable for use outside of testing).
|
||||
export const assertParse = (code: string): Node<Program> => {
|
||||
const result = parse(code)
|
||||
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
|
||||
if (err(result) || !resultIsOk(result)) throw result
|
||||
return result.program
|
||||
}
|
||||
|
||||
export type PathToNode = [string | number, string][]
|
||||
|
||||
export const isPathToNodeNumber = (
|
||||
@ -150,7 +216,6 @@ export const isPathToNodeNumber = (
|
||||
|
||||
export interface ExecState {
|
||||
memory: ProgramMemory
|
||||
idGenerator: IdGenerator
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,21 +225,12 @@ export interface ExecState {
|
||||
export function emptyExecState(): ExecState {
|
||||
return {
|
||||
memory: ProgramMemory.empty(),
|
||||
idGenerator: defaultIdGenerator(),
|
||||
}
|
||||
}
|
||||
|
||||
function execStateFromRaw(raw: RawExecState): ExecState {
|
||||
return {
|
||||
memory: ProgramMemory.fromRaw(raw.memory),
|
||||
idGenerator: raw.idGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultIdGenerator(): IdGenerator {
|
||||
return {
|
||||
nextId: 0,
|
||||
ids: [],
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +244,19 @@ function emptyEnvironment(): Environment {
|
||||
return { bindings: {}, parent: null }
|
||||
}
|
||||
|
||||
function emptyRootEnvironment(): Environment {
|
||||
return {
|
||||
// This is dumb this is copied from rust.
|
||||
bindings: {
|
||||
ZERO: { type: 'Number', value: 0.0, __meta: [] },
|
||||
QUARTER_TURN: { type: 'Number', value: 90.0, __meta: [] },
|
||||
HALF_TURN: { type: 'Number', value: 180.0, __meta: [] },
|
||||
THREE_QUARTER_TURN: { type: 'Number', value: 270.0, __meta: [] },
|
||||
},
|
||||
parent: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals
|
||||
* isolated from the rest of the TypeScript code so that we can move it to Rust
|
||||
@ -210,7 +279,7 @@ export class ProgramMemory {
|
||||
}
|
||||
|
||||
constructor(
|
||||
environments: Environment[] = [emptyEnvironment()],
|
||||
environments: Environment[] = [emptyRootEnvironment()],
|
||||
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
|
||||
returnVal: KclValue | null = null
|
||||
) {
|
||||
@ -397,36 +466,31 @@ export function sketchFromKclValue(
|
||||
|
||||
export const executor = async (
|
||||
node: Node<Program>,
|
||||
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
idGenerator: IdGenerator = defaultIdGenerator(),
|
||||
engineCommandManager: EngineCommandManager,
|
||||
isMock: boolean = false
|
||||
programMemoryOverride: ProgramMemory | Error | null = null
|
||||
): Promise<ExecState> => {
|
||||
if (err(programMemory)) return Promise.reject(programMemory)
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.startNewSession()
|
||||
const _programMemory = await _executor(
|
||||
node,
|
||||
programMemory,
|
||||
idGenerator,
|
||||
engineCommandManager,
|
||||
isMock
|
||||
programMemoryOverride
|
||||
)
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
engineCommandManager.endSession()
|
||||
return _programMemory
|
||||
}
|
||||
|
||||
export const _executor = async (
|
||||
node: Node<Program>,
|
||||
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
idGenerator: IdGenerator = defaultIdGenerator(),
|
||||
engineCommandManager: EngineCommandManager,
|
||||
isMock: boolean
|
||||
programMemoryOverride: ProgramMemory | Error | null = null
|
||||
): Promise<ExecState> => {
|
||||
if (err(programMemory)) return Promise.reject(programMemory)
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
try {
|
||||
let baseUnit = 'mm'
|
||||
@ -439,13 +503,10 @@ export const _executor = async (
|
||||
}
|
||||
const execState: RawExecState = await execute_wasm(
|
||||
JSON.stringify(node),
|
||||
JSON.stringify(programMemory.toRaw()),
|
||||
JSON.stringify(idGenerator),
|
||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
||||
baseUnit,
|
||||
engineCommandManager,
|
||||
fileSystemManager,
|
||||
undefined,
|
||||
isMock
|
||||
fileSystemManager
|
||||
)
|
||||
return execStateFromRaw(execState)
|
||||
} catch (e: any) {
|
||||
@ -454,7 +515,7 @@ export const _executor = async (
|
||||
const kclError = new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
sourceRangeFromRust(parsed.sourceRanges[0])
|
||||
)
|
||||
|
||||
return Promise.reject(kclError)
|
||||
@ -527,7 +588,7 @@ export const modifyAstForSketch = async (
|
||||
const kclError = new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
sourceRangeFromRust(parsed.sourceRanges[0])
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
@ -595,7 +656,7 @@ export function programMemoryInit(): ProgramMemory | Error {
|
||||
return new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
sourceRangeFromRust(parsed.sourceRanges[0])
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -638,6 +699,21 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
|
||||
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(
|
||||
toml: string
|
||||
): DeepPartial<Configuration> | Error {
|
||||
|
||||
@ -13,7 +13,6 @@ import {
|
||||
listProjects,
|
||||
readAppSettingsFile,
|
||||
} from './desktop'
|
||||
import { engineCommandManager } from './singletons'
|
||||
|
||||
export const isHidden = (fileOrDir: FileEntry) =>
|
||||
!!fileOrDir.name?.startsWith('.')
|
||||
@ -116,9 +115,6 @@ export async function createAndOpenNewTutorialProject({
|
||||
) => void
|
||||
navigate: (path: string) => void
|
||||
}) {
|
||||
// Clear the scene and end the session.
|
||||
engineCommandManager.endSession()
|
||||
|
||||
// Create a new project with the onboarding project name
|
||||
const configuration = await readAppSettingsFile()
|
||||
const projects = await listProjects(configuration)
|
||||
|
||||
@ -3,109 +3,109 @@ export const bracket = `// Shelf Bracket
|
||||
|
||||
|
||||
// Define constants
|
||||
const sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
||||
const width = 6 // inch
|
||||
const p = 300 // Force on shelf - lbs
|
||||
const factorOfSafety = 1.2 // FOS of 1.2
|
||||
const shelfMountL = 5 // inches
|
||||
const wallMountL = 2 // inches
|
||||
const 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)
|
||||
sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
||||
width = 6 // inch
|
||||
p = 300 // Force on shelf - lbs
|
||||
factorOfSafety = 1.2 // FOS of 1.2
|
||||
shelfMountL = 5 // inches
|
||||
wallMountL = 2 // inches
|
||||
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||
|
||||
|
||||
const filletRadius = .375 // inches
|
||||
const extFilletRadius = .25 // inches
|
||||
const mountingHoleDiameter = 0.5 // inches
|
||||
filletRadius = .375 // inches
|
||||
extFilletRadius = .25 // inches
|
||||
mountingHoleDiameter = 0.5 // inches
|
||||
|
||||
|
||||
// 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
|
||||
const bracketLeg1Sketch = startSketchOn('XY')
|
||||
bracketLeg1Sketch = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([shelfMountL - filletRadius, 0], %, $fillet1)
|
||||
|> line([0, width], %, $fillet2)
|
||||
|> line([-shelfMountL + filletRadius, 0], %)
|
||||
|> close(%)
|
||||
|> hole(circle({
|
||||
center: [1, 1],
|
||||
radius: mountingHoleDiameter / 2
|
||||
center = [1, 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center: [shelfMountL - 1.5, width - 1],
|
||||
radius: mountingHoleDiameter / 2
|
||||
center = [shelfMountL - 1.5, width - 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center: [1, width - 1],
|
||||
radius: mountingHoleDiameter / 2
|
||||
center = [1, width - 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center: [shelfMountL - 1.5, 1],
|
||||
radius: mountingHoleDiameter / 2
|
||||
center = [shelfMountL - 1.5, 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|
||||
// Extrude the leg 2 bracket sketch
|
||||
const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|
||||
bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|
||||
|> fillet({
|
||||
radius: extFilletRadius,
|
||||
tags: [
|
||||
radius = extFilletRadius,
|
||||
tags = [
|
||||
getNextAdjacentEdge(fillet1),
|
||||
getNextAdjacentEdge(fillet2)
|
||||
]
|
||||
}, %)
|
||||
|
||||
// Sketch the fillet arc
|
||||
const filletSketch = startSketchOn('XZ')
|
||||
filletSketch = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, thickness], %)
|
||||
|> arc({
|
||||
angleEnd: 180,
|
||||
angleStart: 90,
|
||||
radius: filletRadius + thickness
|
||||
angleEnd = 180,
|
||||
angleStart = 90,
|
||||
radius = filletRadius + thickness
|
||||
}, %)
|
||||
|> line([thickness, 0], %)
|
||||
|> arc({
|
||||
angleEnd: 90,
|
||||
angleStart: 180,
|
||||
radius: filletRadius
|
||||
angleEnd = 90,
|
||||
angleStart = 180,
|
||||
radius = filletRadius
|
||||
}, %)
|
||||
|
||||
// Sketch the bend
|
||||
const filletExtrude = extrude(-width, filletSketch)
|
||||
filletExtrude = extrude(-width, filletSketch)
|
||||
|
||||
// Create a custom plane for the leg that sits on the wall
|
||||
const customPlane = {
|
||||
plane: {
|
||||
origin: { x: -filletRadius, y: 0, z: 0 },
|
||||
xAxis: { x: 0, y: 1, z: 0 },
|
||||
yAxis: { x: 0, y: 0, z: 1 },
|
||||
zAxis: { x: 1, y: 0, z: 0 }
|
||||
customPlane = {
|
||||
plane = {
|
||||
origin = { x = -filletRadius, y = 0, z = 0 },
|
||||
xAxis = { x = 0, y = 1, z = 0 },
|
||||
yAxis = { x = 0, y = 0, z = 1 },
|
||||
zAxis = { x = 1, y = 0, z = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// Create a sketch for the second leg
|
||||
const bracketLeg2Sketch = startSketchOn(customPlane)
|
||||
bracketLeg2Sketch = startSketchOn(customPlane)
|
||||
|> startProfileAt([0, -filletRadius], %)
|
||||
|> line([width, 0], %)
|
||||
|> line([0, -wallMountL], %, $fillet3)
|
||||
|> line([-width, 0], %, $fillet4)
|
||||
|> close(%)
|
||||
|> hole(circle({
|
||||
center: [1, -1.5],
|
||||
radius: mountingHoleDiameter / 2
|
||||
center = [1, -1.5],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center: [5, -1.5],
|
||||
radius: mountingHoleDiameter / 2
|
||||
center = [5, -1.5],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|
||||
// Extrude the second leg
|
||||
const bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|
||||
bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|
||||
|> fillet({
|
||||
radius: extFilletRadius,
|
||||
tags: [
|
||||
radius = extFilletRadius,
|
||||
tags = [
|
||||
getNextAdjacentEdge(fillet3),
|
||||
getNextAdjacentEdge(fillet4)
|
||||
]
|
||||
@ -135,8 +135,8 @@ function findLineInExampleCode({
|
||||
}
|
||||
|
||||
export const bracketWidthConstantLine = findLineInExampleCode({
|
||||
searchText: 'const width',
|
||||
searchText: 'width =',
|
||||
})
|
||||
export const bracketThicknessCalculationLine = findLineInExampleCode({
|
||||
searchText: 'const thickness',
|
||||
searchText: 'thickness =',
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ import { isDesktop } from './isDesktop'
|
||||
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
|
||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||
import { parseProjectSettings } from 'lang/wasm'
|
||||
import { err } from './trap'
|
||||
import { err, reportRejection } from './trap'
|
||||
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
|
||||
|
||||
interface OnSubmitProps {
|
||||
@ -28,7 +28,7 @@ export function kclCommands(
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: () => {
|
||||
kclManager.format()
|
||||
kclManager.format().catch(reportRejection)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -5,7 +5,12 @@ import {
|
||||
kclManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { CallExpression, SourceRange, Expr } from 'lang/wasm'
|
||||
import {
|
||||
CallExpression,
|
||||
SourceRange,
|
||||
Expr,
|
||||
defaultSourceRange,
|
||||
} from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { isNonNullable, uuidv4 } from 'lib/utils'
|
||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||
@ -266,7 +271,7 @@ export function getEventForSegmentSelection(
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
codeRef: {
|
||||
range: [node.node.start, node.node.end],
|
||||
range: [node.node.start, node.node.end, true],
|
||||
pathToNode: group.userData.pathToNode,
|
||||
},
|
||||
},
|
||||
@ -309,10 +314,11 @@ export function handleSelectionBatch({
|
||||
selectionToEngine.push({
|
||||
type: 'default',
|
||||
id: artifact?.id,
|
||||
range: getCodeRefsByArtifactId(
|
||||
artifact.id,
|
||||
engineCommandManager.artifactGraph
|
||||
)?.[0].range || [0, 0],
|
||||
range:
|
||||
getCodeRefsByArtifactId(
|
||||
artifact.id,
|
||||
engineCommandManager.artifactGraph
|
||||
)?.[0].range || defaultSourceRange(),
|
||||
})
|
||||
})
|
||||
const engineEvents: Models['WebSocketRequest_type'][] =
|
||||
@ -376,10 +382,10 @@ export function processCodeMirrorRanges({
|
||||
if (!isChange) return null
|
||||
const codeBasedSelections: Selections['graphSelections'] =
|
||||
codeMirrorRanges.map(({ from, to }) => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [from, to])
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [from, to, true])
|
||||
return {
|
||||
codeRef: {
|
||||
range: [from, to],
|
||||
range: [from, to, true],
|
||||
pathToNode,
|
||||
},
|
||||
}
|
||||
@ -442,7 +448,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
if (err(nodeMeta)) return
|
||||
const node = nodeMeta.node
|
||||
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||
return isOverlap(selection?.codeRef?.range, [node.start, node.end])
|
||||
return isOverlap(selection?.codeRef?.range, [node.start, node.end, true])
|
||||
})
|
||||
|
||||
const color = groupHasCursor
|
||||
@ -936,7 +942,7 @@ export function updateSelections(
|
||||
return {
|
||||
artifact: artifact,
|
||||
codeRef: {
|
||||
range: [node.start, node.end],
|
||||
range: [node.start, node.end, true],
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
}
|
||||
@ -950,7 +956,7 @@ export function updateSelections(
|
||||
if (err(node)) return node
|
||||
pathToNodeBasedSelections.push({
|
||||
codeRef: {
|
||||
range: [node.node.start, node.node.end],
|
||||
range: [node.node.start, node.node.end, true],
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
})
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
_executor,
|
||||
SourceRange,
|
||||
ExecState,
|
||||
defaultIdGenerator,
|
||||
} from '../lang/wasm'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
@ -16,7 +15,6 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { toSync } from './utils'
|
||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
type WebSocketResponse = Models['WebSocketResponse_type']
|
||||
@ -85,12 +83,10 @@ class MockEngineCommandManager {
|
||||
}
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Node<Program> | Error,
|
||||
pm: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
idGenerator: IdGenerator = defaultIdGenerator()
|
||||
ast: Node<Program>,
|
||||
pmo: ProgramMemory | Error = ProgramMemory.empty()
|
||||
): Promise<ExecState> {
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
if (err(pm)) return Promise.reject(pm)
|
||||
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
|
||||
|
||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
@ -98,21 +94,14 @@ export async function enginelessExecutor(
|
||||
}) as any as EngineCommandManager
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
mockEngineCommandManager.startNewSession()
|
||||
const execState = await _executor(
|
||||
ast,
|
||||
pm,
|
||||
idGenerator,
|
||||
mockEngineCommandManager,
|
||||
true
|
||||
)
|
||||
const execState = await _executor(ast, mockEngineCommandManager, pmo)
|
||||
await mockEngineCommandManager.waitForAllCommands()
|
||||
return execState
|
||||
}
|
||||
|
||||
export async function executor(
|
||||
ast: Node<Program>,
|
||||
pm: ProgramMemory = ProgramMemory.empty(),
|
||||
idGenerator: IdGenerator = defaultIdGenerator()
|
||||
pmo: ProgramMemory = ProgramMemory.empty()
|
||||
): Promise<ExecState> {
|
||||
const engineCommandManager = new EngineCommandManager()
|
||||
engineCommandManager.start({
|
||||
@ -134,13 +123,7 @@ export async function executor(
|
||||
toSync(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.startNewSession()
|
||||
const execState = await _executor(
|
||||
ast,
|
||||
pm,
|
||||
idGenerator,
|
||||
engineCommandManager,
|
||||
false
|
||||
)
|
||||
const execState = await _executor(ast, engineCommandManager, pmo)
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
resolve(execState)
|
||||
}, reportRejection)
|
||||
|
||||
@ -3,7 +3,7 @@ import { kclManager, engineCommandManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { findUniqueName } from 'lang/modifyAst'
|
||||
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { ProgramMemory, Expr, parse } from 'lang/wasm'
|
||||
import { ProgramMemory, Expr, parse, resultIsOk } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { err, trap } from 'lib/trap'
|
||||
@ -87,9 +87,9 @@ export function useCalculateKclExpression({
|
||||
useEffect(() => {
|
||||
const execAstAndSetResult = async () => {
|
||||
const _code = `const __result__ = ${value}`
|
||||
const ast = parse(_code)
|
||||
if (err(ast)) return
|
||||
if (trap(ast, { suppress: true })) return
|
||||
const pResult = parse(_code)
|
||||
if (err(pResult) || !resultIsOk(pResult)) return
|
||||
const ast = pResult.program
|
||||
|
||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||
for (const { key, value } of availableVarInfo.variables) {
|
||||
@ -103,18 +103,17 @@ export function useCalculateKclExpression({
|
||||
const { execState } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: kclManager.programMemory.clone(),
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declarations?.[0]?.id?.name === '__result__'
|
||||
a.declaration.id?.name === '__result__'
|
||||
)
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declarations?.[0]?.init
|
||||
resultDeclaration?.declaration.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
|
||||
@ -9,13 +9,13 @@ import {
|
||||
import { SourceRange } from '../lang/wasm'
|
||||
|
||||
describe('testing isOverlapping', () => {
|
||||
testBothOrders([0, 3], [3, 10])
|
||||
testBothOrders([0, 5], [3, 4])
|
||||
testBothOrders([0, 5], [5, 10])
|
||||
testBothOrders([0, 5], [6, 10], false)
|
||||
testBothOrders([0, 5], [-1, 1])
|
||||
testBothOrders([0, 5], [-1, 0])
|
||||
testBothOrders([0, 5], [-2, -1], false)
|
||||
testBothOrders([0, 3, true], [3, 10, true])
|
||||
testBothOrders([0, 5, true], [3, 4, true])
|
||||
testBothOrders([0, 5, true], [5, 10, true])
|
||||
testBothOrders([0, 5, true], [6, 10, true], false)
|
||||
testBothOrders([0, 5, true], [-1, 1, true])
|
||||
testBothOrders([0, 5, true], [-1, 0, true])
|
||||
testBothOrders([0, 5, true], [-2, -1, true], false)
|
||||
})
|
||||
|
||||
function testBothOrders(a: SourceRange, b: SourceRange, result = true) {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import {
|
||||
PathToNode,
|
||||
ProgramMemory,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
parse,
|
||||
recast,
|
||||
resultIsOk,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
Axis,
|
||||
@ -545,8 +547,11 @@ export const modelingMachine = setup({
|
||||
if (event.type !== 'Convert to variable') return false
|
||||
if (!event.data) return false
|
||||
const ast = parse(recast(kclManager.ast))
|
||||
if (err(ast)) return false
|
||||
const isSafeRetVal = isNodeSafeToReplacePath(ast, event.data.pathToNode)
|
||||
if (err(ast) || !ast.program || ast.errors.length > 0) return false
|
||||
const isSafeRetVal = isNodeSafeToReplacePath(
|
||||
ast.program,
|
||||
event.data.pathToNode
|
||||
)
|
||||
if (err(isSafeRetVal)) return false
|
||||
return isSafeRetVal.isSafe
|
||||
},
|
||||
@ -729,9 +734,9 @@ export const modelingMachine = setup({
|
||||
|
||||
const testExecute = await executeAst({
|
||||
ast: modifiedAst,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
toast.error('Unable to delete part')
|
||||
@ -1335,9 +1340,12 @@ export const modelingMachine = setup({
|
||||
return
|
||||
}
|
||||
|
||||
const recastAst = parse(recast(modifiedAst))
|
||||
if (err(recastAst) || !resultIsOk(recastAst)) return
|
||||
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
parse(recast(modifiedAst)),
|
||||
recastAst.program,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
@ -2566,7 +2574,7 @@ export function canRectangleOrCircleTool({
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
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 */
|
||||
@ -2583,8 +2591,8 @@ export function isClosedSketch({
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
if (err(node)) return false
|
||||
if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false
|
||||
return node.node.declarations[0].init.body.some(
|
||||
if (node.node?.declaration.init.type !== 'PipeExpression') return false
|
||||
return node.node.declaration.init.body.some(
|
||||
(node) =>
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'close' || node.callee.name === 'circle')
|
||||
|
||||
25
src/wasm-lib/Cargo.lock
generated
25
src/wasm-lib/Cargo.lock
generated
@ -712,29 +712,6 @@ version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
||||
|
||||
[[package]]
|
||||
name = "databake"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a04fbfbecca8f0679c8c06fef907594adcc3e2052e11163a6d30535a1a5604d"
|
||||
dependencies = [
|
||||
"databake-derive",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "databake-derive"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@ -1719,7 +1696,6 @@ dependencies = [
|
||||
"convert_case",
|
||||
"criterion",
|
||||
"dashmap 6.1.0",
|
||||
"databake",
|
||||
"derive-docs",
|
||||
"dhat",
|
||||
"expectorate",
|
||||
@ -4332,6 +4308,7 @@ dependencies = [
|
||||
"kcl-lib",
|
||||
"kittycad",
|
||||
"kittycad-modeling-cmds",
|
||||
"lazy_static",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
|
||||
@ -15,6 +15,7 @@ data-encoding = "2.6.0"
|
||||
gloo-utils = "0.2.0"
|
||||
kcl-lib = { path = "kcl" }
|
||||
kittycad.workspace = true
|
||||
lazy_static = "1.5.0"
|
||||
serde_json = "1.0.128"
|
||||
tokio = { version = "1.41.1", features = ["sync"] }
|
||||
toml = "0.8.19"
|
||||
|
||||
@ -182,7 +182,7 @@ fn do_stdlib_inner(
|
||||
quote! {
|
||||
let code_blocks = vec![#(#cb),*];
|
||||
code_blocks.iter().map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
@ -395,10 +395,10 @@ fn do_stdlib_inner(
|
||||
#const_struct
|
||||
|
||||
fn #boxed_fn_name_ident(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> 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))
|
||||
}
|
||||
@ -769,23 +769,23 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
quote! {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn #test_name_mock() {
|
||||
let program = crate::Program::parse(#code_block).unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program = crate::Program::parse_no_errs(#code_block).unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
|
||||
ctx.run(&program, &mut crate::ExecState::default()).await.unwrap();
|
||||
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn #test_name() {
|
||||
let code = #code_block;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_someFn0() {
|
||||
let program = crate::Program::parse("someFn()").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program = crate::Program::parse_no_errs("someFn()").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +12,9 @@ mod test_examples_someFn {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +22,13 @@ mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_someFn0() {
|
||||
let code = "someFn()";
|
||||
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", "serial_test_example_someFn0"),
|
||||
&result,
|
||||
@ -44,12 +47,12 @@ pub(crate) struct SomeFn {}
|
||||
#[doc = "Std lib function: someFn\nDocs"]
|
||||
pub(crate) const SomeFn: SomeFn = SomeFn {};
|
||||
fn boxed_someFn(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +121,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_someFn0() {
|
||||
let program = crate::Program::parse("someFn()").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program = crate::Program::parse_no_errs("someFn()").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +12,9 @@ mod test_examples_someFn {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +22,13 @@ mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_someFn0() {
|
||||
let code = "someFn()";
|
||||
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", "serial_test_example_someFn0"),
|
||||
&result,
|
||||
@ -44,12 +47,12 @@ pub(crate) struct SomeFn {}
|
||||
#[doc = "Std lib function: someFn\nDocs"]
|
||||
pub(crate) const SomeFn: SomeFn = SomeFn {};
|
||||
fn boxed_someFn(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +121,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -3,8 +3,8 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,9 +13,9 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -23,10 +23,13 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nshow";
|
||||
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", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -36,8 +39,9 @@ mod test_examples_show {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show1() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -46,9 +50,9 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -56,10 +60,13 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show1() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
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", "serial_test_example_show1"),
|
||||
&result,
|
||||
@ -78,12 +85,12 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -155,7 +162,7 @@ impl crate::docs::StdLibFn for Show {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +13,9 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +23,13 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
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", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -44,12 +48,12 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +122,7 @@ impl crate::docs::StdLibFn for Show {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -3,8 +3,9 @@ mod test_examples_my_func {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_my_func0() {
|
||||
let program =
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc")
|
||||
.unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,9 +14,9 @@ mod test_examples_my_func {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -23,10 +24,13 @@ mod test_examples_my_func {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_my_func0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nmyFunc";
|
||||
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", "serial_test_example_my_func0"),
|
||||
&result,
|
||||
@ -36,8 +40,9 @@ mod test_examples_my_func {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_my_func1() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -46,9 +51,9 @@ mod test_examples_my_func {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -56,10 +61,13 @@ mod test_examples_my_func {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_my_func1() {
|
||||
let code = "This is code.\nIt does other shit.\nmyFunc";
|
||||
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", "serial_test_example_my_func1"),
|
||||
&result,
|
||||
@ -78,12 +86,12 @@ pub(crate) struct MyFunc {}
|
||||
#[doc = "Std lib function: myFunc\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const MyFunc: MyFunc = MyFunc {};
|
||||
fn boxed_my_func(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -155,7 +163,7 @@ impl crate::docs::StdLibFn for MyFunc {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -3,8 +3,9 @@ mod test_examples_line_to {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_line_to0() {
|
||||
let program =
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nlineTo").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nlineTo")
|
||||
.unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,9 +14,9 @@ mod test_examples_line_to {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -23,10 +24,13 @@ mod test_examples_line_to {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_line_to0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nlineTo";
|
||||
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", "serial_test_example_line_to0"),
|
||||
&result,
|
||||
@ -36,8 +40,9 @@ mod test_examples_line_to {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_line_to1() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nlineTo").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nlineTo").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -46,9 +51,9 @@ mod test_examples_line_to {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -56,10 +61,13 @@ mod test_examples_line_to {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_line_to1() {
|
||||
let code = "This is code.\nIt does other shit.\nlineTo";
|
||||
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", "serial_test_example_line_to1"),
|
||||
&result,
|
||||
@ -78,12 +86,12 @@ pub(crate) struct LineTo {}
|
||||
#[doc = "Std lib function: lineTo\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const LineTo: LineTo = LineTo {};
|
||||
fn boxed_line_to(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -164,7 +172,7 @@ impl crate::docs::StdLibFn for LineTo {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -3,8 +3,8 @@ mod test_examples_min {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_min0() {
|
||||
let program =
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nmin").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmin").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,9 +13,9 @@ mod test_examples_min {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -23,10 +23,13 @@ mod test_examples_min {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_min0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nmin";
|
||||
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", "serial_test_example_min0"),
|
||||
&result,
|
||||
@ -36,8 +39,9 @@ mod test_examples_min {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_min1() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nmin").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmin").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -46,9 +50,9 @@ mod test_examples_min {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -56,10 +60,13 @@ mod test_examples_min {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_min1() {
|
||||
let code = "This is code.\nIt does other shit.\nmin";
|
||||
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", "serial_test_example_min1"),
|
||||
&result,
|
||||
@ -78,12 +85,12 @@ pub(crate) struct Min {}
|
||||
#[doc = "Std lib function: min\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Min: Min = Min {};
|
||||
fn boxed_min(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -155,7 +162,7 @@ impl crate::docs::StdLibFn for Min {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +13,9 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +23,13 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
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", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -44,12 +48,12 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +122,7 @@ impl crate::docs::StdLibFn for Show {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_import0() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +13,9 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +23,13 @@ mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_import0() {
|
||||
let code = "This is code.\nIt does other shit.\nimport";
|
||||
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", "serial_test_example_import0"),
|
||||
&result,
|
||||
@ -44,12 +48,12 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +122,7 @@ impl crate::docs::StdLibFn for Import {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_import0() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +13,9 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +23,13 @@ mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_import0() {
|
||||
let code = "This is code.\nIt does other shit.\nimport";
|
||||
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", "serial_test_example_import0"),
|
||||
&result,
|
||||
@ -44,12 +48,12 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +122,7 @@ impl crate::docs::StdLibFn for Import {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_import0() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +13,9 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +23,13 @@ mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_import0() {
|
||||
let code = "This is code.\nIt does other shit.\nimport";
|
||||
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", "serial_test_example_import0"),
|
||||
&result,
|
||||
@ -44,12 +48,12 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +122,7 @@ impl crate::docs::StdLibFn for Import {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +13,9 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +23,13 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
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", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -44,12 +48,12 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -118,7 +122,7 @@ impl crate::docs::StdLibFn for Show {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
mod test_examples_some_function {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_some_function0() {
|
||||
let program = crate::Program::parse("someFunction()").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
let program = crate::Program::parse_no_errs("someFunction()").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,9 +12,9 @@ mod test_examples_some_function {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, &mut crate::ExecState::default())
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -22,10 +22,13 @@ mod test_examples_some_function {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_some_function0() {
|
||||
let code = "someFunction()";
|
||||
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", "serial_test_example_some_function0"),
|
||||
&result,
|
||||
@ -44,12 +47,12 @@ pub(crate) struct SomeFunction {}
|
||||
#[doc = "Std lib function: someFunction\nDocs"]
|
||||
pub(crate) const SomeFunction: SomeFunction = SomeFunction {};
|
||||
fn boxed_some_function(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
exec_state: &mut crate::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
@ -112,7 +115,7 @@ impl crate::docs::StdLibFn for SomeFunction {
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let program = crate::Program::parse(cb).unwrap();
|
||||
let program = crate::Program::parse_no_errs(cb).unwrap();
|
||||
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.ast.recast(&options, 0)
|
||||
|
||||
@ -15,7 +15,6 @@ redo-kcl-stdlib-docs:
|
||||
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
|
||||
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-exec-test-into-sim-test test_name:
|
||||
mkdir -p kcl/tests/{{test_name}}
|
||||
|
||||
@ -158,7 +158,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
|
||||
};
|
||||
let RequestBody { kcl_program, test_name } = body;
|
||||
|
||||
let program = match Program::parse(&kcl_program) {
|
||||
let program = match Program::parse_no_errs(&kcl_program) {
|
||||
Ok(pr) => pr,
|
||||
Err(e) => return bad_request(format!("Parse error: {e}")),
|
||||
};
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_lib::{
|
||||
@ -11,10 +16,6 @@ use kittycad_modeling_cmds::{
|
||||
shared::PathSegment::{self, *},
|
||||
websocket::{ModelingBatch, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest, WebSocketResponse},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
const CPP_PREFIX: &str = "const double scaleFactor = 100;\n";
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_lib::{ExecState, ExecutorContext};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod conn_mock_core;
|
||||
|
||||
///Converts the given kcl code to an engine test
|
||||
pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
let program = kcl_lib::Program::parse(code)?;
|
||||
let program = kcl_lib::Program::parse_no_errs(code)?;
|
||||
|
||||
let result = Arc::new(Mutex::new("".into()));
|
||||
let ref_result = Arc::clone(&result);
|
||||
@ -15,7 +16,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
||||
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
||||
)));
|
||||
ctx.run(&program, &mut ExecState::default()).await?;
|
||||
ctx.run(program.into(), &mut ExecState::default()).await?;
|
||||
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use kcl_to_core::*;
|
||||
use std::{env, fs};
|
||||
|
||||
use kcl_to_core::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
@ -22,7 +22,6 @@ clap = { version = "4.5.21", default-features = false, optional = true, features
|
||||
] }
|
||||
convert_case = "0.6.0"
|
||||
dashmap = "6.1.0"
|
||||
databake = { version = "0.1.8", features = ["derive"] }
|
||||
derive-docs = { version = "0.1.32", path = "../derive-docs" }
|
||||
dhat = { version = "0.3", optional = true }
|
||||
fnv = "1.0.7"
|
||||
|
||||
@ -9,7 +9,7 @@ pub fn bench_digest(c: &mut Criterion) {
|
||||
("mike_stress_test", MIKE_STRESS_TEST_PROGRAM),
|
||||
("lsystem", LSYSTEM_PROGRAM),
|
||||
] {
|
||||
let prog = kcl_lib::Program::parse(file).unwrap();
|
||||
let prog = kcl_lib::Program::parse_no_errs(file).unwrap();
|
||||
c.bench_function(&format!("digest_{name}"), move |b| {
|
||||
let prog = prog.clone();
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ pub fn bench_execute(c: &mut Criterion) {
|
||||
let rt = Runtime::new().unwrap();
|
||||
// Spawn a future onto the runtime
|
||||
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();
|
||||
@ -38,7 +38,7 @@ pub fn bench_lego(c: &mut Criterion) {
|
||||
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
|
||||
// Spawn a future onto the runtime
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ use iai::black_box;
|
||||
async fn execute_server_rack_heavy() {
|
||||
let code = SERVER_RACK_HEAVY_PROGRAM;
|
||||
black_box(
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
@ -12,7 +12,7 @@ async fn execute_server_rack_heavy() {
|
||||
async fn execute_server_rack_lite() {
|
||||
let code = SERVER_RACK_LITE_PROGRAM;
|
||||
black_box(
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
@ -22,7 +22,7 @@ use super::ExecutionKind;
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ use kittycad_modeling_cmds::{self as kcmc};
|
||||
use super::ExecutionKind;
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ use wasm_bindgen::prelude::*;
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{DefaultPlanes, IdGenerator, Point3d},
|
||||
execution::{DefaultPlanes, IdGenerator, Point3d},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
||||
@ -177,7 +177,7 @@ impl KclError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
||||
pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical(e) => e.source_ranges = source_ranges,
|
||||
@ -197,7 +197,7 @@ impl KclError {
|
||||
new
|
||||
}
|
||||
|
||||
pub fn add_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
||||
pub(crate) fn add_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
|
||||
@ -279,3 +279,114 @@ impl From<KclError> for pyo3::PyErr {
|
||||
pyo3::exceptions::PyException::new_err(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// An error which occurred during parsing, etc.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct CompilationError {
|
||||
#[serde(rename = "sourceRange")]
|
||||
pub source_range: SourceRange,
|
||||
#[serde(rename = "contextRange")]
|
||||
pub context_range: Option<SourceRange>,
|
||||
pub message: String,
|
||||
pub suggestion: Option<Suggestion>,
|
||||
pub severity: Severity,
|
||||
pub tag: Tag,
|
||||
}
|
||||
|
||||
impl CompilationError {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError {
|
||||
CompilationError {
|
||||
source_range,
|
||||
context_range: None,
|
||||
message: message.to_string(),
|
||||
suggestion: None,
|
||||
severity: Severity::Error,
|
||||
tag: Tag::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError {
|
||||
CompilationError {
|
||||
source_range,
|
||||
context_range: None,
|
||||
message: message.to_string(),
|
||||
suggestion: None,
|
||||
severity: Severity::Fatal,
|
||||
tag: Tag::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_suggestion(
|
||||
source_range: SourceRange,
|
||||
context_range: Option<SourceRange>,
|
||||
message: impl ToString,
|
||||
suggestion: Option<(impl ToString, impl ToString)>,
|
||||
tag: Tag,
|
||||
) -> CompilationError {
|
||||
CompilationError {
|
||||
source_range,
|
||||
context_range,
|
||||
message: message.to_string(),
|
||||
suggestion: suggestion.map(|(t, i)| Suggestion {
|
||||
title: t.to_string(),
|
||||
insert: i.to_string(),
|
||||
}),
|
||||
severity: Severity::Error,
|
||||
tag,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn apply_suggestion(&self, src: &str) -> Option<String> {
|
||||
let suggestion = self.suggestion.as_ref()?;
|
||||
Some(format!(
|
||||
"{}{}{}",
|
||||
&src[0..self.source_range.start()],
|
||||
suggestion.insert,
|
||||
&src[self.source_range.end()..]
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CompilationError> for KclErrorDetails {
|
||||
fn from(err: CompilationError) -> Self {
|
||||
KclErrorDetails {
|
||||
source_ranges: vec![err.source_range],
|
||||
message: err.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub enum Severity {
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
}
|
||||
|
||||
impl Severity {
|
||||
pub fn is_err(self) -> bool {
|
||||
match self {
|
||||
Severity::Warning => false,
|
||||
Severity::Error | Severity::Fatal => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub enum Tag {
|
||||
Deprecated,
|
||||
Unnecessary,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct Suggestion {
|
||||
pub title: String,
|
||||
pub insert: String,
|
||||
}
|
||||
|
||||
@ -4,14 +4,19 @@ use async_recursion::async_recursion;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier},
|
||||
execution::{
|
||||
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
|
||||
},
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
|
||||
CallExpressionKw, Expr, IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
|
||||
Node, ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
CallExpressionKw, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
|
||||
ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::{args::Arg, FunctionKind},
|
||||
std::{
|
||||
args::{Arg, KwArgs},
|
||||
FunctionKind,
|
||||
},
|
||||
};
|
||||
|
||||
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
|
||||
@ -386,7 +391,14 @@ impl Node<CallExpressionKw> {
|
||||
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) {
|
||||
FunctionKind::Core(func) => {
|
||||
// Attempt to call the function.
|
||||
@ -397,7 +409,6 @@ impl Node<CallExpressionKw> {
|
||||
FunctionKind::UserDefined => {
|
||||
todo!("Part of modeling-app#4600: Support keyword arguments for user-defined functions")
|
||||
}
|
||||
FunctionKind::Std(_) => todo!("There is no KCL std anymore, it's all core."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -428,88 +439,6 @@ impl Node<CallExpression> {
|
||||
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||
Ok(result)
|
||||
}
|
||||
FunctionKind::Std(func) => {
|
||||
let function_expression = func.function();
|
||||
let (required_params, optional_params) =
|
||||
function_expression.required_and_optional_params().map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Error getting parts of function: {}", e),
|
||||
source_ranges: vec![self.into()],
|
||||
})
|
||||
})?;
|
||||
if fn_args.len() < required_params.len() || fn_args.len() > function_expression.params.len() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"this function expected {} arguments, got {}",
|
||||
required_params.len(),
|
||||
fn_args.len(),
|
||||
),
|
||||
source_ranges: vec![self.into()],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the arguments to the memory.
|
||||
let mut fn_memory = exec_state.memory.clone();
|
||||
for (index, param) in required_params.iter().enumerate() {
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
fn_args.get(index).unwrap().value.clone(),
|
||||
param.identifier.clone().into(),
|
||||
)?;
|
||||
}
|
||||
// Add the optional arguments to the memory.
|
||||
for (index, param) in optional_params.iter().enumerate() {
|
||||
if let Some(arg) = fn_args.get(index + required_params.len()) {
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
arg.value.clone(),
|
||||
param.identifier.clone().into(),
|
||||
)?;
|
||||
} else {
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
KclValue::KclNone {
|
||||
value: KclNone::new(),
|
||||
meta: vec![self.into()],
|
||||
},
|
||||
param.identifier.clone().into(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let fn_dynamic_state = exec_state.dynamic_state.clone();
|
||||
// TODO: Shouldn't we merge program memory into fn_dynamic_state
|
||||
// here?
|
||||
|
||||
// Call the stdlib function
|
||||
let p = &func.function().body;
|
||||
|
||||
let (exec_result, fn_memory) = {
|
||||
let previous_memory = std::mem::replace(&mut exec_state.memory, fn_memory);
|
||||
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||
let result = ctx.inner_execute(p, exec_state, BodyType::Block).await;
|
||||
exec_state.dynamic_state = previous_dynamic_state;
|
||||
let fn_memory = std::mem::replace(&mut exec_state.memory, previous_memory);
|
||||
(result, fn_memory)
|
||||
};
|
||||
|
||||
match exec_result {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
// We need to override the source ranges so we don't get the embedded kcl
|
||||
// function from the stdlib.
|
||||
return Err(err.override_source_ranges(vec![self.into()]));
|
||||
}
|
||||
};
|
||||
let out = fn_memory.return_;
|
||||
let result = out.ok_or_else(|| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Result of stdlib function {} is undefined", fn_name),
|
||||
source_ranges: vec![self.into()],
|
||||
})
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
FunctionKind::UserDefined => {
|
||||
let source_range = SourceRange::from(self);
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
@ -521,6 +450,7 @@ impl Node<CallExpression> {
|
||||
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
|
||||
// Add the call expression to the source ranges.
|
||||
// TODO currently ignored by the frontend
|
||||
e.add_source_ranges(vec![source_range])
|
||||
});
|
||||
exec_state.dynamic_state = previous_dynamic_state;
|
||||
@ -889,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
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
executor::{
|
||||
execution::{
|
||||
call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
|
||||
},
|
||||
parsing::ast::types::FunctionExpression,
|
||||
@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
exec::{ProgramMemory, Sketch},
|
||||
executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
||||
parsing::ast::types::{FunctionExpression, KclNone, TagDeclarator, TagNode},
|
||||
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
||||
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
||||
std::{args::Arg, FnAsArg},
|
||||
ExecState, ExecutorContext, KclError, SourceRange,
|
||||
};
|
||||
@ -221,6 +221,14 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn none() -> Self {
|
||||
Self::KclNone {
|
||||
value: Default::default(),
|
||||
meta: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Human readable type name used in error messages. Should not be relied
|
||||
/// on for program logic.
|
||||
pub(crate) fn human_friendly_type(&self) -> &'static str {
|
||||
@ -246,9 +254,14 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function(&self) -> bool {
|
||||
matches!(self, KclValue::Function { .. })
|
||||
pub(crate) fn from_literal(literal: LiteralValue, meta: Vec<Metadata>) -> Self {
|
||||
match literal {
|
||||
LiteralValue::Number(value) => KclValue::Number { value, meta },
|
||||
LiteralValue::String(value) => KclValue::String { value, meta },
|
||||
LiteralValue::Bool(value) => KclValue::Bool { value, meta },
|
||||
}
|
||||
}
|
||||
|
||||
/// Put the number into a KCL value.
|
||||
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
||||
Self::Number { value: f, meta }
|
||||
@ -480,7 +493,7 @@ impl KclValue {
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
crate::executor::call_user_defined_function(
|
||||
crate::execution::call_user_defined_function(
|
||||
args,
|
||||
closure_memory.as_ref(),
|
||||
expression.as_ref(),
|
||||
@ -1,6 +1,6 @@
|
||||
//! The executor for the AST.
|
||||
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_recursion::async_recursion;
|
||||
@ -20,13 +20,18 @@ use serde::{Deserialize, Serialize};
|
||||
type Point2D = kcmc::shared::Point2d<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::{
|
||||
engine::{EngineManager, ExecutionKind},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::{FileManager, FileSystem},
|
||||
parsing::ast::types::{
|
||||
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, Node, NodeRef, TagDeclarator, TagNode,
|
||||
parsing::ast::{
|
||||
cache::{get_changed_program, CacheInformation},
|
||||
types::{
|
||||
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode,
|
||||
},
|
||||
},
|
||||
settings::types::UnitLength,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
@ -34,6 +39,10 @@ use crate::{
|
||||
ExecError, Program,
|
||||
};
|
||||
|
||||
mod exec_ast;
|
||||
mod function_param;
|
||||
mod kcl_value;
|
||||
|
||||
/// State for executing a program.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -49,7 +58,7 @@ pub struct ExecState {
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
pub pipe_value: Option<KclValue>,
|
||||
/// 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.
|
||||
/// If this is empty, we're not currently executing an import statement.
|
||||
pub import_stack: Vec<std::path::PathBuf>,
|
||||
@ -57,13 +66,10 @@ pub struct ExecState {
|
||||
pub path_to_source_id: IndexMap<std::path::PathBuf, ModuleId>,
|
||||
/// Map from module ID to module info.
|
||||
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
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.
|
||||
let new_module_id = ModuleId::from_usize(self.path_to_source_id.len());
|
||||
let mut is_new = false;
|
||||
@ -1486,7 +1492,8 @@ pub struct ExecutorContext {
|
||||
}
|
||||
|
||||
/// The executor settings.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct ExecutorSettings {
|
||||
/// The unit to use in modeling dimensions.
|
||||
pub units: UnitLength,
|
||||
@ -1499,6 +1506,9 @@ pub struct ExecutorSettings {
|
||||
/// Should engine store this for replay?
|
||||
/// If so, under what name?
|
||||
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 {
|
||||
@ -1509,6 +1519,7 @@ impl Default for ExecutorSettings {
|
||||
enable_ssao: false,
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1521,6 +1532,7 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
|
||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1533,6 +1545,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
|
||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1545,6 +1558,7 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
||||
enable_ssao: modeling.enable_ssao.into(),
|
||||
show_grid: modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1775,6 +1789,7 @@ impl ExecutorContext {
|
||||
enable_ssao: false,
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
},
|
||||
None,
|
||||
engine_addr,
|
||||
@ -1786,19 +1801,22 @@ impl ExecutorContext {
|
||||
pub async fn reset_scene(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<()> {
|
||||
source_range: crate::execution::SourceRange,
|
||||
) -> Result<(), KclError> {
|
||||
self.engine
|
||||
.clear_scene(&mut exec_state.id_generator, source_range)
|
||||
.await?;
|
||||
|
||||
// We do not create the planes here as the post hook in wasm will do that
|
||||
// AND if we aren't in wasm it doesn't really matter.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
/// You can optionally pass in some initialization memory.
|
||||
/// Kurt uses this for partial execution.
|
||||
pub async fn run(&self, program: &Program, exec_state: &mut ExecState) -> Result<(), KclError> {
|
||||
self.run_with_session_data(program, exec_state).await?;
|
||||
pub async fn run(&self, cache_info: CacheInformation, exec_state: &mut ExecState) -> Result<(), KclError> {
|
||||
self.run_with_session_data(cache_info, exec_state).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1807,10 +1825,27 @@ impl ExecutorContext {
|
||||
/// Kurt uses this for partial execution.
|
||||
pub async fn run_with_session_data(
|
||||
&self,
|
||||
program: &Program,
|
||||
cache_info: CacheInformation,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<Option<ModelingSessionData>, KclError> {
|
||||
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
||||
|
||||
// Get the program that actually changed from the old and new information.
|
||||
let cache_result = get_changed_program(cache_info.clone(), &self.settings);
|
||||
|
||||
// Check if we don't need to re-execute.
|
||||
let Some(cache_result) = cache_result else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if cache_result.clear_scene && !self.is_mock() {
|
||||
// We don't do this in mock mode since there is no engine connection
|
||||
// anyways and from the TS side we override memory and don't want to clear it.
|
||||
self.reset_scene(exec_state, Default::default()).await?;
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
*exec_state = Default::default();
|
||||
}
|
||||
|
||||
// TODO: Use the top-level file's path.
|
||||
exec_state.add_module(std::path::PathBuf::from(""));
|
||||
// Before we even start executing the program, set the units.
|
||||
@ -1831,7 +1866,7 @@ impl ExecutorContext {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.inner_execute(&program.ast, exec_state, crate::executor::BodyType::Root)
|
||||
self.inner_execute(&cache_result.program, exec_state, crate::execution::BodyType::Root)
|
||||
.await?;
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok(session_data)
|
||||
@ -1851,95 +1886,66 @@ impl ExecutorContext {
|
||||
match statement {
|
||||
BodyItem::ImportStatement(import_stmt) => {
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
let path = import_stmt.path.clone();
|
||||
// Empty path is used by the top-level module.
|
||||
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 = if let Some(project_dir) = &exec_state.project_directory {
|
||||
std::path::PathBuf::from(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![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();
|
||||
let (module_memory, module_exports) =
|
||||
self.open_module(&import_stmt.path, exec_state, source_range).await?;
|
||||
|
||||
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],
|
||||
})
|
||||
match &import_stmt.selector {
|
||||
ImportSelector::List { items } => {
|
||||
for import_item in items {
|
||||
// Extract the item from the module.
|
||||
let item =
|
||||
module_memory
|
||||
.get(&import_item.name.name, import_item.into())
|
||||
.map_err(|_err| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("{} is not defined in module", import_item.name.name),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
})
|
||||
})?;
|
||||
// Check that the item is allowed to be imported.
|
||||
if !module_exports.contains(&import_item.name.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the item to the current module.
|
||||
exec_state.memory.add(
|
||||
import_item.identifier(),
|
||||
item.clone(),
|
||||
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)?;
|
||||
|
||||
(module_memory, module_exports)
|
||||
};
|
||||
for import_item in &import_stmt.items {
|
||||
// Extract the item from the module.
|
||||
let item = module_memory
|
||||
.get(&import_item.name.name, import_item.into())
|
||||
.map_err(|_err| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("{} is not defined in module", import_item.name.name),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
})
|
||||
})?;
|
||||
// Check that the item is allowed to be imported.
|
||||
if !module_exports.contains(&import_item.name.name) {
|
||||
if let ItemVisibility::Export = import_stmt.visibility {
|
||||
exec_state.module_exports.push(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
ImportSelector::None(_) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
message: "Importing whole module is not yet implemented, sorry.".to_owned(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the item to the current module.
|
||||
exec_state.memory.add(
|
||||
import_item.identifier(),
|
||||
item.clone(),
|
||||
SourceRange::from(&import_item.name),
|
||||
)?;
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
@ -1956,34 +1962,23 @@ impl ExecutorContext {
|
||||
);
|
||||
}
|
||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||
for declaration in &variable_declaration.declarations {
|
||||
let var_name = declaration.id.name.to_string();
|
||||
let source_range = SourceRange::from(&declaration.init);
|
||||
let metadata = Metadata { source_range };
|
||||
let var_name = variable_declaration.declaration.id.name.to_string();
|
||||
let source_range = SourceRange::from(&variable_declaration.declaration.init);
|
||||
let metadata = Metadata { source_range };
|
||||
|
||||
let memory_item = self
|
||||
.execute_expr(
|
||||
&declaration.init,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
let is_function = memory_item.is_function();
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
// Track exports.
|
||||
match variable_declaration.visibility {
|
||||
ItemVisibility::Export => {
|
||||
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 => {}
|
||||
}
|
||||
let memory_item = self
|
||||
.execute_expr(
|
||||
&variable_declaration.declaration.init,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
exec_state.module_exports.push(var_name);
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
@ -2018,6 +2013,68 @@ impl ExecutorContext {
|
||||
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>(
|
||||
&self,
|
||||
init: &Expr,
|
||||
@ -2099,13 +2156,13 @@ impl ExecutorContext {
|
||||
program: &Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> std::result::Result<TakeSnapshot, ExecError> {
|
||||
self.run(program, exec_state).await?;
|
||||
self.run(program.clone().into(), exec_state).await?;
|
||||
|
||||
// Zoom to fit.
|
||||
self.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::executor::SourceRange::default(),
|
||||
crate::execution::SourceRange::default(),
|
||||
ModelingCmd::from(mcmd::ZoomToFit {
|
||||
object_ids: Default::default(),
|
||||
animated: false,
|
||||
@ -2119,7 +2176,7 @@ impl ExecutorContext {
|
||||
.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::executor::SourceRange::default(),
|
||||
crate::execution::SourceRange::default(),
|
||||
ModelingCmd::from(mcmd::TakeSnapshot {
|
||||
format: ImageFormat::Png,
|
||||
}),
|
||||
@ -2172,18 +2229,12 @@ fn assign_args_to_params(
|
||||
fn_memory.add(¶m.identifier.name, arg.value.clone(), (¶m.identifier).into())?;
|
||||
} else {
|
||||
// Argument was not provided.
|
||||
if param.optional {
|
||||
if let Some(ref default_val) = param.default_value {
|
||||
// If the corresponding parameter is optional,
|
||||
// then it's fine, the user doesn't need to supply it.
|
||||
let none = Node {
|
||||
inner: KclNone::new(),
|
||||
start: param.identifier.start,
|
||||
end: param.identifier.end,
|
||||
module_id: param.identifier.module_id,
|
||||
};
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
KclValue::from(&none),
|
||||
default_val.clone().into(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
} else {
|
||||
@ -2238,10 +2289,10 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::parsing::ast::types::{Identifier, Node, Parameter};
|
||||
use crate::parsing::ast::types::{DefaultParamVal, Identifier, Node, Parameter};
|
||||
|
||||
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
||||
let program = Program::parse(code)?;
|
||||
let program = Program::parse_no_errs(code)?;
|
||||
|
||||
let ctx = ExecutorContext {
|
||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
||||
@ -2251,7 +2302,7 @@ mod tests {
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::default();
|
||||
ctx.run(&program, &mut exec_state).await?;
|
||||
ctx.run(program.into(), &mut exec_state).await?;
|
||||
|
||||
Ok(exec_state.memory)
|
||||
}
|
||||
@ -2998,7 +3049,8 @@ let w = f() + f()
|
||||
Parameter {
|
||||
identifier: ident(s),
|
||||
type_: None,
|
||||
optional: true,
|
||||
default_value: Some(DefaultParamVal::none()),
|
||||
labeled: true,
|
||||
digest: None,
|
||||
}
|
||||
}
|
||||
@ -3006,7 +3058,8 @@ let w = f() + f()
|
||||
Parameter {
|
||||
identifier: ident(s),
|
||||
type_: None,
|
||||
optional: false,
|
||||
default_value: None,
|
||||
labeled: true,
|
||||
digest: None,
|
||||
}
|
||||
}
|
||||
@ -3041,10 +3094,7 @@ let w = f() + f()
|
||||
"all params optional, none given, should be OK",
|
||||
vec![opt_param("x")],
|
||||
vec![],
|
||||
Ok(additional_program_memory(&[(
|
||||
"x".to_owned(),
|
||||
KclValue::from(&KclNone::default()),
|
||||
)])),
|
||||
Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
|
||||
),
|
||||
(
|
||||
"mixed params, too few given",
|
||||
@ -3061,7 +3111,7 @@ let w = f() + f()
|
||||
vec![mem(1)],
|
||||
Ok(additional_program_memory(&[
|
||||
("x".to_owned(), mem(1)),
|
||||
("y".to_owned(), KclValue::from(&KclNone::default())),
|
||||
("y".to_owned(), KclValue::none()),
|
||||
])),
|
||||
),
|
||||
(
|
||||
@ -60,10 +60,8 @@ mod coredump;
|
||||
mod docs;
|
||||
mod engine;
|
||||
mod errors;
|
||||
mod executor;
|
||||
mod execution;
|
||||
mod fs;
|
||||
mod function_param;
|
||||
mod kcl_value;
|
||||
pub mod lint;
|
||||
mod log;
|
||||
mod lsp;
|
||||
@ -83,20 +81,24 @@ mod wasm;
|
||||
|
||||
pub use coredump::CoreDump;
|
||||
pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use errors::{ConnectionError, ExecError, KclError};
|
||||
pub use executor::{ExecState, ExecutorContext, ExecutorSettings};
|
||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError};
|
||||
pub use execution::{ExecState, ExecutorContext, ExecutorSettings};
|
||||
pub use lsp::{
|
||||
copilot::Backend as CopilotLspBackend,
|
||||
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
||||
};
|
||||
pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
|
||||
pub use parsing::ast::{
|
||||
cache::{CacheInformation, OldAstState},
|
||||
modify::modify_ast_for_sketch,
|
||||
types::FormatOptions,
|
||||
};
|
||||
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
|
||||
pub use source_range::{ModuleId, SourceRange};
|
||||
|
||||
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
|
||||
// Ideally we wouldn't export these things at all, they should only be used for testing.
|
||||
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")]
|
||||
@ -125,7 +127,7 @@ use crate::log::{log, logln};
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Program {
|
||||
#[serde(flatten)]
|
||||
ast: parsing::ast::types::Node<parsing::ast::types::Program>,
|
||||
pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "lsp-test-util"))]
|
||||
@ -134,10 +136,17 @@ pub use lsp::test_util::copilot_lsp_server;
|
||||
pub use lsp::test_util::kcl_lsp_server;
|
||||
|
||||
impl Program {
|
||||
pub fn parse(input: &str) -> Result<Program, KclError> {
|
||||
pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
|
||||
let module_id = ModuleId::default();
|
||||
let tokens = parsing::token::lexer(input, module_id)?;
|
||||
let (ast, errs) = parsing::parse_tokens(tokens).0?;
|
||||
|
||||
Ok((ast.map(|ast| Program { ast }), errs))
|
||||
}
|
||||
|
||||
pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
|
||||
let module_id = ModuleId::default();
|
||||
let tokens = parsing::token::lexer(input, module_id)?;
|
||||
// TODO handle parsing errors properly
|
||||
let ast = parsing::parse_tokens(tokens).parse_errs_as_err()?;
|
||||
|
||||
Ok(Program { ast })
|
||||
|
||||
@ -60,11 +60,7 @@ pub fn lint_variables(decl: Node) -> Result<Vec<Discovered>> {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
|
||||
Ok(decl
|
||||
.declarations
|
||||
.iter()
|
||||
.flat_map(|v| lint_lower_camel_case_var(v).unwrap_or_default())
|
||||
.collect())
|
||||
lint_lower_camel_case_var(&decl.declaration)
|
||||
}
|
||||
|
||||
pub fn lint_object_properties(decl: Node) -> Result<Vec<Discovered>> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user