Compare commits
11 Commits
derive-doc
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
e0924d90b4 | |||
592c5259c6 | |||
8d8dcd4104 | |||
950f5cebfd | |||
4c0ea136e0 | |||
81c0035adc | |||
c68e5d7774 | |||
ed8a0e4aaa | |||
322f398049 | |||
bc4d254297 | |||
10b7a772bf |
File diff suppressed because one or more lines are too long
@ -33,7 +33,14 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
|
||||
|
||||
```js
|
||||
// Create a helix around the Z axis.
|
||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = 'Z')
|
||||
helixPath = helix(
|
||||
angleStart = 0,
|
||||
ccw = true,
|
||||
revolutions = 5,
|
||||
length = 10,
|
||||
radius = 5,
|
||||
axis = 'Z',
|
||||
)
|
||||
|
||||
// Create a spring by sweeping around the helix path.
|
||||
springSketch = startSketchOn('YZ')
|
||||
@ -49,7 +56,14 @@ helper001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0, 10], tag = $edge001)
|
||||
|
||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = edge001)
|
||||
helixPath = helix(
|
||||
angleStart = 0,
|
||||
ccw = true,
|
||||
revolutions = 5,
|
||||
length = 10,
|
||||
radius = 5,
|
||||
axis = edge001,
|
||||
)
|
||||
|
||||
// Create a spring by sweeping around the helix path.
|
||||
springSketch = startSketchOn('XY')
|
||||
@ -61,12 +75,19 @@ springSketch = startSketchOn('XY')
|
||||
|
||||
```js
|
||||
// Create a helix around a custom axis.
|
||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = {
|
||||
helixPath = helix(
|
||||
angleStart = 0,
|
||||
ccw = true,
|
||||
revolutions = 5,
|
||||
length = 10,
|
||||
radius = 5,
|
||||
axis = {
|
||||
custom = {
|
||||
axis = [0, 0, 1.0],
|
||||
origin = [0, 0.25, 0]
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
// Create a spring by sweeping around the helix path.
|
||||
springSketch = startSketchOn('XY')
|
||||
|
File diff suppressed because one or more lines are too long
@ -17,7 +17,7 @@ lastSegX(sketch: Sketch) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | The sketch whose line segment is being queried | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ lastSegY(sketch: Sketch) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | The sketch whose line segment is being queried | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -57,3 +57,55 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
|
||||
```
|
||||
import increment as inc, decrement as dec from "util.kcl"
|
||||
```
|
||||
|
||||
## Importing files from other CAD systems
|
||||
|
||||
`import` can also be used to import files from other CAD systems. The format of the statement is the
|
||||
same as for KCL files. You can only import the whole file, not items from it. E.g.,
|
||||
|
||||
```
|
||||
import "tests/inputs/cube.obj"
|
||||
|
||||
// Use `cube` just like a KCL object.
|
||||
```
|
||||
|
||||
```
|
||||
import "tests/inputs/cube-2.sldprt" as cube
|
||||
|
||||
// Use `cube` just like a KCL object.
|
||||
```
|
||||
|
||||
You can make the file format explicit using a format attribute (useful if using a different
|
||||
extension), e.g.,
|
||||
|
||||
```
|
||||
@(format = obj)
|
||||
import "tests/inputs/cube"
|
||||
```
|
||||
|
||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
|
||||
unit of measurement is millimeters. Alternatively you may specify the unit
|
||||
by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
|
||||
|
||||
```
|
||||
@(unitLength = ft, coords = opengl)
|
||||
import "tests/inputs/cube.obj"
|
||||
```
|
||||
|
||||
When importing a GLTF file, the bin file will be imported as well.
|
||||
|
||||
Import paths are relative to the current project directory. Imports currently only work when
|
||||
using the native Modeling App, not in the browser.
|
||||
|
||||
### Supported values
|
||||
|
||||
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
|
||||
'+' support customising the length unit and coordinate system).
|
||||
|
||||
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
|
||||
|
||||
Coordinate systems:
|
||||
|
||||
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
|
||||
- `opengl`, forward: +Z, up: +Y, handedness: right
|
||||
- `vulkan`, forward: +Z, up: -Y, handedness: left
|
||||
|
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or
|
||||
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
||||
|
||||
```js
|
||||
patternCircular2d(data: CircularPattern2dData, sketch_set: SketchSet) -> [Sketch]
|
||||
patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -17,8 +17,12 @@ patternCircular2d(data: CircularPattern2dData, sketch_set: SketchSet) -> [Sketch
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`CircularPattern2dData`](/docs/kcl/types/CircularPattern2dData) | Data for a circular pattern on a 2D sketch. | Yes |
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `center` | `[number]` | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -34,12 +38,12 @@ exampleSketch = startSketchOn('XZ')
|
||||
|> line(end = [-1, 0])
|
||||
|> line(end = [0, -5])
|
||||
|> close()
|
||||
|> patternCircular2d({
|
||||
|> patternCircular2d(
|
||||
center = [0, 0],
|
||||
instances = 13,
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true
|
||||
}, %)
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
example = extrude(exampleSketch, length = 1)
|
||||
```
|
||||
|
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?: bool) -> [Sketch]
|
||||
patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -17,9 +17,11 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `use_original` | `bool` | | No |
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
|
||||
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -31,11 +33,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
|
||||
```js
|
||||
exampleSketch = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
|> patternLinear2d({
|
||||
axis = [1, 0],
|
||||
instances = 7,
|
||||
distance = 4
|
||||
}, %)
|
||||
|> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
|
||||
|
||||
example = extrude(exampleSketch, length = 1)
|
||||
```
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -17,7 +17,7 @@ segAng(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segEnd(tag: TagIdentifier) -> [number]
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segEndX(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segEndY(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segLen(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segStart(tag: TagIdentifier) -> [number]
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segStartX(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -17,7 +17,7 @@ segStartY(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
38310
docs/kcl/std.json
38310
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
@ -59,7 +59,14 @@ sweepSketch = startSketchOn('XY')
|
||||
|
||||
|
||||
// Create a helix around the Z axis.
|
||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 4, length = 10, radius = 5, axis = 'Z')
|
||||
helixPath = helix(
|
||||
angleStart = 0,
|
||||
ccw = true,
|
||||
revolutions = 4,
|
||||
length = 10,
|
||||
radius = 5,
|
||||
axis = 'Z',
|
||||
)
|
||||
|
||||
// Create a spring by sweeping around the helix path.
|
||||
springSketch = startSketchOn('YZ')
|
||||
|
@ -17,7 +17,7 @@ tangentToEnd(tag: TagIdentifier) -> number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
16
docs/kcl/types/EnvironmentRef.md
Normal file
16
docs/kcl/types/EnvironmentRef.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "EnvironmentRef"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -311,7 +311,7 @@ Data for an imported geometry.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Function`| | No |
|
||||
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
||||
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
@ -351,6 +351,23 @@ Data for an imported geometry.
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Tombstone`| | No |
|
||||
| `value` |`null`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -17,6 +17,5 @@ layout: manual
|
||||
|----------|------|-------------|----------|
|
||||
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
||||
| `currentEnv` |`integer`| | No |
|
||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
||||
|
||||
|
||||
|
16
docs/kcl/types/SnapshotRef.md
Normal file
16
docs/kcl/types/SnapshotRef.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "SnapshotRef"
|
||||
excerpt: "An index pointing to a snapshot within a specific (unspecified) environment."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An index pointing to a snapshot within a specific (unspecified) environment.
|
||||
|
||||
**Type:** `integer` (`uint`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1117,13 +1117,14 @@ openSketch = startSketchOn('XY')
|
||||
}) => {
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 620, y: 257 }
|
||||
const expectedOutput = `helix001 = helix(revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5)`
|
||||
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5,)`
|
||||
const expectedLine = `revolutions=1,`
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await test.step(`Look for the red of the default plane`, async () => {
|
||||
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
||||
})
|
||||
// await test.step(`Look for the red of the default plane`, async () => {
|
||||
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
||||
// })
|
||||
await test.step(`Go through the command bar flow`, async () => {
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
@ -1154,7 +1155,7 @@ openSketch = startSketchOn('XY')
|
||||
await editor.expectEditor.toContain(expectedOutput)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [expectedOutput],
|
||||
activeLines: [expectedLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
// Red plane is now gone, white helix is there
|
||||
|
@ -36,7 +36,7 @@ extrude003 = extrude(sketch003, length = 20)
|
||||
`
|
||||
|
||||
test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
test.fixme('Check the happy path, for basic changing color', () => {
|
||||
test.describe('Check the happy path, for basic changing color', () => {
|
||||
const cases = [
|
||||
{
|
||||
desc: 'User accepts change',
|
||||
|
@ -192,11 +192,11 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|> line(end = [0, -1])
|
||||
|> close()
|
||||
|> extrude(length = 1)
|
||||
|> patternLinear3d({
|
||||
axis: [1, 0, 1],
|
||||
repetitions: 3,
|
||||
distance: 6
|
||||
}, %)`
|
||||
|> patternLinear3d(
|
||||
axis = [1, 0, 1],
|
||||
repetitions = 3,
|
||||
distance = 6,
|
||||
)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Binary file not shown.
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
Binary file not shown.
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
@ -85,7 +85,7 @@
|
||||
"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",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-appearance/manifest.json",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-pattern-transform2/manifest.json",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
|
@ -31,7 +31,6 @@ import {
|
||||
recast,
|
||||
defaultSourceRange,
|
||||
resultIsOk,
|
||||
ProgramMemory,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
@ -425,7 +424,7 @@ export async function deleteSegment({
|
||||
modifiedAst = deleteSegmentFromPipeExpression(
|
||||
dependentRanges,
|
||||
modifiedAst,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
codeManager.code,
|
||||
pathToNode
|
||||
)
|
||||
@ -439,8 +438,8 @@ export async function deleteSegment({
|
||||
const testExecute = await executeAst({
|
||||
ast: modifiedAst,
|
||||
engineCommandManager: engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
isMock: true,
|
||||
usePrevMemory: false,
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
||||
@ -675,7 +674,7 @@ const ConstraintSymbol = ({
|
||||
shallowPath,
|
||||
argPosition,
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
kclManager.variables
|
||||
)
|
||||
|
||||
if (!transform) return
|
||||
|
@ -47,7 +47,6 @@ import {
|
||||
PathToNode,
|
||||
PipeExpression,
|
||||
Program,
|
||||
ProgramMemory,
|
||||
recast,
|
||||
Sketch,
|
||||
Solid,
|
||||
@ -61,6 +60,7 @@ import {
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
CallExpressionKw,
|
||||
VariableMap,
|
||||
} from 'lang/wasm'
|
||||
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
||||
import {
|
||||
@ -158,7 +158,6 @@ type Vec3Array = [number, number, number]
|
||||
export class SceneEntities {
|
||||
engineCommandManager: EngineCommandManager
|
||||
scene: Scene
|
||||
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
|
||||
activeSegments: { [key: string]: Group } = {}
|
||||
intersectionPlane: Mesh | null = null
|
||||
axisGroup: Group | null = null
|
||||
@ -502,29 +501,25 @@ export class SceneEntities {
|
||||
selectionRanges?: Selections
|
||||
}): Promise<{
|
||||
truncatedAst: Node<Program>
|
||||
programMemoryOverride: ProgramMemory
|
||||
sketch: Sketch
|
||||
variableDeclarationName: string
|
||||
}> {
|
||||
const prepared = this.prepareTruncatedMemoryAndAst(
|
||||
const prepared = this.prepareTruncatedAst(
|
||||
sketchPathToNode || [],
|
||||
maybeModdedAst
|
||||
)
|
||||
if (err(prepared)) return Promise.reject(prepared)
|
||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||
prepared
|
||||
const { truncatedAst, variableDeclarationName } = prepared
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
const sketch = sketchFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: maybeModdedAst,
|
||||
programMemory,
|
||||
variables: execState.variables,
|
||||
})
|
||||
if (err(sketch)) return Promise.reject(sketch)
|
||||
if (!sketch) return Promise.reject('sketch not found')
|
||||
@ -532,11 +527,9 @@ export class SceneEntities {
|
||||
if (!isArray(sketch?.paths))
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
sketch,
|
||||
variableDeclarationName,
|
||||
}
|
||||
this.sceneProgramMemory = programMemory
|
||||
const group = new Group()
|
||||
position && group.position.set(...position)
|
||||
group.userData = {
|
||||
@ -684,7 +677,6 @@ export class SceneEntities {
|
||||
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
sketch,
|
||||
variableDeclarationName,
|
||||
}
|
||||
@ -733,7 +725,7 @@ export class SceneEntities {
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
|
||||
const sg = sketchFromKclValue(
|
||||
kclManager.programMemory.get(variableDeclarationName),
|
||||
kclManager.variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sg)) return Promise.reject(sg)
|
||||
@ -742,7 +734,7 @@ export class SceneEntities {
|
||||
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||
const mod = addNewSketchLn({
|
||||
node: _ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
input: {
|
||||
type: 'straight-segment',
|
||||
to: lastSeg.to,
|
||||
@ -761,15 +753,14 @@ export class SceneEntities {
|
||||
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
||||
sceneInfra.resetMouseListeners()
|
||||
|
||||
const { truncatedAst, programMemoryOverride, sketch } =
|
||||
await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
position: origin,
|
||||
maybeModdedAst: modifiedAst,
|
||||
draftExpressionsIndices,
|
||||
})
|
||||
const { truncatedAst, sketch } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
position: origin,
|
||||
maybeModdedAst: modifiedAst,
|
||||
draftExpressionsIndices,
|
||||
})
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: async (args) => {
|
||||
if (!args) return
|
||||
@ -801,7 +792,7 @@ export class SceneEntities {
|
||||
])
|
||||
modifiedAst = addCallExpressionsToPipe({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
pathToNode: sketchPathToNode,
|
||||
expressions: [
|
||||
lastSegment.type === 'TangentialArcTo'
|
||||
@ -817,7 +808,7 @@ export class SceneEntities {
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
modifiedAst = addCloseToPipe({
|
||||
node: modifiedAst,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
@ -867,7 +858,7 @@ export class SceneEntities {
|
||||
|
||||
const tmp = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
input: {
|
||||
type: 'straight-segment',
|
||||
from: [lastSegment.to[0], lastSegment.to[1]],
|
||||
@ -908,7 +899,6 @@ export class SceneEntities {
|
||||
sketchPathToNode,
|
||||
draftInfo: {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
variableDeclarationName,
|
||||
},
|
||||
})
|
||||
@ -949,7 +939,7 @@ export class SceneEntities {
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
const { truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
@ -982,13 +972,10 @@ export class SceneEntities {
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketch = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
execState.variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return Promise.reject(sketch)
|
||||
@ -1045,15 +1032,12 @@ export class SceneEntities {
|
||||
const { execState } = await executeAst({
|
||||
ast: _ast,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
|
||||
// Prepare to update the THREEjs scene
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketch = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
execState.variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return
|
||||
@ -1104,7 +1088,7 @@ export class SceneEntities {
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
const { truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
@ -1144,13 +1128,10 @@ export class SceneEntities {
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketch = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
execState.variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return Promise.reject(sketch)
|
||||
@ -1210,15 +1191,12 @@ export class SceneEntities {
|
||||
const { execState } = await executeAst({
|
||||
ast: _ast,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
|
||||
// Prepare to update the THREEjs scene
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketch = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
execState.variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return
|
||||
@ -1605,7 +1583,7 @@ export class SceneEntities {
|
||||
// do a quick mock execution to get the program memory up-to-date
|
||||
await kclManager.executeAstMock(_ast)
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
const { truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
@ -1634,7 +1612,7 @@ export class SceneEntities {
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
const moddedResult = changeSketchArguments(
|
||||
modded,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
{
|
||||
type: 'path',
|
||||
pathToNode: [
|
||||
@ -1657,13 +1635,10 @@ export class SceneEntities {
|
||||
const { execState } = await executeAst({
|
||||
ast: modded,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketch = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
execState.variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return
|
||||
@ -1700,7 +1675,7 @@ export class SceneEntities {
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
const moddedResult = changeSketchArguments(
|
||||
modded,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
{
|
||||
type: 'path',
|
||||
pathToNode: [
|
||||
@ -1788,7 +1763,7 @@ export class SceneEntities {
|
||||
const sketch = sketchFromPathToNode({
|
||||
pathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
})
|
||||
if (trap(sketch)) return
|
||||
if (!sketch) {
|
||||
@ -1801,7 +1776,7 @@ export class SceneEntities {
|
||||
const prevSegment = sketch.paths[pipeIndex - 2]
|
||||
const mod = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
input: {
|
||||
type: 'straight-segment',
|
||||
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
|
||||
@ -1873,15 +1848,15 @@ export class SceneEntities {
|
||||
...this.mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
}
|
||||
prepareTruncatedMemoryAndAst = (
|
||||
prepareTruncatedAst = (
|
||||
sketchPathToNode: PathToNode,
|
||||
ast?: Node<Program>,
|
||||
draftSegment?: DraftSegment
|
||||
) =>
|
||||
prepareTruncatedMemoryAndAst(
|
||||
prepareTruncatedAst(
|
||||
sketchPathToNode,
|
||||
ast || kclManager.ast,
|
||||
kclManager.lastSuccessfulProgramMemory,
|
||||
kclManager.lastSuccessfulVariables,
|
||||
draftSegment
|
||||
)
|
||||
onDragSegment({
|
||||
@ -1897,7 +1872,6 @@ export class SceneEntities {
|
||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||
draftInfo?: {
|
||||
truncatedAst: Node<Program>
|
||||
programMemoryOverride: ProgramMemory
|
||||
variableDeclarationName: string
|
||||
}
|
||||
}) {
|
||||
@ -2010,12 +1984,12 @@ export class SceneEntities {
|
||||
to: dragTo,
|
||||
from,
|
||||
},
|
||||
previousProgramMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
})
|
||||
} else {
|
||||
modded = changeSketchArguments(
|
||||
modifiedAst,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: topLevelRange(node.start, node.end),
|
||||
@ -2028,10 +2002,9 @@ export class SceneEntities {
|
||||
modifiedAst = modded.modifiedAst
|
||||
const info = draftInfo
|
||||
? draftInfo
|
||||
: this.prepareTruncatedMemoryAndAst(pathToNode || [])
|
||||
: this.prepareTruncatedAst(pathToNode || [])
|
||||
if (trap(info, { suppress: true })) return
|
||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||
info
|
||||
const { truncatedAst, variableDeclarationName } = info
|
||||
;(async () => {
|
||||
const code = recast(modifiedAst)
|
||||
if (trap(code)) return
|
||||
@ -2042,13 +2015,11 @@ export class SceneEntities {
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
isMock: true,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
const variables = execState.variables
|
||||
|
||||
const maybeSketch = programMemory.get(variableDeclarationName)
|
||||
const maybeSketch = variables[variableDeclarationName]
|
||||
let sketch: Sketch | undefined
|
||||
const sk = sketchFromKclValueOptional(
|
||||
maybeSketch,
|
||||
@ -2394,15 +2365,14 @@ export class SceneEntities {
|
||||
|
||||
// calculations/pure-functions/easy to test so no excuse not to
|
||||
|
||||
function prepareTruncatedMemoryAndAst(
|
||||
function prepareTruncatedAst(
|
||||
sketchPathToNode: PathToNode,
|
||||
ast: Node<Program>,
|
||||
programMemory: ProgramMemory,
|
||||
variables: VariableMap,
|
||||
draftSegment?: DraftSegment
|
||||
):
|
||||
| {
|
||||
truncatedAst: Node<Program>
|
||||
programMemoryOverride: ProgramMemory
|
||||
variableDeclarationName: string
|
||||
}
|
||||
| Error {
|
||||
@ -2417,7 +2387,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
if (err(_node)) return _node
|
||||
const variableDeclarationName = _node.node?.declaration.id?.name || ''
|
||||
const sg = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variables[variableDeclarationName],
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sg)) return sg
|
||||
@ -2476,43 +2446,8 @@ function prepareTruncatedMemoryAndAst(
|
||||
body: [structuredClone(_ast.body[bodyIndex])],
|
||||
}
|
||||
|
||||
// Grab all the TagDeclarators and TagIdentifiers from memory.
|
||||
let start = _node.node.start
|
||||
const programMemoryOverride = programMemory.filterVariables(true, (value) => {
|
||||
if (
|
||||
!('__meta' in value) ||
|
||||
value.__meta === undefined ||
|
||||
value.__meta.length === 0 ||
|
||||
value.__meta[0].sourceRange === undefined
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (value.__meta[0].sourceRange[0] >= start) {
|
||||
// We only want things before our start point.
|
||||
return false
|
||||
}
|
||||
|
||||
return value.type === 'TagIdentifier'
|
||||
})
|
||||
if (err(programMemoryOverride)) return programMemoryOverride
|
||||
|
||||
for (let i = 0; i < bodyIndex; i++) {
|
||||
const node = _ast.body[i]
|
||||
if (node.type !== 'VariableDeclaration') {
|
||||
continue
|
||||
}
|
||||
const name = node.declaration.id.name
|
||||
const memoryItem = programMemory.get(name)
|
||||
if (!memoryItem) {
|
||||
continue
|
||||
}
|
||||
const error = programMemoryOverride.set(name, structuredClone(memoryItem))
|
||||
if (err(error)) return error
|
||||
}
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
variableDeclarationName,
|
||||
}
|
||||
}
|
||||
@ -2532,11 +2467,11 @@ export function getParentGroup(
|
||||
export function sketchFromPathToNode({
|
||||
pathToNode,
|
||||
ast,
|
||||
programMemory,
|
||||
variables,
|
||||
}: {
|
||||
pathToNode: PathToNode
|
||||
ast: Program
|
||||
programMemory: ProgramMemory
|
||||
variables: VariableMap
|
||||
}): Sketch | null | Error {
|
||||
const _varDec = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
@ -2545,7 +2480,7 @@ export function sketchFromPathToNode({
|
||||
)
|
||||
if (err(_varDec)) return _varDec
|
||||
const varDec = _varDec.node
|
||||
const result = programMemory.get(varDec?.id?.name || '')
|
||||
const result = variables[varDec?.id?.name || '']
|
||||
if (result?.type === 'Solid') {
|
||||
return result.value.sketch
|
||||
}
|
||||
@ -2584,7 +2519,7 @@ export function getSketchQuaternion(
|
||||
const sketch = sketchFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
})
|
||||
if (err(sketch)) return sketch
|
||||
const zAxis = sketch?.on.zAxis || sketchNormalBackUp
|
||||
@ -2601,7 +2536,7 @@ export async function getSketchOrientationDetails(
|
||||
const sketch = sketchFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
variables: kclManager.variables,
|
||||
})
|
||||
if (err(sketch)) return Promise.reject(sketch)
|
||||
if (!sketch) return Promise.reject('sketch not found')
|
||||
|
@ -1,11 +1,5 @@
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import {
|
||||
parse,
|
||||
BinaryPart,
|
||||
Expr,
|
||||
ProgramMemory,
|
||||
resultIsOk,
|
||||
} from '../lang/wasm'
|
||||
import { parse, BinaryPart, Expr, resultIsOk, VariableMap } from '../lang/wasm'
|
||||
import {
|
||||
createIdentifier,
|
||||
createLiteral,
|
||||
@ -100,7 +94,7 @@ export function useCalc({
|
||||
newVariableInsertIndex: number
|
||||
setNewVariableName: (a: string) => void
|
||||
} {
|
||||
const { programMemory } = useKclContext()
|
||||
const { variables } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
const selectionRange =
|
||||
context.selectionRanges?.graphSelections[0]?.codeRef?.range
|
||||
@ -127,7 +121,7 @@ export function useCalc({
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (programMemory.has(newVariableName)) {
|
||||
if (variables[newVariableName]) {
|
||||
setIsNewVariableNameUnique(false)
|
||||
} else {
|
||||
setIsNewVariableNameUnique(true)
|
||||
@ -135,14 +129,14 @@ export function useCalc({
|
||||
}, [newVariableName])
|
||||
|
||||
useEffect(() => {
|
||||
if (!programMemory || !selectionRange) return
|
||||
if (!variables || !selectionRange) return
|
||||
const varInfo = findAllPreviousVariables(
|
||||
kclManager.ast,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
selectionRange
|
||||
)
|
||||
setAvailableVarInfo(varInfo)
|
||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
||||
}, [kclManager.ast, kclManager.variables, selectionRange])
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
@ -150,9 +144,9 @@ export function useCalc({
|
||||
const pResult = parse(code)
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return
|
||||
const ast = pResult.program
|
||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||
const _variables: VariableMap = {}
|
||||
for (const { key, value } of availableVarInfo.variables) {
|
||||
const error = _programMem.set(key, {
|
||||
const error = (_variables[key] = {
|
||||
type: 'String',
|
||||
value,
|
||||
__meta: [],
|
||||
@ -163,8 +157,8 @@ export function useCalc({
|
||||
executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: kclManager.programMemory.clone(),
|
||||
isMock: true,
|
||||
variables,
|
||||
}).then(({ execState }) => {
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
@ -174,7 +168,7 @@ export function useCalc({
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declaration.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
const result = execState.variables['__result__']?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
})
|
||||
|
@ -157,8 +157,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
plugin.requestSemanticTokens()
|
||||
break
|
||||
case 'kcl/memoryUpdated':
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { processMemory } from './MemoryPane'
|
||||
import { enginelessExecutor } from '../../../lib/testHelpers'
|
||||
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
|
||||
import { assertParse, initPromise } from '../../../lang/wasm'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -29,15 +29,11 @@ describe('processMemory', () => {
|
||||
|> line(endAbsolute = [2.15, 4.32])
|
||||
// |> rx(90, %)`
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
|
||||
const output = processMemory(execState.memory)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const output = processMemory(execState.variables)
|
||||
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,
|
||||
|
@ -2,10 +2,10 @@ import toast from 'react-hot-toast'
|
||||
import ReactJson from 'react-json-view'
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
ExtrudeSurface,
|
||||
sketchFromKclValueOptional,
|
||||
VariableMap,
|
||||
} from 'lang/wasm'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
||||
@ -15,12 +15,12 @@ import Tooltip from 'components/Tooltip'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export const MemoryPaneMenu = () => {
|
||||
const { programMemory } = useKclContext()
|
||||
const { variables } = useKclContext()
|
||||
|
||||
function copyProgramMemoryToClipboard() {
|
||||
if (globalThis && 'navigator' in globalThis) {
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify(programMemory))
|
||||
.writeText(JSON.stringify(variables))
|
||||
.then(() => toast.success('Program memory copied to clipboard'))
|
||||
.catch((e) =>
|
||||
trap(new Error('Failed to copy program memory to clipboard'))
|
||||
@ -50,12 +50,9 @@ export const MemoryPaneMenu = () => {
|
||||
|
||||
export const MemoryPane = () => {
|
||||
const theme = useResolvedTheme()
|
||||
const { programMemory } = useKclContext()
|
||||
const { variables } = useKclContext()
|
||||
const { state } = useModelingContext()
|
||||
const ProcessedMemory = useMemo(
|
||||
() => processMemory(programMemory),
|
||||
[programMemory]
|
||||
)
|
||||
const ProcessedMemory = useMemo(() => processMemory(variables), [variables])
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<div className="absolute inset-0 p-2 flex flex-col items-start">
|
||||
@ -85,9 +82,10 @@ export const MemoryPane = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const processMemory = (programMemory: ProgramMemory) => {
|
||||
export const processMemory = (variables: VariableMap) => {
|
||||
const processedMemory: any = {}
|
||||
for (const [key, val] of programMemory?.visibleEntries()) {
|
||||
for (const [key, val] of Object.entries(variables)) {
|
||||
if (val === undefined) continue
|
||||
if (
|
||||
val.type === 'Sketch' ||
|
||||
// @ts-ignore
|
||||
|
@ -19,7 +19,6 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { copyFileShareLink } from 'lib/links'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
const ProjectSidebarMenu = ({
|
||||
@ -194,7 +193,7 @@ function ProjectMenuPopover({
|
||||
id: 'share-link',
|
||||
Element: 'button',
|
||||
children: 'Share current part (via Zoo link)',
|
||||
disabled: !(IS_NIGHTLY_OR_DEBUG && findCommand(shareCommandInfo)),
|
||||
disabled: !findCommand(shareCommandInfo),
|
||||
onClick: async () => {
|
||||
await copyFileShareLink({
|
||||
token: token ?? '',
|
||||
|
@ -95,7 +95,7 @@ export function applyConstraintEqualAngle({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
})
|
||||
if (err(transform)) return transform
|
||||
const { modifiedAst, pathToNodeMap } = transform
|
||||
|
@ -93,7 +93,7 @@ export function applyConstraintEqualLength({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
})
|
||||
if (err(transform)) return transform
|
||||
const { modifiedAst, pathToNodeMap } = transform
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, ProgramMemory, Expr } from '../../lang/wasm'
|
||||
import { Program, Expr, VariableMap } from '../../lang/wasm'
|
||||
import { getNodeFromPath } from '../../lang/queryAst'
|
||||
import {
|
||||
PathToNodeMap,
|
||||
@ -51,7 +51,7 @@ export function applyConstraintHorzVert(
|
||||
selectionRanges: Selections,
|
||||
horOrVert: 'vertical' | 'horizontal',
|
||||
ast: Node<Program>,
|
||||
programMemory: ProgramMemory
|
||||
memVars: VariableMap
|
||||
):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
@ -66,7 +66,7 @@ export function applyConstraintHorzVert(
|
||||
ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory,
|
||||
memVars,
|
||||
referenceSegName: '',
|
||||
})
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export function intersectInfo({
|
||||
isLinesParallelAndConstrained(
|
||||
kclManager.ast,
|
||||
engineCommandManager.artifactGraph,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
selectionRanges.graphSelections[0],
|
||||
selectionRanges.graphSelections[1]
|
||||
)
|
||||
@ -147,7 +147,7 @@ export async function applyConstraintIntersect({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges: forcedSelectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
})
|
||||
if (err(transform1)) return Promise.reject(transform1)
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
@ -184,7 +184,7 @@ export async function applyConstraintIntersect({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: forcedSelectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
forceSegName: segName,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
|
@ -88,7 +88,7 @@ export function applyRemoveConstrainingValues({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: updatedSelectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
})
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ export async function applyConstraintAbsDistance({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(transform1)) return Promise.reject(transform1)
|
||||
@ -124,7 +124,7 @@ export async function applyConstraintAbsDistance({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
@ -172,7 +172,7 @@ export function applyConstraintAxisAlign({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
|
@ -95,7 +95,7 @@ export async function applyConstraintAngleBetween({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
})
|
||||
if (err(transformed1)) return Promise.reject(transformed1)
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
@ -133,7 +133,7 @@ export async function applyConstraintAngleBetween({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
forceSegName: segName,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
|
@ -107,7 +107,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
})
|
||||
if (err(transformed)) return Promise.reject(transformed)
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
@ -145,7 +145,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
forceSegName: segName,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
@ -195,7 +195,7 @@ export function applyConstraintHorzVertAlign({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
if (err(retval)) return retval
|
||||
|
@ -105,7 +105,7 @@ export async function applyConstraintLength({
|
||||
ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: distanceExpression,
|
||||
})
|
||||
@ -137,7 +137,7 @@ export async function applyConstraintAngleLength({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(sketched)) return Promise.reject(sketched)
|
||||
@ -189,7 +189,7 @@ export async function applyConstraintAngleLength({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
|
@ -55,7 +55,7 @@ export function useConvertToVariable(range?: SourceRange) {
|
||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||
moveValueIntoNewVariable(
|
||||
ast,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
|
||||
variableName
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import { KCLError } from './errors'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
programMemory: kclManager?.programMemory,
|
||||
variables: kclManager?.variables,
|
||||
ast: kclManager?.ast,
|
||||
isExecuting: kclManager?.isExecuting,
|
||||
diagnostics: kclManager?.diagnostics,
|
||||
@ -31,7 +31,7 @@ export function KclContextProvider({
|
||||
// Both the code state and the editor state start off with the same code.
|
||||
const [code, setCode] = useState(loadedCode || codeManager.code)
|
||||
|
||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||
const [variables, setVariables] = useState(kclManager.variables)
|
||||
const [ast, setAst] = useState(kclManager.ast)
|
||||
const [isExecuting, setIsExecuting] = useState(false)
|
||||
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
|
||||
@ -44,7 +44,7 @@ export function KclContextProvider({
|
||||
setCode,
|
||||
})
|
||||
kclManager.registerCallBacks({
|
||||
setProgramMemory,
|
||||
setVariables,
|
||||
setAst,
|
||||
setLogs,
|
||||
setErrors,
|
||||
@ -58,7 +58,7 @@ export function KclContextProvider({
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
code,
|
||||
programMemory,
|
||||
variables,
|
||||
ast,
|
||||
isExecuting,
|
||||
diagnostics,
|
||||
|
@ -17,13 +17,14 @@ import {
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
initPromise,
|
||||
KclValue,
|
||||
parse,
|
||||
PathToNode,
|
||||
Program,
|
||||
ProgramMemory,
|
||||
recast,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
VariableMap,
|
||||
} from 'lang/wasm'
|
||||
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
|
||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||
@ -61,8 +62,8 @@ export class KclManager {
|
||||
trivia: [],
|
||||
}
|
||||
private _execState: ExecState = emptyExecState()
|
||||
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
||||
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
|
||||
private _variables: VariableMap = {}
|
||||
lastSuccessfulVariables: VariableMap = {}
|
||||
lastSuccessfulOperations: Operation[] = []
|
||||
private _logs: string[] = []
|
||||
private _errors: KCLError[] = []
|
||||
@ -78,7 +79,9 @@ export class KclManager {
|
||||
|
||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||
private _variablesCallBack: (arg: {
|
||||
[key in string]?: KclValue | undefined
|
||||
}) => void = () => {}
|
||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||
private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
|
||||
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
|
||||
@ -97,18 +100,18 @@ export class KclManager {
|
||||
this._switchedFiles = switchedFiles
|
||||
}
|
||||
|
||||
get programMemory() {
|
||||
return this._programMemory
|
||||
get variables() {
|
||||
return this._variables
|
||||
}
|
||||
// This is private because callers should be setting the entire execState.
|
||||
private set programMemory(programMemory) {
|
||||
this._programMemory = programMemory
|
||||
this._programMemoryCallBack(programMemory)
|
||||
private set variables(variables) {
|
||||
this._variables = variables
|
||||
this._variablesCallBack(variables)
|
||||
}
|
||||
|
||||
private set execState(execState) {
|
||||
this._execState = execState
|
||||
this.programMemory = execState.memory
|
||||
this.variables = execState.variables
|
||||
}
|
||||
|
||||
get execState() {
|
||||
@ -201,7 +204,7 @@ export class KclManager {
|
||||
}
|
||||
|
||||
registerCallBacks({
|
||||
setProgramMemory,
|
||||
setVariables,
|
||||
setAst,
|
||||
setLogs,
|
||||
setErrors,
|
||||
@ -209,7 +212,7 @@ export class KclManager {
|
||||
setIsExecuting,
|
||||
setWasmInitFailed,
|
||||
}: {
|
||||
setProgramMemory: (arg: ProgramMemory) => void
|
||||
setVariables: (arg: VariableMap) => void
|
||||
setAst: (arg: Node<Program>) => void
|
||||
setLogs: (arg: string[]) => void
|
||||
setErrors: (errors: KCLError[]) => void
|
||||
@ -217,7 +220,7 @@ export class KclManager {
|
||||
setIsExecuting: (arg: boolean) => void
|
||||
setWasmInitFailed: (arg: boolean) => void
|
||||
}) {
|
||||
this._programMemoryCallBack = setProgramMemory
|
||||
this._variablesCallBack = setVariables
|
||||
this._astCallBack = setAst
|
||||
this._logsCallBack = setLogs
|
||||
this._kclErrorsCallBack = setErrors
|
||||
@ -329,6 +332,7 @@ export class KclManager {
|
||||
ast,
|
||||
path: codeManager.currentFilePath || undefined,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
isMock: false,
|
||||
})
|
||||
|
||||
// Program was not interrupted, setup the scene
|
||||
@ -385,11 +389,11 @@ export class KclManager {
|
||||
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
||||
this.execState = execState
|
||||
if (!errors.length) {
|
||||
this.lastSuccessfulProgramMemory = execState.memory
|
||||
this.lastSuccessfulVariables = execState.variables
|
||||
this.lastSuccessfulOperations = execState.operations
|
||||
}
|
||||
this.ast = { ...ast }
|
||||
// updateArtifactGraph relies on updated executeState/programMemory
|
||||
// updateArtifactGraph relies on updated executeState/variables
|
||||
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
||||
this._executeCallback()
|
||||
if (!isInterrupted) {
|
||||
@ -442,16 +446,15 @@ export class KclManager {
|
||||
const { logs, errors, execState } = await executeAst({
|
||||
ast: newAst,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
isMock: true,
|
||||
})
|
||||
|
||||
this._logs = logs
|
||||
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||
this._execState = execState
|
||||
this._programMemory = execState.memory
|
||||
this._variables = execState.variables
|
||||
if (!errors.length) {
|
||||
this.lastSuccessfulProgramMemory = execState.memory
|
||||
this.lastSuccessfulVariables = execState.variables
|
||||
this.lastSuccessfulOperations = execState.operations
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { assertParse, initPromise, parse } from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing AST', () => {
|
||||
test('5 + 6', () => {
|
||||
const ast = assertParse('5 +6')
|
||||
delete (ast as any).nonCodeMeta
|
||||
expect(ast.body).toEqual([
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
start: 0,
|
||||
end: 4,
|
||||
|
||||
expression: {
|
||||
type: 'BinaryExpression',
|
||||
start: 0,
|
||||
end: 4,
|
||||
|
||||
left: {
|
||||
type: 'Literal',
|
||||
start: 0,
|
||||
end: 1,
|
||||
value: {
|
||||
suffix: 'None',
|
||||
value: 5,
|
||||
},
|
||||
raw: '5',
|
||||
},
|
||||
operator: '+',
|
||||
right: {
|
||||
type: 'Literal',
|
||||
start: 3,
|
||||
end: 4,
|
||||
value: {
|
||||
suffix: 'None',
|
||||
value: 6,
|
||||
},
|
||||
raw: '6',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('parsing errors', () => {
|
||||
it('should return an error when there is a unexpected closed curly brace', async () => {
|
||||
const code = `const myVar = startSketchAt([}], %)`
|
||||
const result = parse(code)
|
||||
if (err(result)) throw result
|
||||
const error = result.errors[0]
|
||||
expect(error.message).toBe('Array is missing a closing bracket(`]`)')
|
||||
expect(error.sourceRange).toEqual([28, 29, 0])
|
||||
})
|
||||
})
|
@ -15,8 +15,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
|> line(endAbsolute = [0.46, -5.82])
|
||||
// |> rx(45, %)`
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
// @ts-ignore
|
||||
const sketch001 = execState.memory.get('mySketch001')
|
||||
const sketch001 = execState.variables['mySketch001']
|
||||
expect(sketch001).toEqual({
|
||||
type: 'Sketch',
|
||||
value: {
|
||||
@ -73,8 +72,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
// |> rx(45, %)
|
||||
|> extrude(length = 2)`
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
// @ts-ignore
|
||||
const sketch001 = execState.memory.get('mySketch001')
|
||||
const sketch001 = execState.variables['mySketch001']
|
||||
expect(sketch001).toEqual({
|
||||
type: 'Solid',
|
||||
value: {
|
||||
@ -165,9 +163,9 @@ const sk2 = startSketchOn('XY')
|
||||
|
||||
`
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
const programMemory = execState.memory
|
||||
const variables = execState.variables
|
||||
// @ts-ignore
|
||||
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
|
||||
const geos = [variables['theExtrude'], variables['sk2']]
|
||||
expect(geos).toEqual([
|
||||
{
|
||||
type: 'Solid',
|
||||
|
@ -2,12 +2,12 @@ import fs from 'node:fs'
|
||||
|
||||
import {
|
||||
assertParse,
|
||||
ProgramMemory,
|
||||
Sketch,
|
||||
initPromise,
|
||||
sketchFromKclValue,
|
||||
defaultArtifactGraph,
|
||||
topLevelRange,
|
||||
VariableMap,
|
||||
} from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { KCLError } from './errors'
|
||||
@ -21,13 +21,13 @@ describe('test executor', () => {
|
||||
const code = `const myVar = 5
|
||||
const newVar = myVar + 1`
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(5)
|
||||
expect(mem.get('newVar')?.value).toBe(6)
|
||||
expect(mem['myVar']?.value).toBe(5)
|
||||
expect(mem['newVar']?.value).toBe(6)
|
||||
})
|
||||
it('test assigning a var with a string', async () => {
|
||||
const code = `const myVar = "a str"`
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe('a str')
|
||||
expect(mem['myVar']?.value).toBe('a str')
|
||||
})
|
||||
it('test assigning a var by cont concatenating two strings string execute', async () => {
|
||||
const code = fs.readFileSync(
|
||||
@ -35,7 +35,7 @@ const newVar = myVar + 1`
|
||||
'utf-8'
|
||||
)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe('a str another str')
|
||||
expect(mem['myVar']?.value).toBe('a str another str')
|
||||
})
|
||||
it('fn funcN = () => {} execute', async () => {
|
||||
const mem = await exe(
|
||||
@ -47,8 +47,8 @@ const newVar = myVar + 1`
|
||||
'const magicNum = funcN(9, theVar)',
|
||||
].join('\n')
|
||||
)
|
||||
expect(mem.get('theVar')?.value).toBe(60)
|
||||
expect(mem.get('magicNum')?.value).toBe(69)
|
||||
expect(mem['theVar']?.value).toBe(60)
|
||||
expect(mem['magicNum']?.value).toBe(69)
|
||||
})
|
||||
it('sketch declaration', async () => {
|
||||
let code = `const mySketch = startSketchOn('XY')
|
||||
@ -60,7 +60,7 @@ const newVar = myVar + 1`
|
||||
`
|
||||
const mem = await exe(code)
|
||||
// geo is three js buffer geometry and is very bloated to have in tests
|
||||
const sk = mem.get('mySketch')
|
||||
const sk = mem['mySketch']
|
||||
expect(sk?.type).toEqual('Sketch')
|
||||
if (sk?.type !== 'Sketch') {
|
||||
return
|
||||
@ -117,7 +117,7 @@ const newVar = myVar + 1`
|
||||
'const myVar = 5 + 1 |> myFn(%)',
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(7)
|
||||
expect(mem['myVar']?.value).toBe(7)
|
||||
})
|
||||
|
||||
// Enable rotations #152
|
||||
@ -130,15 +130,15 @@ const newVar = myVar + 1`
|
||||
// 'const rotated = rx(90, mySk1)',
|
||||
// ].join('\n')
|
||||
// const mem = await exe(code)
|
||||
// expect(mem.get('mySk1')?.value).toHaveLength(3)
|
||||
// expect(mem.get('rotated')?.type).toBe('Sketch')
|
||||
// expect(mem['mySk1']?.value).toHaveLength(3)
|
||||
// expect(mem['rotated')?.type).toBe('Sketch']
|
||||
// if (
|
||||
// mem.get('mySk1')?.type !== 'Sketch' ||
|
||||
// mem.get('rotated')?.type !== 'Sketch'
|
||||
// mem['mySk1']?.type !== 'Sketch' ||
|
||||
// mem['rotated']?.type !== 'Sketch'
|
||||
// )
|
||||
// throw new Error('not a sketch')
|
||||
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1])
|
||||
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([
|
||||
// expect(mem['mySk1']?.rotation).toEqual([0, 0, 0, 1])
|
||||
// expect(mem['rotated']?.rotation.map((a) => a.toFixed(4))).toEqual([
|
||||
// '0.7071',
|
||||
// '0.0000',
|
||||
// '0.0000',
|
||||
@ -157,7 +157,7 @@ const newVar = myVar + 1`
|
||||
// ' |> rx(90, %)',
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('mySk1')).toEqual({
|
||||
expect(mem['mySk1']).toEqual({
|
||||
type: 'Sketch',
|
||||
value: {
|
||||
type: 'Sketch',
|
||||
@ -236,7 +236,7 @@ const newVar = myVar + 1`
|
||||
)
|
||||
const mem = await exe(code)
|
||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||
expect(mem.get('three')).toEqual({
|
||||
expect(mem['three']).toEqual({
|
||||
type: 'Number',
|
||||
value: 3,
|
||||
__meta: [
|
||||
@ -245,7 +245,7 @@ const newVar = myVar + 1`
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(mem.get('yo')).toEqual({
|
||||
expect(mem['yo']).toEqual({
|
||||
type: 'Array',
|
||||
value: [
|
||||
{ type: 'Number', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
|
||||
@ -263,9 +263,6 @@ const newVar = myVar + 1`
|
||||
},
|
||||
],
|
||||
})
|
||||
// Check that there are no other variables or environments.
|
||||
expect(mem.numEnvironments()).toBe(1)
|
||||
expect(mem.numVariables(0)).toBe(2)
|
||||
})
|
||||
it('execute object expression', async () => {
|
||||
const code = [
|
||||
@ -273,7 +270,7 @@ const newVar = myVar + 1`
|
||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('yo')).toEqual({
|
||||
expect(mem['yo']).toEqual({
|
||||
type: 'Object',
|
||||
value: {
|
||||
aStr: {
|
||||
@ -309,7 +306,7 @@ const newVar = myVar + 1`
|
||||
'\n'
|
||||
)
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')).toEqual({
|
||||
expect(mem['myVar']).toEqual({
|
||||
type: 'String',
|
||||
value: '123',
|
||||
__meta: [
|
||||
@ -325,80 +322,80 @@ describe('testing math operators', () => {
|
||||
it('can sum', async () => {
|
||||
const code = ['const myVar = 1 + 2'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(3)
|
||||
expect(mem['myVar']?.value).toBe(3)
|
||||
})
|
||||
it('can subtract', async () => {
|
||||
const code = ['const myVar = 1 - 2'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(-1)
|
||||
expect(mem['myVar']?.value).toBe(-1)
|
||||
})
|
||||
it('can multiply', async () => {
|
||||
const code = ['const myVar = 1 * 2'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(2)
|
||||
expect(mem['myVar']?.value).toBe(2)
|
||||
})
|
||||
it('can divide', async () => {
|
||||
const code = ['const myVar = 1 / 2'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(0.5)
|
||||
expect(mem['myVar']?.value).toBe(0.5)
|
||||
})
|
||||
it('can modulus', async () => {
|
||||
const code = ['const myVar = 5 % 2'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(1)
|
||||
expect(mem['myVar']?.value).toBe(1)
|
||||
})
|
||||
it('can do multiple operations', async () => {
|
||||
const code = ['const myVar = 1 + 2 * 3'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(7)
|
||||
expect(mem['myVar']?.value).toBe(7)
|
||||
})
|
||||
it('big example with parans', async () => {
|
||||
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(7.4)
|
||||
expect(mem['myVar']?.value).toBe(7.4)
|
||||
})
|
||||
it('with identifier', async () => {
|
||||
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(3)
|
||||
expect(mem['myVar']?.value).toBe(3)
|
||||
})
|
||||
it('with lots of testing', async () => {
|
||||
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(12.5)
|
||||
expect(mem['myVar']?.value).toBe(12.5)
|
||||
})
|
||||
it('with callExpression at start', async () => {
|
||||
const code = 'const myVar = min(4, 100) + 2'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(6)
|
||||
expect(mem['myVar']?.value).toBe(6)
|
||||
})
|
||||
it('with callExpression at end', async () => {
|
||||
const code = 'const myVar = 2 + min(4, 100)'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(6)
|
||||
expect(mem['myVar']?.value).toBe(6)
|
||||
})
|
||||
it('with nested callExpression', async () => {
|
||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(6)
|
||||
expect(mem['myVar']?.value).toBe(6)
|
||||
})
|
||||
it('with unaryExpression', async () => {
|
||||
const code = 'const myVar = -min(100, 3)'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(-3)
|
||||
expect(mem['myVar']?.value).toBe(-3)
|
||||
})
|
||||
it('with unaryExpression in callExpression', async () => {
|
||||
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
||||
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
||||
const mem = await exe(code)
|
||||
const mem2 = await exe(code2)
|
||||
expect(mem.get('myVar')?.value).toBe(-3)
|
||||
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value)
|
||||
expect(mem['myVar']?.value).toBe(-3)
|
||||
expect(mem['myVar']?.value).toBe(mem2['myVar']?.value)
|
||||
})
|
||||
it('with unaryExpression in ArrayExpression', async () => {
|
||||
const code = 'const myVar = [1,-legLen(5, 4)]'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toEqual([
|
||||
expect(mem['myVar']?.value).toEqual([
|
||||
{
|
||||
__meta: [
|
||||
{
|
||||
@ -426,7 +423,7 @@ describe('testing math operators', () => {
|
||||
'|> line(end = [-2.21, -legLen(5, min(3, 999))])',
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
||||
const sketch = sketchFromKclValue(mem['part001'], 'part001')
|
||||
// result of `-legLen(5, min(3, 999))` should be -4
|
||||
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
|
||||
expect(yVal).toBe(-4)
|
||||
@ -444,7 +441,7 @@ describe('testing math operators', () => {
|
||||
``,
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
||||
const sketch = sketchFromKclValue(mem['part001'], 'part001')
|
||||
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
||||
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
|
||||
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
|
||||
@ -454,7 +451,7 @@ describe('testing math operators', () => {
|
||||
)
|
||||
const removedUnaryExpMem = await exe(removedUnaryExp)
|
||||
const removedUnaryExpMemSketch = sketchFromKclValue(
|
||||
removedUnaryExpMem.get('part001'),
|
||||
removedUnaryExpMem['part001'],
|
||||
'part001'
|
||||
)
|
||||
|
||||
@ -464,12 +461,12 @@ describe('testing math operators', () => {
|
||||
it('with nested callExpression and binaryExpression', async () => {
|
||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(5)
|
||||
expect(mem['myVar']?.value).toBe(5)
|
||||
})
|
||||
it('can do power of math', async () => {
|
||||
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myNeg2')?.value).toBe(-2)
|
||||
expect(mem['myNeg2']?.value).toBe(-2)
|
||||
})
|
||||
})
|
||||
|
||||
@ -498,12 +495,9 @@ const theExtrude = startSketchOn('XY')
|
||||
|
||||
// helpers
|
||||
|
||||
async function exe(
|
||||
code: string,
|
||||
programMemory: ProgramMemory = ProgramMemory.empty()
|
||||
) {
|
||||
async function exe(code: string, variables: VariableMap = {}) {
|
||||
const ast = assertParse(code)
|
||||
|
||||
const execState = await enginelessExecutor(ast, programMemory)
|
||||
return execState.memory
|
||||
const execState = await enginelessExecutor(ast, true, undefined, variables)
|
||||
return execState.variables
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { assertParse, initPromise, programMemoryInit } from './wasm'
|
||||
import { assertParse, initPromise } from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
|
||||
import path from 'node:path'
|
||||
@ -32,7 +32,7 @@ child_process.spawnSync('git', [
|
||||
'clone',
|
||||
'--single-branch',
|
||||
'--branch',
|
||||
'achalmers/kw-appearance',
|
||||
'achalmers/kw-pattern-transform2',
|
||||
URL_GIT_KCL_SAMPLES,
|
||||
DIR_KCL_SAMPLES,
|
||||
])
|
||||
@ -72,7 +72,7 @@ describe('Test KCL Samples from public Github repository', () => {
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(
|
||||
ast,
|
||||
programMemoryInit(),
|
||||
false,
|
||||
file.pathFromProjectDirectoryToFirstFile
|
||||
)
|
||||
},
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {
|
||||
Program,
|
||||
executor,
|
||||
ProgramMemory,
|
||||
executeWithEngine,
|
||||
executeMock,
|
||||
kclLint,
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
VariableMap,
|
||||
} from 'lang/wasm'
|
||||
import { enginelessExecutor } from 'lib/testHelpers'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { KCLError } from 'lang/errors'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
@ -48,14 +48,16 @@ export async function executeAst({
|
||||
ast,
|
||||
path,
|
||||
engineCommandManager,
|
||||
// If you set programMemoryOverride we assume you mean mock mode. Since that
|
||||
// is the only way to go about it.
|
||||
programMemoryOverride,
|
||||
isMock,
|
||||
usePrevMemory,
|
||||
variables,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
path?: string
|
||||
engineCommandManager: EngineCommandManager
|
||||
programMemoryOverride?: ProgramMemory
|
||||
isMock: boolean
|
||||
usePrevMemory?: boolean
|
||||
variables?: VariableMap
|
||||
isInterrupted?: boolean
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
@ -64,9 +66,9 @@ export async function executeAst({
|
||||
isInterrupted: boolean
|
||||
}> {
|
||||
try {
|
||||
const execState = await (programMemoryOverride
|
||||
? enginelessExecutor(ast, programMemoryOverride, path)
|
||||
: executor(ast, engineCommandManager, path))
|
||||
const execState = await (isMock
|
||||
? executeMock(ast, usePrevMemory, path, variables)
|
||||
: executeWithEngine(ast, engineCommandManager, path))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
|
@ -315,7 +315,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const startIndex = code.indexOf('100 + 100') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
@ -329,7 +329,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const startIndex = code.indexOf('2.8') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
@ -343,7 +343,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const startIndex = code.indexOf('def(')
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
@ -357,7 +357,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const startIndex = code.indexOf('jkl(') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
@ -371,7 +371,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const startIndex = code.indexOf('identifierGuy +') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
@ -557,7 +557,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||
[],
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
code,
|
||||
pathToNode
|
||||
)
|
||||
@ -639,7 +639,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||
dependentSegments,
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
code,
|
||||
pathToNode
|
||||
)
|
||||
@ -745,7 +745,7 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
pathToNode,
|
||||
argPosition,
|
||||
ast,
|
||||
execState.memory
|
||||
execState.variables
|
||||
)
|
||||
if (!mod) return new Error('mod is undefined')
|
||||
const recastCode = recast(mod.modifiedAst)
|
||||
@ -794,7 +794,7 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
pathToNode,
|
||||
argPosition,
|
||||
ast,
|
||||
execState.memory
|
||||
execState.variables
|
||||
)
|
||||
if (!mod) return new Error('mod is undefined')
|
||||
const recastCode = recast(mod.modifiedAst)
|
||||
@ -978,7 +978,7 @@ sketch002 = startSketchOn({
|
||||
codeRef: codeRefFromRange(range, ast),
|
||||
artifact,
|
||||
},
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
return {
|
||||
|
@ -18,11 +18,11 @@ import {
|
||||
UnaryExpression,
|
||||
BinaryExpression,
|
||||
PathToNode,
|
||||
ProgramMemory,
|
||||
SourceRange,
|
||||
sketchFromKclValue,
|
||||
isPathToNodeNumber,
|
||||
formatNumber,
|
||||
VariableMap,
|
||||
} from './wasm'
|
||||
import {
|
||||
isNodeSafeToReplacePath,
|
||||
@ -1218,7 +1218,7 @@ export function replaceValueAtNodePath({
|
||||
|
||||
export function moveValueIntoNewVariablePath(
|
||||
ast: Node<Program>,
|
||||
programMemory: ProgramMemory,
|
||||
memVars: VariableMap,
|
||||
pathToNode: PathToNode,
|
||||
variableName: string
|
||||
): {
|
||||
@ -1231,11 +1231,7 @@ export function moveValueIntoNewVariablePath(
|
||||
|
||||
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
||||
|
||||
const { insertIndex } = findAllPreviousVariablesPath(
|
||||
ast,
|
||||
programMemory,
|
||||
pathToNode
|
||||
)
|
||||
const { insertIndex } = findAllPreviousVariablesPath(ast, memVars, pathToNode)
|
||||
let _node = structuredClone(ast)
|
||||
const boop = replacer(_node, variableName)
|
||||
if (trap(boop)) return { modifiedAst: ast }
|
||||
@ -1251,7 +1247,7 @@ export function moveValueIntoNewVariablePath(
|
||||
|
||||
export function moveValueIntoNewVariable(
|
||||
ast: Node<Program>,
|
||||
programMemory: ProgramMemory,
|
||||
memVars: VariableMap,
|
||||
sourceRange: SourceRange,
|
||||
variableName: string
|
||||
): {
|
||||
@ -1263,11 +1259,7 @@ export function moveValueIntoNewVariable(
|
||||
const { isSafe, value, replacer } = meta
|
||||
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
||||
|
||||
const { insertIndex } = findAllPreviousVariables(
|
||||
ast,
|
||||
programMemory,
|
||||
sourceRange
|
||||
)
|
||||
const { insertIndex } = findAllPreviousVariables(ast, memVars, sourceRange)
|
||||
let _node = structuredClone(ast)
|
||||
const replaced = replacer(_node, variableName)
|
||||
if (trap(replaced)) return { modifiedAst: ast }
|
||||
@ -1289,7 +1281,7 @@ export function moveValueIntoNewVariable(
|
||||
export function deleteSegmentFromPipeExpression(
|
||||
dependentRanges: SourceRange[],
|
||||
modifiedAst: Node<Program>,
|
||||
programMemory: ProgramMemory,
|
||||
memVars: VariableMap,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
): Node<Program> | Error {
|
||||
@ -1321,7 +1313,7 @@ export function deleteSegmentFromPipeExpression(
|
||||
callExp.shallowPath,
|
||||
constraintInfo.argPosition,
|
||||
_modifiedAst,
|
||||
programMemory
|
||||
memVars
|
||||
)
|
||||
if (!transform) return
|
||||
_modifiedAst = transform.modifiedAst
|
||||
@ -1350,7 +1342,7 @@ export function removeSingleConstraintInfo(
|
||||
pathToCallExp: PathToNode,
|
||||
argDetails: SimplifiedArgDetails,
|
||||
ast: Node<Program>,
|
||||
programMemory: ProgramMemory
|
||||
memVars: VariableMap
|
||||
):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
@ -1367,7 +1359,7 @@ export function removeSingleConstraintInfo(
|
||||
ast,
|
||||
selectionRanges: [pathToCallExp],
|
||||
transformInfos: [transform],
|
||||
programMemory,
|
||||
memVars,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(retval)) return false
|
||||
@ -1377,7 +1369,7 @@ export function removeSingleConstraintInfo(
|
||||
export async function deleteFromSelection(
|
||||
ast: Node<Program>,
|
||||
selection: Selection,
|
||||
programMemory: ProgramMemory,
|
||||
variables: VariableMap,
|
||||
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
|
||||
({} as any)
|
||||
): Promise<Node<Program> | Error> {
|
||||
@ -1506,7 +1498,7 @@ export async function deleteFromSelection(
|
||||
return
|
||||
}
|
||||
const sketchToPreserve = sketchFromKclValue(
|
||||
programMemory.get(sketchName),
|
||||
variables[sketchName],
|
||||
sketchName
|
||||
)
|
||||
if (err(sketchToPreserve)) return sketchToPreserve
|
||||
|
@ -298,7 +298,7 @@ export function getPathToExtrudeForSegmentSelection(
|
||||
const sketchVar = varDecNode.node.declaration.id.name
|
||||
|
||||
const sketch = sketchFromKclValue(
|
||||
dependencies.kclManager.programMemory.get(sketchVar),
|
||||
dependencies.kclManager.variables[sketchVar],
|
||||
sketchVar
|
||||
)
|
||||
if (trap(sketch)) return sketch
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
CallExpression,
|
||||
VariableDeclarator,
|
||||
} from './wasm'
|
||||
import { ProgramMemory } from 'lang/wasm'
|
||||
import {
|
||||
findAllPreviousVariables,
|
||||
isNodeSafeToReplace,
|
||||
@ -63,7 +62,7 @@ variableBelowShouldNotBeIncluded = 3
|
||||
|
||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
expect(variables).toEqual([
|
||||
@ -398,7 +397,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
selection: {
|
||||
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
})
|
||||
expect(result).toEqual(true)
|
||||
})
|
||||
@ -418,7 +417,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
selection: {
|
||||
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
})
|
||||
expect(result).toEqual(true)
|
||||
})
|
||||
@ -432,7 +431,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
selection: {
|
||||
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
})
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
@ -722,7 +721,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
|
||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||
const modifiedAst = addCallExpressionsToPipe({
|
||||
node: ast,
|
||||
programMemory: ProgramMemory.empty(),
|
||||
variables: {},
|
||||
pathToNode: sketchPathToNode,
|
||||
expressions: [
|
||||
createCallExpressionStdLib(
|
||||
@ -777,7 +776,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
|
||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||
const modifiedAst = addCloseToPipe({
|
||||
node: ast,
|
||||
programMemory: ProgramMemory.empty(),
|
||||
variables: {},
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
PathToNode,
|
||||
PipeExpression,
|
||||
Program,
|
||||
ProgramMemory,
|
||||
ReturnStatement,
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
@ -26,6 +25,7 @@ import {
|
||||
kclSettings,
|
||||
unitLenToUnitLength,
|
||||
unitAngToUnitAngle,
|
||||
VariableMap,
|
||||
} from './wasm'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||
@ -287,7 +287,7 @@ export interface PrevVariable<T> {
|
||||
|
||||
export function findAllPreviousVariablesPath(
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory,
|
||||
memVars: VariableMap,
|
||||
path: PathToNode,
|
||||
type: 'number' | 'string' = 'number'
|
||||
): {
|
||||
@ -325,7 +325,7 @@ export function findAllPreviousVariablesPath(
|
||||
bodyItems?.forEach?.((item) => {
|
||||
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
||||
const varName = item.declaration.id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
const varValue = memVars[varName]
|
||||
if (!varValue || typeof varValue?.value !== type) return
|
||||
variables.push({
|
||||
key: varName,
|
||||
@ -342,7 +342,7 @@ export function findAllPreviousVariablesPath(
|
||||
|
||||
export function findAllPreviousVariables(
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory,
|
||||
memVars: VariableMap,
|
||||
sourceRange: SourceRange,
|
||||
type: 'number' | 'string' = 'number'
|
||||
): {
|
||||
@ -351,7 +351,7 @@ export function findAllPreviousVariables(
|
||||
insertIndex: number
|
||||
} {
|
||||
const path = getNodePathFromSourceRange(ast, sourceRange)
|
||||
return findAllPreviousVariablesPath(ast, programMemory, path, type)
|
||||
return findAllPreviousVariablesPath(ast, memVars, path, type)
|
||||
}
|
||||
|
||||
type ReplacerFn = (
|
||||
@ -479,7 +479,7 @@ function isTypeInArrayExp(
|
||||
export function isLinesParallelAndConstrained(
|
||||
ast: Program,
|
||||
artifactGraph: ArtifactGraph,
|
||||
programMemory: ProgramMemory,
|
||||
memVars: VariableMap,
|
||||
primaryLine: Selection,
|
||||
secondaryLine: Selection
|
||||
):
|
||||
@ -509,7 +509,7 @@ export function isLinesParallelAndConstrained(
|
||||
if (err(_varDec)) return _varDec
|
||||
const varDec = _varDec.node
|
||||
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
|
||||
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
|
||||
const sg = sketchFromKclValue(memVars[varName], varName)
|
||||
if (err(sg)) return sg
|
||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||
sg,
|
||||
@ -589,11 +589,11 @@ export function isLinesParallelAndConstrained(
|
||||
export function hasExtrudeSketch({
|
||||
ast,
|
||||
selection,
|
||||
programMemory,
|
||||
memVars,
|
||||
}: {
|
||||
ast: Program
|
||||
selection: Selection
|
||||
programMemory: ProgramMemory
|
||||
memVars: VariableMap
|
||||
}): boolean {
|
||||
const varDecMeta = getNodeFromPath<VariableDeclaration>(
|
||||
ast,
|
||||
@ -607,7 +607,7 @@ export function hasExtrudeSketch({
|
||||
const varDec = varDecMeta.node
|
||||
if (varDec.type !== 'VariableDeclaration') return false
|
||||
const varName = varDec.declaration.id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
const varValue = memVars[varName]
|
||||
return (
|
||||
varValue?.type === 'Solid' ||
|
||||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
|
||||
|
@ -124,7 +124,7 @@ describe('testing changeSketchArguments', () => {
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
const changeSketchArgsRetVal = changeSketchArguments(
|
||||
ast,
|
||||
execState.memory,
|
||||
execState.variables,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: topLevelRange(
|
||||
@ -160,7 +160,7 @@ mySketch001 = startSketchOn('XY')
|
||||
expect(sourceStart).toBe(89)
|
||||
const newSketchLnRetVal = addNewSketchLn({
|
||||
node: ast,
|
||||
programMemory: execState.memory,
|
||||
variables: execState.variables,
|
||||
input: {
|
||||
type: 'straight-segment',
|
||||
from: [0, 0],
|
||||
@ -190,7 +190,7 @@ mySketch001 = startSketchOn('XY')
|
||||
|
||||
const modifiedAst2 = addCloseToPipe({
|
||||
node: ast,
|
||||
programMemory: execState.memory,
|
||||
variables: execState.variables,
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
Sketch,
|
||||
SourceRange,
|
||||
@ -14,6 +13,7 @@ import {
|
||||
Identifier,
|
||||
sketchFromKclValue,
|
||||
topLevelRange,
|
||||
VariableMap,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
@ -355,7 +355,7 @@ function getTagKwArg(): SketchLineHelperKw['getTag'] {
|
||||
export const line: SketchLineHelperKw = {
|
||||
add: ({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
variables,
|
||||
pathToNode,
|
||||
segmentInput,
|
||||
replaceExistingCallback,
|
||||
@ -494,7 +494,7 @@ export const line: SketchLineHelperKw = {
|
||||
export const lineTo: SketchLineHelperKw = {
|
||||
add: ({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
variables,
|
||||
pathToNode,
|
||||
segmentInput,
|
||||
replaceExistingCallback,
|
||||
@ -1313,7 +1313,7 @@ export const angledLine: SketchLineHelper = {
|
||||
export const angledLineOfXLength: SketchLineHelper = {
|
||||
add: ({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
variables,
|
||||
pathToNode,
|
||||
segmentInput,
|
||||
replaceExistingCallback,
|
||||
@ -1337,10 +1337,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
const { node: varDec } = nodeMeta2
|
||||
|
||||
const variableName = varDec.id.name
|
||||
const sketch = sketchFromKclValue(
|
||||
previousProgramMemory?.get(variableName),
|
||||
variableName
|
||||
)
|
||||
const sketch = sketchFromKclValue(variables[variableName], variableName)
|
||||
if (err(sketch)) {
|
||||
return sketch
|
||||
}
|
||||
@ -1429,7 +1426,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
export const angledLineOfYLength: SketchLineHelper = {
|
||||
add: ({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
variables,
|
||||
pathToNode,
|
||||
segmentInput,
|
||||
replaceExistingCallback,
|
||||
@ -1452,10 +1449,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
const { node: varDec } = nodeMeta2
|
||||
const variableName = varDec.id.name
|
||||
const sketch = sketchFromKclValue(
|
||||
previousProgramMemory?.get(variableName),
|
||||
variableName
|
||||
)
|
||||
const sketch = sketchFromKclValue(variables[variableName], variableName)
|
||||
if (err(sketch)) return sketch
|
||||
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
@ -1793,7 +1787,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
}
|
||||
return new Error('not implemented')
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => {
|
||||
updateArgs: ({ node, pathToNode, input, variables }) => {
|
||||
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const { to, from } = input
|
||||
const _node = { ...node }
|
||||
@ -1820,10 +1814,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
|
||||
const { node: varDec } = nodeMeta2
|
||||
const varName = varDec.declaration.id.name
|
||||
const sketch = sketchFromKclValue(
|
||||
previousProgramMemory.get(varName),
|
||||
varName
|
||||
)
|
||||
const sketch = sketchFromKclValue(variables[varName], varName)
|
||||
if (err(sketch)) return sketch
|
||||
const intersectPath = sketch.paths.find(
|
||||
({ tag }: Path) => tag && tag.value === intersectTagName
|
||||
@ -1996,7 +1987,7 @@ export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
||||
|
||||
export function changeSketchArguments(
|
||||
node: Node<Program>,
|
||||
programMemory: ProgramMemory,
|
||||
variables: VariableMap,
|
||||
sourceRangeOrPath:
|
||||
| {
|
||||
type: 'sourceRange'
|
||||
@ -2030,7 +2021,7 @@ export function changeSketchArguments(
|
||||
|
||||
return updateArgs({
|
||||
node: _node,
|
||||
previousProgramMemory: programMemory,
|
||||
variables,
|
||||
pathToNode: shallowPath,
|
||||
input,
|
||||
})
|
||||
@ -2047,7 +2038,7 @@ export function changeSketchArguments(
|
||||
|
||||
return updateArgs({
|
||||
node: _node,
|
||||
previousProgramMemory: programMemory,
|
||||
variables,
|
||||
pathToNode: shallowPath,
|
||||
input,
|
||||
})
|
||||
@ -2112,7 +2103,7 @@ export function compareVec2Epsilon2(
|
||||
|
||||
interface CreateLineFnCallArgs {
|
||||
node: Node<Program>
|
||||
programMemory: ProgramMemory
|
||||
variables: VariableMap
|
||||
input: SegmentInputs
|
||||
fnName: ToolTip
|
||||
pathToNode: PathToNode
|
||||
@ -2121,7 +2112,7 @@ interface CreateLineFnCallArgs {
|
||||
|
||||
export function addNewSketchLn({
|
||||
node: _node,
|
||||
programMemory: previousProgramMemory,
|
||||
variables,
|
||||
fnName,
|
||||
pathToNode,
|
||||
input: segmentInput,
|
||||
@ -2151,7 +2142,7 @@ export function addNewSketchLn({
|
||||
)
|
||||
return add({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
variables,
|
||||
pathToNode,
|
||||
segmentInput,
|
||||
spliceBetween,
|
||||
@ -2164,7 +2155,7 @@ export function addCallExpressionsToPipe({
|
||||
expressions,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
programMemory: ProgramMemory
|
||||
variables: VariableMap
|
||||
pathToNode: PathToNode
|
||||
expressions: Node<CallExpression | CallExpressionKw>[]
|
||||
}) {
|
||||
@ -2188,7 +2179,7 @@ export function addCloseToPipe({
|
||||
pathToNode,
|
||||
}: {
|
||||
node: Program
|
||||
programMemory: ProgramMemory
|
||||
variables: VariableMap
|
||||
pathToNode: PathToNode
|
||||
}) {
|
||||
const _node = { ...node }
|
||||
@ -2209,7 +2200,7 @@ export function addCloseToPipe({
|
||||
|
||||
export function replaceSketchLine({
|
||||
node,
|
||||
programMemory,
|
||||
variables,
|
||||
pathToNode: _pathToNode,
|
||||
fnName,
|
||||
segmentInput,
|
||||
@ -2217,7 +2208,7 @@ export function replaceSketchLine({
|
||||
referencedSegment,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
programMemory: ProgramMemory
|
||||
variables: VariableMap
|
||||
pathToNode: PathToNode
|
||||
fnName: ToolTip
|
||||
segmentInput: SegmentInputs
|
||||
@ -2241,7 +2232,7 @@ export function replaceSketchLine({
|
||||
: sketchLineHelperMap[fnName]
|
||||
const addRetVal = add({
|
||||
node: _node,
|
||||
previousProgramMemory: programMemory,
|
||||
variables,
|
||||
pathToNode: _pathToNode,
|
||||
referencedSegment,
|
||||
segmentInput,
|
||||
|
@ -53,7 +53,7 @@ async function testingSwapSketchFnCall({
|
||||
return Promise.reject(new Error('transformInfos undefined'))
|
||||
const ast2 = transformAstSketchLines({
|
||||
ast,
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
selectionRanges: selections,
|
||||
transformInfos,
|
||||
referenceSegName: '',
|
||||
@ -373,7 +373,7 @@ part001 = startSketchOn('XY')
|
||||
const execState = await enginelessExecutor(assertParse(code))
|
||||
const index = code.indexOf('// normal-segment') - 7
|
||||
const sg = sketchFromKclValue(
|
||||
execState.memory.get('part001'),
|
||||
execState.variables['part001'],
|
||||
'part001'
|
||||
) as Sketch
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
@ -393,7 +393,7 @@ part001 = startSketchOn('XY')
|
||||
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,
|
||||
sketchFromKclValue(execState.variables['part001'], 'part001') as Sketch,
|
||||
topLevelRange(index, index)
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
|
@ -193,7 +193,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
||||
ast,
|
||||
selectionRanges: transformedSelection,
|
||||
transformInfos,
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
|
||||
@ -356,7 +356,7 @@ part001 = startSketchOn('XY')
|
||||
ast,
|
||||
selectionRanges: makeSelections(selectionRanges),
|
||||
transformInfos,
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
|
||||
@ -445,7 +445,7 @@ part001 = startSketchOn('XY')
|
||||
ast,
|
||||
selectionRanges: makeSelections(selectionRanges),
|
||||
transformInfos,
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
@ -505,7 +505,7 @@ part001 = startSketchOn('XY')
|
||||
ast,
|
||||
selectionRanges: makeSelections(selectionRanges),
|
||||
transformInfos,
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
@ -600,7 +600,7 @@ async function helperThing(
|
||||
ast,
|
||||
selectionRanges: makeSelections(selectionRanges),
|
||||
transformInfos,
|
||||
programMemory: execState.memory,
|
||||
memVars: execState.variables,
|
||||
})
|
||||
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
|
@ -17,13 +17,13 @@ import {
|
||||
BinaryPart,
|
||||
VariableDeclarator,
|
||||
PathToNode,
|
||||
ProgramMemory,
|
||||
sketchFromKclValue,
|
||||
Literal,
|
||||
SourceRange,
|
||||
LiteralValue,
|
||||
recast,
|
||||
LabeledArg,
|
||||
VariableMap,
|
||||
} from '../wasm'
|
||||
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
@ -1745,14 +1745,14 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory,
|
||||
memVars,
|
||||
forceSegName,
|
||||
forceValueUsedInTransform,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
selectionRanges: Selections
|
||||
transformInfos: TransformInfo[]
|
||||
programMemory: ProgramMemory
|
||||
memVars: VariableMap
|
||||
forceSegName?: string
|
||||
forceValueUsedInTransform?: BinaryPart
|
||||
}):
|
||||
@ -1788,7 +1788,7 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
},
|
||||
referencedSegmentRange: primarySelection,
|
||||
transformInfos,
|
||||
programMemory,
|
||||
memVars,
|
||||
referenceSegName: tag,
|
||||
forceValueUsedInTransform,
|
||||
})
|
||||
@ -1822,7 +1822,7 @@ export function transformAstSketchLines({
|
||||
ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory,
|
||||
memVars,
|
||||
referenceSegName,
|
||||
forceValueUsedInTransform,
|
||||
referencedSegmentRange,
|
||||
@ -1830,7 +1830,7 @@ export function transformAstSketchLines({
|
||||
ast: Node<Program>
|
||||
selectionRanges: Selections | PathToNode[]
|
||||
transformInfos: TransformInfo[]
|
||||
programMemory: ProgramMemory
|
||||
memVars: VariableMap
|
||||
referenceSegName: string
|
||||
referencedSegmentRange?: SourceRange
|
||||
forceValueUsedInTransform?: BinaryPart
|
||||
@ -1946,7 +1946,7 @@ export function transformAstSketchLines({
|
||||
})
|
||||
|
||||
const varName = varDec.node.id.name
|
||||
let kclVal = programMemory.get(varName)
|
||||
let kclVal = memVars[varName]
|
||||
let sketch
|
||||
if (kclVal?.type === 'Solid') {
|
||||
sketch = kclVal.value.sketch
|
||||
@ -1977,7 +1977,7 @@ export function transformAstSketchLines({
|
||||
// Note to ADAM: Here is where the replaceExisting call gets sent.
|
||||
const replacedSketchLine = replaceSketchLine({
|
||||
node: node,
|
||||
programMemory,
|
||||
variables: memVars,
|
||||
pathToNode: _pathToNode,
|
||||
referencedSegment,
|
||||
fnName: transformTo || (call.node.callee.name as ToolTip),
|
||||
|
@ -18,8 +18,8 @@ describe('testing angledLineThatIntersects', () => {
|
||||
}, %, $yo2)
|
||||
intersect = segEndX(yo2)`
|
||||
const execState = await enginelessExecutor(assertParse(code('-1')))
|
||||
expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
|
||||
expect(execState.variables['intersect']?.value).toBe(1 + Math.sqrt(2))
|
||||
const noOffset = await enginelessExecutor(assertParse(code('0')))
|
||||
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
|
||||
expect(noOffset.variables['intersect']?.value).toBeCloseTo(1)
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ToolTip } from 'lang/langHelpers'
|
||||
import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
SourceRange,
|
||||
Program,
|
||||
@ -10,14 +9,15 @@ import {
|
||||
Literal,
|
||||
BinaryPart,
|
||||
CallExpressionKw,
|
||||
VariableMap,
|
||||
} from '../wasm'
|
||||
import { LineInputsType } from './sketchcombos'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
export interface ModifyAstBase {
|
||||
node: Node<Program>
|
||||
// TODO #896: Remove ProgramMemory from this interface
|
||||
previousProgramMemory: ProgramMemory
|
||||
// TODO #896: Remove memory variables from this interface
|
||||
variables: VariableMap
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ it('can execute parsed AST', async () => {
|
||||
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)
|
||||
expect(execState.variables['x']?.value).toEqual(1)
|
||||
})
|
||||
|
||||
it('formats numbers with units', () => {
|
||||
|
343
src/lang/wasm.ts
343
src/lang/wasm.ts
@ -3,7 +3,8 @@ import {
|
||||
parse_wasm,
|
||||
recast_wasm,
|
||||
format_number,
|
||||
execute,
|
||||
execute_with_engine,
|
||||
execute_mock,
|
||||
kcl_lint,
|
||||
modify_ast_for_sketch_wasm,
|
||||
is_points_ccw,
|
||||
@ -281,6 +282,8 @@ export const assertParse = (code: string): Node<Program> => {
|
||||
return result.program
|
||||
}
|
||||
|
||||
export type VariableMap = { [key in string]?: KclValue }
|
||||
|
||||
export type PathToNode = [string | number, string][]
|
||||
|
||||
export const isPathToNodeNumber = (
|
||||
@ -290,7 +293,7 @@ export const isPathToNodeNumber = (
|
||||
}
|
||||
|
||||
export interface ExecState {
|
||||
memory: ProgramMemory
|
||||
variables: { [key in string]?: KclValue }
|
||||
operations: Operation[]
|
||||
artifacts: { [key in ArtifactId]?: RustArtifact }
|
||||
artifactCommands: ArtifactCommand[]
|
||||
@ -303,7 +306,7 @@ export interface ExecState {
|
||||
*/
|
||||
export function emptyExecState(): ExecState {
|
||||
return {
|
||||
memory: ProgramMemory.empty(),
|
||||
variables: {},
|
||||
operations: [],
|
||||
artifacts: {},
|
||||
artifactCommands: [],
|
||||
@ -328,7 +331,7 @@ function execStateFromRust(
|
||||
}
|
||||
|
||||
return {
|
||||
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
||||
variables: execOutcome.variables,
|
||||
operations: execOutcome.operations,
|
||||
artifacts: execOutcome.artifacts,
|
||||
artifactCommands: execOutcome.artifactCommands,
|
||||
@ -336,6 +339,16 @@ function execStateFromRust(
|
||||
}
|
||||
}
|
||||
|
||||
function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
|
||||
return {
|
||||
variables: execOutcome.variables,
|
||||
operations: execOutcome.operations,
|
||||
artifacts: execOutcome.artifacts,
|
||||
artifactCommands: execOutcome.artifactCommands,
|
||||
artifactGraph: new Map<ArtifactId, Artifact>(),
|
||||
}
|
||||
}
|
||||
|
||||
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
||||
|
||||
function rustArtifactGraphToMap(
|
||||
@ -354,203 +367,6 @@ export function defaultArtifactGraph(): ArtifactGraph {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
interface Memory {
|
||||
[key: string]: KclValue | undefined
|
||||
}
|
||||
|
||||
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
|
||||
|
||||
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
|
||||
* in the future.
|
||||
*/
|
||||
export class ProgramMemory {
|
||||
private environments: Environment[]
|
||||
private currentEnv: EnvironmentRef
|
||||
private return: KclValue | null
|
||||
|
||||
/**
|
||||
* Empty memory doesn't include prelude definitions.
|
||||
*/
|
||||
static empty(): ProgramMemory {
|
||||
return new ProgramMemory()
|
||||
}
|
||||
|
||||
static fromRaw(raw: RawProgramMemory): ProgramMemory {
|
||||
return new ProgramMemory(raw.environments, raw.currentEnv, raw.return)
|
||||
}
|
||||
|
||||
constructor(
|
||||
environments: Environment[] = [emptyRootEnvironment()],
|
||||
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
|
||||
returnVal: KclValue | null = null
|
||||
) {
|
||||
this.environments = environments
|
||||
this.currentEnv = currentEnv
|
||||
this.return = returnVal
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep copy.
|
||||
*/
|
||||
clone(): ProgramMemory {
|
||||
return ProgramMemory.fromRaw(structuredClone(this.toRaw()))
|
||||
}
|
||||
|
||||
has(name: string): boolean {
|
||||
let envRef = this.currentEnv
|
||||
while (true) {
|
||||
const env = this.environments[envRef]
|
||||
if (env.bindings.hasOwnProperty(name)) {
|
||||
return true
|
||||
}
|
||||
if (!env.parent) {
|
||||
break
|
||||
}
|
||||
envRef = env.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
get(name: string): KclValue | null {
|
||||
let envRef = this.currentEnv
|
||||
while (true) {
|
||||
const env = this.environments[envRef]
|
||||
if (env.bindings.hasOwnProperty(name)) {
|
||||
return env.bindings[name] ?? null
|
||||
}
|
||||
if (!env.parent) {
|
||||
break
|
||||
}
|
||||
envRef = env.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
set(name: string, value: KclValue): Error | null {
|
||||
if (this.environments.length === 0) {
|
||||
return new Error('No environment to set memory in')
|
||||
}
|
||||
const env = this.environments[this.currentEnv]
|
||||
env.bindings[name] = value
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ProgramMemory with only `KclValue`s that pass the
|
||||
* predicate. Values are deep copied.
|
||||
*
|
||||
* Note: Return value of the returned ProgramMemory is always null.
|
||||
*/
|
||||
filterVariables(
|
||||
keepPrelude: boolean,
|
||||
predicate: (value: KclValue) => boolean
|
||||
): ProgramMemory | Error {
|
||||
const environments: Environment[] = []
|
||||
for (const [i, env] of this.environments.entries()) {
|
||||
let bindings: Memory
|
||||
if (i === ROOT_ENVIRONMENT_REF && keepPrelude) {
|
||||
// Get prelude definitions. Create these first so that they're always
|
||||
// first in iteration order.
|
||||
const memoryOrError = programMemoryInit()
|
||||
if (err(memoryOrError)) return memoryOrError
|
||||
bindings = memoryOrError.environments[0].bindings
|
||||
} else {
|
||||
bindings = emptyEnvironment().bindings
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(env.bindings)) {
|
||||
if (value === undefined) continue
|
||||
// Check the predicate.
|
||||
if (!predicate(value)) {
|
||||
continue
|
||||
}
|
||||
// Deep copy.
|
||||
bindings[name] = structuredClone(value)
|
||||
}
|
||||
environments.push({ bindings, parent: env.parent })
|
||||
}
|
||||
return new ProgramMemory(environments, this.currentEnv, null)
|
||||
}
|
||||
|
||||
numEnvironments(): number {
|
||||
return this.environments.length
|
||||
}
|
||||
|
||||
numVariables(envRef: EnvironmentRef): number {
|
||||
return Object.keys(this.environments[envRef]).length
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all variable entries in memory that are visible, in a flat
|
||||
* structure. If variables are shadowed, they're not visible, and therefore,
|
||||
* not included.
|
||||
*
|
||||
* This should only be used to display in the MemoryPane UI.
|
||||
*/
|
||||
visibleEntries(): Map<string, KclValue> {
|
||||
const map = new Map<string, KclValue>()
|
||||
let envRef = this.currentEnv
|
||||
while (true) {
|
||||
const env = this.environments[envRef]
|
||||
for (const [name, value] of Object.entries(env.bindings)) {
|
||||
if (value === undefined) continue
|
||||
// Don't include shadowed variables.
|
||||
if (!map.has(name)) {
|
||||
map.set(name, value)
|
||||
}
|
||||
}
|
||||
if (!env.parent) {
|
||||
break
|
||||
}
|
||||
envRef = env.parent
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any visible variables are a Sketch or Solid.
|
||||
*/
|
||||
hasSketchOrSolid(): boolean {
|
||||
for (const node of this.visibleEntries().values()) {
|
||||
if (node.type === 'Solid' || node.type === 'Sketch') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the representation that can be serialized to JSON. This should only
|
||||
* be used within this module.
|
||||
*/
|
||||
toRaw(): RawProgramMemory {
|
||||
return {
|
||||
environments: this.environments,
|
||||
currentEnv: this.currentEnv,
|
||||
return: this.return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: In the future, make the parameter be a KclValue.
|
||||
export function sketchFromKclValueOptional(
|
||||
obj: any,
|
||||
@ -590,58 +406,85 @@ export function sketchFromKclValue(
|
||||
* @param node The AST of the program to execute.
|
||||
* @param path The full path of the file being executed. Use `null` for
|
||||
* expressions that don't have a file, like expressions in the command bar.
|
||||
* @param programMemoryOverride If this is not `null`, this will be used as the
|
||||
* initial program memory, and the execution will be engineless (AKA mock
|
||||
* execution).
|
||||
*/
|
||||
export const executor = async (
|
||||
export const executeMock = async (
|
||||
node: Node<Program>,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
usePrevMemory?: boolean,
|
||||
path?: string,
|
||||
programMemoryOverride: ProgramMemory | Error | null = null
|
||||
variables?: { [key in string]?: KclValue }
|
||||
): Promise<ExecState> => {
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
try {
|
||||
let jsAppSettings = default_app_settings()
|
||||
if (!TEST) {
|
||||
const lastSettingsSnapshot = await import(
|
||||
'components/SettingsAuthProvider'
|
||||
).then((module) => module.lastSettingsContextSnapshot)
|
||||
if (lastSettingsSnapshot) {
|
||||
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
||||
}
|
||||
if (!variables) {
|
||||
variables = {}
|
||||
}
|
||||
const execOutcome: RustExecOutcome = await execute(
|
||||
if (usePrevMemory === undefined) {
|
||||
usePrevMemory = true
|
||||
}
|
||||
const execOutcome: RustExecOutcome = await execute_mock(
|
||||
JSON.stringify(node),
|
||||
path,
|
||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
||||
JSON.stringify({ settings: jsAppSettings }),
|
||||
JSON.stringify({ settings: await jsAppSettings() }),
|
||||
usePrevMemory,
|
||||
JSON.stringify(variables),
|
||||
fileSystemManager
|
||||
)
|
||||
return mockExecStateFromRust(execOutcome)
|
||||
} catch (e: any) {
|
||||
return Promise.reject(errFromErrWithOutputs(e))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a KCL program.
|
||||
* @param node The AST of the program to execute.
|
||||
* @param path The full path of the file being executed. Use `null` for
|
||||
* expressions that don't have a file, like expressions in the command bar.
|
||||
*/
|
||||
export const executeWithEngine = async (
|
||||
node: Node<Program>,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
path?: string
|
||||
): Promise<ExecState> => {
|
||||
try {
|
||||
const execOutcome: RustExecOutcome = await execute_with_engine(
|
||||
JSON.stringify(node),
|
||||
path,
|
||||
JSON.stringify({ settings: await jsAppSettings() }),
|
||||
engineCommandManager,
|
||||
fileSystemManager
|
||||
)
|
||||
return execStateFromRust(execOutcome, node)
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||
const kclError = new KCLError(
|
||||
parsed.error.kind,
|
||||
parsed.error.msg,
|
||||
firstSourceRange(parsed.error),
|
||||
parsed.operations,
|
||||
parsed.artifactCommands,
|
||||
rustArtifactGraphToMap(parsed.artifactGraph)
|
||||
)
|
||||
|
||||
return Promise.reject(kclError)
|
||||
return Promise.reject(errFromErrWithOutputs(e))
|
||||
}
|
||||
}
|
||||
|
||||
const jsAppSettings = async () => {
|
||||
let jsAppSettings = default_app_settings()
|
||||
if (!TEST) {
|
||||
const lastSettingsSnapshot = await import(
|
||||
'components/SettingsAuthProvider'
|
||||
).then((module) => module.lastSettingsContextSnapshot)
|
||||
if (lastSettingsSnapshot) {
|
||||
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
||||
}
|
||||
}
|
||||
return jsAppSettings
|
||||
}
|
||||
|
||||
const errFromErrWithOutputs = (e: any): KCLError => {
|
||||
console.log('execute error', e)
|
||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||
return new KCLError(
|
||||
parsed.error.kind,
|
||||
parsed.error.msg,
|
||||
firstSourceRange(parsed.error),
|
||||
parsed.operations,
|
||||
parsed.artifactCommands,
|
||||
rustArtifactGraphToMap(parsed.artifactGraph)
|
||||
)
|
||||
}
|
||||
|
||||
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
|
||||
try {
|
||||
const discovered_findings: Array<Discovered> = await kcl_lint(
|
||||
@ -707,7 +550,6 @@ export const modifyAstForSketch = async (
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
return Promise.reject(kclError)
|
||||
}
|
||||
}
|
||||
@ -755,31 +597,6 @@ export function getTangentialArcToInfo({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new ProgramMemory with prelude definitions.
|
||||
*/
|
||||
export function programMemoryInit(): ProgramMemory | Error {
|
||||
try {
|
||||
const memory: RawProgramMemory = program_memory_init()
|
||||
return new ProgramMemory(
|
||||
memory.environments,
|
||||
memory.currentEnv,
|
||||
memory.return
|
||||
)
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
return new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
firstSourceRange(parsed),
|
||||
[],
|
||||
[],
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function coreDump(
|
||||
coreDumpManager: CoreDumpManager,
|
||||
openGithubIssue: boolean = false
|
||||
|
@ -576,7 +576,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
memVars: kclManager.variables,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(sketched)) return KCL_DEFAULT_LENGTH
|
||||
|
@ -51,16 +51,16 @@ sketch002 = startSketchOn(sketch001, seg03)
|
||||
center = [-1.25, 1],
|
||||
radius = mountingHoleDiameter / 2,
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = 2.5,
|
||||
axis = [-1, 0],
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = 4,
|
||||
axis = [0, 1],
|
||||
}, %)
|
||||
)
|
||||
|> extrude(%, length = -thickness-.01)
|
||||
|
||||
sketch003 = startSketchOn(sketch001, seg04)
|
||||
@ -68,11 +68,11 @@ sketch003 = startSketchOn(sketch001, seg04)
|
||||
center = [1, -1],
|
||||
radius = mountingHoleDiameter / 2,
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = 4,
|
||||
axis = [1, 0],
|
||||
}, %)
|
||||
)
|
||||
|> extrude(%, length = -thickness-0.1)
|
||||
`
|
||||
|
||||
|
@ -6,7 +6,6 @@ import { FILE_EXT } from './constants'
|
||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||
import { reportRejection } from './trap'
|
||||
import { IndexLoaderData } from './types'
|
||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
||||
import { copyFileShareLink } from './links'
|
||||
|
||||
interface OnSubmitProps {
|
||||
@ -137,7 +136,6 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
||||
{
|
||||
name: 'share-file-link',
|
||||
displayName: 'Share current part (via Zoo link)',
|
||||
hide: IS_NIGHTLY_OR_DEBUG ? undefined : 'desktop',
|
||||
description: 'Create a link that contains a copy of the current file.',
|
||||
groupId: 'code',
|
||||
needsReview: false,
|
||||
|
@ -1,67 +1,55 @@
|
||||
import { ParseResult, ProgramMemory } from 'lang/wasm'
|
||||
import { ParseResult, VariableMap } from 'lang/wasm'
|
||||
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
||||
|
||||
describe('KCL expression calculations', () => {
|
||||
it('calculates a simple expression', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + 2',
|
||||
programMemory: ProgramMemory.empty(),
|
||||
})
|
||||
const actual = await getCalculatedKclExpressionValue('1 + 2', {})
|
||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||
expect(coercedActual).not.toHaveProperty('errors')
|
||||
expect(coercedActual.valueAsString).toEqual('3')
|
||||
expect(coercedActual?.astNode).toBeDefined()
|
||||
})
|
||||
it('calculates a simple expression with a variable', async () => {
|
||||
const programMemory = ProgramMemory.empty()
|
||||
programMemory.set('x', {
|
||||
const variables: VariableMap = {}
|
||||
variables['x'] = {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
__meta: [],
|
||||
})
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + x',
|
||||
programMemory,
|
||||
})
|
||||
}
|
||||
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
|
||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||
expect(coercedActual.valueAsString).toEqual('3')
|
||||
expect(coercedActual.astNode).toBeDefined()
|
||||
})
|
||||
it('returns NAN for an invalid expression', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + x',
|
||||
programMemory: ProgramMemory.empty(),
|
||||
})
|
||||
const actual = await getCalculatedKclExpressionValue('1 + x', {})
|
||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||
expect(coercedActual.valueAsString).toEqual('NAN')
|
||||
expect(coercedActual.astNode).toBeDefined()
|
||||
})
|
||||
it('returns NAN for an expression with an invalid variable', async () => {
|
||||
const programMemory = ProgramMemory.empty()
|
||||
programMemory.set('y', {
|
||||
const variables: VariableMap = {}
|
||||
variables['y'] = {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
__meta: [],
|
||||
})
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + x',
|
||||
programMemory,
|
||||
})
|
||||
}
|
||||
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
|
||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||
expect(coercedActual.valueAsString).toEqual('NAN')
|
||||
expect(coercedActual.astNode).toBeDefined()
|
||||
})
|
||||
it('calculates a more complex expression with a variable', async () => {
|
||||
const programMemory = ProgramMemory.empty()
|
||||
programMemory.set('x', {
|
||||
const variables: VariableMap = {}
|
||||
variables['x'] = {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
__meta: [],
|
||||
})
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '(1 + x * x) * 2',
|
||||
programMemory,
|
||||
})
|
||||
}
|
||||
const actual = await getCalculatedKclExpressionValue(
|
||||
'(1 + x * x) * 2',
|
||||
variables
|
||||
)
|
||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||
expect(coercedActual.valueAsString).toEqual('10')
|
||||
expect(coercedActual.astNode).toBeDefined()
|
||||
|
@ -1,48 +1,20 @@
|
||||
import { err } from './trap'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { parse, ProgramMemory, programMemoryInit, resultIsOk } from 'lang/wasm'
|
||||
import { parse, resultIsOk, VariableMap } from 'lang/wasm'
|
||||
import { PrevVariable } from 'lang/queryAst'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { KclExpression } from './commandTypes'
|
||||
|
||||
const DUMMY_VARIABLE_NAME = '__result__'
|
||||
|
||||
export function programMemoryFromVariables(
|
||||
variables: PrevVariable<string | number>[]
|
||||
): ProgramMemory | Error {
|
||||
const memory = programMemoryInit()
|
||||
if (err(memory)) return memory
|
||||
for (const { key, value } of variables) {
|
||||
const error = memory.set(
|
||||
key,
|
||||
typeof value === 'number'
|
||||
? {
|
||||
type: 'Number',
|
||||
value,
|
||||
__meta: [],
|
||||
}
|
||||
: {
|
||||
type: 'String',
|
||||
value,
|
||||
__meta: [],
|
||||
}
|
||||
)
|
||||
if (err(error)) return error
|
||||
}
|
||||
return memory
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the value of the KCL expression,
|
||||
* given the value and the variables that are available
|
||||
*/
|
||||
export async function getCalculatedKclExpressionValue({
|
||||
value,
|
||||
programMemory,
|
||||
}: {
|
||||
value: string
|
||||
programMemory: ProgramMemory
|
||||
}) {
|
||||
export async function getCalculatedKclExpressionValue(
|
||||
value: string,
|
||||
variables: VariableMap
|
||||
) {
|
||||
// Create a one-line program that assigns the value to a variable
|
||||
const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}`
|
||||
const pResult = parse(dummyProgramCode)
|
||||
@ -53,7 +25,8 @@ export async function getCalculatedKclExpressionValue({
|
||||
const { execState } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
programMemoryOverride: programMemory,
|
||||
isMock: true,
|
||||
variables,
|
||||
})
|
||||
|
||||
// Find the variable declaration for the result
|
||||
@ -65,7 +38,7 @@ export async function getCalculatedKclExpressionValue({
|
||||
const variableDeclaratorAstNode =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declaration.init
|
||||
const resultRawValue = execState.memory?.get(DUMMY_VARIABLE_NAME)?.value
|
||||
const resultRawValue = execState.variables[DUMMY_VARIABLE_NAME]?.value
|
||||
|
||||
return {
|
||||
astNode: variableDeclaratorAstNode,
|
||||
@ -74,17 +47,14 @@ export async function getCalculatedKclExpressionValue({
|
||||
}
|
||||
}
|
||||
|
||||
export async function stringToKclExpression({
|
||||
value,
|
||||
programMemory,
|
||||
}: {
|
||||
value: string
|
||||
programMemory: ProgramMemory
|
||||
}) {
|
||||
const calculatedResult = await getCalculatedKclExpressionValue({
|
||||
export async function stringToKclExpression(
|
||||
value: string,
|
||||
variables: VariableMap
|
||||
) {
|
||||
const calculatedResult = await getCalculatedKclExpressionValue(
|
||||
value,
|
||||
programMemory,
|
||||
})
|
||||
variables
|
||||
)
|
||||
if (err(calculatedResult) || 'errors' in calculatedResult) {
|
||||
return calculatedResult
|
||||
} else if (!calculatedResult.astNode) {
|
||||
|
@ -80,13 +80,13 @@ const prepareToEditExtrude: PrepareToEditCallback =
|
||||
}
|
||||
|
||||
// Convert the length argument from a string to a KCL expression
|
||||
const distanceResult = await stringToKclExpression({
|
||||
value: codeManager.code.slice(
|
||||
const distanceResult = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs?.['length']?.sourceRange[0],
|
||||
operation.labeledArgs?.['length']?.sourceRange[1]
|
||||
),
|
||||
programMemory: kclManager.programMemory.clone(),
|
||||
})
|
||||
{}
|
||||
)
|
||||
if (err(distanceResult) || 'errors' in distanceResult) {
|
||||
return baseCommand
|
||||
}
|
||||
@ -163,13 +163,13 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
|
||||
}
|
||||
|
||||
// Convert the distance argument from a string to a KCL expression
|
||||
const distanceResult = await stringToKclExpression({
|
||||
value: codeManager.code.slice(
|
||||
const distanceResult = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.offset.sourceRange[0],
|
||||
operation.labeledArgs.offset.sourceRange[1]
|
||||
),
|
||||
programMemory: kclManager.programMemory.clone(),
|
||||
})
|
||||
{}
|
||||
)
|
||||
|
||||
if (err(distanceResult) || 'errors' in distanceResult) {
|
||||
return baseCommand
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {
|
||||
Program,
|
||||
ProgramMemory,
|
||||
executor,
|
||||
executeMock,
|
||||
SourceRange,
|
||||
ExecState,
|
||||
VariableMap,
|
||||
} from '../lang/wasm'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
@ -80,18 +80,9 @@ class MockEngineCommandManager {
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Node<Program>,
|
||||
pmo: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
path?: string
|
||||
usePrevMemory?: boolean,
|
||||
path?: string,
|
||||
variables?: VariableMap
|
||||
): Promise<ExecState> {
|
||||
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
|
||||
|
||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
setMediaStream: () => {},
|
||||
}) as any as EngineCommandManager
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
mockEngineCommandManager.startNewSession()
|
||||
const execState = await executor(ast, mockEngineCommandManager, path, pmo)
|
||||
await mockEngineCommandManager.waitForAllCommands()
|
||||
return execState
|
||||
return await executeMock(ast, usePrevMemory, path, variables)
|
||||
}
|
||||
|
@ -5,10 +5,7 @@ import { findUniqueName } from 'lang/modifyAst'
|
||||
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { Expr } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
getCalculatedKclExpressionValue,
|
||||
programMemoryFromVariables,
|
||||
} from './kclHelpers'
|
||||
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
||||
import { parse, resultIsOk } from 'lang/wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
@ -36,7 +33,7 @@ export function useCalculateKclExpression({
|
||||
newVariableInsertIndex: number
|
||||
setNewVariableName: (a: string) => void
|
||||
} {
|
||||
const { programMemory, code } = useKclContext()
|
||||
const { variables, code } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
// If there is no selection, use the end of the code
|
||||
// so all variables are available
|
||||
@ -80,7 +77,7 @@ export function useCalculateKclExpression({
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
programMemory.has(newVariableName) ||
|
||||
variables[newVariableName] ||
|
||||
newVariableName === '' ||
|
||||
!isValidVariableName(newVariableName)
|
||||
) {
|
||||
@ -88,33 +85,22 @@ export function useCalculateKclExpression({
|
||||
} else {
|
||||
setIsNewVariableNameUnique(true)
|
||||
}
|
||||
}, [programMemory, newVariableName])
|
||||
}, [variables, newVariableName])
|
||||
|
||||
useEffect(() => {
|
||||
if (!programMemory) return
|
||||
if (!variables) return
|
||||
const varInfo = findAllPreviousVariables(
|
||||
kclManager.ast,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
// If there is no selection, use the end of the code
|
||||
selectionRange || [code.length, code.length]
|
||||
)
|
||||
setAvailableVarInfo(varInfo)
|
||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
||||
}, [kclManager.ast, kclManager.variables, selectionRange])
|
||||
|
||||
useEffect(() => {
|
||||
const execAstAndSetResult = async () => {
|
||||
const programMemory = programMemoryFromVariables(
|
||||
availableVarInfo.variables
|
||||
)
|
||||
if (programMemory instanceof Error) {
|
||||
setCalcResult('NAN')
|
||||
setValueNode(null)
|
||||
return
|
||||
}
|
||||
const result = await getCalculatedKclExpressionValue({
|
||||
value,
|
||||
programMemory,
|
||||
})
|
||||
const result = await getCalculatedKclExpressionValue(value, {})
|
||||
if (result instanceof Error || 'errors' in result) {
|
||||
setCalcResult('NAN')
|
||||
setValueNode(null)
|
||||
@ -128,7 +114,7 @@ export function useCalculateKclExpression({
|
||||
setCalcResult('NAN')
|
||||
setValueNode(null)
|
||||
})
|
||||
}, [value, availableVarInfo, code, kclManager.programMemory])
|
||||
}, [value, availableVarInfo, code, kclManager.variables])
|
||||
|
||||
return {
|
||||
valueNode,
|
||||
|
@ -5,7 +5,7 @@ import { findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function usePreviousVariables() {
|
||||
const { programMemory, code } = useKclContext()
|
||||
const { variables, code } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
|
||||
?.range || [code.length, code.length]
|
||||
@ -18,14 +18,14 @@ export function usePreviousVariables() {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!programMemory || !selectionRange) return
|
||||
if (!variables || !selectionRange) return
|
||||
const varInfo = findAllPreviousVariables(
|
||||
kclManager.ast,
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
selectionRange
|
||||
)
|
||||
setPreviousVariablesInfo(varInfo)
|
||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
||||
}, [kclManager.ast, kclManager.variables, selectionRange])
|
||||
|
||||
return previousVariablesInfo
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
parse_wasm as ParseWasm,
|
||||
recast_wasm as RecastWasm,
|
||||
format_number as FormatNumber,
|
||||
execute as Execute,
|
||||
execute_with_engine as ExecuteWithEngine,
|
||||
execute_mock as ExecuteMock,
|
||||
kcl_lint as KclLint,
|
||||
modify_ast_for_sketch_wasm as ModifyAstForSketch,
|
||||
is_points_ccw as IsPointsCcw,
|
||||
@ -57,8 +58,11 @@ export const recast_wasm: typeof RecastWasm = (...args) => {
|
||||
export const format_number: typeof FormatNumber = (...args) => {
|
||||
return getModule().format_number(...args)
|
||||
}
|
||||
export const execute: typeof Execute = (...args) => {
|
||||
return getModule().execute(...args)
|
||||
export const execute_with_engine: typeof ExecuteWithEngine = (...args) => {
|
||||
return getModule().execute_with_engine(...args)
|
||||
}
|
||||
export const execute_mock: typeof ExecuteMock = (...args) => {
|
||||
return getModule().execute_mock(...args)
|
||||
}
|
||||
export const kcl_lint: typeof KclLint = (...args) => {
|
||||
return getModule().kcl_lint(...args)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
PathToNode,
|
||||
ProgramMemory,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
parse,
|
||||
@ -708,7 +707,7 @@ export const modelingMachine = setup({
|
||||
const modifiedAst = await deleteFromSelection(
|
||||
ast,
|
||||
selectionRanges.graphSelections[0],
|
||||
kclManager.programMemory,
|
||||
kclManager.variables,
|
||||
getFaceDetails
|
||||
)
|
||||
if (err(modifiedAst)) {
|
||||
@ -719,8 +718,7 @@ export const modelingMachine = setup({
|
||||
const testExecute = await executeAst({
|
||||
ast: modifiedAst,
|
||||
engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
isMock: true,
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
toast.error(errorMessage)
|
||||
@ -1087,7 +1085,7 @@ export const modelingMachine = setup({
|
||||
selectionRanges,
|
||||
'horizontal',
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
kclManager.variables
|
||||
)
|
||||
if (trap(constraint)) return false
|
||||
const { modifiedAst, pathToNodeMap } = constraint
|
||||
@ -1122,7 +1120,7 @@ export const modelingMachine = setup({
|
||||
selectionRanges,
|
||||
'vertical',
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
kclManager.variables
|
||||
)
|
||||
if (trap(constraint)) return false
|
||||
const { modifiedAst, pathToNodeMap } = constraint
|
||||
|
2
src/wasm-lib/Cargo.lock
generated
2
src/wasm-lib/Cargo.lock
generated
@ -1779,7 +1779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.32",
|
||||
|
@ -33,6 +33,9 @@ tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8"
|
||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||
|
||||
[features]
|
||||
dhat-heap = ["kcl-lib/dhat-heap"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.31"
|
||||
|
@ -36,5 +36,11 @@ run-sim-test test_name:
|
||||
{{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
||||
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
||||
|
||||
overwrite-sim-test test_name:
|
||||
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::parse
|
||||
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
||||
|
||||
|
||||
test:
|
||||
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-test-server"
|
||||
description = "A test server for KCL"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -944,13 +944,7 @@ mod tests {
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternCircular3d({
|
||||
instances = ${0:10},
|
||||
axis = [${1:3.14}, ${2:3.14}, ${3:3.14}],
|
||||
center = [${4:3.14}, ${5:3.14}, ${6:3.14}],
|
||||
arcDegrees = ${7:3.14},
|
||||
rotateDuplicates = ${8:false},
|
||||
}, ${9:%})${}"#
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arc_degrees = ${8:3.14}, rotate_duplicates = ${9:false})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1006,11 +1000,7 @@ mod tests {
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternLinear2d({
|
||||
instances = ${0:10},
|
||||
distance = ${1:3.14},
|
||||
axis = [${2:3.14}, ${3:3.14}],
|
||||
}, ${4:%})${}"#
|
||||
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
//! Data on available annotations.
|
||||
|
||||
use super::kcl_value::{UnitAngle, UnitLen};
|
||||
use kittycad_modeling_cmds::coord::{System, KITTYCAD, OPENGL, VULKAN};
|
||||
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::kcl_value::{UnitAngle, UnitLen},
|
||||
parsing::ast::types::{Expr, Node, NonCodeValue, ObjectProperty},
|
||||
KclError, SourceRange,
|
||||
};
|
||||
@ -11,6 +13,12 @@ pub(crate) const SETTINGS: &str = "settings";
|
||||
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
|
||||
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
||||
pub(super) const NO_PRELUDE: &str = "no_prelude";
|
||||
pub(super) const IMPORT_FORMAT: &str = "format";
|
||||
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
|
||||
pub(super) const IMPORT_COORDS: &str = "coords";
|
||||
pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
|
||||
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
|
||||
pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub(super) enum AnnotationScope {
|
||||
@ -36,6 +44,18 @@ pub(super) fn expect_properties<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn unnamed_properties<'a>(
|
||||
annotations: impl Iterator<Item = &'a NonCodeValue>,
|
||||
) -> Option<&'a [Node<ObjectProperty>]> {
|
||||
for annotation in annotations {
|
||||
if let NonCodeValue::Annotation { name: None, properties } = annotation {
|
||||
return properties.as_deref();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||
match expr {
|
||||
Expr::Identifier(id) => Ok(&id.name),
|
||||
@ -57,7 +77,7 @@ impl UnitLen {
|
||||
"yd" => Ok(UnitLen::Yards),
|
||||
value => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unexpected settings value: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
|
||||
"Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
@ -71,7 +91,7 @@ impl UnitAngle {
|
||||
"deg" => Ok(UnitAngle::Degrees),
|
||||
"rad" => Ok(UnitAngle::Radians),
|
||||
value => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Unexpected settings value: `{value}`; expected one of `deg`, `rad`"),
|
||||
message: format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
}
|
||||
|
@ -462,98 +462,4 @@ impl ArtifactGraph {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Output the Mermaid mind map for the artifact graph.
|
||||
///
|
||||
/// This is sometimes easier to read than the flowchart. But since it
|
||||
/// does a depth-first traversal starting from all the planes, it may
|
||||
/// not include all the artifacts. It also doesn't show edge direction.
|
||||
/// It's useful for a high-level overview of the graph, not for
|
||||
/// including all the information.
|
||||
pub(crate) fn to_mermaid_mind_map(&self) -> Result<String, std::fmt::Error> {
|
||||
let mut output = String::new();
|
||||
output.push_str("```mermaid\n");
|
||||
output.push_str("mindmap\n");
|
||||
output.push_str(" root\n");
|
||||
|
||||
let mut ids_seen: fnv::FnvHashSet<ArtifactId> = Default::default();
|
||||
|
||||
for (_, artifact) in &self.map {
|
||||
// Only the planes are roots.
|
||||
let Artifact::Plane(_) = artifact else {
|
||||
continue;
|
||||
};
|
||||
self.mind_map_artifact(&mut output, &mut ids_seen, artifact, " ")?;
|
||||
}
|
||||
|
||||
output.push_str("```\n");
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn mind_map_artifact<W: Write>(
|
||||
&self,
|
||||
output: &mut W,
|
||||
ids_seen: &mut fnv::FnvHashSet<ArtifactId>,
|
||||
artifact: &Artifact,
|
||||
prefix: &str,
|
||||
) -> std::fmt::Result {
|
||||
match artifact {
|
||||
Artifact::Plane(_plane) => {
|
||||
ids_seen.clear();
|
||||
writeln!(output, "{prefix}Plane")?;
|
||||
}
|
||||
Artifact::Path(_path) => {
|
||||
writeln!(output, "{prefix}Path")?;
|
||||
}
|
||||
Artifact::Segment(_segment) => {
|
||||
writeln!(output, "{prefix}Segment")?;
|
||||
}
|
||||
Artifact::Solid2d(_solid2d) => {
|
||||
writeln!(output, "{prefix}Solid2d")?;
|
||||
}
|
||||
Artifact::StartSketchOnFace { .. } => {
|
||||
writeln!(output, "{prefix}StartSketchOnFace")?;
|
||||
}
|
||||
Artifact::StartSketchOnPlane { .. } => {
|
||||
writeln!(output, "{prefix}StartSketchOnPlane")?;
|
||||
}
|
||||
Artifact::Sweep(sweep) => {
|
||||
writeln!(output, "{prefix}Sweep {:?}", sweep.sub_type)?;
|
||||
}
|
||||
Artifact::Wall(_wall) => {
|
||||
writeln!(output, "{prefix}Wall")?;
|
||||
}
|
||||
Artifact::Cap(cap) => {
|
||||
writeln!(output, "{prefix}Cap {:?}", cap.sub_type)?;
|
||||
}
|
||||
Artifact::SweepEdge(sweep_edge) => {
|
||||
writeln!(output, "{prefix}SweepEdge {:?}", sweep_edge.sub_type,)?;
|
||||
}
|
||||
Artifact::EdgeCut(edge_cut) => {
|
||||
writeln!(output, "{prefix}EdgeCut {:?}", edge_cut.sub_type)?;
|
||||
}
|
||||
Artifact::EdgeCutEdge(_edge_cut_edge) => {
|
||||
writeln!(output, "{prefix}EdgeCutEdge")?;
|
||||
}
|
||||
Artifact::Helix(_) => {
|
||||
writeln!(output, "{prefix}Helix")?;
|
||||
}
|
||||
}
|
||||
|
||||
if ids_seen.contains(&artifact.id()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ids_seen.insert(artifact.id());
|
||||
|
||||
for child_id in artifact.child_ids() {
|
||||
let Some(child_artifact) = self.map.get(&child_id) else {
|
||||
continue;
|
||||
};
|
||||
self.mind_map_artifact(output, ids_seen, child_artifact, &format!("{} ", prefix))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -11,28 +11,46 @@ use crate::{
|
||||
walk::Node as WalkNode,
|
||||
};
|
||||
|
||||
use super::ProgramMemory;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// A static mutable lock for updating the last successful execution state for the cache.
|
||||
static ref OLD_AST_MEMORY: Arc<RwLock<Option<OldAstState>>> = Default::default();
|
||||
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
|
||||
// The last successful run's memory. Not cleared after an unssuccessful run.
|
||||
static ref PREV_MEMORY: Arc<RwLock<Option<ProgramMemory>>> = Default::default();
|
||||
}
|
||||
|
||||
/// Read the old ast memory from the lock.
|
||||
pub(super) async fn read_old_ast_memory() -> Option<OldAstState> {
|
||||
let old_ast = OLD_AST_MEMORY.read().await;
|
||||
pub(super) async fn read_old_ast() -> Option<OldAstState> {
|
||||
let old_ast = OLD_AST.read().await;
|
||||
old_ast.clone()
|
||||
}
|
||||
|
||||
pub(super) async fn write_old_ast_memory(old_state: OldAstState) {
|
||||
let mut old_ast = OLD_AST_MEMORY.write().await;
|
||||
pub(super) async fn write_old_ast(old_state: OldAstState) {
|
||||
let mut old_ast = OLD_AST.write().await;
|
||||
*old_ast = Some(old_state);
|
||||
}
|
||||
|
||||
pub(super) async fn read_old_memory() -> Option<ProgramMemory> {
|
||||
let old_mem = PREV_MEMORY.read().await;
|
||||
old_mem.clone()
|
||||
}
|
||||
|
||||
pub(super) async fn write_old_memory(mem: ProgramMemory) {
|
||||
let mut old_mem = PREV_MEMORY.write().await;
|
||||
*old_mem = Some(mem);
|
||||
}
|
||||
|
||||
pub async fn bust_cache() {
|
||||
let mut old_ast = OLD_AST_MEMORY.write().await;
|
||||
// Set the cache to None.
|
||||
let mut old_ast = OLD_AST.write().await;
|
||||
*old_ast = None;
|
||||
}
|
||||
|
||||
pub async fn clear_mem_cache() {
|
||||
let mut old_mem = PREV_MEMORY.write().await;
|
||||
*old_mem = None;
|
||||
}
|
||||
|
||||
/// Information for the caching an AST and smartly re-executing it if we can.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CacheInformation<'a> {
|
||||
|
@ -9,16 +9,17 @@ use crate::{
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::{OpArg, Operation},
|
||||
memory,
|
||||
state::ModuleState,
|
||||
BodyType, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, TagEngineInfo,
|
||||
TagIdentifier,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
|
||||
TagEngineInfo, TagIdentifier,
|
||||
},
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression,
|
||||
PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeNode, NonCodeValue,
|
||||
ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::{
|
||||
@ -109,24 +110,28 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
let module_id = self.open_module(&import_stmt.path, exec_state, source_range).await?;
|
||||
let meta_nodes = program.non_code_meta.get(i);
|
||||
let module_id = self
|
||||
.open_module(&import_stmt.path, meta_nodes, exec_state, source_range)
|
||||
.await?;
|
||||
|
||||
match &import_stmt.selector {
|
||||
ImportSelector::List { items } => {
|
||||
let (_, module_memory, module_exports) = self
|
||||
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||
let (env_ref, module_exports) = self
|
||||
.exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||
.await?;
|
||||
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)],
|
||||
})
|
||||
})?;
|
||||
let item = exec_state
|
||||
.memory()
|
||||
.get_from(&import_item.name.name, env_ref, 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)],
|
||||
})
|
||||
})?
|
||||
.clone();
|
||||
// Check that the item is allowed to be imported.
|
||||
if !module_exports.contains(&import_item.name.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -140,8 +145,8 @@ impl ExecutorContext {
|
||||
|
||||
// Add the item to the current module.
|
||||
exec_state.mut_memory().add(
|
||||
import_item.identifier(),
|
||||
item.clone(),
|
||||
import_item.identifier().to_owned(),
|
||||
item,
|
||||
SourceRange::from(&import_item.name),
|
||||
)?;
|
||||
|
||||
@ -154,17 +159,21 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
ImportSelector::Glob(_) => {
|
||||
let (_, module_memory, module_exports) = self
|
||||
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||
let (env_ref, module_exports) = self
|
||||
.exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||
.await?;
|
||||
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.mut_memory().add(name, item.clone(), source_range)?;
|
||||
let item = exec_state
|
||||
.memory()
|
||||
.get_from(name, env_ref, source_range)
|
||||
.map_err(|_err| {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!("{} is not defined in module (but was exported?)", name),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?
|
||||
.clone();
|
||||
exec_state.mut_memory().add(name.to_owned(), item, source_range)?;
|
||||
|
||||
if let ItemVisibility::Export = import_stmt.visibility {
|
||||
exec_state.mod_local.module_exports.push(name.clone());
|
||||
@ -177,7 +186,7 @@ impl ExecutorContext {
|
||||
value: module_id,
|
||||
meta: vec![source_range.into()],
|
||||
};
|
||||
exec_state.mut_memory().add(&name, item, source_range)?;
|
||||
exec_state.mut_memory().add(name, item, source_range)?;
|
||||
}
|
||||
}
|
||||
last_expr = None;
|
||||
@ -199,13 +208,7 @@ impl ExecutorContext {
|
||||
let source_range = SourceRange::from(&variable_declaration.declaration.init);
|
||||
let metadata = Metadata { source_range };
|
||||
|
||||
let _meta_nodes = if i == 0 {
|
||||
&program.non_code_meta.start_nodes
|
||||
} else if let Some(meta) = program.non_code_meta.non_code_nodes.get(&(i - 1)) {
|
||||
meta
|
||||
} else {
|
||||
&Vec::new()
|
||||
};
|
||||
let _meta_nodes = program.non_code_meta.get(i);
|
||||
|
||||
let memory_item = self
|
||||
.execute_expr(
|
||||
@ -215,7 +218,9 @@ impl ExecutorContext {
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
exec_state.mut_memory().add(&var_name, memory_item, source_range)?;
|
||||
exec_state
|
||||
.mut_memory()
|
||||
.add(var_name.clone(), memory_item, source_range)?;
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
@ -225,6 +230,14 @@ impl ExecutorContext {
|
||||
}
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
|
||||
if body_type == BodyType::Root {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot return from outside a function.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
let value = self
|
||||
.execute_expr(
|
||||
&return_statement.argument,
|
||||
@ -233,7 +246,15 @@ impl ExecutorContext {
|
||||
StatementKind::Expression,
|
||||
)
|
||||
.await?;
|
||||
exec_state.mut_memory().return_ = Some(value);
|
||||
exec_state
|
||||
.mut_memory()
|
||||
.add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: "Multiple returns from a single function.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
})
|
||||
})?;
|
||||
last_expr = None;
|
||||
}
|
||||
}
|
||||
@ -257,6 +278,7 @@ impl ExecutorContext {
|
||||
async fn open_module(
|
||||
&self,
|
||||
path: &ImportPath,
|
||||
non_code_meta: &[Node<NonCodeNode>],
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<ModuleId, KclError> {
|
||||
@ -273,7 +295,8 @@ impl ExecutorContext {
|
||||
let source = resolved_path.source(&self.fs, source_range).await?;
|
||||
// TODO handle parsing errors properly
|
||||
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed));
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
ImportPath::Foreign { .. } => {
|
||||
@ -282,9 +305,9 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
let id = exec_state.next_module_id();
|
||||
let geom =
|
||||
super::import::import_foreign(resolved_path.expect_path(), None, exec_state, self, source_range)
|
||||
.await?;
|
||||
let path = resolved_path.expect_path();
|
||||
let format = super::import::format_from_annotations(non_code_meta, path, source_range)?;
|
||||
let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom));
|
||||
Ok(id)
|
||||
}
|
||||
@ -296,80 +319,116 @@ impl ExecutorContext {
|
||||
let id = exec_state.next_module_id();
|
||||
let source = resolved_path.source(&self.fs, source_range).await?;
|
||||
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err().unwrap();
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed));
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn exec_module(
|
||||
async fn exec_module_for_items(
|
||||
&self,
|
||||
module_id: ModuleId,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> {
|
||||
let old_units = exec_state.length_unit();
|
||||
// TODO It sucks that we have to clone the whole module AST here
|
||||
let info = exec_state.global.module_infos[&module_id].clone();
|
||||
) -> Result<(EnvironmentRef, Vec<String>), KclError> {
|
||||
let path = exec_state.global.module_infos[&module_id].path.clone();
|
||||
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||
// DON'T EARLY RETURN! We need to restore the module repr
|
||||
|
||||
match &info.repr {
|
||||
ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
exec_state
|
||||
.global
|
||||
.mod_loader
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
info.path
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
let result = match &mut repr {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())),
|
||||
ModuleRepr::Kcl(program, cache) => self
|
||||
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||
.await
|
||||
.map(|(_, er, items)| {
|
||||
*cache = Some((er, items.clone()));
|
||||
(er, items)
|
||||
}),
|
||||
ModuleRepr::Foreign(geom) => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot import items from foreign modules".to_owned(),
|
||||
source_ranges: vec![geom.source_range],
|
||||
})),
|
||||
ModuleRepr::Kcl(program) => {
|
||||
let mut local_state = ModuleState::new(&self.settings);
|
||||
exec_state.global.mod_loader.enter_module(&info.path);
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
||||
ModuleRepr::Dummy => unreachable!(),
|
||||
};
|
||||
|
||||
let result = self
|
||||
.exec_program(program, exec_state, crate::execution::BodyType::Root)
|
||||
.await;
|
||||
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||
result
|
||||
}
|
||||
|
||||
let new_units = exec_state.length_unit();
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
exec_state.global.mod_loader.leave_module(&info.path);
|
||||
if !exec_kind.is_isolated() && new_units != old_units {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution);
|
||||
async fn exec_module_for_result(
|
||||
&self,
|
||||
module_id: ModuleId,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let path = exec_state.global.module_infos[&module_id].path.clone();
|
||||
let repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||
// DON'T EARLY RETURN! We need to restore the module repr
|
||||
|
||||
let result = 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. {}: {}",
|
||||
info.path,
|
||||
err.message()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
})?;
|
||||
let result = match &repr {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(program, _) => self
|
||||
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||
.await
|
||||
.map(|(val, _, _)| val),
|
||||
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
|
||||
.await
|
||||
.map(|geom| Some(KclValue::ImportedGeometry(geom))),
|
||||
ModuleRepr::Dummy => unreachable!(),
|
||||
};
|
||||
|
||||
Ok((result, local_state.memory, local_state.module_exports))
|
||||
}
|
||||
ModuleRepr::Foreign(geom) => {
|
||||
let geom = super::import::send_to_engine(geom.clone(), self).await?;
|
||||
Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new()))
|
||||
}
|
||||
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||
result
|
||||
}
|
||||
|
||||
async fn exec_module_from_ast(
|
||||
&self,
|
||||
program: &Node<Program>,
|
||||
path: &ModulePath,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
let old_units = exec_state.length_unit();
|
||||
let mut local_state = ModuleState::new(&self.settings);
|
||||
exec_state.global.mod_loader.enter_module(path);
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
exec_state.mut_memory().push_new_root_env();
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
||||
|
||||
let result = self
|
||||
.exec_program(program, exec_state, crate::execution::BodyType::Root)
|
||||
.await;
|
||||
|
||||
let new_units = exec_state.length_unit();
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
let env_ref = exec_state.mut_memory().pop_env();
|
||||
exec_state.global.mod_loader.leave_module(path);
|
||||
if !exec_kind.is_isolated() && new_units != old_units {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution);
|
||||
|
||||
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],
|
||||
})
|
||||
}
|
||||
})
|
||||
.map(|result| (result, env_ref, local_state.module_exports))
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
@ -387,39 +446,33 @@ impl ExecutorContext {
|
||||
Expr::Identifier(identifier) => {
|
||||
let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
|
||||
if let KclValue::Module { value: module_id, meta } = value {
|
||||
let (result, _, _) = self
|
||||
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
||||
.await?;
|
||||
result.unwrap_or_else(|| {
|
||||
// The module didn't have a return value. Currently,
|
||||
// the only way to have a return value is with the final
|
||||
// statement being an expression statement.
|
||||
//
|
||||
// TODO: Make a warning when we support them in the
|
||||
// execution phase.
|
||||
let mut new_meta = vec![metadata.to_owned()];
|
||||
new_meta.extend(meta);
|
||||
KclValue::KclNone {
|
||||
value: Default::default(),
|
||||
meta: new_meta,
|
||||
}
|
||||
})
|
||||
self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
||||
.await?
|
||||
.unwrap_or_else(|| {
|
||||
// The module didn't have a return value. Currently,
|
||||
// the only way to have a return value is with the final
|
||||
// statement being an expression statement.
|
||||
//
|
||||
// TODO: Make a warning when we support them in the
|
||||
// execution phase.
|
||||
let mut new_meta = vec![metadata.to_owned()];
|
||||
new_meta.extend(meta);
|
||||
KclValue::KclNone {
|
||||
value: Default::default(),
|
||||
meta: new_meta,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
|
||||
Expr::FunctionExpression(function_expression) => {
|
||||
// Cloning memory here is crucial for semantics so that we close
|
||||
// over variables. Variables defined lexically later shouldn't
|
||||
// be available to the function body.
|
||||
KclValue::Function {
|
||||
expression: function_expression.clone(),
|
||||
meta: vec![metadata.to_owned()],
|
||||
func: None,
|
||||
memory: Box::new(exec_state.memory().clone()),
|
||||
}
|
||||
}
|
||||
Expr::FunctionExpression(function_expression) => KclValue::Function {
|
||||
expression: function_expression.clone(),
|
||||
meta: vec![metadata.to_owned()],
|
||||
func: None,
|
||||
memory: exec_state.mut_memory().snapshot(),
|
||||
},
|
||||
Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?,
|
||||
Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
|
||||
Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
|
||||
@ -456,7 +509,7 @@ impl ExecutorContext {
|
||||
.await?;
|
||||
exec_state
|
||||
.mut_memory()
|
||||
.add(&expr.label.name, result.clone(), init.into())?;
|
||||
.add(expr.label.name.clone(), result.clone(), init.into())?;
|
||||
// TODO this lets us use the label as a variable name, but not as a tag in most cases
|
||||
result
|
||||
}
|
||||
@ -886,7 +939,7 @@ impl Node<CallExpressionKw> {
|
||||
};
|
||||
|
||||
// Attempt to call the function.
|
||||
let result = {
|
||||
let mut return_value = {
|
||||
// Don't early-return in this block.
|
||||
let result = func.std_lib_fn()(exec_state, args).await;
|
||||
|
||||
@ -900,9 +953,8 @@ impl Node<CallExpressionKw> {
|
||||
exec_state.mod_local.operations.push(op);
|
||||
}
|
||||
result
|
||||
};
|
||||
}?;
|
||||
|
||||
let mut return_value = result?;
|
||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
||||
|
||||
Ok(return_value)
|
||||
@ -912,7 +964,6 @@ impl Node<CallExpressionKw> {
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
// exec_state.
|
||||
let func = exec_state.memory().get(fn_name, source_range)?.clone();
|
||||
let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
|
||||
|
||||
// Track call operation.
|
||||
let op_labeled_args = args
|
||||
@ -932,20 +983,14 @@ impl Node<CallExpressionKw> {
|
||||
source_range: callsite,
|
||||
});
|
||||
|
||||
let return_value = {
|
||||
let previous_dynamic_state =
|
||||
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
|
||||
let result = func
|
||||
.call_fn_kw(args, exec_state, ctx.clone(), callsite)
|
||||
.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.mod_local.dynamic_state = previous_dynamic_state;
|
||||
result?
|
||||
};
|
||||
let return_value = func
|
||||
.call_fn_kw(args, exec_state, ctx.clone(), callsite)
|
||||
.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])
|
||||
})?;
|
||||
|
||||
let result = return_value.ok_or_else(move || {
|
||||
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
||||
@ -1018,9 +1063,11 @@ impl Node<CallExpression> {
|
||||
ctx.clone(),
|
||||
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
|
||||
);
|
||||
let result = {
|
||||
let mut return_value = {
|
||||
// Don't early-return in this block.
|
||||
exec_state.mut_memory().push_new_env_for_rust_call();
|
||||
let result = func.std_lib_fn()(exec_state, args).await;
|
||||
exec_state.mut_memory().pop_env();
|
||||
|
||||
if let Some(mut op) = op {
|
||||
op.set_std_lib_call_is_error(result.is_err());
|
||||
@ -1032,9 +1079,8 @@ impl Node<CallExpression> {
|
||||
exec_state.mod_local.operations.push(op);
|
||||
}
|
||||
result
|
||||
};
|
||||
}?;
|
||||
|
||||
let mut return_value = result?;
|
||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
||||
|
||||
Ok(return_value)
|
||||
@ -1044,7 +1090,6 @@ impl Node<CallExpression> {
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
// exec_state.
|
||||
let func = exec_state.memory().get(fn_name, source_range)?.clone();
|
||||
let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
|
||||
|
||||
// Track call operation.
|
||||
exec_state
|
||||
@ -1059,17 +1104,11 @@ impl Node<CallExpression> {
|
||||
source_range: callsite,
|
||||
});
|
||||
|
||||
let return_value = {
|
||||
let previous_dynamic_state =
|
||||
std::mem::replace(&mut exec_state.mod_local.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.mod_local.dynamic_state = previous_dynamic_state;
|
||||
result?
|
||||
};
|
||||
let return_value = 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])
|
||||
})?;
|
||||
|
||||
let result = return_value.ok_or_else(move || {
|
||||
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
||||
@ -1103,15 +1142,29 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
match result {
|
||||
KclValue::Sketch { value: ref mut sketch } => {
|
||||
for (_, tag) in sketch.tags.iter() {
|
||||
exec_state.mut_memory().update_tag(&tag.value, tag.clone())?;
|
||||
exec_state
|
||||
.mut_memory()
|
||||
.insert_or_update(tag.value.clone(), KclValue::TagIdentifier(Box::new(tag.clone())));
|
||||
}
|
||||
}
|
||||
KclValue::Solid { ref mut value } => {
|
||||
for v in &value.value {
|
||||
if let Some(tag) = v.get_tag() {
|
||||
// Get the past tag and update it.
|
||||
let mut t = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
||||
t.clone()
|
||||
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
||||
let mut t = t.clone();
|
||||
let Some(ref info) = t.info else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: format!("Tag {} does not have path info", tag.name),
|
||||
source_ranges: vec![tag.into()],
|
||||
}));
|
||||
};
|
||||
|
||||
let mut info = info.clone();
|
||||
info.surface = Some(v.clone());
|
||||
info.sketch = value.id;
|
||||
t.info = Some(info);
|
||||
t
|
||||
} else {
|
||||
// It's probably a fillet or a chamfer.
|
||||
// Initialize it.
|
||||
@ -1129,29 +1182,40 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
}
|
||||
};
|
||||
|
||||
let Some(ref info) = t.info else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Tag {} does not have path info", tag.name),
|
||||
source_ranges: vec![tag.into()],
|
||||
}));
|
||||
};
|
||||
|
||||
let mut info = info.clone();
|
||||
info.surface = Some(v.clone());
|
||||
info.sketch = value.id;
|
||||
t.info = Some(info);
|
||||
|
||||
exec_state.mut_memory().update_tag(&tag.name, t.clone())?;
|
||||
exec_state
|
||||
.mut_memory()
|
||||
.insert_or_update(tag.name.clone(), KclValue::TagIdentifier(Box::new(tag_id.clone())));
|
||||
|
||||
// update the sketch tags.
|
||||
value.sketch.tags.insert(tag.name.clone(), t);
|
||||
value.sketch.tags.insert(tag.name.clone(), tag_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the stale sketch in memory and update it.
|
||||
let cur_env_index = exec_state.memory().current_env.index();
|
||||
if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) {
|
||||
current_env.update_sketch_tags(&value.sketch);
|
||||
if !value.sketch.tags.is_empty() {
|
||||
let updates: Vec<_> = exec_state
|
||||
.memory()
|
||||
.find_all_in_current_env(|v| match v {
|
||||
KclValue::Sketch { value: sk } => sk.artifact_id == value.sketch.artifact_id,
|
||||
_ => false,
|
||||
})
|
||||
.map(|(k, v)| {
|
||||
let mut sketch = v.as_sketch().unwrap().clone();
|
||||
for (tag_name, tag_id) in value.sketch.tags.iter() {
|
||||
sketch.tags.insert(tag_name.clone(), tag_id.clone());
|
||||
}
|
||||
(
|
||||
k.clone(),
|
||||
KclValue::Sketch {
|
||||
value: Box::new(sketch),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
updates
|
||||
.into_iter()
|
||||
.for_each(|(k, v)| exec_state.mut_memory().insert_or_update(k, v))
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -1171,7 +1235,7 @@ impl Node<TagDeclarator> {
|
||||
|
||||
exec_state
|
||||
.mut_memory()
|
||||
.add(&self.name, memory_item.clone(), self.into())?;
|
||||
.add(self.name.clone(), memory_item.clone(), self.into())?;
|
||||
|
||||
Ok(self.into())
|
||||
}
|
||||
@ -1450,8 +1514,8 @@ impl Node<PipeExpression> {
|
||||
fn assign_args_to_params(
|
||||
function_expression: NodeRef<'_, FunctionExpression>,
|
||||
args: Vec<Arg>,
|
||||
mut fn_memory: ProgramMemory,
|
||||
) -> Result<ProgramMemory, KclError> {
|
||||
fn_memory: &mut ProgramMemory,
|
||||
) -> Result<(), KclError> {
|
||||
let num_args = function_expression.number_of_args();
|
||||
let (min_params, max_params) = num_args.into_inner();
|
||||
let n = args.len();
|
||||
@ -1475,14 +1539,18 @@ fn assign_args_to_params(
|
||||
for (index, param) in function_expression.params.iter().enumerate() {
|
||||
if let Some(arg) = args.get(index) {
|
||||
// Argument was provided.
|
||||
fn_memory.add(¶m.identifier.name, arg.value.clone(), (¶m.identifier).into())?;
|
||||
fn_memory.add(
|
||||
param.identifier.name.clone(),
|
||||
arg.value.clone(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
} else {
|
||||
// Argument was not provided.
|
||||
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.
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
param.identifier.name.clone(),
|
||||
default_val.clone().into(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
@ -1493,14 +1561,14 @@ fn assign_args_to_params(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(fn_memory)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assign_args_to_params_kw(
|
||||
function_expression: NodeRef<'_, FunctionExpression>,
|
||||
mut args: crate::std::args::KwArgs,
|
||||
mut fn_memory: ProgramMemory,
|
||||
) -> Result<ProgramMemory, KclError> {
|
||||
fn_memory: &mut ProgramMemory,
|
||||
) -> Result<(), KclError> {
|
||||
// Add the arguments to the memory. A new call frame should have already
|
||||
// been created.
|
||||
let source_ranges = vec![function_expression.into()];
|
||||
@ -1522,7 +1590,7 @@ fn assign_args_to_params_kw(
|
||||
}
|
||||
},
|
||||
};
|
||||
fn_memory.add(¶m.identifier.name, arg_val, (¶m.identifier).into())?;
|
||||
fn_memory.add(param.identifier.name.clone(), arg_val, (¶m.identifier).into())?;
|
||||
} else {
|
||||
let Some(unlabeled) = args.unlabeled.take() else {
|
||||
let param_name = ¶m.identifier.name;
|
||||
@ -1540,18 +1608,18 @@ fn assign_args_to_params_kw(
|
||||
});
|
||||
};
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
param.identifier.name.clone(),
|
||||
unlabeled.value.clone(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(fn_memory)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn call_user_defined_function(
|
||||
args: Vec<Arg>,
|
||||
memory: &ProgramMemory,
|
||||
memory: EnvironmentRef,
|
||||
function_expression: NodeRef<'_, FunctionExpression>,
|
||||
exec_state: &mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
@ -1559,29 +1627,32 @@ pub(crate) async fn call_user_defined_function(
|
||||
// Create a new environment to execute the function body in so that local
|
||||
// variables shadow variables in the parent scope. The new environment's
|
||||
// parent should be the environment of the closure.
|
||||
let mut body_memory = memory.clone();
|
||||
let body_env = body_memory.new_env_for_call(memory.current_env);
|
||||
body_memory.current_env = body_env;
|
||||
let fn_memory = assign_args_to_params(function_expression, args, body_memory)?;
|
||||
exec_state.mut_memory().push_new_env_for_call(memory);
|
||||
if let Err(e) = assign_args_to_params(function_expression, args, exec_state.mut_memory()) {
|
||||
exec_state.mut_memory().pop_env();
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Execute the function body using the memory we just created.
|
||||
let (result, fn_memory) = {
|
||||
let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
|
||||
let result = ctx
|
||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||
.await;
|
||||
// Restore the previous memory.
|
||||
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory);
|
||||
let result = ctx
|
||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||
.await;
|
||||
let result = result.map(|_| {
|
||||
exec_state
|
||||
.memory()
|
||||
.get(memory::RETURN_NAME, function_expression.as_source_range())
|
||||
.ok()
|
||||
.cloned()
|
||||
});
|
||||
// Restore the previous memory.
|
||||
exec_state.mut_memory().pop_env();
|
||||
|
||||
(result, fn_memory)
|
||||
};
|
||||
|
||||
result.map(|_| fn_memory.return_)
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) async fn call_user_defined_function_kw(
|
||||
args: crate::std::args::KwArgs,
|
||||
memory: &ProgramMemory,
|
||||
memory: EnvironmentRef,
|
||||
function_expression: NodeRef<'_, FunctionExpression>,
|
||||
exec_state: &mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
@ -1589,31 +1660,34 @@ pub(crate) async fn call_user_defined_function_kw(
|
||||
// Create a new environment to execute the function body in so that local
|
||||
// variables shadow variables in the parent scope. The new environment's
|
||||
// parent should be the environment of the closure.
|
||||
let mut body_memory = memory.clone();
|
||||
let body_env = body_memory.new_env_for_call(memory.current_env);
|
||||
body_memory.current_env = body_env;
|
||||
let fn_memory = assign_args_to_params_kw(function_expression, args, body_memory)?;
|
||||
exec_state.mut_memory().push_new_env_for_call(memory);
|
||||
if let Err(e) = assign_args_to_params_kw(function_expression, args, exec_state.mut_memory()) {
|
||||
exec_state.mut_memory().pop_env();
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Execute the function body using the memory we just created.
|
||||
let (result, fn_memory) = {
|
||||
let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
|
||||
let result = ctx
|
||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||
.await;
|
||||
// Restore the previous memory.
|
||||
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory);
|
||||
let result = ctx
|
||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||
.await;
|
||||
let result = result.map(|_| {
|
||||
exec_state
|
||||
.memory()
|
||||
.get(memory::RETURN_NAME, function_expression.as_source_range())
|
||||
.ok()
|
||||
.cloned()
|
||||
});
|
||||
// Restore the previous memory.
|
||||
exec_state.mut_memory().pop_env();
|
||||
|
||||
(result, fn_memory)
|
||||
};
|
||||
|
||||
result.map(|_| fn_memory.return_)
|
||||
result
|
||||
}
|
||||
|
||||
/// A function being used as a parameter into a stdlib function. This is a
|
||||
/// closure, plus everything needed to execute it.
|
||||
pub struct FunctionParam<'a> {
|
||||
pub inner: Option<&'a MemoryFunction>,
|
||||
pub memory: ProgramMemory,
|
||||
pub memory: EnvironmentRef,
|
||||
pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
pub meta: Vec<Metadata>,
|
||||
pub ctx: ExecutorContext,
|
||||
@ -1624,7 +1698,7 @@ impl FunctionParam<'_> {
|
||||
if let Some(inner) = self.inner {
|
||||
inner(
|
||||
args,
|
||||
self.memory.clone(),
|
||||
self.memory,
|
||||
self.fn_expr.clone(),
|
||||
self.meta.clone(),
|
||||
exec_state,
|
||||
@ -1632,7 +1706,7 @@ impl FunctionParam<'_> {
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
call_user_defined_function(args, &self.memory, self.fn_expr.as_ref(), exec_state, &self.ctx).await
|
||||
call_user_defined_function(args, self.memory, self.fn_expr.as_ref(), exec_state, &self.ctx).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1650,8 +1724,12 @@ impl JsonSchema for FunctionParam<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
execution::parse_execute,
|
||||
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::parsing::ast::types::{DefaultParamVal, Identifier, Parameter};
|
||||
|
||||
#[test]
|
||||
fn test_assign_args_to_params() {
|
||||
@ -1690,7 +1768,7 @@ mod test {
|
||||
let mut program_memory = ProgramMemory::new();
|
||||
for (name, item) in items {
|
||||
program_memory
|
||||
.add(name.as_str(), item.clone(), SourceRange::default())
|
||||
.add(name.clone(), item.clone(), SourceRange::default())
|
||||
.unwrap();
|
||||
}
|
||||
program_memory
|
||||
@ -1775,11 +1853,26 @@ mod test {
|
||||
digest: None,
|
||||
});
|
||||
let args = args.into_iter().map(Arg::synthetic).collect();
|
||||
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
|
||||
let mut actual = ProgramMemory::new();
|
||||
let actual = assign_args_to_params(func_expr, args, &mut actual).map(|_| actual);
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn multiple_returns() {
|
||||
let program = r#"fn foo() {
|
||||
return 0
|
||||
return 42
|
||||
}
|
||||
|
||||
a = foo()
|
||||
"#;
|
||||
|
||||
let result = parse_execute(program).await;
|
||||
assert!(result.unwrap_err().to_string().contains("return"));
|
||||
}
|
||||
}
|
||||
|
@ -564,29 +564,8 @@ pub struct Solid {
|
||||
}
|
||||
|
||||
impl Solid {
|
||||
pub(crate) fn get_all_edge_cut_ids(&self) -> Vec<uuid::Uuid> {
|
||||
self.edge_cuts.iter().map(|foc| foc.id()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// An solid ID and its fillet and chamfer IDs. This is needed for lazy
|
||||
/// fillet evaluation.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct SolidLazyIds {
|
||||
pub solid_id: uuid::Uuid,
|
||||
pub sketch_id: uuid::Uuid,
|
||||
/// Chamfers or fillets on this solid.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_cuts: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
impl From<&Solid> for SolidLazyIds {
|
||||
fn from(eg: &Solid) -> Self {
|
||||
Self {
|
||||
solid_id: eg.id,
|
||||
sketch_id: eg.sketch.id,
|
||||
edge_cuts: eg.edge_cuts.iter().map(|foc| foc.id()).collect(),
|
||||
}
|
||||
pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
|
||||
self.edge_cuts.iter().map(|foc| foc.id())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use std::{ffi::OsStr, path::Path, str::FromStr};
|
||||
|
||||
use anyhow::Result;
|
||||
use kcmc::{
|
||||
coord::{Axis, AxisDirectionPair, Direction, System},
|
||||
coord::{System, KITTYCAD},
|
||||
each_cmd as mcmd,
|
||||
format::InputFormat,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
@ -15,11 +15,11 @@ use kittycad_modeling_cmds as kcmc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecutorContext;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{ExecState, ImportedGeometry},
|
||||
execution::{annotations, kcl_value::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
|
||||
fs::FileSystem,
|
||||
parsing::ast::types::{Node, NonCodeNode},
|
||||
source_range::SourceRange,
|
||||
};
|
||||
|
||||
@ -28,16 +28,7 @@ use crate::{
|
||||
// * Forward: -Y
|
||||
// * Up: +Z
|
||||
// * Handedness: Right
|
||||
pub const ZOO_COORD_SYSTEM: System = System {
|
||||
forward: AxisDirectionPair {
|
||||
axis: Axis::Y,
|
||||
direction: Direction::Negative,
|
||||
},
|
||||
up: AxisDirectionPair {
|
||||
axis: Axis::Z,
|
||||
direction: Direction::Positive,
|
||||
},
|
||||
};
|
||||
pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
|
||||
|
||||
pub async fn import_foreign(
|
||||
file_path: &Path,
|
||||
@ -54,18 +45,19 @@ pub async fn import_foreign(
|
||||
}));
|
||||
}
|
||||
|
||||
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("No file extension found for `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: e.to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let ext_format =
|
||||
get_import_format_from_extension(file_path.extension().and_then(OsStr::to_str).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("No file extension found for `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: e.to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
// Get the format type from the extension of the file.
|
||||
let format = if let Some(format) = format {
|
||||
@ -162,11 +154,136 @@ pub async fn import_foreign(
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn format_from_annotations(
|
||||
non_code_meta: &[Node<NonCodeNode>],
|
||||
path: &Path,
|
||||
import_source_range: SourceRange,
|
||||
) -> Result<Option<InputFormat>, KclError> {
|
||||
let Some(props) = annotations::unnamed_properties(non_code_meta.iter().map(|n| &n.value)) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut result = None;
|
||||
for p in props {
|
||||
if p.key.name == annotations::IMPORT_FORMAT {
|
||||
result = Some(
|
||||
get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unknown format for import, expected one of: {}",
|
||||
annotations::IMPORT_FORMAT_VALUES.join(", ")
|
||||
),
|
||||
source_ranges: vec![p.as_source_range()],
|
||||
})
|
||||
})?,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = result
|
||||
.or_else(|| {
|
||||
path.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.and_then(|ext| get_import_format_from_extension(ext).ok())
|
||||
})
|
||||
.ok_or(KclError::Semantic(KclErrorDetails {
|
||||
message: "Unknown or missing extension, and no specified format for imported file".to_owned(),
|
||||
source_ranges: vec![import_source_range],
|
||||
}))?;
|
||||
|
||||
for p in props {
|
||||
match p.key.name.as_str() {
|
||||
annotations::IMPORT_COORDS => {
|
||||
set_coords(&mut result, annotations::expect_ident(&p.value)?, p.as_source_range())?;
|
||||
}
|
||||
annotations::IMPORT_LENGTH_UNIT => {
|
||||
set_length_unit(&mut result, annotations::expect_ident(&p.value)?, p.as_source_range())?;
|
||||
}
|
||||
annotations::IMPORT_FORMAT => {}
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unexpected annotation for import, expected one of: {}, {}, {}",
|
||||
annotations::IMPORT_FORMAT,
|
||||
annotations::IMPORT_COORDS,
|
||||
annotations::IMPORT_LENGTH_UNIT
|
||||
),
|
||||
source_ranges: vec![p.as_source_range()],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let mut coords = None;
|
||||
for (name, val) in annotations::IMPORT_COORDS_VALUES {
|
||||
if coords_str == name {
|
||||
coords = Some(*val);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(coords) = coords else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unknown coordinate system: {coords_str}, expected one of: {}",
|
||||
annotations::IMPORT_COORDS_VALUES
|
||||
.iter()
|
||||
.map(|(n, _)| *n)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
match fmt {
|
||||
InputFormat::Obj(opts) => opts.coords = coords,
|
||||
InputFormat::Ply(opts) => opts.coords = coords,
|
||||
InputFormat::Stl(opts) => opts.coords = coords,
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"`{}` option cannot be applied to the specified format",
|
||||
annotations::IMPORT_COORDS
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_length_unit(fmt: &mut InputFormat, units_str: &str, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let units = UnitLen::from_str(units_str, source_range)?;
|
||||
|
||||
match fmt {
|
||||
InputFormat::Obj(opts) => opts.units = units.into(),
|
||||
InputFormat::Ply(opts) => opts.units = units.into(),
|
||||
InputFormat::Stl(opts) => opts.units = units.into(),
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"`{}` option cannot be applied to the specified format",
|
||||
annotations::IMPORT_LENGTH_UNIT
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PreImportedGeometry {
|
||||
id: Uuid,
|
||||
command: mcmd::ImportFiles,
|
||||
source_range: SourceRange,
|
||||
pub source_range: SourceRange,
|
||||
}
|
||||
|
||||
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
|
||||
@ -201,10 +318,7 @@ pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) ->
|
||||
}
|
||||
|
||||
/// Get the source format from the extension.
|
||||
fn get_import_format_from_extension(ext: &OsStr) -> Result<InputFormat> {
|
||||
let ext = ext
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid file extension: `{ext:?}`"))?;
|
||||
fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> {
|
||||
let format = match FileImportFormat::from_str(ext) {
|
||||
Ok(format) => format,
|
||||
Err(_) => {
|
||||
@ -291,3 +405,130 @@ fn get_name_of_format(type_: InputFormat) -> &'static str {
|
||||
InputFormat::Stl(_) => "stl",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn annotations() {
|
||||
// no annotations
|
||||
assert!(
|
||||
format_from_annotations(&[], Path::new("../foo.txt"), SourceRange::default(),)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// no format, no options
|
||||
let text = "@()\nimport '../foo.gltf' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let non_code_meta = parsed.non_code_meta.get(0);
|
||||
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.gltf"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
);
|
||||
|
||||
// format, no options
|
||||
let text = "@(format = gltf)\nimport '../foo.txt' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let non_code_meta = parsed.non_code_meta.get(0);
|
||||
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.txt"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
);
|
||||
|
||||
// format, no extension (wouldn't parse but might some day)
|
||||
let fmt = format_from_annotations(non_code_meta, Path::new("../foo"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
);
|
||||
|
||||
// format, options
|
||||
let text = "@(format = obj, coords = vulkan, lengthUnit = ft)\nimport '../foo.txt' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let non_code_meta = parsed.non_code_meta.get(0);
|
||||
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.txt"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Obj(kittycad_modeling_cmds::format::obj::import::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::VULKAN,
|
||||
units: kittycad_modeling_cmds::units::UnitLength::Feet,
|
||||
})
|
||||
);
|
||||
|
||||
// no format, options
|
||||
let text = "@(coords = vulkan, lengthUnit = ft)\nimport '../foo.obj' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let non_code_meta = parsed.non_code_meta.get(0);
|
||||
let fmt = format_from_annotations(non_code_meta, Path::new("../foo.obj"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Obj(kittycad_modeling_cmds::format::obj::import::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::VULKAN,
|
||||
units: kittycad_modeling_cmds::units::UnitLength::Feet,
|
||||
})
|
||||
);
|
||||
|
||||
// err - format, options, but no options for specified format
|
||||
assert_annotation_error(
|
||||
"@(format = gltf, lengthUnit = ft)\nimport '../foo.txt' as foo",
|
||||
"../foo.txt",
|
||||
"`lengthUnit` option cannot be applied",
|
||||
);
|
||||
// err - no format, options, but no options for specified format
|
||||
assert_annotation_error(
|
||||
"@(lengthUnit = ft)\nimport '../foo.gltf' as foo",
|
||||
"../foo.gltf",
|
||||
"lengthUnit` option cannot be applied",
|
||||
);
|
||||
// err - bad option
|
||||
assert_annotation_error(
|
||||
"@(format = obj, coords = vulkan, lengthUni = ft)\nimport '../foo.txt' as foo",
|
||||
"../foo.txt",
|
||||
"Unexpected annotation",
|
||||
);
|
||||
// err - bad format
|
||||
assert_annotation_error(
|
||||
"@(format = foo)\nimport '../foo.txt' as foo",
|
||||
"../foo.txt",
|
||||
"Unknown format for import",
|
||||
);
|
||||
// err - bad coord value
|
||||
assert_annotation_error(
|
||||
"@(format = gltf, coords = north)\nimport '../foo.txt' as foo",
|
||||
"../foo.txt",
|
||||
"Unknown coordinate system",
|
||||
);
|
||||
// err - bad unit value
|
||||
assert_annotation_error(
|
||||
"@(format = gltf, lengthUnit = gallons)\nimport '../foo.txt' as foo",
|
||||
"../foo.txt",
|
||||
"Unexpected value for length units",
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_annotation_error(src: &str, path: &str, expected: &str) {
|
||||
let parsed = crate::Program::parse_no_errs(src).unwrap().ast;
|
||||
let non_code_meta = parsed.non_code_meta.get(0);
|
||||
let err = format_from_annotations(non_code_meta, Path::new(path), SourceRange::default()).unwrap_err();
|
||||
assert!(
|
||||
err.message().contains(expected),
|
||||
"Expected: `{expected}`, found `{}`",
|
||||
err.message()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
exec::{ProgramMemory, Sketch},
|
||||
exec::Sketch,
|
||||
execution::{
|
||||
Face, Helix, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier,
|
||||
},
|
||||
@ -18,6 +18,8 @@ use crate::{
|
||||
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
|
||||
use super::memory::EnvironmentRef;
|
||||
|
||||
pub type KclObjectFields = HashMap<String, KclValue>;
|
||||
|
||||
/// Any KCL value.
|
||||
@ -94,7 +96,7 @@ pub enum KclValue {
|
||||
func: Option<MemoryFunction>,
|
||||
#[schemars(skip)]
|
||||
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
memory: Box<ProgramMemory>,
|
||||
memory: EnvironmentRef,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
@ -108,6 +110,12 @@ pub enum KclValue {
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
// Only used for memory management. Should never be visible outside of the memory module.
|
||||
Tombstone {
|
||||
value: (),
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<SketchSet> for KclValue {
|
||||
@ -166,6 +174,7 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
KclValue::Module { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone SourceRange"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,6 +206,7 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
KclValue::Object { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Module { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::KclNone { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone &SourceRange"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -224,6 +234,7 @@ impl KclValue {
|
||||
KclValue::Function { meta, .. } => meta.clone(),
|
||||
KclValue::Module { meta, .. } => meta.clone(),
|
||||
KclValue::KclNone { meta, .. } => meta.clone(),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone Metadata"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,6 +302,7 @@ impl KclValue {
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
KclValue::Tombstone { .. } => "TOMBSTONE",
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,6 +314,16 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self {
|
||||
let mut result = self.clone();
|
||||
if let KclValue::Function { ref mut memory, .. } = result {
|
||||
if let Some(new) = env_map.get(memory) {
|
||||
*memory = *new;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Put the number into a KCL value.
|
||||
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
||||
Self::Number { value: f, meta }
|
||||
@ -406,6 +428,14 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_sketch(&self) -> Option<&Sketch> {
|
||||
if let KclValue::Sketch { value } = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
if let KclValue::Number { value, meta: _ } = &self {
|
||||
Some(*value)
|
||||
@ -454,7 +484,7 @@ impl KclValue {
|
||||
Some(FnAsArg {
|
||||
func: func.as_ref(),
|
||||
expr: expression.to_owned(),
|
||||
memory: memory.to_owned(),
|
||||
memory: *memory,
|
||||
})
|
||||
}
|
||||
|
||||
@ -518,24 +548,19 @@ impl KclValue {
|
||||
} = &self
|
||||
else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "not a in memory function".to_string(),
|
||||
message: "not an in-memory function".to_string(),
|
||||
source_ranges: vec![],
|
||||
}));
|
||||
};
|
||||
if let Some(func) = func {
|
||||
func(
|
||||
args,
|
||||
closure_memory.as_ref().clone(),
|
||||
expression.clone(),
|
||||
meta.clone(),
|
||||
exec_state,
|
||||
ctx,
|
||||
)
|
||||
.await
|
||||
exec_state.mut_memory().push_new_env_for_call(*closure_memory);
|
||||
let result = func(args, *closure_memory, expression.clone(), meta.clone(), exec_state, ctx).await;
|
||||
exec_state.mut_memory().pop_env();
|
||||
result
|
||||
} else {
|
||||
crate::execution::exec_ast::call_user_defined_function(
|
||||
args,
|
||||
closure_memory.as_ref(),
|
||||
*closure_memory,
|
||||
expression.as_ref(),
|
||||
exec_state,
|
||||
&ctx,
|
||||
@ -570,7 +595,7 @@ impl KclValue {
|
||||
} else {
|
||||
crate::execution::exec_ast::call_user_defined_function_kw(
|
||||
args.kw_args,
|
||||
closure_memory.as_ref(),
|
||||
*closure_memory,
|
||||
expression.as_ref(),
|
||||
exec_state,
|
||||
&ctx,
|
||||
@ -649,6 +674,19 @@ impl From<UnitLen> for crate::UnitLength {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
|
||||
fn from(unit: UnitLen) -> Self {
|
||||
match unit {
|
||||
UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
|
||||
UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
|
||||
UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
|
||||
UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
|
||||
UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
|
||||
UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,17 +3,8 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||
pub use cache::bust_cache;
|
||||
use cache::OldAstState;
|
||||
pub use cad_op::Operation;
|
||||
pub use exec_ast::FunctionParam;
|
||||
pub use geometry::*;
|
||||
pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
|
||||
@ -21,10 +12,8 @@ use kcmc::{
|
||||
ImageFormat, ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
pub use memory::ProgramMemory;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use state::{ExecState, IdGenerator, MetaSettings};
|
||||
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
@ -41,6 +30,18 @@ use crate::{
|
||||
ExecError, KclErrorWithOutputs,
|
||||
};
|
||||
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||
pub use cache::{bust_cache, clear_mem_cache};
|
||||
pub use cad_op::Operation;
|
||||
pub use exec_ast::FunctionParam;
|
||||
pub use geometry::*;
|
||||
pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
pub use memory::{EnvironmentRef, ProgramMemory};
|
||||
pub use state::{ExecState, IdGenerator, MetaSettings};
|
||||
|
||||
pub(crate) mod annotations;
|
||||
mod artifact;
|
||||
pub(crate) mod cache;
|
||||
@ -53,12 +54,12 @@ mod memory;
|
||||
mod state;
|
||||
|
||||
/// Outcome of executing a program. This is used in TS.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecOutcome {
|
||||
/// Program variable bindings of the top-level module.
|
||||
pub memory: ProgramMemory,
|
||||
/// Variables in the top-level of the root module. Note that functions will have an invalid env ref.
|
||||
pub variables: IndexMap<String, KclValue>,
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
pub operations: Vec<Operation>,
|
||||
@ -133,7 +134,7 @@ impl std::hash::Hash for TagIdentifier {
|
||||
pub type MemoryFunction =
|
||||
fn(
|
||||
s: Vec<Arg>,
|
||||
memory: ProgramMemory,
|
||||
memory: EnvironmentRef,
|
||||
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
metadata: Vec<Metadata>,
|
||||
exec_state: &ExecState,
|
||||
@ -160,7 +161,6 @@ pub struct TagEngineInfo {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BodyType {
|
||||
Root,
|
||||
Sketch,
|
||||
Block,
|
||||
}
|
||||
|
||||
@ -496,17 +496,56 @@ impl ExecutorContext {
|
||||
pub async fn run_mock(
|
||||
&self,
|
||||
program: crate::Program,
|
||||
program_memory_override: Option<ProgramMemory>,
|
||||
use_prev_memory: bool,
|
||||
variables: IndexMap<String, KclValue>,
|
||||
) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
assert!(self.is_mock());
|
||||
|
||||
let mut exec_state = ExecState::new(&self.settings);
|
||||
if let Some(program_memory_override) = program_memory_override {
|
||||
exec_state.mod_local.memory = program_memory_override;
|
||||
let mut mem = if use_prev_memory {
|
||||
cache::read_old_memory()
|
||||
.await
|
||||
.unwrap_or_else(|| exec_state.memory().clone())
|
||||
} else {
|
||||
exec_state.memory().clone()
|
||||
};
|
||||
|
||||
// Add any extra variables to memory
|
||||
let mut to_restore = Vec::new();
|
||||
for (k, v) in variables {
|
||||
crate::log::log(format!("add var: {k}"));
|
||||
to_restore.push((k.clone(), mem.get(&k, SourceRange::default()).ok().cloned()));
|
||||
mem.add(k, v, SourceRange::synthetic())
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
}
|
||||
|
||||
// Push a scope so that old variables can be overwritten (since we might be re-executing some
|
||||
// part of the scene).
|
||||
mem.push_new_env_for_scope();
|
||||
|
||||
*exec_state.mut_memory() = mem;
|
||||
|
||||
self.inner_run(&program.ast, &mut exec_state).await?;
|
||||
Ok(exec_state.to_wasm_outcome())
|
||||
|
||||
// Restore any temporary variables, then save any newly created variables back to
|
||||
// memory in case another run wants to use them. Note this is just saved to the preserved
|
||||
// memory, not to the exec_state which is not cached for mock execution.
|
||||
let mut mem = exec_state.memory().clone();
|
||||
|
||||
let top = mem.pop_and_preserve_env();
|
||||
for (k, v) in to_restore {
|
||||
match v {
|
||||
Some(v) => mem.insert_or_update(k, v),
|
||||
None => mem.clear(k),
|
||||
}
|
||||
}
|
||||
mem.squash_env(top);
|
||||
|
||||
cache::write_old_memory(mem).await;
|
||||
|
||||
let outcome = exec_state.to_mock_wasm_outcome();
|
||||
crate::log::log(format!("return mock {:#?}", outcome.variables));
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
@ -516,7 +555,7 @@ impl ExecutorContext {
|
||||
ast: old_ast,
|
||||
exec_state: old_state,
|
||||
settings: old_settings,
|
||||
}) = cache::read_old_ast_memory().await
|
||||
}) = cache::read_old_ast().await
|
||||
{
|
||||
let old = CacheInformation {
|
||||
ast: &old_ast,
|
||||
@ -595,7 +634,7 @@ impl ExecutorContext {
|
||||
result?;
|
||||
|
||||
// Save this as the last successful execution to the cache.
|
||||
cache::write_old_ast_memory(OldAstState {
|
||||
cache::write_old_ast(OldAstState {
|
||||
ast: program,
|
||||
exec_state: exec_state.clone(),
|
||||
settings: self.settings.clone(),
|
||||
@ -661,6 +700,14 @@ impl ExecutorContext {
|
||||
)
|
||||
})?;
|
||||
|
||||
if !self.is_mock() {
|
||||
cache::write_old_memory(exec_state.memory().clone()).await;
|
||||
}
|
||||
|
||||
crate::log::log(format!(
|
||||
"Post interpretation KCL memory stats: {:#?}",
|
||||
exec_state.memory().stats
|
||||
));
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok(session_data)
|
||||
}
|
||||
@ -1188,7 +1235,7 @@ fn layer = () => {
|
||||
const x = 5
|
||||
|
||||
// The 10 layers are replicas of each other, with a transform applied to each.
|
||||
let shape = layer() |> patternTransform(10, transform, %)
|
||||
let shape = layer() |> patternTransform(instances = 10, transform = transform)
|
||||
"#;
|
||||
|
||||
let result = parse_execute(ast).await;
|
||||
@ -1588,12 +1635,7 @@ let w = f() + f()
|
||||
ctx.run_with_caching(old_program).await.unwrap();
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let id_generator = cache::read_old_ast_memory()
|
||||
.await
|
||||
.unwrap()
|
||||
.exec_state
|
||||
.global
|
||||
.id_generator;
|
||||
let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
|
||||
|
||||
let code = r#"sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([62.74, 206.13], %)
|
||||
@ -1614,12 +1656,7 @@ let w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(program).await.unwrap();
|
||||
|
||||
let new_id_generator = cache::read_old_ast_memory()
|
||||
.await
|
||||
.unwrap()
|
||||
.exec_state
|
||||
.global
|
||||
.id_generator;
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
|
||||
|
||||
assert_eq!(id_generator, new_id_generator);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, ExecutorSettings,
|
||||
KclValue, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen,
|
||||
KclValue, Operation, ProgramMemory, UnitAngle, UnitLen,
|
||||
},
|
||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::NonCodeValue,
|
||||
@ -27,6 +27,8 @@ pub struct ExecState {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GlobalState {
|
||||
/// Program variable bindings.
|
||||
pub memory: ProgramMemory,
|
||||
/// The stable artifact ID generator.
|
||||
pub id_generator: IdGenerator,
|
||||
/// Map from source file absolute path to module ID.
|
||||
@ -50,13 +52,9 @@ pub struct GlobalState {
|
||||
pub mod_loader: ModuleLoader,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleState {
|
||||
/// Program variable bindings.
|
||||
pub memory: ProgramMemory,
|
||||
/// Dynamic state that follows dynamic flow of the program.
|
||||
pub dynamic_state: DynamicState,
|
||||
/// The current value of the pipe operator returned from the previous
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
pub pipe_value: Option<KclValue>,
|
||||
@ -99,7 +97,11 @@ impl ExecState {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
memory: self.mod_local.memory,
|
||||
variables: self
|
||||
.memory()
|
||||
.find_all_in_current_env(|_| true)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
operations: self.mod_local.operations,
|
||||
artifacts: self.global.artifacts,
|
||||
artifact_commands: self.global.artifact_commands,
|
||||
@ -107,12 +109,28 @@ impl ExecState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_mock_wasm_outcome(self) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.memory()
|
||||
.find_all_in_current_env(|_| true)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
operations: Default::default(),
|
||||
artifacts: Default::default(),
|
||||
artifact_commands: Default::default(),
|
||||
artifact_graph: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory(&self) -> &ProgramMemory {
|
||||
&self.mod_local.memory
|
||||
&self.global.memory
|
||||
}
|
||||
|
||||
pub fn mut_memory(&mut self) -> &mut ProgramMemory {
|
||||
&mut self.mod_local.memory
|
||||
&mut self.global.memory
|
||||
}
|
||||
|
||||
pub fn next_uuid(&mut self) -> Uuid {
|
||||
@ -148,11 +166,29 @@ impl ExecState {
|
||||
pub fn angle_unit(&self) -> UnitAngle {
|
||||
self.mod_local.settings.default_angle_units
|
||||
}
|
||||
|
||||
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
|
||||
KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
self.global
|
||||
.mod_loader
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
path,
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
fn new(settings: &ExecutorSettings) -> Self {
|
||||
let mut global = GlobalState {
|
||||
memory: ProgramMemory::new(),
|
||||
id_generator: Default::default(),
|
||||
path_to_source_id: Default::default(),
|
||||
module_infos: Default::default(),
|
||||
@ -181,8 +217,6 @@ impl GlobalState {
|
||||
impl ModuleState {
|
||||
pub(super) fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
ModuleState {
|
||||
memory: Default::default(),
|
||||
dynamic_state: Default::default(),
|
||||
pipe_value: Default::default(),
|
||||
module_exports: Default::default(),
|
||||
operations: Default::default(),
|
||||
@ -239,46 +273,6 @@ impl MetaSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Dynamic state that depends on the dynamic flow of the program, like the call
|
||||
/// stack. If the language had exceptions, for example, you could store the
|
||||
/// stack of exception handlers here.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct DynamicState {
|
||||
pub solid_ids: Vec<SolidLazyIds>,
|
||||
}
|
||||
|
||||
impl DynamicState {
|
||||
#[must_use]
|
||||
pub(super) fn merge(&self, memory: &ProgramMemory) -> Self {
|
||||
let mut merged = self.clone();
|
||||
merged.append(memory);
|
||||
merged
|
||||
}
|
||||
|
||||
fn append(&mut self, memory: &ProgramMemory) {
|
||||
for env in &memory.environments {
|
||||
for item in env.bindings.values() {
|
||||
if let KclValue::Solid { value } = item {
|
||||
self.solid_ids.push(SolidLazyIds::from(value.as_ref()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<uuid::Uuid> {
|
||||
self.solid_ids
|
||||
.iter()
|
||||
.flat_map(|eg| {
|
||||
if eg.sketch_id == sketch_id {
|
||||
eg.edge_cuts.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// A generator for ArtifactIds that can be stable across executions.
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -83,7 +83,9 @@ mod wasm;
|
||||
pub use coredump::CoreDump;
|
||||
pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
|
||||
pub use execution::{bust_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d};
|
||||
pub use execution::{
|
||||
bust_cache, clear_mem_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
|
||||
};
|
||||
pub use lsp::{
|
||||
copilot::Backend as CopilotLspBackend,
|
||||
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user