Compare commits

...

7 Commits

Author SHA1 Message Date
4c0ea136e0 Remove artifact graph mind maps (#5349)
* Remove artifact graph mind maps

* Delete mind map snap files
2025-02-11 19:28:00 -05:00
81c0035adc Remove unneeded AST test in TypeScript (#5343) 2025-02-11 23:55:05 +00:00
c68e5d7774 KCL: Linear/circular pattern in stdlib should use kwargs (#5315)
Part of https://github.com/KittyCAD/modeling-app/issues/4600
2025-02-11 16:06:47 -06:00
ed8a0e4aaa Fix formatting to preserve annotations when program is otherwise empty (#5310)
* Fix formatting to preserve annotations when program is otherwise empty

* Change to not allocate extra String
2025-02-11 21:58:48 +00:00
322f398049 ProgramMemory refactor - eliminate copies of memory (#5273)
* refactor program memory for encapsulation and remove special treatment of return values

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

* Refactor ProgramMemory to isolate mutation

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

* Refactor ProgramMemory

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

* Generated output

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

* Cache the result of executing modules for their items

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

* Compress envs when popped

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

* Remove the last traces of geometry from the memory module

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

* docs

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

* Fixups

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

* Frontend

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

* Improve Environment GC

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

* Fixup mock execution frontend and interpreter, misc bug fixes

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-11 21:22:56 +00:00
bc4d254297 Release the Share Link feature (#5228)
* WIP: Release the Share Link feature
Fixes #5227

* Clean up
2025-02-11 16:06:24 -05:00
10b7a772bf Release kcl-test-server 0.1.21 (#5345) 2025-02-11 14:05:31 -06:00
358 changed files with 31193 additions and 21980 deletions

File diff suppressed because one or more lines are too long

View File

@ -33,7 +33,14 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
```js ```js
// Create a helix around the Z axis. // 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. // Create a spring by sweeping around the helix path.
springSketch = startSketchOn('YZ') springSketch = startSketchOn('YZ')
@ -49,7 +56,14 @@ helper001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [0, 10], tag = $edge001) |> 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. // Create a spring by sweeping around the helix path.
springSketch = startSketchOn('XY') springSketch = startSketchOn('XY')
@ -61,12 +75,19 @@ springSketch = startSketchOn('XY')
```js ```js
// Create a helix around a custom axis. // 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 = { custom = {
axis = [0, 0, 1.0], axis = [0, 0, 1.0],
origin = [0, 0.25, 0] origin = [0, 0.25, 0]
} }
}) },
)
// Create a spring by sweeping around the helix path. // Create a spring by sweeping around the helix path.
springSketch = startSketchOn('XY') springSketch = startSketchOn('XY')

File diff suppressed because one or more lines are too long

View File

@ -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. 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 ```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 | | 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) | Which sketch(es) to pattern | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | 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 ### Returns
@ -34,12 +38,12 @@ exampleSketch = startSketchOn('XZ')
|> line(end = [-1, 0]) |> line(end = [-1, 0])
|> line(end = [0, -5]) |> line(end = [0, -5])
|> close() |> close()
|> patternCircular2d({ |> patternCircular2d(
center = [0, 0], center = [0, 0],
instances = 13, instances = 13,
arcDegrees = 360, arcDegrees = 360,
rotateDuplicates = true rotateDuplicates = true,
}, %) )
example = extrude(exampleSketch, length = 1) example = extrude(exampleSketch, length = 1)
``` ```

File diff suppressed because one or more lines are too long

View File

@ -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. of distance between each repetition, some specified number of times.
```js ```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 | | 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) | The sketch(es) to duplicate | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | 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 |
| `use_original` | `bool` | | No | | `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 ### Returns
@ -31,11 +33,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
```js ```js
exampleSketch = startSketchOn('XZ') exampleSketch = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %) |> circle({ center = [0, 0], radius = 1 }, %)
|> patternLinear2d({ |> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
axis = [1, 0],
instances = 7,
distance = 4
}, %)
example = extrude(exampleSketch, length = 1) example = extrude(exampleSketch, length = 1)
``` ```

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,14 @@ sweepSketch = startSketchOn('XY')
// Create a helix around the Z axis. // 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. // Create a spring by sweeping around the helix path.
springSketch = startSketchOn('YZ') springSketch = startSketchOn('YZ')

View File

@ -0,0 +1,16 @@
---
title: "EnvironmentRef"
excerpt: ""
layout: manual
---
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)

View File

@ -311,7 +311,7 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Function`| | No | | `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 | | `__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 |
----

View File

@ -17,6 +17,5 @@ layout: manual
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No | | `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
| `currentEnv` |`integer`| | No | | `currentEnv` |`integer`| | No |
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |

View 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`)

View File

@ -1117,13 +1117,14 @@ openSketch = startSketchOn('XY')
}) => { }) => {
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 } 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 homePage.goToModelingScene()
await test.step(`Look for the red of the default plane`, async () => { // await test.step(`Look for the red of the default plane`, async () => {
await scene.expectPixelColor([96, 52, 52], testPoint, 15) // await scene.expectPixelColor([96, 52, 52], testPoint, 15)
}) // })
await test.step(`Go through the command bar flow`, async () => { await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click() await toolbar.helixButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
@ -1154,7 +1155,7 @@ openSketch = startSketchOn('XY')
await editor.expectEditor.toContain(expectedOutput) await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({ await editor.expectState({
diagnostics: [], diagnostics: [],
activeLines: [expectedOutput], activeLines: [expectedLine],
highlightedCode: '', highlightedCode: '',
}) })
// Red plane is now gone, white helix is there // Red plane is now gone, white helix is there

View File

@ -192,11 +192,11 @@ extrude001 = extrude(sketch001, length = 50)
|> line(end = [0, -1]) |> line(end = [0, -1])
|> close() |> close()
|> extrude(length = 1) |> extrude(length = 1)
|> patternLinear3d({ |> patternLinear3d(
axis: [1, 0, 1], axis = [1, 0, 1],
repetitions: 3, repetitions = 3,
distance: 6 distance = 6,
}, %)` )`
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) 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

View File

@ -85,7 +85,7 @@
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh", "fetch:wasm": "./get-latest-wasm-bundle.sh",
"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/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)", "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-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", "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",

View File

@ -31,7 +31,6 @@ import {
recast, recast,
defaultSourceRange, defaultSourceRange,
resultIsOk, resultIsOk,
ProgramMemory,
topLevelRange, topLevelRange,
} from 'lang/wasm' } from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon' import { CustomIcon, CustomIconName } from 'components/CustomIcon'
@ -425,7 +424,7 @@ export async function deleteSegment({
modifiedAst = deleteSegmentFromPipeExpression( modifiedAst = deleteSegmentFromPipeExpression(
dependentRanges, dependentRanges,
modifiedAst, modifiedAst,
kclManager.programMemory, kclManager.variables,
codeManager.code, codeManager.code,
pathToNode pathToNode
) )
@ -439,8 +438,8 @@ export async function deleteSegment({
const testExecute = await executeAst({ const testExecute = await executeAst({
ast: modifiedAst, ast: modifiedAst,
engineCommandManager: engineCommandManager, engineCommandManager: engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride: ProgramMemory.empty(), usePrevMemory: false,
}) })
if (testExecute.errors.length) { if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.') toast.error('Segment tag used outside of current Sketch. Could not delete.')
@ -675,7 +674,7 @@ const ConstraintSymbol = ({
shallowPath, shallowPath,
argPosition, argPosition,
kclManager.ast, kclManager.ast,
kclManager.programMemory kclManager.variables
) )
if (!transform) return if (!transform) return

View File

@ -47,7 +47,6 @@ import {
PathToNode, PathToNode,
PipeExpression, PipeExpression,
Program, Program,
ProgramMemory,
recast, recast,
Sketch, Sketch,
Solid, Solid,
@ -61,6 +60,7 @@ import {
SourceRange, SourceRange,
topLevelRange, topLevelRange,
CallExpressionKw, CallExpressionKw,
VariableMap,
} from 'lang/wasm' } from 'lang/wasm'
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib' import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
import { import {
@ -158,7 +158,6 @@ type Vec3Array = [number, number, number]
export class SceneEntities { export class SceneEntities {
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
scene: Scene scene: Scene
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
activeSegments: { [key: string]: Group } = {} activeSegments: { [key: string]: Group } = {}
intersectionPlane: Mesh | null = null intersectionPlane: Mesh | null = null
axisGroup: Group | null = null axisGroup: Group | null = null
@ -502,29 +501,25 @@ export class SceneEntities {
selectionRanges?: Selections selectionRanges?: Selections
}): Promise<{ }): Promise<{
truncatedAst: Node<Program> truncatedAst: Node<Program>
programMemoryOverride: ProgramMemory
sketch: Sketch sketch: Sketch
variableDeclarationName: string variableDeclarationName: string
}> { }> {
const prepared = this.prepareTruncatedMemoryAndAst( const prepared = this.prepareTruncatedAst(
sketchPathToNode || [], sketchPathToNode || [],
maybeModdedAst maybeModdedAst
) )
if (err(prepared)) return Promise.reject(prepared) if (err(prepared)) return Promise.reject(prepared)
const { truncatedAst, programMemoryOverride, variableDeclarationName } = const { truncatedAst, variableDeclarationName } = prepared
prepared
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory
const sketch = sketchFromPathToNode({ const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
ast: maybeModdedAst, ast: maybeModdedAst,
programMemory, variables: execState.variables,
}) })
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found') if (!sketch) return Promise.reject('sketch not found')
@ -532,11 +527,9 @@ export class SceneEntities {
if (!isArray(sketch?.paths)) if (!isArray(sketch?.paths))
return { return {
truncatedAst, truncatedAst,
programMemoryOverride,
sketch, sketch,
variableDeclarationName, variableDeclarationName,
} }
this.sceneProgramMemory = programMemory
const group = new Group() const group = new Group()
position && group.position.set(...position) position && group.position.set(...position)
group.userData = { group.userData = {
@ -684,7 +677,6 @@ export class SceneEntities {
return { return {
truncatedAst, truncatedAst,
programMemoryOverride,
sketch, sketch,
variableDeclarationName, variableDeclarationName,
} }
@ -733,7 +725,7 @@ export class SceneEntities {
const variableDeclarationName = _node1.node?.declaration.id?.name || '' const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
kclManager.programMemory.get(variableDeclarationName), kclManager.variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return Promise.reject(sg) 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 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({ const mod = addNewSketchLn({
node: _ast, node: _ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
to: lastSeg.to, to: lastSeg.to,
@ -761,15 +753,14 @@ export class SceneEntities {
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false }) if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners() sceneInfra.resetMouseListeners()
const { truncatedAst, programMemoryOverride, sketch } = const { truncatedAst, sketch } = await this.setupSketch({
await this.setupSketch({ sketchPathToNode,
sketchPathToNode, forward,
forward, up,
up, position: origin,
position: origin, maybeModdedAst: modifiedAst,
maybeModdedAst: modifiedAst, draftExpressionsIndices,
draftExpressionsIndices, })
})
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
@ -801,7 +792,7 @@ export class SceneEntities {
]) ])
modifiedAst = addCallExpressionsToPipe({ modifiedAst = addCallExpressionsToPipe({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
expressions: [ expressions: [
lastSegment.type === 'TangentialArcTo' lastSegment.type === 'TangentialArcTo'
@ -817,7 +808,7 @@ export class SceneEntities {
if (trap(modifiedAst)) return Promise.reject(modifiedAst) if (trap(modifiedAst)) return Promise.reject(modifiedAst)
modifiedAst = addCloseToPipe({ modifiedAst = addCloseToPipe({
node: modifiedAst, node: modifiedAst,
programMemory: kclManager.programMemory, variables: kclManager.variables,
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })
if (trap(modifiedAst)) return Promise.reject(modifiedAst) if (trap(modifiedAst)) return Promise.reject(modifiedAst)
@ -867,7 +858,7 @@ export class SceneEntities {
const tmp = addNewSketchLn({ const tmp = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
from: [lastSegment.to[0], lastSegment.to[1]], from: [lastSegment.to[0], lastSegment.to[1]],
@ -908,7 +899,6 @@ export class SceneEntities {
sketchPathToNode, sketchPathToNode,
draftInfo: { draftInfo: {
truncatedAst, truncatedAst,
programMemoryOverride,
variableDeclarationName, variableDeclarationName,
}, },
}) })
@ -949,7 +939,7 @@ export class SceneEntities {
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program _ast = pResult.program
const { programMemoryOverride, truncatedAst } = await this.setupSketch({ const { truncatedAst } = await this.setupSketch({
sketchPathToNode, sketchPathToNode,
forward, forward,
up, up,
@ -982,13 +972,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), execState.variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
@ -1045,15 +1032,12 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory
// Prepare to update the THREEjs scene // Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), execState.variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
@ -1104,7 +1088,7 @@ export class SceneEntities {
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program _ast = pResult.program
const { programMemoryOverride, truncatedAst } = await this.setupSketch({ const { truncatedAst } = await this.setupSketch({
sketchPathToNode, sketchPathToNode,
forward, forward,
up, up,
@ -1144,13 +1128,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), execState.variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
@ -1210,15 +1191,12 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory
// Prepare to update the THREEjs scene // Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), execState.variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
@ -1605,7 +1583,7 @@ export class SceneEntities {
// do a quick mock execution to get the program memory up-to-date // do a quick mock execution to get the program memory up-to-date
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
const { programMemoryOverride, truncatedAst } = await this.setupSketch({ const { truncatedAst } = await this.setupSketch({
sketchPathToNode, sketchPathToNode,
forward, forward,
up, up,
@ -1634,7 +1612,7 @@ export class SceneEntities {
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
const moddedResult = changeSketchArguments( const moddedResult = changeSketchArguments(
modded, modded,
kclManager.programMemory, kclManager.variables,
{ {
type: 'path', type: 'path',
pathToNode: [ pathToNode: [
@ -1657,13 +1635,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: modded, ast: modded,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), execState.variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
@ -1700,7 +1675,7 @@ export class SceneEntities {
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
const moddedResult = changeSketchArguments( const moddedResult = changeSketchArguments(
modded, modded,
kclManager.programMemory, kclManager.variables,
{ {
type: 'path', type: 'path',
pathToNode: [ pathToNode: [
@ -1788,7 +1763,7 @@ export class SceneEntities {
const sketch = sketchFromPathToNode({ const sketch = sketchFromPathToNode({
pathToNode, pathToNode,
ast: kclManager.ast, ast: kclManager.ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
}) })
if (trap(sketch)) return if (trap(sketch)) return
if (!sketch) { if (!sketch) {
@ -1801,7 +1776,7 @@ export class SceneEntities {
const prevSegment = sketch.paths[pipeIndex - 2] const prevSegment = sketch.paths[pipeIndex - 2]
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
@ -1873,15 +1848,15 @@ export class SceneEntities {
...this.mouseEnterLeaveCallbacks(), ...this.mouseEnterLeaveCallbacks(),
}) })
} }
prepareTruncatedMemoryAndAst = ( prepareTruncatedAst = (
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
ast?: Node<Program>, ast?: Node<Program>,
draftSegment?: DraftSegment draftSegment?: DraftSegment
) => ) =>
prepareTruncatedMemoryAndAst( prepareTruncatedAst(
sketchPathToNode, sketchPathToNode,
ast || kclManager.ast, ast || kclManager.ast,
kclManager.lastSuccessfulProgramMemory, kclManager.lastSuccessfulVariables,
draftSegment draftSegment
) )
onDragSegment({ onDragSegment({
@ -1897,7 +1872,6 @@ export class SceneEntities {
intersects: Intersection<Object3D<Object3DEventMap>>[] intersects: Intersection<Object3D<Object3DEventMap>>[]
draftInfo?: { draftInfo?: {
truncatedAst: Node<Program> truncatedAst: Node<Program>
programMemoryOverride: ProgramMemory
variableDeclarationName: string variableDeclarationName: string
} }
}) { }) {
@ -2010,12 +1984,12 @@ export class SceneEntities {
to: dragTo, to: dragTo,
from, from,
}, },
previousProgramMemory: kclManager.programMemory, variables: kclManager.variables,
}) })
} else { } else {
modded = changeSketchArguments( modded = changeSketchArguments(
modifiedAst, modifiedAst,
kclManager.programMemory, kclManager.variables,
{ {
type: 'sourceRange', type: 'sourceRange',
sourceRange: topLevelRange(node.start, node.end), sourceRange: topLevelRange(node.start, node.end),
@ -2028,10 +2002,9 @@ export class SceneEntities {
modifiedAst = modded.modifiedAst modifiedAst = modded.modifiedAst
const info = draftInfo const info = draftInfo
? draftInfo ? draftInfo
: this.prepareTruncatedMemoryAndAst(pathToNode || []) : this.prepareTruncatedAst(pathToNode || [])
if (trap(info, { suppress: true })) return if (trap(info, { suppress: true })) return
const { truncatedAst, programMemoryOverride, variableDeclarationName } = const { truncatedAst, variableDeclarationName } = info
info
;(async () => { ;(async () => {
const code = recast(modifiedAst) const code = recast(modifiedAst)
if (trap(code)) return if (trap(code)) return
@ -2042,13 +2015,11 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride,
}) })
const programMemory = execState.memory const variables = execState.variables
this.sceneProgramMemory = programMemory
const maybeSketch = programMemory.get(variableDeclarationName) const maybeSketch = variables[variableDeclarationName]
let sketch: Sketch | undefined let sketch: Sketch | undefined
const sk = sketchFromKclValueOptional( const sk = sketchFromKclValueOptional(
maybeSketch, maybeSketch,
@ -2394,15 +2365,14 @@ export class SceneEntities {
// calculations/pure-functions/easy to test so no excuse not to // calculations/pure-functions/easy to test so no excuse not to
function prepareTruncatedMemoryAndAst( function prepareTruncatedAst(
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory, variables: VariableMap,
draftSegment?: DraftSegment draftSegment?: DraftSegment
): ):
| { | {
truncatedAst: Node<Program> truncatedAst: Node<Program>
programMemoryOverride: ProgramMemory
variableDeclarationName: string variableDeclarationName: string
} }
| Error { | Error {
@ -2417,7 +2387,7 @@ function prepareTruncatedMemoryAndAst(
if (err(_node)) return _node if (err(_node)) return _node
const variableDeclarationName = _node.node?.declaration.id?.name || '' const variableDeclarationName = _node.node?.declaration.id?.name || ''
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
programMemory.get(variableDeclarationName), variables[variableDeclarationName],
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return sg if (err(sg)) return sg
@ -2476,43 +2446,8 @@ function prepareTruncatedMemoryAndAst(
body: [structuredClone(_ast.body[bodyIndex])], 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 { return {
truncatedAst, truncatedAst,
programMemoryOverride,
variableDeclarationName, variableDeclarationName,
} }
} }
@ -2532,11 +2467,11 @@ export function getParentGroup(
export function sketchFromPathToNode({ export function sketchFromPathToNode({
pathToNode, pathToNode,
ast, ast,
programMemory, variables,
}: { }: {
pathToNode: PathToNode pathToNode: PathToNode
ast: Program ast: Program
programMemory: ProgramMemory variables: VariableMap
}): Sketch | null | Error { }): Sketch | null | Error {
const _varDec = getNodeFromPath<VariableDeclarator>( const _varDec = getNodeFromPath<VariableDeclarator>(
kclManager.ast, kclManager.ast,
@ -2545,7 +2480,7 @@ export function sketchFromPathToNode({
) )
if (err(_varDec)) return _varDec if (err(_varDec)) return _varDec
const varDec = _varDec.node const varDec = _varDec.node
const result = programMemory.get(varDec?.id?.name || '') const result = variables[varDec?.id?.name || '']
if (result?.type === 'Solid') { if (result?.type === 'Solid') {
return result.value.sketch return result.value.sketch
} }
@ -2584,7 +2519,7 @@ export function getSketchQuaternion(
const sketch = sketchFromPathToNode({ const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
ast: kclManager.ast, ast: kclManager.ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
}) })
if (err(sketch)) return sketch if (err(sketch)) return sketch
const zAxis = sketch?.on.zAxis || sketchNormalBackUp const zAxis = sketch?.on.zAxis || sketchNormalBackUp
@ -2601,7 +2536,7 @@ export async function getSketchOrientationDetails(
const sketch = sketchFromPathToNode({ const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
ast: kclManager.ast, ast: kclManager.ast,
programMemory: kclManager.programMemory, variables: kclManager.variables,
}) })
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found') if (!sketch) return Promise.reject('sketch not found')

View File

@ -1,11 +1,5 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from 'react'
import { import { parse, BinaryPart, Expr, resultIsOk, VariableMap } from '../lang/wasm'
parse,
BinaryPart,
Expr,
ProgramMemory,
resultIsOk,
} from '../lang/wasm'
import { import {
createIdentifier, createIdentifier,
createLiteral, createLiteral,
@ -100,7 +94,7 @@ export function useCalc({
newVariableInsertIndex: number newVariableInsertIndex: number
setNewVariableName: (a: string) => void setNewVariableName: (a: string) => void
} { } {
const { programMemory } = useKclContext() const { variables } = useKclContext()
const { context } = useModelingContext() const { context } = useModelingContext()
const selectionRange = const selectionRange =
context.selectionRanges?.graphSelections[0]?.codeRef?.range context.selectionRanges?.graphSelections[0]?.codeRef?.range
@ -127,7 +121,7 @@ export function useCalc({
}, []) }, [])
useEffect(() => { useEffect(() => {
if (programMemory.has(newVariableName)) { if (variables[newVariableName]) {
setIsNewVariableNameUnique(false) setIsNewVariableNameUnique(false)
} else { } else {
setIsNewVariableNameUnique(true) setIsNewVariableNameUnique(true)
@ -135,14 +129,14 @@ export function useCalc({
}, [newVariableName]) }, [newVariableName])
useEffect(() => { useEffect(() => {
if (!programMemory || !selectionRange) return if (!variables || !selectionRange) return
const varInfo = findAllPreviousVariables( const varInfo = findAllPreviousVariables(
kclManager.ast, kclManager.ast,
kclManager.programMemory, kclManager.variables,
selectionRange selectionRange
) )
setAvailableVarInfo(varInfo) setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange]) }, [kclManager.ast, kclManager.variables, selectionRange])
useEffect(() => { useEffect(() => {
try { try {
@ -150,9 +144,9 @@ export function useCalc({
const pResult = parse(code) const pResult = parse(code)
if (trap(pResult) || !resultIsOk(pResult)) return if (trap(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program const ast = pResult.program
const _programMem: ProgramMemory = ProgramMemory.empty() const _variables: VariableMap = {}
for (const { key, value } of availableVarInfo.variables) { for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, { const error = (_variables[key] = {
type: 'String', type: 'String',
value, value,
__meta: [], __meta: [],
@ -163,8 +157,8 @@ export function useCalc({
executeAst({ executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride: kclManager.programMemory.clone(), variables,
}).then(({ execState }) => { }).then(({ execState }) => {
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
@ -174,7 +168,7 @@ export function useCalc({
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init resultDeclaration?.declaration.init
const result = execState.memory?.get('__result__')?.value const result = execState.variables['__result__']?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)
}) })

View File

@ -157,8 +157,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
plugin.requestSemanticTokens() plugin.requestSemanticTokens()
break break
case 'kcl/memoryUpdated':
break
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)

View File

@ -1,6 +1,6 @@
import { processMemory } from './MemoryPane' import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers' import { enginelessExecutor } from '../../../lib/testHelpers'
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm' import { assertParse, initPromise } from '../../../lang/wasm'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
@ -29,15 +29,11 @@ describe('processMemory', () => {
|> line(endAbsolute = [2.15, 4.32]) |> line(endAbsolute = [2.15, 4.32])
// |> rx(90, %)` // |> rx(90, %)`
const ast = assertParse(code) const ast = assertParse(code)
const execState = await enginelessExecutor(ast, ProgramMemory.empty()) const execState = await enginelessExecutor(ast)
const output = processMemory(execState.memory) const output = processMemory(execState.variables)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3) expect(output.otherVar).toEqual(3)
expect(output).toEqual({ expect(output).toEqual({
HALF_TURN: 180,
QUARTER_TURN: 90,
THREE_QUARTER_TURN: 270,
ZERO: 0,
myVar: 5, myVar: 5,
myFn: '__function(a)__', myFn: '__function(a)__',
otherVar: 3, otherVar: 3,

View File

@ -2,10 +2,10 @@ import toast from 'react-hot-toast'
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
import { useMemo } from 'react' import { useMemo } from 'react'
import { import {
ProgramMemory,
Path, Path,
ExtrudeSurface, ExtrudeSurface,
sketchFromKclValueOptional, sketchFromKclValueOptional,
VariableMap,
} from 'lang/wasm' } from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { useResolvedTheme } from 'hooks/useResolvedTheme' import { useResolvedTheme } from 'hooks/useResolvedTheme'
@ -15,12 +15,12 @@ import Tooltip from 'components/Tooltip'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
export const MemoryPaneMenu = () => { export const MemoryPaneMenu = () => {
const { programMemory } = useKclContext() const { variables } = useKclContext()
function copyProgramMemoryToClipboard() { function copyProgramMemoryToClipboard() {
if (globalThis && 'navigator' in globalThis) { if (globalThis && 'navigator' in globalThis) {
navigator.clipboard navigator.clipboard
.writeText(JSON.stringify(programMemory)) .writeText(JSON.stringify(variables))
.then(() => toast.success('Program memory copied to clipboard')) .then(() => toast.success('Program memory copied to clipboard'))
.catch((e) => .catch((e) =>
trap(new Error('Failed to copy program memory to clipboard')) trap(new Error('Failed to copy program memory to clipboard'))
@ -50,12 +50,9 @@ export const MemoryPaneMenu = () => {
export const MemoryPane = () => { export const MemoryPane = () => {
const theme = useResolvedTheme() const theme = useResolvedTheme()
const { programMemory } = useKclContext() const { variables } = useKclContext()
const { state } = useModelingContext() const { state } = useModelingContext()
const ProcessedMemory = useMemo( const ProcessedMemory = useMemo(() => processMemory(variables), [variables])
() => processMemory(programMemory),
[programMemory]
)
return ( return (
<div className="h-full relative"> <div className="h-full relative">
<div className="absolute inset-0 p-2 flex flex-col items-start"> <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 = {} const processedMemory: any = {}
for (const [key, val] of programMemory?.visibleEntries()) { for (const [key, val] of Object.entries(variables)) {
if (val === undefined) continue
if ( if (
val.type === 'Sketch' || val.type === 'Sketch' ||
// @ts-ignore // @ts-ignore

View File

@ -19,7 +19,6 @@ import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { copyFileShareLink } from 'lib/links' import { copyFileShareLink } from 'lib/links'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { useToken } from 'machines/appMachine' import { useToken } from 'machines/appMachine'
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
@ -194,7 +193,7 @@ function ProjectMenuPopover({
id: 'share-link', id: 'share-link',
Element: 'button', Element: 'button',
children: 'Share current part (via Zoo link)', children: 'Share current part (via Zoo link)',
disabled: !(IS_NIGHTLY_OR_DEBUG && findCommand(shareCommandInfo)), disabled: !findCommand(shareCommandInfo),
onClick: async () => { onClick: async () => {
await copyFileShareLink({ await copyFileShareLink({
token: token ?? '', token: token ?? '',

View File

@ -95,7 +95,7 @@ export function applyConstraintEqualAngle({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
}) })
if (err(transform)) return transform if (err(transform)) return transform
const { modifiedAst, pathToNodeMap } = transform const { modifiedAst, pathToNodeMap } = transform

View File

@ -93,7 +93,7 @@ export function applyConstraintEqualLength({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
}) })
if (err(transform)) return transform if (err(transform)) return transform
const { modifiedAst, pathToNodeMap } = transform const { modifiedAst, pathToNodeMap } = transform

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers' import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections' 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 { getNodeFromPath } from '../../lang/queryAst'
import { import {
PathToNodeMap, PathToNodeMap,
@ -51,7 +51,7 @@ export function applyConstraintHorzVert(
selectionRanges: Selections, selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal', horOrVert: 'vertical' | 'horizontal',
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory memVars: VariableMap
): ):
| { | {
modifiedAst: Node<Program> modifiedAst: Node<Program>
@ -66,7 +66,7 @@ export function applyConstraintHorzVert(
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, memVars,
referenceSegName: '', referenceSegName: '',
}) })
} }

View File

@ -46,7 +46,7 @@ export function intersectInfo({
isLinesParallelAndConstrained( isLinesParallelAndConstrained(
kclManager.ast, kclManager.ast,
engineCommandManager.artifactGraph, engineCommandManager.artifactGraph,
kclManager.programMemory, kclManager.variables,
selectionRanges.graphSelections[0], selectionRanges.graphSelections[0],
selectionRanges.graphSelections[1] selectionRanges.graphSelections[1]
) )
@ -147,7 +147,7 @@ export async function applyConstraintIntersect({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges: forcedSelectionRanges, selectionRanges: forcedSelectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
}) })
if (err(transform1)) return Promise.reject(transform1) if (err(transform1)) return Promise.reject(transform1)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } = const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -184,7 +184,7 @@ export async function applyConstraintIntersect({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges: forcedSelectionRanges, selectionRanges: forcedSelectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
forceSegName: segName, forceSegName: segName,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })

View File

@ -88,7 +88,7 @@ export function applyRemoveConstrainingValues({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges: updatedSelectionRanges, selectionRanges: updatedSelectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
}) })
} }

View File

@ -104,7 +104,7 @@ export async function applyConstraintAbsDistance({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
}) })
if (err(transform1)) return Promise.reject(transform1) if (err(transform1)) return Promise.reject(transform1)
@ -124,7 +124,7 @@ export async function applyConstraintAbsDistance({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -172,7 +172,7 @@ export function applyConstraintAxisAlign({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })

View File

@ -95,7 +95,7 @@ export async function applyConstraintAngleBetween({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
}) })
if (err(transformed1)) return Promise.reject(transformed1) if (err(transformed1)) return Promise.reject(transformed1)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } = const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -133,7 +133,7 @@ export async function applyConstraintAngleBetween({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
forceSegName: segName, forceSegName: segName,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })

View File

@ -107,7 +107,7 @@ export async function applyConstraintHorzVertDistance({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
}) })
if (err(transformed)) return Promise.reject(transformed) if (err(transformed)) return Promise.reject(transformed)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } = const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -145,7 +145,7 @@ export async function applyConstraintHorzVertDistance({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
forceSegName: segName, forceSegName: segName,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -195,7 +195,7 @@ export function applyConstraintHorzVertAlign({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
if (err(retval)) return retval if (err(retval)) return retval

View File

@ -105,7 +105,7 @@ export async function applyConstraintLength({
ast, ast,
selectionRanges, selectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
forceValueUsedInTransform: distanceExpression, forceValueUsedInTransform: distanceExpression,
}) })
@ -137,7 +137,7 @@ export async function applyConstraintAngleLength({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
}) })
if (err(sketched)) return Promise.reject(sketched) if (err(sketched)) return Promise.reject(sketched)
@ -189,7 +189,7 @@ export async function applyConstraintAngleLength({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })

View File

@ -55,7 +55,7 @@ export function useConvertToVariable(range?: SourceRange) {
const { modifiedAst: _modifiedAst, pathToReplacedNode } = const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariable( moveValueIntoNewVariable(
ast, ast,
kclManager.programMemory, kclManager.variables,
range || context.selectionRanges.graphSelections[0]?.codeRef?.range, range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
variableName variableName
) )

View File

@ -7,7 +7,7 @@ import { KCLError } from './errors'
const KclContext = createContext({ const KclContext = createContext({
code: codeManager?.code || '', code: codeManager?.code || '',
programMemory: kclManager?.programMemory, variables: kclManager?.variables,
ast: kclManager?.ast, ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting, isExecuting: kclManager?.isExecuting,
diagnostics: kclManager?.diagnostics, diagnostics: kclManager?.diagnostics,
@ -31,7 +31,7 @@ export function KclContextProvider({
// Both the code state and the editor state start off with the same code. // Both the code state and the editor state start off with the same code.
const [code, setCode] = useState(loadedCode || codeManager.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 [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false) const [isExecuting, setIsExecuting] = useState(false)
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([]) const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
@ -44,7 +44,7 @@ export function KclContextProvider({
setCode, setCode,
}) })
kclManager.registerCallBacks({ kclManager.registerCallBacks({
setProgramMemory, setVariables,
setAst, setAst,
setLogs, setLogs,
setErrors, setErrors,
@ -58,7 +58,7 @@ export function KclContextProvider({
<KclContext.Provider <KclContext.Provider
value={{ value={{
code, code,
programMemory, variables,
ast, ast,
isExecuting, isExecuting,
diagnostics, diagnostics,

View File

@ -17,13 +17,14 @@ import {
emptyExecState, emptyExecState,
ExecState, ExecState,
initPromise, initPromise,
KclValue,
parse, parse,
PathToNode, PathToNode,
Program, Program,
ProgramMemory,
recast, recast,
SourceRange, SourceRange,
topLevelRange, topLevelRange,
VariableMap,
} from 'lang/wasm' } from 'lang/wasm'
import { getNodeFromPath, getSettingsAnnotation } from './queryAst' import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons' import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
@ -61,8 +62,8 @@ export class KclManager {
trivia: [], trivia: [],
} }
private _execState: ExecState = emptyExecState() private _execState: ExecState = emptyExecState()
private _programMemory: ProgramMemory = ProgramMemory.empty() private _variables: VariableMap = {}
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty() lastSuccessfulVariables: VariableMap = {}
lastSuccessfulOperations: Operation[] = [] lastSuccessfulOperations: Operation[] = []
private _logs: string[] = [] private _logs: string[] = []
private _errors: KCLError[] = [] private _errors: KCLError[] = []
@ -78,7 +79,9 @@ export class KclManager {
private _isExecutingCallback: (arg: boolean) => void = () => {} private _isExecutingCallback: (arg: boolean) => void = () => {}
private _astCallBack: (arg: Node<Program>) => 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 _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {} private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {} private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
@ -97,18 +100,18 @@ export class KclManager {
this._switchedFiles = switchedFiles this._switchedFiles = switchedFiles
} }
get programMemory() { get variables() {
return this._programMemory return this._variables
} }
// This is private because callers should be setting the entire execState. // This is private because callers should be setting the entire execState.
private set programMemory(programMemory) { private set variables(variables) {
this._programMemory = programMemory this._variables = variables
this._programMemoryCallBack(programMemory) this._variablesCallBack(variables)
} }
private set execState(execState) { private set execState(execState) {
this._execState = execState this._execState = execState
this.programMemory = execState.memory this.variables = execState.variables
} }
get execState() { get execState() {
@ -201,7 +204,7 @@ export class KclManager {
} }
registerCallBacks({ registerCallBacks({
setProgramMemory, setVariables,
setAst, setAst,
setLogs, setLogs,
setErrors, setErrors,
@ -209,7 +212,7 @@ export class KclManager {
setIsExecuting, setIsExecuting,
setWasmInitFailed, setWasmInitFailed,
}: { }: {
setProgramMemory: (arg: ProgramMemory) => void setVariables: (arg: VariableMap) => void
setAst: (arg: Node<Program>) => void setAst: (arg: Node<Program>) => void
setLogs: (arg: string[]) => void setLogs: (arg: string[]) => void
setErrors: (errors: KCLError[]) => void setErrors: (errors: KCLError[]) => void
@ -217,7 +220,7 @@ export class KclManager {
setIsExecuting: (arg: boolean) => void setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void setWasmInitFailed: (arg: boolean) => void
}) { }) {
this._programMemoryCallBack = setProgramMemory this._variablesCallBack = setVariables
this._astCallBack = setAst this._astCallBack = setAst
this._logsCallBack = setLogs this._logsCallBack = setLogs
this._kclErrorsCallBack = setErrors this._kclErrorsCallBack = setErrors
@ -329,6 +332,7 @@ export class KclManager {
ast, ast,
path: codeManager.currentFilePath || undefined, path: codeManager.currentFilePath || undefined,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
isMock: false,
}) })
// Program was not interrupted, setup the scene // Program was not interrupted, setup the scene
@ -385,11 +389,11 @@ export class KclManager {
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors)) this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
this.execState = execState this.execState = execState
if (!errors.length) { if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations this.lastSuccessfulOperations = execState.operations
} }
this.ast = { ...ast } this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/programMemory // updateArtifactGraph relies on updated executeState/variables
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph) this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
this._executeCallback() this._executeCallback()
if (!isInterrupted) { if (!isInterrupted) {
@ -442,16 +446,15 @@ export class KclManager {
const { logs, errors, execState } = await executeAst({ const { logs, errors, execState } = await executeAst({
ast: newAst, ast: newAst,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride: ProgramMemory.empty(),
}) })
this._logs = logs this._logs = logs
this.addDiagnostics(kclErrorsToDiagnostics(errors)) this.addDiagnostics(kclErrorsToDiagnostics(errors))
this._execState = execState this._execState = execState
this._programMemory = execState.memory this._variables = execState.variables
if (!errors.length) { if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations this.lastSuccessfulOperations = execState.operations
} }
} }

View File

@ -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])
})
})

View File

@ -15,8 +15,7 @@ const mySketch001 = startSketchOn('XY')
|> line(endAbsolute = [0.46, -5.82]) |> line(endAbsolute = [0.46, -5.82])
// |> rx(45, %)` // |> rx(45, %)`
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore const sketch001 = execState.variables['mySketch001']
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'Sketch', type: 'Sketch',
value: { value: {
@ -73,8 +72,7 @@ const mySketch001 = startSketchOn('XY')
// |> rx(45, %) // |> rx(45, %)
|> extrude(length = 2)` |> extrude(length = 2)`
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore const sketch001 = execState.variables['mySketch001']
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'Solid', type: 'Solid',
value: { value: {
@ -165,9 +163,9 @@ const sk2 = startSketchOn('XY')
` `
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(assertParse(code))
const programMemory = execState.memory const variables = execState.variables
// @ts-ignore // @ts-ignore
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')] const geos = [variables['theExtrude'], variables['sk2']]
expect(geos).toEqual([ expect(geos).toEqual([
{ {
type: 'Solid', type: 'Solid',

View File

@ -2,12 +2,12 @@ import fs from 'node:fs'
import { import {
assertParse, assertParse,
ProgramMemory,
Sketch, Sketch,
initPromise, initPromise,
sketchFromKclValue, sketchFromKclValue,
defaultArtifactGraph, defaultArtifactGraph,
topLevelRange, topLevelRange,
VariableMap,
} from './wasm' } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { KCLError } from './errors' import { KCLError } from './errors'
@ -21,13 +21,13 @@ describe('test executor', () => {
const code = `const myVar = 5 const code = `const myVar = 5
const newVar = myVar + 1` const newVar = myVar + 1`
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(5) expect(mem['myVar']?.value).toBe(5)
expect(mem.get('newVar')?.value).toBe(6) expect(mem['newVar']?.value).toBe(6)
}) })
it('test assigning a var with a string', async () => { it('test assigning a var with a string', async () => {
const code = `const myVar = "a str"` const code = `const myVar = "a str"`
const mem = await exe(code) 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 () => { it('test assigning a var by cont concatenating two strings string execute', async () => {
const code = fs.readFileSync( const code = fs.readFileSync(
@ -35,7 +35,7 @@ const newVar = myVar + 1`
'utf-8' 'utf-8'
) )
const mem = await exe(code) 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 () => { it('fn funcN = () => {} execute', async () => {
const mem = await exe( const mem = await exe(
@ -47,8 +47,8 @@ const newVar = myVar + 1`
'const magicNum = funcN(9, theVar)', 'const magicNum = funcN(9, theVar)',
].join('\n') ].join('\n')
) )
expect(mem.get('theVar')?.value).toBe(60) expect(mem['theVar']?.value).toBe(60)
expect(mem.get('magicNum')?.value).toBe(69) expect(mem['magicNum']?.value).toBe(69)
}) })
it('sketch declaration', async () => { it('sketch declaration', async () => {
let code = `const mySketch = startSketchOn('XY') let code = `const mySketch = startSketchOn('XY')
@ -60,7 +60,7 @@ const newVar = myVar + 1`
` `
const mem = await exe(code) const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests // 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') expect(sk?.type).toEqual('Sketch')
if (sk?.type !== 'Sketch') { if (sk?.type !== 'Sketch') {
return return
@ -117,7 +117,7 @@ const newVar = myVar + 1`
'const myVar = 5 + 1 |> myFn(%)', 'const myVar = 5 + 1 |> myFn(%)',
].join('\n') ].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7) expect(mem['myVar']?.value).toBe(7)
}) })
// Enable rotations #152 // Enable rotations #152
@ -130,15 +130,15 @@ const newVar = myVar + 1`
// 'const rotated = rx(90, mySk1)', // 'const rotated = rx(90, mySk1)',
// ].join('\n') // ].join('\n')
// const mem = await exe(code) // const mem = await exe(code)
// expect(mem.get('mySk1')?.value).toHaveLength(3) // expect(mem['mySk1']?.value).toHaveLength(3)
// expect(mem.get('rotated')?.type).toBe('Sketch') // expect(mem['rotated')?.type).toBe('Sketch']
// if ( // if (
// mem.get('mySk1')?.type !== 'Sketch' || // mem['mySk1']?.type !== 'Sketch' ||
// mem.get('rotated')?.type !== 'Sketch' // mem['rotated']?.type !== 'Sketch'
// ) // )
// throw new Error('not a sketch') // throw new Error('not a sketch')
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1]) // expect(mem['mySk1']?.rotation).toEqual([0, 0, 0, 1])
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([ // expect(mem['rotated']?.rotation.map((a) => a.toFixed(4))).toEqual([
// '0.7071', // '0.7071',
// '0.0000', // '0.0000',
// '0.0000', // '0.0000',
@ -157,7 +157,7 @@ const newVar = myVar + 1`
// ' |> rx(90, %)', // ' |> rx(90, %)',
].join('\n') ].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({ expect(mem['mySk1']).toEqual({
type: 'Sketch', type: 'Sketch',
value: { value: {
type: 'Sketch', type: 'Sketch',
@ -236,7 +236,7 @@ const newVar = myVar + 1`
) )
const mem = await exe(code) const mem = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct // TODO path to node is probably wrong here, zero indexes are not correct
expect(mem.get('three')).toEqual({ expect(mem['three']).toEqual({
type: 'Number', type: 'Number',
value: 3, value: 3,
__meta: [ __meta: [
@ -245,7 +245,7 @@ const newVar = myVar + 1`
}, },
], ],
}) })
expect(mem.get('yo')).toEqual({ expect(mem['yo']).toEqual({
type: 'Array', type: 'Array',
value: [ value: [
{ type: 'Number', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] }, { 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 () => { it('execute object expression', async () => {
const code = [ const code = [
@ -273,7 +270,7 @@ const newVar = myVar + 1`
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}", "const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n') ].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('yo')).toEqual({ expect(mem['yo']).toEqual({
type: 'Object', type: 'Object',
value: { value: {
aStr: { aStr: {
@ -309,7 +306,7 @@ const newVar = myVar + 1`
'\n' '\n'
) )
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')).toEqual({ expect(mem['myVar']).toEqual({
type: 'String', type: 'String',
value: '123', value: '123',
__meta: [ __meta: [
@ -325,80 +322,80 @@ describe('testing math operators', () => {
it('can sum', async () => { it('can sum', async () => {
const code = ['const myVar = 1 + 2'].join('\n') const code = ['const myVar = 1 + 2'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(3) expect(mem['myVar']?.value).toBe(3)
}) })
it('can subtract', async () => { it('can subtract', async () => {
const code = ['const myVar = 1 - 2'].join('\n') const code = ['const myVar = 1 - 2'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(-1) expect(mem['myVar']?.value).toBe(-1)
}) })
it('can multiply', async () => { it('can multiply', async () => {
const code = ['const myVar = 1 * 2'].join('\n') const code = ['const myVar = 1 * 2'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(2) expect(mem['myVar']?.value).toBe(2)
}) })
it('can divide', async () => { it('can divide', async () => {
const code = ['const myVar = 1 / 2'].join('\n') const code = ['const myVar = 1 / 2'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(0.5) expect(mem['myVar']?.value).toBe(0.5)
}) })
it('can modulus', async () => { it('can modulus', async () => {
const code = ['const myVar = 5 % 2'].join('\n') const code = ['const myVar = 5 % 2'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(1) expect(mem['myVar']?.value).toBe(1)
}) })
it('can do multiple operations', async () => { it('can do multiple operations', async () => {
const code = ['const myVar = 1 + 2 * 3'].join('\n') const code = ['const myVar = 1 + 2 * 3'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7) expect(mem['myVar']?.value).toBe(7)
}) })
it('big example with parans', async () => { it('big example with parans', async () => {
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n') const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7.4) expect(mem['myVar']?.value).toBe(7.4)
}) })
it('with identifier', async () => { it('with identifier', async () => {
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n') const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(3) expect(mem['myVar']?.value).toBe(3)
}) })
it('with lots of testing', async () => { it('with lots of testing', async () => {
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n') const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
const mem = await exe(code) 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 () => { it('with callExpression at start', async () => {
const code = 'const myVar = min(4, 100) + 2' const code = 'const myVar = min(4, 100) + 2'
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6) expect(mem['myVar']?.value).toBe(6)
}) })
it('with callExpression at end', async () => { it('with callExpression at end', async () => {
const code = 'const myVar = 2 + min(4, 100)' const code = 'const myVar = 2 + min(4, 100)'
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6) expect(mem['myVar']?.value).toBe(6)
}) })
it('with nested callExpression', async () => { it('with nested callExpression', async () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))' const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6) expect(mem['myVar']?.value).toBe(6)
}) })
it('with unaryExpression', async () => { it('with unaryExpression', async () => {
const code = 'const myVar = -min(100, 3)' const code = 'const myVar = -min(100, 3)'
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(-3) expect(mem['myVar']?.value).toBe(-3)
}) })
it('with unaryExpression in callExpression', async () => { it('with unaryExpression in callExpression', async () => {
const code = 'const myVar = min(-legLen(5, 4), 5)' const code = 'const myVar = min(-legLen(5, 4), 5)'
const code2 = 'const myVar = min(5 , -legLen(5, 4))' const code2 = 'const myVar = min(5 , -legLen(5, 4))'
const mem = await exe(code) const mem = await exe(code)
const mem2 = await exe(code2) const mem2 = await exe(code2)
expect(mem.get('myVar')?.value).toBe(-3) expect(mem['myVar']?.value).toBe(-3)
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value) expect(mem['myVar']?.value).toBe(mem2['myVar']?.value)
}) })
it('with unaryExpression in ArrayExpression', async () => { it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]' const code = 'const myVar = [1,-legLen(5, 4)]'
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toEqual([ expect(mem['myVar']?.value).toEqual([
{ {
__meta: [ __meta: [
{ {
@ -426,7 +423,7 @@ describe('testing math operators', () => {
'|> line(end = [-2.21, -legLen(5, min(3, 999))])', '|> line(end = [-2.21, -legLen(5, min(3, 999))])',
].join('\n') ].join('\n')
const mem = await exe(code) 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 // result of `-legLen(5, min(3, 999))` should be -4
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1] const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
expect(yVal).toBe(-4) expect(yVal).toBe(-4)
@ -444,7 +441,7 @@ describe('testing math operators', () => {
``, ``,
].join('\n') ].join('\n')
const mem = await exe(code) 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 -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]?.from).toEqual([3, 4])
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0]) expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
@ -454,7 +451,7 @@ describe('testing math operators', () => {
) )
const removedUnaryExpMem = await exe(removedUnaryExp) const removedUnaryExpMem = await exe(removedUnaryExp)
const removedUnaryExpMemSketch = sketchFromKclValue( const removedUnaryExpMemSketch = sketchFromKclValue(
removedUnaryExpMem.get('part001'), removedUnaryExpMem['part001'],
'part001' 'part001'
) )
@ -464,12 +461,12 @@ describe('testing math operators', () => {
it('with nested callExpression and binaryExpression', async () => { it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))' const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
const mem = await exe(code) 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 () => { it('can do power of math', async () => {
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2' const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
const mem = await exe(code) 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 // helpers
async function exe( async function exe(code: string, variables: VariableMap = {}) {
code: string,
programMemory: ProgramMemory = ProgramMemory.empty()
) {
const ast = assertParse(code) const ast = assertParse(code)
const execState = await enginelessExecutor(ast, programMemory) const execState = await enginelessExecutor(ast, true, undefined, variables)
return execState.memory return execState.variables
} }

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise, programMemoryInit } from './wasm' import { assertParse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import path from 'node:path' import path from 'node:path'
@ -32,7 +32,7 @@ child_process.spawnSync('git', [
'clone', 'clone',
'--single-branch', '--single-branch',
'--branch', '--branch',
'achalmers/kw-appearance', 'achalmers/kw-pattern',
URL_GIT_KCL_SAMPLES, URL_GIT_KCL_SAMPLES,
DIR_KCL_SAMPLES, DIR_KCL_SAMPLES,
]) ])
@ -72,7 +72,7 @@ describe('Test KCL Samples from public Github repository', () => {
const ast = assertParse(code) const ast = assertParse(code)
await enginelessExecutor( await enginelessExecutor(
ast, ast,
programMemoryInit(), false,
file.pathFromProjectDirectoryToFirstFile file.pathFromProjectDirectoryToFirstFile
) )
}, },

View File

@ -1,12 +1,12 @@
import { import {
Program, Program,
executor, executeWithEngine,
ProgramMemory, executeMock,
kclLint, kclLint,
emptyExecState, emptyExecState,
ExecState, ExecState,
VariableMap,
} from 'lang/wasm' } from 'lang/wasm'
import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection' import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors' import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint' import { Diagnostic } from '@codemirror/lint'
@ -48,14 +48,16 @@ export async function executeAst({
ast, ast,
path, path,
engineCommandManager, engineCommandManager,
// If you set programMemoryOverride we assume you mean mock mode. Since that isMock,
// is the only way to go about it. usePrevMemory,
programMemoryOverride, variables,
}: { }: {
ast: Node<Program> ast: Node<Program>
path?: string path?: string
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
programMemoryOverride?: ProgramMemory isMock: boolean
usePrevMemory?: boolean
variables?: VariableMap
isInterrupted?: boolean isInterrupted?: boolean
}): Promise<{ }): Promise<{
logs: string[] logs: string[]
@ -64,9 +66,9 @@ export async function executeAst({
isInterrupted: boolean isInterrupted: boolean
}> { }> {
try { try {
const execState = await (programMemoryOverride const execState = await (isMock
? enginelessExecutor(ast, programMemoryOverride, path) ? executeMock(ast, usePrevMemory, path, variables)
: executor(ast, engineCommandManager, path)) : executeWithEngine(ast, engineCommandManager, path))
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()

View File

@ -315,7 +315,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('100 + 100') + 1 const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.variables,
topLevelRange(startIndex, startIndex), topLevelRange(startIndex, startIndex),
'newVar' 'newVar'
) )
@ -329,7 +329,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('2.8') + 1 const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.variables,
topLevelRange(startIndex, startIndex), topLevelRange(startIndex, startIndex),
'newVar' 'newVar'
) )
@ -343,7 +343,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('def(') const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.variables,
topLevelRange(startIndex, startIndex), topLevelRange(startIndex, startIndex),
'newVar' 'newVar'
) )
@ -357,7 +357,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('jkl(') + 1 const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.variables,
topLevelRange(startIndex, startIndex), topLevelRange(startIndex, startIndex),
'newVar' 'newVar'
) )
@ -371,7 +371,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('identifierGuy +') + 1 const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.variables,
topLevelRange(startIndex, startIndex), topLevelRange(startIndex, startIndex),
'newVar' 'newVar'
) )
@ -557,7 +557,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
[], [],
ast, ast,
execState.memory, execState.variables,
code, code,
pathToNode pathToNode
) )
@ -639,7 +639,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
dependentSegments, dependentSegments,
ast, ast,
execState.memory, execState.variables,
code, code,
pathToNode pathToNode
) )
@ -745,7 +745,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode, pathToNode,
argPosition, argPosition,
ast, ast,
execState.memory execState.variables
) )
if (!mod) return new Error('mod is undefined') if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst) const recastCode = recast(mod.modifiedAst)
@ -794,7 +794,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode, pathToNode,
argPosition, argPosition,
ast, ast,
execState.memory execState.variables
) )
if (!mod) return new Error('mod is undefined') if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst) const recastCode = recast(mod.modifiedAst)
@ -978,7 +978,7 @@ sketch002 = startSketchOn({
codeRef: codeRefFromRange(range, ast), codeRef: codeRefFromRange(range, ast),
artifact, artifact,
}, },
execState.memory, execState.variables,
async () => { async () => {
await new Promise((resolve) => setTimeout(resolve, 100)) await new Promise((resolve) => setTimeout(resolve, 100))
return { return {

View File

@ -18,11 +18,11 @@ import {
UnaryExpression, UnaryExpression,
BinaryExpression, BinaryExpression,
PathToNode, PathToNode,
ProgramMemory,
SourceRange, SourceRange,
sketchFromKclValue, sketchFromKclValue,
isPathToNodeNumber, isPathToNodeNumber,
formatNumber, formatNumber,
VariableMap,
} from './wasm' } from './wasm'
import { import {
isNodeSafeToReplacePath, isNodeSafeToReplacePath,
@ -1218,7 +1218,7 @@ export function replaceValueAtNodePath({
export function moveValueIntoNewVariablePath( export function moveValueIntoNewVariablePath(
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory, memVars: VariableMap,
pathToNode: PathToNode, pathToNode: PathToNode,
variableName: string variableName: string
): { ): {
@ -1231,11 +1231,7 @@ export function moveValueIntoNewVariablePath(
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast } if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
const { insertIndex } = findAllPreviousVariablesPath( const { insertIndex } = findAllPreviousVariablesPath(ast, memVars, pathToNode)
ast,
programMemory,
pathToNode
)
let _node = structuredClone(ast) let _node = structuredClone(ast)
const boop = replacer(_node, variableName) const boop = replacer(_node, variableName)
if (trap(boop)) return { modifiedAst: ast } if (trap(boop)) return { modifiedAst: ast }
@ -1251,7 +1247,7 @@ export function moveValueIntoNewVariablePath(
export function moveValueIntoNewVariable( export function moveValueIntoNewVariable(
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory, memVars: VariableMap,
sourceRange: SourceRange, sourceRange: SourceRange,
variableName: string variableName: string
): { ): {
@ -1263,11 +1259,7 @@ export function moveValueIntoNewVariable(
const { isSafe, value, replacer } = meta const { isSafe, value, replacer } = meta
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast } if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
const { insertIndex } = findAllPreviousVariables( const { insertIndex } = findAllPreviousVariables(ast, memVars, sourceRange)
ast,
programMemory,
sourceRange
)
let _node = structuredClone(ast) let _node = structuredClone(ast)
const replaced = replacer(_node, variableName) const replaced = replacer(_node, variableName)
if (trap(replaced)) return { modifiedAst: ast } if (trap(replaced)) return { modifiedAst: ast }
@ -1289,7 +1281,7 @@ export function moveValueIntoNewVariable(
export function deleteSegmentFromPipeExpression( export function deleteSegmentFromPipeExpression(
dependentRanges: SourceRange[], dependentRanges: SourceRange[],
modifiedAst: Node<Program>, modifiedAst: Node<Program>,
programMemory: ProgramMemory, memVars: VariableMap,
code: string, code: string,
pathToNode: PathToNode pathToNode: PathToNode
): Node<Program> | Error { ): Node<Program> | Error {
@ -1321,7 +1313,7 @@ export function deleteSegmentFromPipeExpression(
callExp.shallowPath, callExp.shallowPath,
constraintInfo.argPosition, constraintInfo.argPosition,
_modifiedAst, _modifiedAst,
programMemory memVars
) )
if (!transform) return if (!transform) return
_modifiedAst = transform.modifiedAst _modifiedAst = transform.modifiedAst
@ -1350,7 +1342,7 @@ export function removeSingleConstraintInfo(
pathToCallExp: PathToNode, pathToCallExp: PathToNode,
argDetails: SimplifiedArgDetails, argDetails: SimplifiedArgDetails,
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory memVars: VariableMap
): ):
| { | {
modifiedAst: Node<Program> modifiedAst: Node<Program>
@ -1367,7 +1359,7 @@ export function removeSingleConstraintInfo(
ast, ast,
selectionRanges: [pathToCallExp], selectionRanges: [pathToCallExp],
transformInfos: [transform], transformInfos: [transform],
programMemory, memVars,
referenceSegName: '', referenceSegName: '',
}) })
if (err(retval)) return false if (err(retval)) return false
@ -1377,7 +1369,7 @@ export function removeSingleConstraintInfo(
export async function deleteFromSelection( export async function deleteFromSelection(
ast: Node<Program>, ast: Node<Program>,
selection: Selection, selection: Selection,
programMemory: ProgramMemory, variables: VariableMap,
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () => getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
({} as any) ({} as any)
): Promise<Node<Program> | Error> { ): Promise<Node<Program> | Error> {
@ -1506,7 +1498,7 @@ export async function deleteFromSelection(
return return
} }
const sketchToPreserve = sketchFromKclValue( const sketchToPreserve = sketchFromKclValue(
programMemory.get(sketchName), variables[sketchName],
sketchName sketchName
) )
if (err(sketchToPreserve)) return sketchToPreserve if (err(sketchToPreserve)) return sketchToPreserve

View File

@ -298,7 +298,7 @@ export function getPathToExtrudeForSegmentSelection(
const sketchVar = varDecNode.node.declaration.id.name const sketchVar = varDecNode.node.declaration.id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
dependencies.kclManager.programMemory.get(sketchVar), dependencies.kclManager.variables[sketchVar],
sketchVar sketchVar
) )
if (trap(sketch)) return sketch if (trap(sketch)) return sketch

View File

@ -9,7 +9,6 @@ import {
CallExpression, CallExpression,
VariableDeclarator, VariableDeclarator,
} from './wasm' } from './wasm'
import { ProgramMemory } from 'lang/wasm'
import { import {
findAllPreviousVariables, findAllPreviousVariables,
isNodeSafeToReplace, isNodeSafeToReplace,
@ -63,7 +62,7 @@ variableBelowShouldNotBeIncluded = 3
const { variables, bodyPath, insertIndex } = findAllPreviousVariables( const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast, ast,
execState.memory, execState.variables,
topLevelRange(rangeStart, rangeStart) topLevelRange(rangeStart, rangeStart)
) )
expect(variables).toEqual([ expect(variables).toEqual([
@ -398,7 +397,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: { selection: {
codeRef: codeRefFromRange(topLevelRange(100, 101), ast), codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
}, },
programMemory: execState.memory, memVars: execState.variables,
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
}) })
@ -418,7 +417,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: { selection: {
codeRef: codeRefFromRange(topLevelRange(100, 101), ast), codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
}, },
programMemory: execState.memory, memVars: execState.variables,
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
}) })
@ -432,7 +431,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: { selection: {
codeRef: codeRefFromRange(topLevelRange(10, 11), ast), codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
}, },
programMemory: execState.memory, memVars: execState.variables,
}) })
expect(result).toEqual(false) expect(result).toEqual(false)
}) })
@ -722,7 +721,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange) const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const modifiedAst = addCallExpressionsToPipe({ const modifiedAst = addCallExpressionsToPipe({
node: ast, node: ast,
programMemory: ProgramMemory.empty(), variables: {},
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
expressions: [ expressions: [
createCallExpressionStdLib( createCallExpressionStdLib(
@ -777,7 +776,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange) const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const modifiedAst = addCloseToPipe({ const modifiedAst = addCloseToPipe({
node: ast, node: ast,
programMemory: ProgramMemory.empty(), variables: {},
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })

View File

@ -13,7 +13,6 @@ import {
PathToNode, PathToNode,
PipeExpression, PipeExpression,
Program, Program,
ProgramMemory,
ReturnStatement, ReturnStatement,
sketchFromKclValue, sketchFromKclValue,
sketchFromKclValueOptional, sketchFromKclValueOptional,
@ -26,6 +25,7 @@ import {
kclSettings, kclSettings,
unitLenToUnitLength, unitLenToUnitLength,
unitAngToUnitAngle, unitAngToUnitAngle,
VariableMap,
} from './wasm' } from './wasm'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst' import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
@ -287,7 +287,7 @@ export interface PrevVariable<T> {
export function findAllPreviousVariablesPath( export function findAllPreviousVariablesPath(
ast: Program, ast: Program,
programMemory: ProgramMemory, memVars: VariableMap,
path: PathToNode, path: PathToNode,
type: 'number' | 'string' = 'number' type: 'number' | 'string' = 'number'
): { ): {
@ -325,7 +325,7 @@ export function findAllPreviousVariablesPath(
bodyItems?.forEach?.((item) => { bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return if (item.type !== 'VariableDeclaration' || item.end > startRange) return
const varName = item.declaration.id.name const varName = item.declaration.id.name
const varValue = programMemory?.get(varName) const varValue = memVars[varName]
if (!varValue || typeof varValue?.value !== type) return if (!varValue || typeof varValue?.value !== type) return
variables.push({ variables.push({
key: varName, key: varName,
@ -342,7 +342,7 @@ export function findAllPreviousVariablesPath(
export function findAllPreviousVariables( export function findAllPreviousVariables(
ast: Program, ast: Program,
programMemory: ProgramMemory, memVars: VariableMap,
sourceRange: SourceRange, sourceRange: SourceRange,
type: 'number' | 'string' = 'number' type: 'number' | 'string' = 'number'
): { ): {
@ -351,7 +351,7 @@ export function findAllPreviousVariables(
insertIndex: number insertIndex: number
} { } {
const path = getNodePathFromSourceRange(ast, sourceRange) const path = getNodePathFromSourceRange(ast, sourceRange)
return findAllPreviousVariablesPath(ast, programMemory, path, type) return findAllPreviousVariablesPath(ast, memVars, path, type)
} }
type ReplacerFn = ( type ReplacerFn = (
@ -479,7 +479,7 @@ function isTypeInArrayExp(
export function isLinesParallelAndConstrained( export function isLinesParallelAndConstrained(
ast: Program, ast: Program,
artifactGraph: ArtifactGraph, artifactGraph: ArtifactGraph,
programMemory: ProgramMemory, memVars: VariableMap,
primaryLine: Selection, primaryLine: Selection,
secondaryLine: Selection secondaryLine: Selection
): ):
@ -509,7 +509,7 @@ export function isLinesParallelAndConstrained(
if (err(_varDec)) return _varDec if (err(_varDec)) return _varDec
const varDec = _varDec.node const varDec = _varDec.node
const varName = (varDec as VariableDeclaration)?.declaration.id?.name 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 if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange( const _primarySegment = getSketchSegmentFromSourceRange(
sg, sg,
@ -589,11 +589,11 @@ export function isLinesParallelAndConstrained(
export function hasExtrudeSketch({ export function hasExtrudeSketch({
ast, ast,
selection, selection,
programMemory, memVars,
}: { }: {
ast: Program ast: Program
selection: Selection selection: Selection
programMemory: ProgramMemory memVars: VariableMap
}): boolean { }): boolean {
const varDecMeta = getNodeFromPath<VariableDeclaration>( const varDecMeta = getNodeFromPath<VariableDeclaration>(
ast, ast,
@ -607,7 +607,7 @@ export function hasExtrudeSketch({
const varDec = varDecMeta.node const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declaration.id.name const varName = varDec.declaration.id.name
const varValue = programMemory?.get(varName) const varValue = memVars[varName]
return ( return (
varValue?.type === 'Solid' || varValue?.type === 'Solid' ||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason) !(sketchFromKclValueOptional(varValue, varName) instanceof Reason)

View File

@ -124,7 +124,7 @@ describe('testing changeSketchArguments', () => {
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
const changeSketchArgsRetVal = changeSketchArguments( const changeSketchArgsRetVal = changeSketchArguments(
ast, ast,
execState.memory, execState.variables,
{ {
type: 'sourceRange', type: 'sourceRange',
sourceRange: topLevelRange( sourceRange: topLevelRange(
@ -160,7 +160,7 @@ mySketch001 = startSketchOn('XY')
expect(sourceStart).toBe(89) expect(sourceStart).toBe(89)
const newSketchLnRetVal = addNewSketchLn({ const newSketchLnRetVal = addNewSketchLn({
node: ast, node: ast,
programMemory: execState.memory, variables: execState.variables,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
from: [0, 0], from: [0, 0],
@ -190,7 +190,7 @@ mySketch001 = startSketchOn('XY')
const modifiedAst2 = addCloseToPipe({ const modifiedAst2 = addCloseToPipe({
node: ast, node: ast,
programMemory: execState.memory, variables: execState.variables,
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],

View File

@ -1,5 +1,4 @@
import { import {
ProgramMemory,
Path, Path,
Sketch, Sketch,
SourceRange, SourceRange,
@ -14,6 +13,7 @@ import {
Identifier, Identifier,
sketchFromKclValue, sketchFromKclValue,
topLevelRange, topLevelRange,
VariableMap,
} from 'lang/wasm' } from 'lang/wasm'
import { import {
getNodeFromPath, getNodeFromPath,
@ -355,7 +355,7 @@ function getTagKwArg(): SketchLineHelperKw['getTag'] {
export const line: SketchLineHelperKw = { export const line: SketchLineHelperKw = {
add: ({ add: ({
node, node,
previousProgramMemory, variables,
pathToNode, pathToNode,
segmentInput, segmentInput,
replaceExistingCallback, replaceExistingCallback,
@ -494,7 +494,7 @@ export const line: SketchLineHelperKw = {
export const lineTo: SketchLineHelperKw = { export const lineTo: SketchLineHelperKw = {
add: ({ add: ({
node, node,
previousProgramMemory, variables,
pathToNode, pathToNode,
segmentInput, segmentInput,
replaceExistingCallback, replaceExistingCallback,
@ -1313,7 +1313,7 @@ export const angledLine: SketchLineHelper = {
export const angledLineOfXLength: SketchLineHelper = { export const angledLineOfXLength: SketchLineHelper = {
add: ({ add: ({
node, node,
previousProgramMemory, variables,
pathToNode, pathToNode,
segmentInput, segmentInput,
replaceExistingCallback, replaceExistingCallback,
@ -1337,10 +1337,7 @@ export const angledLineOfXLength: SketchLineHelper = {
const { node: varDec } = nodeMeta2 const { node: varDec } = nodeMeta2
const variableName = varDec.id.name const variableName = varDec.id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(variables[variableName], variableName)
previousProgramMemory?.get(variableName),
variableName
)
if (err(sketch)) { if (err(sketch)) {
return sketch return sketch
} }
@ -1429,7 +1426,7 @@ export const angledLineOfXLength: SketchLineHelper = {
export const angledLineOfYLength: SketchLineHelper = { export const angledLineOfYLength: SketchLineHelper = {
add: ({ add: ({
node, node,
previousProgramMemory, variables,
pathToNode, pathToNode,
segmentInput, segmentInput,
replaceExistingCallback, replaceExistingCallback,
@ -1452,10 +1449,7 @@ export const angledLineOfYLength: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2 if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2 const { node: varDec } = nodeMeta2
const variableName = varDec.id.name const variableName = varDec.id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(variables[variableName], variableName)
previousProgramMemory?.get(variableName),
variableName
)
if (err(sketch)) return sketch if (err(sketch)) return sketch
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
@ -1793,7 +1787,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
} }
return new Error('not implemented') return new Error('not implemented')
}, },
updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => { updateArgs: ({ node, pathToNode, input, variables }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input const { to, from } = input
const _node = { ...node } const _node = { ...node }
@ -1820,10 +1814,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
const { node: varDec } = nodeMeta2 const { node: varDec } = nodeMeta2
const varName = varDec.declaration.id.name const varName = varDec.declaration.id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(variables[varName], varName)
previousProgramMemory.get(varName),
varName
)
if (err(sketch)) return sketch if (err(sketch)) return sketch
const intersectPath = sketch.paths.find( const intersectPath = sketch.paths.find(
({ tag }: Path) => tag && tag.value === intersectTagName ({ tag }: Path) => tag && tag.value === intersectTagName
@ -1996,7 +1987,7 @@ export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
export function changeSketchArguments( export function changeSketchArguments(
node: Node<Program>, node: Node<Program>,
programMemory: ProgramMemory, variables: VariableMap,
sourceRangeOrPath: sourceRangeOrPath:
| { | {
type: 'sourceRange' type: 'sourceRange'
@ -2030,7 +2021,7 @@ export function changeSketchArguments(
return updateArgs({ return updateArgs({
node: _node, node: _node,
previousProgramMemory: programMemory, variables,
pathToNode: shallowPath, pathToNode: shallowPath,
input, input,
}) })
@ -2047,7 +2038,7 @@ export function changeSketchArguments(
return updateArgs({ return updateArgs({
node: _node, node: _node,
previousProgramMemory: programMemory, variables,
pathToNode: shallowPath, pathToNode: shallowPath,
input, input,
}) })
@ -2112,7 +2103,7 @@ export function compareVec2Epsilon2(
interface CreateLineFnCallArgs { interface CreateLineFnCallArgs {
node: Node<Program> node: Node<Program>
programMemory: ProgramMemory variables: VariableMap
input: SegmentInputs input: SegmentInputs
fnName: ToolTip fnName: ToolTip
pathToNode: PathToNode pathToNode: PathToNode
@ -2121,7 +2112,7 @@ interface CreateLineFnCallArgs {
export function addNewSketchLn({ export function addNewSketchLn({
node: _node, node: _node,
programMemory: previousProgramMemory, variables,
fnName, fnName,
pathToNode, pathToNode,
input: segmentInput, input: segmentInput,
@ -2151,7 +2142,7 @@ export function addNewSketchLn({
) )
return add({ return add({
node, node,
previousProgramMemory, variables,
pathToNode, pathToNode,
segmentInput, segmentInput,
spliceBetween, spliceBetween,
@ -2164,7 +2155,7 @@ export function addCallExpressionsToPipe({
expressions, expressions,
}: { }: {
node: Node<Program> node: Node<Program>
programMemory: ProgramMemory variables: VariableMap
pathToNode: PathToNode pathToNode: PathToNode
expressions: Node<CallExpression | CallExpressionKw>[] expressions: Node<CallExpression | CallExpressionKw>[]
}) { }) {
@ -2188,7 +2179,7 @@ export function addCloseToPipe({
pathToNode, pathToNode,
}: { }: {
node: Program node: Program
programMemory: ProgramMemory variables: VariableMap
pathToNode: PathToNode pathToNode: PathToNode
}) { }) {
const _node = { ...node } const _node = { ...node }
@ -2209,7 +2200,7 @@ export function addCloseToPipe({
export function replaceSketchLine({ export function replaceSketchLine({
node, node,
programMemory, variables,
pathToNode: _pathToNode, pathToNode: _pathToNode,
fnName, fnName,
segmentInput, segmentInput,
@ -2217,7 +2208,7 @@ export function replaceSketchLine({
referencedSegment, referencedSegment,
}: { }: {
node: Node<Program> node: Node<Program>
programMemory: ProgramMemory variables: VariableMap
pathToNode: PathToNode pathToNode: PathToNode
fnName: ToolTip fnName: ToolTip
segmentInput: SegmentInputs segmentInput: SegmentInputs
@ -2241,7 +2232,7 @@ export function replaceSketchLine({
: sketchLineHelperMap[fnName] : sketchLineHelperMap[fnName]
const addRetVal = add({ const addRetVal = add({
node: _node, node: _node,
previousProgramMemory: programMemory, variables,
pathToNode: _pathToNode, pathToNode: _pathToNode,
referencedSegment, referencedSegment,
segmentInput, segmentInput,

View File

@ -53,7 +53,7 @@ async function testingSwapSketchFnCall({
return Promise.reject(new Error('transformInfos undefined')) return Promise.reject(new Error('transformInfos undefined'))
const ast2 = transformAstSketchLines({ const ast2 = transformAstSketchLines({
ast, ast,
programMemory: execState.memory, memVars: execState.variables,
selectionRanges: selections, selectionRanges: selections,
transformInfos, transformInfos,
referenceSegName: '', referenceSegName: '',
@ -373,7 +373,7 @@ part001 = startSketchOn('XY')
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(assertParse(code))
const index = code.indexOf('// normal-segment') - 7 const index = code.indexOf('// normal-segment') - 7
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
execState.memory.get('part001'), execState.variables['part001'],
'part001' 'part001'
) as Sketch ) as Sketch
const _segment = getSketchSegmentFromSourceRange( const _segment = getSketchSegmentFromSourceRange(
@ -393,7 +393,7 @@ part001 = startSketchOn('XY')
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(assertParse(code))
const index = code.indexOf('// segment-in-start') - 7 const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange( const _segment = getSketchSegmentFromSourceRange(
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch, sketchFromKclValue(execState.variables['part001'], 'part001') as Sketch,
topLevelRange(index, index) topLevelRange(index, index)
) )
if (err(_segment)) throw _segment if (err(_segment)) throw _segment

View File

@ -193,7 +193,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
ast, ast,
selectionRanges: transformedSelection, selectionRanges: transformedSelection,
transformInfos, transformInfos,
programMemory: execState.memory, memVars: execState.variables,
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -356,7 +356,7 @@ part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory: execState.memory, memVars: execState.variables,
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -445,7 +445,7 @@ part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory: execState.memory, memVars: execState.variables,
referenceSegName: '', referenceSegName: '',
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -505,7 +505,7 @@ part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory: execState.memory, memVars: execState.variables,
referenceSegName: '', referenceSegName: '',
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -600,7 +600,7 @@ async function helperThing(
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory: execState.memory, memVars: execState.variables,
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)

View File

@ -17,13 +17,13 @@ import {
BinaryPart, BinaryPart,
VariableDeclarator, VariableDeclarator,
PathToNode, PathToNode,
ProgramMemory,
sketchFromKclValue, sketchFromKclValue,
Literal, Literal,
SourceRange, SourceRange,
LiteralValue, LiteralValue,
recast, recast,
LabeledArg, LabeledArg,
VariableMap,
} from '../wasm' } from '../wasm'
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst' import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
@ -1745,14 +1745,14 @@ export function transformSecondarySketchLinesTagFirst({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, memVars,
forceSegName, forceSegName,
forceValueUsedInTransform, forceValueUsedInTransform,
}: { }: {
ast: Node<Program> ast: Node<Program>
selectionRanges: Selections selectionRanges: Selections
transformInfos: TransformInfo[] transformInfos: TransformInfo[]
programMemory: ProgramMemory memVars: VariableMap
forceSegName?: string forceSegName?: string
forceValueUsedInTransform?: BinaryPart forceValueUsedInTransform?: BinaryPart
}): }):
@ -1788,7 +1788,7 @@ export function transformSecondarySketchLinesTagFirst({
}, },
referencedSegmentRange: primarySelection, referencedSegmentRange: primarySelection,
transformInfos, transformInfos,
programMemory, memVars,
referenceSegName: tag, referenceSegName: tag,
forceValueUsedInTransform, forceValueUsedInTransform,
}) })
@ -1822,7 +1822,7 @@ export function transformAstSketchLines({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, memVars,
referenceSegName, referenceSegName,
forceValueUsedInTransform, forceValueUsedInTransform,
referencedSegmentRange, referencedSegmentRange,
@ -1830,7 +1830,7 @@ export function transformAstSketchLines({
ast: Node<Program> ast: Node<Program>
selectionRanges: Selections | PathToNode[] selectionRanges: Selections | PathToNode[]
transformInfos: TransformInfo[] transformInfos: TransformInfo[]
programMemory: ProgramMemory memVars: VariableMap
referenceSegName: string referenceSegName: string
referencedSegmentRange?: SourceRange referencedSegmentRange?: SourceRange
forceValueUsedInTransform?: BinaryPart forceValueUsedInTransform?: BinaryPart
@ -1946,7 +1946,7 @@ export function transformAstSketchLines({
}) })
const varName = varDec.node.id.name const varName = varDec.node.id.name
let kclVal = programMemory.get(varName) let kclVal = memVars[varName]
let sketch let sketch
if (kclVal?.type === 'Solid') { if (kclVal?.type === 'Solid') {
sketch = kclVal.value.sketch sketch = kclVal.value.sketch
@ -1977,7 +1977,7 @@ export function transformAstSketchLines({
// Note to ADAM: Here is where the replaceExisting call gets sent. // Note to ADAM: Here is where the replaceExisting call gets sent.
const replacedSketchLine = replaceSketchLine({ const replacedSketchLine = replaceSketchLine({
node: node, node: node,
programMemory, variables: memVars,
pathToNode: _pathToNode, pathToNode: _pathToNode,
referencedSegment, referencedSegment,
fnName: transformTo || (call.node.callee.name as ToolTip), fnName: transformTo || (call.node.callee.name as ToolTip),

View File

@ -18,8 +18,8 @@ describe('testing angledLineThatIntersects', () => {
}, %, $yo2) }, %, $yo2)
intersect = segEndX(yo2)` intersect = segEndX(yo2)`
const execState = await enginelessExecutor(assertParse(code('-1'))) 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'))) const noOffset = await enginelessExecutor(assertParse(code('0')))
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1) expect(noOffset.variables['intersect']?.value).toBeCloseTo(1)
}) })
}) })

View File

@ -1,6 +1,5 @@
import { ToolTip } from 'lang/langHelpers' import { ToolTip } from 'lang/langHelpers'
import { import {
ProgramMemory,
Path, Path,
SourceRange, SourceRange,
Program, Program,
@ -10,14 +9,15 @@ import {
Literal, Literal,
BinaryPart, BinaryPart,
CallExpressionKw, CallExpressionKw,
VariableMap,
} from '../wasm' } from '../wasm'
import { LineInputsType } from './sketchcombos' import { LineInputsType } from './sketchcombos'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
export interface ModifyAstBase { export interface ModifyAstBase {
node: Node<Program> node: Node<Program>
// TODO #896: Remove ProgramMemory from this interface // TODO #896: Remove memory variables from this interface
previousProgramMemory: ProgramMemory variables: VariableMap
pathToNode: PathToNode pathToNode: PathToNode
} }

View File

@ -18,7 +18,7 @@ it('can execute parsed AST', async () => {
expect(pResult.program).not.toEqual(null) expect(pResult.program).not.toEqual(null)
const execState = await enginelessExecutor(pResult.program as Node<Program>) const execState = await enginelessExecutor(pResult.program as Node<Program>)
expect(err(execState)).toEqual(false) 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', () => { it('formats numbers with units', () => {

View File

@ -3,7 +3,8 @@ import {
parse_wasm, parse_wasm,
recast_wasm, recast_wasm,
format_number, format_number,
execute, execute_with_engine,
execute_mock,
kcl_lint, kcl_lint,
modify_ast_for_sketch_wasm, modify_ast_for_sketch_wasm,
is_points_ccw, is_points_ccw,
@ -281,6 +282,8 @@ export const assertParse = (code: string): Node<Program> => {
return result.program return result.program
} }
export type VariableMap = { [key in string]?: KclValue }
export type PathToNode = [string | number, string][] export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = ( export const isPathToNodeNumber = (
@ -290,7 +293,7 @@ export const isPathToNodeNumber = (
} }
export interface ExecState { export interface ExecState {
memory: ProgramMemory variables: { [key in string]?: KclValue }
operations: Operation[] operations: Operation[]
artifacts: { [key in ArtifactId]?: RustArtifact } artifacts: { [key in ArtifactId]?: RustArtifact }
artifactCommands: ArtifactCommand[] artifactCommands: ArtifactCommand[]
@ -303,7 +306,7 @@ export interface ExecState {
*/ */
export function emptyExecState(): ExecState { export function emptyExecState(): ExecState {
return { return {
memory: ProgramMemory.empty(), variables: {},
operations: [], operations: [],
artifacts: {}, artifacts: {},
artifactCommands: [], artifactCommands: [],
@ -328,7 +331,7 @@ function execStateFromRust(
} }
return { return {
memory: ProgramMemory.fromRaw(execOutcome.memory), variables: execOutcome.variables,
operations: execOutcome.operations, operations: execOutcome.operations,
artifacts: execOutcome.artifacts, artifacts: execOutcome.artifacts,
artifactCommands: execOutcome.artifactCommands, 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> export type ArtifactGraph = Map<ArtifactId, Artifact>
function rustArtifactGraphToMap( function rustArtifactGraphToMap(
@ -354,203 +367,6 @@ export function defaultArtifactGraph(): ArtifactGraph {
return new Map() 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. // TODO: In the future, make the parameter be a KclValue.
export function sketchFromKclValueOptional( export function sketchFromKclValueOptional(
obj: any, obj: any,
@ -590,58 +406,85 @@ export function sketchFromKclValue(
* @param node The AST of the program to execute. * @param node The AST of the program to execute.
* @param path The full path of the file being executed. Use `null` for * @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. * 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>, node: Node<Program>,
engineCommandManager: EngineCommandManager, usePrevMemory?: boolean,
path?: string, path?: string,
programMemoryOverride: ProgramMemory | Error | null = null variables?: { [key in string]?: KclValue }
): Promise<ExecState> => { ): 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 { try {
let jsAppSettings = default_app_settings() if (!variables) {
if (!TEST) { variables = {}
const lastSettingsSnapshot = await import(
'components/SettingsAuthProvider'
).then((module) => module.lastSettingsContextSnapshot)
if (lastSettingsSnapshot) {
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
}
} }
const execOutcome: RustExecOutcome = await execute( if (usePrevMemory === undefined) {
usePrevMemory = true
}
const execOutcome: RustExecOutcome = await execute_mock(
JSON.stringify(node), JSON.stringify(node),
path, path,
JSON.stringify(programMemoryOverride?.toRaw() || null), JSON.stringify({ settings: await jsAppSettings() }),
JSON.stringify({ settings: 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, engineCommandManager,
fileSystemManager fileSystemManager
) )
return execStateFromRust(execOutcome, node) return execStateFromRust(execOutcome, node)
} catch (e: any) { } catch (e: any) {
console.log(e) return Promise.reject(errFromErrWithOutputs(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)
} }
} }
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>> => { export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
try { try {
const discovered_findings: Array<Discovered> = await kcl_lint( const discovered_findings: Array<Discovered> = await kcl_lint(
@ -707,7 +550,6 @@ export const modifyAstForSketch = async (
defaultArtifactGraph() defaultArtifactGraph()
) )
console.log(kclError)
return Promise.reject(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( export async function coreDump(
coreDumpManager: CoreDumpManager, coreDumpManager: CoreDumpManager,
openGithubIssue: boolean = false openGithubIssue: boolean = false

View File

@ -576,7 +576,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),
selectionRanges, selectionRanges,
transformInfos: transforms, transformInfos: transforms,
programMemory: kclManager.programMemory, memVars: kclManager.variables,
referenceSegName: '', referenceSegName: '',
}) })
if (err(sketched)) return KCL_DEFAULT_LENGTH if (err(sketched)) return KCL_DEFAULT_LENGTH

View File

@ -51,16 +51,16 @@ sketch002 = startSketchOn(sketch001, seg03)
center = [-1.25, 1], center = [-1.25, 1],
radius = mountingHoleDiameter / 2, radius = mountingHoleDiameter / 2,
}, %) }, %)
|> patternLinear2d({ |> patternLinear2d(
instances = 2, instances = 2,
distance = 2.5, distance = 2.5,
axis = [-1, 0], axis = [-1, 0],
}, %) )
|> patternLinear2d({ |> patternLinear2d(
instances = 2, instances = 2,
distance = 4, distance = 4,
axis = [0, 1], axis = [0, 1],
}, %) )
|> extrude(%, length = -thickness-.01) |> extrude(%, length = -thickness-.01)
sketch003 = startSketchOn(sketch001, seg04) sketch003 = startSketchOn(sketch001, seg04)
@ -68,11 +68,11 @@ sketch003 = startSketchOn(sketch001, seg04)
center = [1, -1], center = [1, -1],
radius = mountingHoleDiameter / 2, radius = mountingHoleDiameter / 2,
}, %) }, %)
|> patternLinear2d({ |> patternLinear2d(
instances = 2, instances = 2,
distance = 4, distance = 4,
axis = [1, 0], axis = [1, 0],
}, %) )
|> extrude(%, length = -thickness-0.1) |> extrude(%, length = -thickness-0.1)
` `

View File

@ -6,7 +6,6 @@ import { FILE_EXT } from './constants'
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { reportRejection } from './trap' import { reportRejection } from './trap'
import { IndexLoaderData } from './types' import { IndexLoaderData } from './types'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { copyFileShareLink } from './links' import { copyFileShareLink } from './links'
interface OnSubmitProps { interface OnSubmitProps {
@ -137,7 +136,6 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
{ {
name: 'share-file-link', name: 'share-file-link',
displayName: 'Share current part (via Zoo 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.', description: 'Create a link that contains a copy of the current file.',
groupId: 'code', groupId: 'code',
needsReview: false, needsReview: false,

View File

@ -1,67 +1,55 @@
import { ParseResult, ProgramMemory } from 'lang/wasm' import { ParseResult, VariableMap } from 'lang/wasm'
import { getCalculatedKclExpressionValue } from './kclHelpers' import { getCalculatedKclExpressionValue } from './kclHelpers'
describe('KCL expression calculations', () => { describe('KCL expression calculations', () => {
it('calculates a simple expression', async () => { it('calculates a simple expression', async () => {
const actual = await getCalculatedKclExpressionValue({ const actual = await getCalculatedKclExpressionValue('1 + 2', {})
value: '1 + 2',
programMemory: ProgramMemory.empty(),
})
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult> const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual).not.toHaveProperty('errors') expect(coercedActual).not.toHaveProperty('errors')
expect(coercedActual.valueAsString).toEqual('3') expect(coercedActual.valueAsString).toEqual('3')
expect(coercedActual?.astNode).toBeDefined() expect(coercedActual?.astNode).toBeDefined()
}) })
it('calculates a simple expression with a variable', async () => { it('calculates a simple expression with a variable', async () => {
const programMemory = ProgramMemory.empty() const variables: VariableMap = {}
programMemory.set('x', { variables['x'] = {
type: 'Number', type: 'Number',
value: 2, value: 2,
__meta: [], __meta: [],
}) }
const actual = await getCalculatedKclExpressionValue({ const actual = await getCalculatedKclExpressionValue('1 + x', variables)
value: '1 + x',
programMemory,
})
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult> const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('3') expect(coercedActual.valueAsString).toEqual('3')
expect(coercedActual.astNode).toBeDefined() expect(coercedActual.astNode).toBeDefined()
}) })
it('returns NAN for an invalid expression', async () => { it('returns NAN for an invalid expression', async () => {
const actual = await getCalculatedKclExpressionValue({ const actual = await getCalculatedKclExpressionValue('1 + x', {})
value: '1 + x',
programMemory: ProgramMemory.empty(),
})
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult> const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('NAN') expect(coercedActual.valueAsString).toEqual('NAN')
expect(coercedActual.astNode).toBeDefined() expect(coercedActual.astNode).toBeDefined()
}) })
it('returns NAN for an expression with an invalid variable', async () => { it('returns NAN for an expression with an invalid variable', async () => {
const programMemory = ProgramMemory.empty() const variables: VariableMap = {}
programMemory.set('y', { variables['y'] = {
type: 'Number', type: 'Number',
value: 2, value: 2,
__meta: [], __meta: [],
}) }
const actual = await getCalculatedKclExpressionValue({ const actual = await getCalculatedKclExpressionValue('1 + x', variables)
value: '1 + x',
programMemory,
})
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult> const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('NAN') expect(coercedActual.valueAsString).toEqual('NAN')
expect(coercedActual.astNode).toBeDefined() expect(coercedActual.astNode).toBeDefined()
}) })
it('calculates a more complex expression with a variable', async () => { it('calculates a more complex expression with a variable', async () => {
const programMemory = ProgramMemory.empty() const variables: VariableMap = {}
programMemory.set('x', { variables['x'] = {
type: 'Number', type: 'Number',
value: 2, value: 2,
__meta: [], __meta: [],
}) }
const actual = await getCalculatedKclExpressionValue({ const actual = await getCalculatedKclExpressionValue(
value: '(1 + x * x) * 2', '(1 + x * x) * 2',
programMemory, variables
}) )
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult> const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('10') expect(coercedActual.valueAsString).toEqual('10')
expect(coercedActual.astNode).toBeDefined() expect(coercedActual.astNode).toBeDefined()

View File

@ -1,48 +1,20 @@
import { err } from './trap' import { err } from './trap'
import { engineCommandManager } from 'lib/singletons' 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 { PrevVariable } from 'lang/queryAst'
import { executeAst } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import { KclExpression } from './commandTypes' import { KclExpression } from './commandTypes'
const DUMMY_VARIABLE_NAME = '__result__' 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, * Calculate the value of the KCL expression,
* given the value and the variables that are available * given the value and the variables that are available
*/ */
export async function getCalculatedKclExpressionValue({ export async function getCalculatedKclExpressionValue(
value, value: string,
programMemory, variables: VariableMap
}: { ) {
value: string
programMemory: ProgramMemory
}) {
// Create a one-line program that assigns the value to a variable // Create a one-line program that assigns the value to a variable
const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}` const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}`
const pResult = parse(dummyProgramCode) const pResult = parse(dummyProgramCode)
@ -53,7 +25,8 @@ export async function getCalculatedKclExpressionValue({
const { execState } = await executeAst({ const { execState } = await executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
programMemoryOverride: programMemory, isMock: true,
variables,
}) })
// Find the variable declaration for the result // Find the variable declaration for the result
@ -65,7 +38,7 @@ export async function getCalculatedKclExpressionValue({
const variableDeclaratorAstNode = const variableDeclaratorAstNode =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init resultDeclaration?.declaration.init
const resultRawValue = execState.memory?.get(DUMMY_VARIABLE_NAME)?.value const resultRawValue = execState.variables[DUMMY_VARIABLE_NAME]?.value
return { return {
astNode: variableDeclaratorAstNode, astNode: variableDeclaratorAstNode,
@ -74,17 +47,14 @@ export async function getCalculatedKclExpressionValue({
} }
} }
export async function stringToKclExpression({ export async function stringToKclExpression(
value, value: string,
programMemory, variables: VariableMap
}: { ) {
value: string const calculatedResult = await getCalculatedKclExpressionValue(
programMemory: ProgramMemory
}) {
const calculatedResult = await getCalculatedKclExpressionValue({
value, value,
programMemory, variables
}) )
if (err(calculatedResult) || 'errors' in calculatedResult) { if (err(calculatedResult) || 'errors' in calculatedResult) {
return calculatedResult return calculatedResult
} else if (!calculatedResult.astNode) { } else if (!calculatedResult.astNode) {

View File

@ -80,13 +80,13 @@ const prepareToEditExtrude: PrepareToEditCallback =
} }
// Convert the length argument from a string to a KCL expression // Convert the length argument from a string to a KCL expression
const distanceResult = await stringToKclExpression({ const distanceResult = await stringToKclExpression(
value: codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs?.['length']?.sourceRange[0], operation.labeledArgs?.['length']?.sourceRange[0],
operation.labeledArgs?.['length']?.sourceRange[1] operation.labeledArgs?.['length']?.sourceRange[1]
), ),
programMemory: kclManager.programMemory.clone(), {}
}) )
if (err(distanceResult) || 'errors' in distanceResult) { if (err(distanceResult) || 'errors' in distanceResult) {
return baseCommand return baseCommand
} }
@ -163,13 +163,13 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
} }
// Convert the distance argument from a string to a KCL expression // Convert the distance argument from a string to a KCL expression
const distanceResult = await stringToKclExpression({ const distanceResult = await stringToKclExpression(
value: codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs.offset.sourceRange[0], operation.labeledArgs.offset.sourceRange[0],
operation.labeledArgs.offset.sourceRange[1] operation.labeledArgs.offset.sourceRange[1]
), ),
programMemory: kclManager.programMemory.clone(), {}
}) )
if (err(distanceResult) || 'errors' in distanceResult) { if (err(distanceResult) || 'errors' in distanceResult) {
return baseCommand return baseCommand

View File

@ -1,9 +1,9 @@
import { import {
Program, Program,
ProgramMemory, executeMock,
executor,
SourceRange, SourceRange,
ExecState, ExecState,
VariableMap,
} from '../lang/wasm' } from '../lang/wasm'
import { EngineCommandManager } from 'lang/std/engineConnection' import { EngineCommandManager } from 'lang/std/engineConnection'
import { EngineCommand } from 'lang/std/artifactGraph' import { EngineCommand } from 'lang/std/artifactGraph'
@ -80,18 +80,9 @@ class MockEngineCommandManager {
export async function enginelessExecutor( export async function enginelessExecutor(
ast: Node<Program>, ast: Node<Program>,
pmo: ProgramMemory | Error = ProgramMemory.empty(), usePrevMemory?: boolean,
path?: string path?: string,
variables?: VariableMap
): Promise<ExecState> { ): Promise<ExecState> {
if (pmo !== null && err(pmo)) return Promise.reject(pmo) return await executeMock(ast, usePrevMemory, path, variables)
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
} }

View File

@ -5,10 +5,7 @@ import { findUniqueName } from 'lang/modifyAst'
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst' import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
import { Expr } from 'lang/wasm' import { Expr } from 'lang/wasm'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { import { getCalculatedKclExpressionValue } from './kclHelpers'
getCalculatedKclExpressionValue,
programMemoryFromVariables,
} from './kclHelpers'
import { parse, resultIsOk } from 'lang/wasm' import { parse, resultIsOk } from 'lang/wasm'
import { err } from 'lib/trap' import { err } from 'lib/trap'
@ -36,7 +33,7 @@ export function useCalculateKclExpression({
newVariableInsertIndex: number newVariableInsertIndex: number
setNewVariableName: (a: string) => void setNewVariableName: (a: string) => void
} { } {
const { programMemory, code } = useKclContext() const { variables, code } = useKclContext()
const { context } = useModelingContext() const { context } = useModelingContext()
// If there is no selection, use the end of the code // If there is no selection, use the end of the code
// so all variables are available // so all variables are available
@ -80,7 +77,7 @@ export function useCalculateKclExpression({
useEffect(() => { useEffect(() => {
if ( if (
programMemory.has(newVariableName) || variables[newVariableName] ||
newVariableName === '' || newVariableName === '' ||
!isValidVariableName(newVariableName) !isValidVariableName(newVariableName)
) { ) {
@ -88,33 +85,22 @@ export function useCalculateKclExpression({
} else { } else {
setIsNewVariableNameUnique(true) setIsNewVariableNameUnique(true)
} }
}, [programMemory, newVariableName]) }, [variables, newVariableName])
useEffect(() => { useEffect(() => {
if (!programMemory) return if (!variables) return
const varInfo = findAllPreviousVariables( const varInfo = findAllPreviousVariables(
kclManager.ast, kclManager.ast,
kclManager.programMemory, kclManager.variables,
// If there is no selection, use the end of the code // If there is no selection, use the end of the code
selectionRange || [code.length, code.length] selectionRange || [code.length, code.length]
) )
setAvailableVarInfo(varInfo) setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange]) }, [kclManager.ast, kclManager.variables, selectionRange])
useEffect(() => { useEffect(() => {
const execAstAndSetResult = async () => { const execAstAndSetResult = async () => {
const programMemory = programMemoryFromVariables( const result = await getCalculatedKclExpressionValue(value, {})
availableVarInfo.variables
)
if (programMemory instanceof Error) {
setCalcResult('NAN')
setValueNode(null)
return
}
const result = await getCalculatedKclExpressionValue({
value,
programMemory,
})
if (result instanceof Error || 'errors' in result) { if (result instanceof Error || 'errors' in result) {
setCalcResult('NAN') setCalcResult('NAN')
setValueNode(null) setValueNode(null)
@ -128,7 +114,7 @@ export function useCalculateKclExpression({
setCalcResult('NAN') setCalcResult('NAN')
setValueNode(null) setValueNode(null)
}) })
}, [value, availableVarInfo, code, kclManager.programMemory]) }, [value, availableVarInfo, code, kclManager.variables])
return { return {
valueNode, valueNode,

View File

@ -5,7 +5,7 @@ import { findAllPreviousVariables } from 'lang/queryAst'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
export function usePreviousVariables() { export function usePreviousVariables() {
const { programMemory, code } = useKclContext() const { variables, code } = useKclContext()
const { context } = useModelingContext() const { context } = useModelingContext()
const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
?.range || [code.length, code.length] ?.range || [code.length, code.length]
@ -18,14 +18,14 @@ export function usePreviousVariables() {
}) })
useEffect(() => { useEffect(() => {
if (!programMemory || !selectionRange) return if (!variables || !selectionRange) return
const varInfo = findAllPreviousVariables( const varInfo = findAllPreviousVariables(
kclManager.ast, kclManager.ast,
kclManager.programMemory, kclManager.variables,
selectionRange selectionRange
) )
setPreviousVariablesInfo(varInfo) setPreviousVariablesInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange]) }, [kclManager.ast, kclManager.variables, selectionRange])
return previousVariablesInfo return previousVariablesInfo
} }

View File

@ -11,7 +11,8 @@ import {
parse_wasm as ParseWasm, parse_wasm as ParseWasm,
recast_wasm as RecastWasm, recast_wasm as RecastWasm,
format_number as FormatNumber, format_number as FormatNumber,
execute as Execute, execute_with_engine as ExecuteWithEngine,
execute_mock as ExecuteMock,
kcl_lint as KclLint, kcl_lint as KclLint,
modify_ast_for_sketch_wasm as ModifyAstForSketch, modify_ast_for_sketch_wasm as ModifyAstForSketch,
is_points_ccw as IsPointsCcw, is_points_ccw as IsPointsCcw,
@ -57,8 +58,11 @@ export const recast_wasm: typeof RecastWasm = (...args) => {
export const format_number: typeof FormatNumber = (...args) => { export const format_number: typeof FormatNumber = (...args) => {
return getModule().format_number(...args) return getModule().format_number(...args)
} }
export const execute: typeof Execute = (...args) => { export const execute_with_engine: typeof ExecuteWithEngine = (...args) => {
return getModule().execute(...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) => { export const kcl_lint: typeof KclLint = (...args) => {
return getModule().kcl_lint(...args) return getModule().kcl_lint(...args)

View File

@ -1,6 +1,5 @@
import { import {
PathToNode, PathToNode,
ProgramMemory,
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
parse, parse,
@ -708,7 +707,7 @@ export const modelingMachine = setup({
const modifiedAst = await deleteFromSelection( const modifiedAst = await deleteFromSelection(
ast, ast,
selectionRanges.graphSelections[0], selectionRanges.graphSelections[0],
kclManager.programMemory, kclManager.variables,
getFaceDetails getFaceDetails
) )
if (err(modifiedAst)) { if (err(modifiedAst)) {
@ -719,8 +718,7 @@ export const modelingMachine = setup({
const testExecute = await executeAst({ const testExecute = await executeAst({
ast: modifiedAst, ast: modifiedAst,
engineCommandManager, engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. isMock: true,
programMemoryOverride: ProgramMemory.empty(),
}) })
if (testExecute.errors.length) { if (testExecute.errors.length) {
toast.error(errorMessage) toast.error(errorMessage)
@ -1087,7 +1085,7 @@ export const modelingMachine = setup({
selectionRanges, selectionRanges,
'horizontal', 'horizontal',
kclManager.ast, kclManager.ast,
kclManager.programMemory kclManager.variables
) )
if (trap(constraint)) return false if (trap(constraint)) return false
const { modifiedAst, pathToNodeMap } = constraint const { modifiedAst, pathToNodeMap } = constraint
@ -1122,7 +1120,7 @@ export const modelingMachine = setup({
selectionRanges, selectionRanges,
'vertical', 'vertical',
kclManager.ast, kclManager.ast,
kclManager.programMemory kclManager.variables
) )
if (trap(constraint)) return false if (trap(constraint)) return false
const { modifiedAst, pathToNodeMap } = constraint const { modifiedAst, pathToNodeMap } = constraint

View File

@ -1779,7 +1779,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.20" version = "0.1.21"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",

View File

@ -33,6 +33,9 @@ tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.8" twenty-twenty = "0.8"
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] } uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
[features]
dhat-heap = ["kcl-lib/dhat-heap"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
futures = "0.3.31" futures = "0.3.31"

View File

@ -36,5 +36,11 @@ run-sim-test test_name:
{{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute 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: test:
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1 export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-test-server" name = "kcl-test-server"
description = "A test server for KCL" description = "A test server for KCL"
version = "0.1.20" version = "0.1.21"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -944,13 +944,7 @@ mod tests {
let snippet = pattern_fn.to_autocomplete_snippet().unwrap(); let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!( assert_eq!(
snippet, snippet,
r#"patternCircular3d({ 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})${}"#
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:%})${}"#
); );
} }
@ -1006,11 +1000,7 @@ mod tests {
let snippet = pattern_fn.to_autocomplete_snippet().unwrap(); let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!( assert_eq!(
snippet, snippet,
r#"patternLinear2d({ r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
instances = ${0:10},
distance = ${1:3.14},
axis = [${2:3.14}, ${3:3.14}],
}, ${4:%})${}"#
); );
} }

View File

@ -462,98 +462,4 @@ impl ArtifactGraph {
Ok(()) 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(())
}
} }

View File

@ -11,28 +11,46 @@ use crate::{
walk::Node as WalkNode, walk::Node as WalkNode,
}; };
use super::ProgramMemory;
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// A static mutable lock for updating the last successful execution state for the cache. /// 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. /// Read the old ast memory from the lock.
pub(super) async fn read_old_ast_memory() -> Option<OldAstState> { pub(super) async fn read_old_ast() -> Option<OldAstState> {
let old_ast = OLD_AST_MEMORY.read().await; let old_ast = OLD_AST.read().await;
old_ast.clone() old_ast.clone()
} }
pub(super) async fn write_old_ast_memory(old_state: OldAstState) { pub(super) async fn write_old_ast(old_state: OldAstState) {
let mut old_ast = OLD_AST_MEMORY.write().await; let mut old_ast = OLD_AST.write().await;
*old_ast = Some(old_state); *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() { pub async fn bust_cache() {
let mut old_ast = OLD_AST_MEMORY.write().await; let mut old_ast = OLD_AST.write().await;
// Set the cache to None.
*old_ast = None; *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. /// Information for the caching an AST and smartly re-executing it if we can.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CacheInformation<'a> { pub struct CacheInformation<'a> {

View File

@ -9,16 +9,17 @@ use crate::{
execution::{ execution::{
annotations, annotations,
cad_op::{OpArg, Operation}, cad_op::{OpArg, Operation},
memory,
state::ModuleState, state::ModuleState,
BodyType, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, TagEngineInfo, BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
TagIdentifier, TagEngineInfo, TagIdentifier,
}, },
modules::{ModuleId, ModulePath, ModuleRepr}, modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{ parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression,
PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator, PipeExpression, Program, TagDeclarator, UnaryExpression, UnaryOperator,
}, },
source_range::SourceRange, source_range::SourceRange,
std::{ std::{
@ -113,20 +114,21 @@ impl ExecutorContext {
match &import_stmt.selector { match &import_stmt.selector {
ImportSelector::List { items } => { ImportSelector::List { items } => {
let (_, module_memory, module_exports) = self let (env_ref, module_exports) = self
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range) .exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
.await?; .await?;
for import_item in items { for import_item in items {
// Extract the item from the module. // Extract the item from the module.
let item = let item = exec_state
module_memory .memory()
.get(&import_item.name.name, import_item.into()) .get_from(&import_item.name.name, env_ref, import_item.into())
.map_err(|_err| { .map_err(|_err| {
KclError::UndefinedValue(KclErrorDetails { KclError::UndefinedValue(KclErrorDetails {
message: format!("{} is not defined in module", import_item.name.name), message: format!("{} is not defined in module", import_item.name.name),
source_ranges: vec![SourceRange::from(&import_item.name)], source_ranges: vec![SourceRange::from(&import_item.name)],
}) })
})?; })?
.clone();
// Check that the item is allowed to be imported. // Check that the item is allowed to be imported.
if !module_exports.contains(&import_item.name.name) { if !module_exports.contains(&import_item.name.name) {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -140,8 +142,8 @@ impl ExecutorContext {
// Add the item to the current module. // Add the item to the current module.
exec_state.mut_memory().add( exec_state.mut_memory().add(
import_item.identifier(), import_item.identifier().to_owned(),
item.clone(), item,
SourceRange::from(&import_item.name), SourceRange::from(&import_item.name),
)?; )?;
@ -154,17 +156,21 @@ impl ExecutorContext {
} }
} }
ImportSelector::Glob(_) => { ImportSelector::Glob(_) => {
let (_, module_memory, module_exports) = self let (env_ref, module_exports) = self
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range) .exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
.await?; .await?;
for name in module_exports.iter() { for name in module_exports.iter() {
let item = module_memory.get(name, source_range).map_err(|_err| { let item = exec_state
KclError::Internal(KclErrorDetails { .memory()
message: format!("{} is not defined in module (but was exported?)", name), .get_from(name, env_ref, source_range)
source_ranges: vec![source_range], .map_err(|_err| {
}) KclError::Internal(KclErrorDetails {
})?; message: format!("{} is not defined in module (but was exported?)", name),
exec_state.mut_memory().add(name, item.clone(), source_range)?; source_ranges: vec![source_range],
})
})?
.clone();
exec_state.mut_memory().add(name.to_owned(), item, source_range)?;
if let ItemVisibility::Export = import_stmt.visibility { if let ItemVisibility::Export = import_stmt.visibility {
exec_state.mod_local.module_exports.push(name.clone()); exec_state.mod_local.module_exports.push(name.clone());
@ -177,7 +183,7 @@ impl ExecutorContext {
value: module_id, value: module_id,
meta: vec![source_range.into()], 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; last_expr = None;
@ -215,7 +221,9 @@ impl ExecutorContext {
StatementKind::Declaration { name: &var_name }, StatementKind::Declaration { name: &var_name },
) )
.await?; .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. // Track exports.
if let ItemVisibility::Export = variable_declaration.visibility { if let ItemVisibility::Export = variable_declaration.visibility {
@ -225,6 +233,14 @@ impl ExecutorContext {
} }
BodyItem::ReturnStatement(return_statement) => { BodyItem::ReturnStatement(return_statement) => {
let metadata = Metadata::from(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 let value = self
.execute_expr( .execute_expr(
&return_statement.argument, &return_statement.argument,
@ -233,7 +249,15 @@ impl ExecutorContext {
StatementKind::Expression, StatementKind::Expression,
) )
.await?; .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; last_expr = None;
} }
} }
@ -273,7 +297,8 @@ impl ExecutorContext {
let source = resolved_path.source(&self.fs, source_range).await?; let source = resolved_path.source(&self.fs, source_range).await?;
// TODO handle parsing errors properly // TODO handle parsing errors properly
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?; 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) Ok(id)
} }
ImportPath::Foreign { .. } => { ImportPath::Foreign { .. } => {
@ -296,80 +321,116 @@ impl ExecutorContext {
let id = exec_state.next_module_id(); let id = exec_state.next_module_id();
let source = resolved_path.source(&self.fs, source_range).await?; let source = resolved_path.source(&self.fs, source_range).await?;
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err().unwrap(); 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) Ok(id)
} }
} }
} }
async fn exec_module( async fn exec_module_for_items(
&self, &self,
module_id: ModuleId, module_id: ModuleId,
exec_state: &mut ExecState, exec_state: &mut ExecState,
exec_kind: ExecutionKind, exec_kind: ExecutionKind,
source_range: SourceRange, source_range: SourceRange,
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> { ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
let old_units = exec_state.length_unit(); let path = exec_state.global.module_infos[&module_id].path.clone();
// TODO It sucks that we have to clone the whole module AST here let mut repr = exec_state.global.module_infos[&module_id].take_repr();
let info = exec_state.global.module_infos[&module_id].clone(); // DON'T EARLY RETURN! We need to restore the module repr
match &info.repr { let result = match &mut repr {
ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails { ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
message: format!( ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())),
"circular import of modules is not allowed: {} -> {}", ModuleRepr::Kcl(program, cache) => self
exec_state .exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
.global .await
.mod_loader .map(|(_, er, items)| {
.import_stack *cache = Some((er, items.clone()));
.iter() (er, items)
.map(|p| p.as_path().to_string_lossy()) }),
.collect::<Vec<_>>() ModuleRepr::Foreign(geom) => Err(KclError::Semantic(KclErrorDetails {
.join(" -> "), message: "Cannot import items from foreign modules".to_owned(),
info.path source_ranges: vec![geom.source_range],
),
source_ranges: vec![source_range],
})), })),
ModuleRepr::Kcl(program) => { ModuleRepr::Dummy => unreachable!(),
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);
let result = self exec_state.global.module_infos[&module_id].restore_repr(repr);
.exec_program(program, exec_state, crate::execution::BodyType::Root) result
.await; }
let new_units = exec_state.length_unit(); async fn exec_module_for_result(
std::mem::swap(&mut exec_state.mod_local, &mut local_state); &self,
exec_state.global.mod_loader.leave_module(&info.path); module_id: ModuleId,
if !exec_kind.is_isolated() && new_units != old_units { exec_state: &mut ExecState,
self.engine.set_units(old_units.into(), Default::default()).await?; exec_kind: ExecutionKind,
} source_range: SourceRange,
self.engine.replace_execution_kind(original_execution); ) -> 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| { let result = match &repr {
if let KclError::ImportCycle(_) = err { ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
// It was an import cycle. Keep the original message. ModuleRepr::Kcl(program, _) => self
err.override_source_ranges(vec![source_range]) .exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
} else { .await
KclError::Semantic(KclErrorDetails { .map(|(val, _, _)| val),
message: format!( ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
"Error loading imported file. Open it to view more details. {}: {}", .await
info.path, .map(|geom| Some(KclValue::ImportedGeometry(geom))),
err.message() ModuleRepr::Dummy => unreachable!(),
), };
source_ranges: vec![source_range],
})
}
})?;
Ok((result, local_state.memory, local_state.module_exports)) exec_state.global.module_infos[&module_id].restore_repr(repr);
} result
ModuleRepr::Foreign(geom) => { }
let geom = super::import::send_to_engine(geom.clone(), self).await?;
Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new())) 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] #[async_recursion]
@ -387,39 +448,33 @@ impl ExecutorContext {
Expr::Identifier(identifier) => { Expr::Identifier(identifier) => {
let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone(); let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
if let KclValue::Module { value: module_id, meta } = value { if let KclValue::Module { value: module_id, meta } = value {
let (result, _, _) = self self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range) .await?
.await?; .unwrap_or_else(|| {
result.unwrap_or_else(|| { // The module didn't have a return value. Currently,
// The module didn't have a return value. Currently, // the only way to have a return value is with the final
// the only way to have a return value is with the final // statement being an expression statement.
// statement being an expression statement. //
// // TODO: Make a warning when we support them in the
// TODO: Make a warning when we support them in the // execution phase.
// execution phase. let mut new_meta = vec![metadata.to_owned()];
let mut new_meta = vec![metadata.to_owned()]; new_meta.extend(meta);
new_meta.extend(meta); KclValue::KclNone {
KclValue::KclNone { value: Default::default(),
value: Default::default(), meta: new_meta,
meta: new_meta, }
} })
})
} else { } else {
value value
} }
} }
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?, Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
Expr::FunctionExpression(function_expression) => { Expr::FunctionExpression(function_expression) => KclValue::Function {
// Cloning memory here is crucial for semantics so that we close expression: function_expression.clone(),
// over variables. Variables defined lexically later shouldn't meta: vec![metadata.to_owned()],
// be available to the function body. func: None,
KclValue::Function { memory: exec_state.mut_memory().snapshot(),
expression: function_expression.clone(), },
meta: vec![metadata.to_owned()],
func: None,
memory: Box::new(exec_state.memory().clone()),
}
}
Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?, Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?,
Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?, Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?, Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
@ -456,7 +511,7 @@ impl ExecutorContext {
.await?; .await?;
exec_state exec_state
.mut_memory() .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 // TODO this lets us use the label as a variable name, but not as a tag in most cases
result result
} }
@ -886,7 +941,7 @@ impl Node<CallExpressionKw> {
}; };
// Attempt to call the function. // Attempt to call the function.
let result = { let mut return_value = {
// Don't early-return in this block. // Don't early-return in this block.
let result = func.std_lib_fn()(exec_state, args).await; let result = func.std_lib_fn()(exec_state, args).await;
@ -900,9 +955,8 @@ impl Node<CallExpressionKw> {
exec_state.mod_local.operations.push(op); exec_state.mod_local.operations.push(op);
} }
result result
}; }?;
let mut return_value = result?;
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?; update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
Ok(return_value) Ok(return_value)
@ -912,7 +966,6 @@ impl Node<CallExpressionKw> {
// Clone the function so that we can use a mutable reference to // Clone the function so that we can use a mutable reference to
// exec_state. // exec_state.
let func = exec_state.memory().get(fn_name, source_range)?.clone(); 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. // Track call operation.
let op_labeled_args = args let op_labeled_args = args
@ -932,20 +985,14 @@ impl Node<CallExpressionKw> {
source_range: callsite, source_range: callsite,
}); });
let return_value = { let return_value = func
let previous_dynamic_state = .call_fn_kw(args, exec_state, ctx.clone(), callsite)
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state); .await
let result = func .map_err(|e| {
.call_fn_kw(args, exec_state, ctx.clone(), callsite) // Add the call expression to the source ranges.
.await // TODO currently ignored by the frontend
.map_err(|e| { e.add_source_ranges(vec![source_range])
// 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 result = return_value.ok_or_else(move || { let result = return_value.ok_or_else(move || {
let mut source_ranges: Vec<SourceRange> = vec![source_range]; let mut source_ranges: Vec<SourceRange> = vec![source_range];
@ -1018,9 +1065,11 @@ impl Node<CallExpression> {
ctx.clone(), ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic), exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
); );
let result = { let mut return_value = {
// Don't early-return in this block. // 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; let result = func.std_lib_fn()(exec_state, args).await;
exec_state.mut_memory().pop_env();
if let Some(mut op) = op { if let Some(mut op) = op {
op.set_std_lib_call_is_error(result.is_err()); op.set_std_lib_call_is_error(result.is_err());
@ -1032,9 +1081,8 @@ impl Node<CallExpression> {
exec_state.mod_local.operations.push(op); exec_state.mod_local.operations.push(op);
} }
result result
}; }?;
let mut return_value = result?;
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?; update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
Ok(return_value) Ok(return_value)
@ -1044,7 +1092,6 @@ impl Node<CallExpression> {
// Clone the function so that we can use a mutable reference to // Clone the function so that we can use a mutable reference to
// exec_state. // exec_state.
let func = exec_state.memory().get(fn_name, source_range)?.clone(); 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. // Track call operation.
exec_state exec_state
@ -1059,17 +1106,11 @@ impl Node<CallExpression> {
source_range: callsite, source_range: callsite,
}); });
let return_value = { let return_value = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
let previous_dynamic_state = // Add the call expression to the source ranges.
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state); // TODO currently ignored by the frontend
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| { e.add_source_ranges(vec![source_range])
// 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 result = return_value.ok_or_else(move || { let result = return_value.ok_or_else(move || {
let mut source_ranges: Vec<SourceRange> = vec![source_range]; let mut source_ranges: Vec<SourceRange> = vec![source_range];
@ -1103,15 +1144,29 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
match result { match result {
KclValue::Sketch { value: ref mut sketch } => { KclValue::Sketch { value: ref mut sketch } => {
for (_, tag) in sketch.tags.iter() { 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 } => { KclValue::Solid { ref mut value } => {
for v in &value.value { for v in &value.value {
if let Some(tag) = v.get_tag() { if let Some(tag) = v.get_tag() {
// Get the past tag and update it. // Get the past tag and update it.
let mut t = if let Some(t) = value.sketch.tags.get(&tag.name) { let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
t.clone() 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 { } else {
// It's probably a fillet or a chamfer. // It's probably a fillet or a chamfer.
// Initialize it. // Initialize it.
@ -1129,29 +1184,40 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
} }
}; };
let Some(ref info) = t.info else { exec_state
return Err(KclError::Semantic(KclErrorDetails { .mut_memory()
message: format!("Tag {} does not have path info", tag.name), .insert_or_update(tag.name.clone(), KclValue::TagIdentifier(Box::new(tag_id.clone())));
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())?;
// update the sketch tags. // 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. // Find the stale sketch in memory and update it.
let cur_env_index = exec_state.memory().current_env.index(); if !value.sketch.tags.is_empty() {
if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) { let updates: Vec<_> = exec_state
current_env.update_sketch_tags(&value.sketch); .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 +1237,7 @@ impl Node<TagDeclarator> {
exec_state exec_state
.mut_memory() .mut_memory()
.add(&self.name, memory_item.clone(), self.into())?; .add(self.name.clone(), memory_item.clone(), self.into())?;
Ok(self.into()) Ok(self.into())
} }
@ -1450,8 +1516,8 @@ impl Node<PipeExpression> {
fn assign_args_to_params( fn assign_args_to_params(
function_expression: NodeRef<'_, FunctionExpression>, function_expression: NodeRef<'_, FunctionExpression>,
args: Vec<Arg>, args: Vec<Arg>,
mut fn_memory: ProgramMemory, fn_memory: &mut ProgramMemory,
) -> Result<ProgramMemory, KclError> { ) -> Result<(), KclError> {
let num_args = function_expression.number_of_args(); let num_args = function_expression.number_of_args();
let (min_params, max_params) = num_args.into_inner(); let (min_params, max_params) = num_args.into_inner();
let n = args.len(); let n = args.len();
@ -1475,14 +1541,18 @@ fn assign_args_to_params(
for (index, param) in function_expression.params.iter().enumerate() { for (index, param) in function_expression.params.iter().enumerate() {
if let Some(arg) = args.get(index) { if let Some(arg) = args.get(index) {
// Argument was provided. // Argument was provided.
fn_memory.add(&param.identifier.name, arg.value.clone(), (&param.identifier).into())?; fn_memory.add(
param.identifier.name.clone(),
arg.value.clone(),
(&param.identifier).into(),
)?;
} else { } else {
// Argument was not provided. // Argument was not provided.
if let Some(ref default_val) = param.default_value { if let Some(ref default_val) = param.default_value {
// If the corresponding parameter is optional, // If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it. // then it's fine, the user doesn't need to supply it.
fn_memory.add( fn_memory.add(
&param.identifier.name, param.identifier.name.clone(),
default_val.clone().into(), default_val.clone().into(),
(&param.identifier).into(), (&param.identifier).into(),
)?; )?;
@ -1493,14 +1563,14 @@ fn assign_args_to_params(
} }
} }
} }
Ok(fn_memory) Ok(())
} }
fn assign_args_to_params_kw( fn assign_args_to_params_kw(
function_expression: NodeRef<'_, FunctionExpression>, function_expression: NodeRef<'_, FunctionExpression>,
mut args: crate::std::args::KwArgs, mut args: crate::std::args::KwArgs,
mut fn_memory: ProgramMemory, fn_memory: &mut ProgramMemory,
) -> Result<ProgramMemory, KclError> { ) -> Result<(), KclError> {
// Add the arguments to the memory. A new call frame should have already // Add the arguments to the memory. A new call frame should have already
// been created. // been created.
let source_ranges = vec![function_expression.into()]; let source_ranges = vec![function_expression.into()];
@ -1522,7 +1592,7 @@ fn assign_args_to_params_kw(
} }
}, },
}; };
fn_memory.add(&param.identifier.name, arg_val, (&param.identifier).into())?; fn_memory.add(param.identifier.name.clone(), arg_val, (&param.identifier).into())?;
} else { } else {
let Some(unlabeled) = args.unlabeled.take() else { let Some(unlabeled) = args.unlabeled.take() else {
let param_name = &param.identifier.name; let param_name = &param.identifier.name;
@ -1540,18 +1610,18 @@ fn assign_args_to_params_kw(
}); });
}; };
fn_memory.add( fn_memory.add(
&param.identifier.name, param.identifier.name.clone(),
unlabeled.value.clone(), unlabeled.value.clone(),
(&param.identifier).into(), (&param.identifier).into(),
)?; )?;
} }
} }
Ok(fn_memory) Ok(())
} }
pub(crate) async fn call_user_defined_function( pub(crate) async fn call_user_defined_function(
args: Vec<Arg>, args: Vec<Arg>,
memory: &ProgramMemory, memory: EnvironmentRef,
function_expression: NodeRef<'_, FunctionExpression>, function_expression: NodeRef<'_, FunctionExpression>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
ctx: &ExecutorContext, ctx: &ExecutorContext,
@ -1559,29 +1629,32 @@ pub(crate) async fn call_user_defined_function(
// Create a new environment to execute the function body in so that local // Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's // variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure. // parent should be the environment of the closure.
let mut body_memory = memory.clone(); exec_state.mut_memory().push_new_env_for_call(memory);
let body_env = body_memory.new_env_for_call(memory.current_env); if let Err(e) = assign_args_to_params(function_expression, args, exec_state.mut_memory()) {
body_memory.current_env = body_env; exec_state.mut_memory().pop_env();
let fn_memory = assign_args_to_params(function_expression, args, body_memory)?; return Err(e);
}
// Execute the function body using the memory we just created. // Execute the function body using the memory we just created.
let (result, fn_memory) = { let result = ctx
let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory); .exec_program(&function_expression.body, exec_state, BodyType::Block)
let result = ctx .await;
.exec_program(&function_expression.body, exec_state, BodyType::Block) let result = result.map(|_| {
.await; exec_state
// Restore the previous memory. .memory()
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_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
};
result.map(|_| fn_memory.return_)
} }
pub(crate) async fn call_user_defined_function_kw( pub(crate) async fn call_user_defined_function_kw(
args: crate::std::args::KwArgs, args: crate::std::args::KwArgs,
memory: &ProgramMemory, memory: EnvironmentRef,
function_expression: NodeRef<'_, FunctionExpression>, function_expression: NodeRef<'_, FunctionExpression>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
ctx: &ExecutorContext, ctx: &ExecutorContext,
@ -1589,31 +1662,34 @@ pub(crate) async fn call_user_defined_function_kw(
// Create a new environment to execute the function body in so that local // Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's // variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure. // parent should be the environment of the closure.
let mut body_memory = memory.clone(); exec_state.mut_memory().push_new_env_for_call(memory);
let body_env = body_memory.new_env_for_call(memory.current_env); if let Err(e) = assign_args_to_params_kw(function_expression, args, exec_state.mut_memory()) {
body_memory.current_env = body_env; exec_state.mut_memory().pop_env();
let fn_memory = assign_args_to_params_kw(function_expression, args, body_memory)?; return Err(e);
}
// Execute the function body using the memory we just created. // Execute the function body using the memory we just created.
let (result, fn_memory) = { let result = ctx
let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory); .exec_program(&function_expression.body, exec_state, BodyType::Block)
let result = ctx .await;
.exec_program(&function_expression.body, exec_state, BodyType::Block) let result = result.map(|_| {
.await; exec_state
// Restore the previous memory. .memory()
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_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
};
result.map(|_| fn_memory.return_)
} }
/// A function being used as a parameter into a stdlib function. This is a /// A function being used as a parameter into a stdlib function. This is a
/// closure, plus everything needed to execute it. /// closure, plus everything needed to execute it.
pub struct FunctionParam<'a> { pub struct FunctionParam<'a> {
pub inner: Option<&'a MemoryFunction>, pub inner: Option<&'a MemoryFunction>,
pub memory: ProgramMemory, pub memory: EnvironmentRef,
pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>, pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
pub ctx: ExecutorContext, pub ctx: ExecutorContext,
@ -1624,7 +1700,7 @@ impl FunctionParam<'_> {
if let Some(inner) = self.inner { if let Some(inner) = self.inner {
inner( inner(
args, args,
self.memory.clone(), self.memory,
self.fn_expr.clone(), self.fn_expr.clone(),
self.meta.clone(), self.meta.clone(),
exec_state, exec_state,
@ -1632,7 +1708,7 @@ impl FunctionParam<'_> {
) )
.await .await
} else { } 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 +1726,12 @@ impl JsonSchema for FunctionParam<'_> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{
execution::parse_execute,
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
};
use super::*; use super::*;
use crate::parsing::ast::types::{DefaultParamVal, Identifier, Parameter};
#[test] #[test]
fn test_assign_args_to_params() { fn test_assign_args_to_params() {
@ -1690,7 +1770,7 @@ mod test {
let mut program_memory = ProgramMemory::new(); let mut program_memory = ProgramMemory::new();
for (name, item) in items { for (name, item) in items {
program_memory program_memory
.add(name.as_str(), item.clone(), SourceRange::default()) .add(name.clone(), item.clone(), SourceRange::default())
.unwrap(); .unwrap();
} }
program_memory program_memory
@ -1775,11 +1855,26 @@ mod test {
digest: None, digest: None,
}); });
let args = args.into_iter().map(Arg::synthetic).collect(); 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!( assert_eq!(
actual, expected, actual, expected,
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{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"));
}
} }

View File

@ -564,29 +564,8 @@ pub struct Solid {
} }
impl Solid { impl Solid {
pub(crate) fn get_all_edge_cut_ids(&self) -> Vec<uuid::Uuid> { pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
self.edge_cuts.iter().map(|foc| foc.id()).collect() self.edge_cuts.iter().map(|foc| foc.id())
}
}
/// 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(),
}
} }
} }

View File

@ -166,7 +166,7 @@ pub async fn import_foreign(
pub struct PreImportedGeometry { pub struct PreImportedGeometry {
id: Uuid, id: Uuid,
command: mcmd::ImportFiles, command: mcmd::ImportFiles,
source_range: SourceRange, pub source_range: SourceRange,
} }
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> { pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::KclErrorDetails, errors::KclErrorDetails,
exec::{ProgramMemory, Sketch}, exec::Sketch,
execution::{ execution::{
Face, Helix, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier, Face, Helix, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier,
}, },
@ -18,6 +18,8 @@ use crate::{
ExecState, ExecutorContext, KclError, ModuleId, SourceRange, ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
}; };
use super::memory::EnvironmentRef;
pub type KclObjectFields = HashMap<String, KclValue>; pub type KclObjectFields = HashMap<String, KclValue>;
/// Any KCL value. /// Any KCL value.
@ -94,7 +96,7 @@ pub enum KclValue {
func: Option<MemoryFunction>, func: Option<MemoryFunction>,
#[schemars(skip)] #[schemars(skip)]
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>, expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
memory: Box<ProgramMemory>, memory: EnvironmentRef,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
@ -108,6 +110,12 @@ pub enum KclValue {
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
meta: Vec<Metadata>, 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 { impl From<SketchSet> for KclValue {
@ -166,6 +174,7 @@ impl From<KclValue> for Vec<SourceRange> {
KclValue::Module { meta, .. } => to_vec_sr(&meta), KclValue::Module { meta, .. } => to_vec_sr(&meta),
KclValue::Uuid { meta, .. } => to_vec_sr(&meta), KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
KclValue::KclNone { 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::Object { meta, .. } => to_vec_sr(meta),
KclValue::Module { meta, .. } => to_vec_sr(meta), KclValue::Module { meta, .. } => to_vec_sr(meta),
KclValue::KclNone { 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::Function { meta, .. } => meta.clone(),
KclValue::Module { meta, .. } => meta.clone(), KclValue::Module { meta, .. } => meta.clone(),
KclValue::KclNone { meta, .. } => meta.clone(), KclValue::KclNone { meta, .. } => meta.clone(),
KclValue::Tombstone { .. } => unreachable!("Tombstone Metadata"),
} }
} }
@ -291,6 +302,7 @@ impl KclValue {
KclValue::Object { .. } => "object", KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module", KclValue::Module { .. } => "module",
KclValue::KclNone { .. } => "None", 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. /// Put the number into a KCL value.
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self { pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Self::Number { value: f, meta } Self::Number { value: f, meta }
@ -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> { pub fn as_f64(&self) -> Option<f64> {
if let KclValue::Number { value, meta: _ } = &self { if let KclValue::Number { value, meta: _ } = &self {
Some(*value) Some(*value)
@ -454,7 +484,7 @@ impl KclValue {
Some(FnAsArg { Some(FnAsArg {
func: func.as_ref(), func: func.as_ref(),
expr: expression.to_owned(), expr: expression.to_owned(),
memory: memory.to_owned(), memory: *memory,
}) })
} }
@ -518,24 +548,19 @@ impl KclValue {
} = &self } = &self
else { else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "not a in memory function".to_string(), message: "not an in-memory function".to_string(),
source_ranges: vec![], source_ranges: vec![],
})); }));
}; };
if let Some(func) = func { if let Some(func) = func {
func( exec_state.mut_memory().push_new_env_for_call(*closure_memory);
args, let result = func(args, *closure_memory, expression.clone(), meta.clone(), exec_state, ctx).await;
closure_memory.as_ref().clone(), exec_state.mut_memory().pop_env();
expression.clone(), result
meta.clone(),
exec_state,
ctx,
)
.await
} else { } else {
crate::execution::exec_ast::call_user_defined_function( crate::execution::exec_ast::call_user_defined_function(
args, args,
closure_memory.as_ref(), *closure_memory,
expression.as_ref(), expression.as_ref(),
exec_state, exec_state,
&ctx, &ctx,
@ -570,7 +595,7 @@ impl KclValue {
} else { } else {
crate::execution::exec_ast::call_user_defined_function_kw( crate::execution::exec_ast::call_user_defined_function_kw(
args.kw_args, args.kw_args,
closure_memory.as_ref(), *closure_memory,
expression.as_ref(), expression.as_ref(),
exec_state, exec_state,
&ctx, &ctx,

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,8 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use anyhow::Result; use anyhow::Result;
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
pub use cache::bust_cache;
use cache::OldAstState; 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; use indexmap::IndexMap;
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
use kcmc::{ use kcmc::{
each_cmd as mcmd, each_cmd as mcmd,
ok_response::{output::TakeSnapshot, OkModelingCmdResponse}, ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
@ -21,10 +12,8 @@ use kcmc::{
ImageFormat, ModelingCmd, ImageFormat, ModelingCmd,
}; };
use kittycad_modeling_cmds as kcmc; use kittycad_modeling_cmds as kcmc;
pub use memory::ProgramMemory;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use state::{ExecState, IdGenerator, MetaSettings};
use crate::{ use crate::{
engine::EngineManager, engine::EngineManager,
@ -41,6 +30,18 @@ use crate::{
ExecError, KclErrorWithOutputs, 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; pub(crate) mod annotations;
mod artifact; mod artifact;
pub(crate) mod cache; pub(crate) mod cache;
@ -53,12 +54,12 @@ mod memory;
mod state; mod state;
/// Outcome of executing a program. This is used in TS. /// 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)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ExecOutcome { pub struct ExecOutcome {
/// Program variable bindings of the top-level module. /// Variables in the top-level of the root module. Note that functions will have an invalid env ref.
pub memory: ProgramMemory, pub variables: IndexMap<String, KclValue>,
/// Operations that have been performed in execution order, for display in /// Operations that have been performed in execution order, for display in
/// the Feature Tree. /// the Feature Tree.
pub operations: Vec<Operation>, pub operations: Vec<Operation>,
@ -133,7 +134,7 @@ impl std::hash::Hash for TagIdentifier {
pub type MemoryFunction = pub type MemoryFunction =
fn( fn(
s: Vec<Arg>, s: Vec<Arg>,
memory: ProgramMemory, memory: EnvironmentRef,
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>, expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
metadata: Vec<Metadata>, metadata: Vec<Metadata>,
exec_state: &ExecState, exec_state: &ExecState,
@ -160,7 +161,6 @@ pub struct TagEngineInfo {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum BodyType { pub enum BodyType {
Root, Root,
Sketch,
Block, Block,
} }
@ -496,17 +496,56 @@ impl ExecutorContext {
pub async fn run_mock( pub async fn run_mock(
&self, &self,
program: crate::Program, program: crate::Program,
program_memory_override: Option<ProgramMemory>, use_prev_memory: bool,
variables: IndexMap<String, KclValue>,
) -> Result<ExecOutcome, KclErrorWithOutputs> { ) -> Result<ExecOutcome, KclErrorWithOutputs> {
assert!(self.is_mock()); assert!(self.is_mock());
let mut exec_state = ExecState::new(&self.settings); let mut exec_state = ExecState::new(&self.settings);
if let Some(program_memory_override) = program_memory_override { let mut mem = if use_prev_memory {
exec_state.mod_local.memory = program_memory_override; 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?; 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> { pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
@ -516,7 +555,7 @@ impl ExecutorContext {
ast: old_ast, ast: old_ast,
exec_state: old_state, exec_state: old_state,
settings: old_settings, settings: old_settings,
}) = cache::read_old_ast_memory().await }) = cache::read_old_ast().await
{ {
let old = CacheInformation { let old = CacheInformation {
ast: &old_ast, ast: &old_ast,
@ -595,7 +634,7 @@ impl ExecutorContext {
result?; result?;
// Save this as the last successful execution to the cache. // Save this as the last successful execution to the cache.
cache::write_old_ast_memory(OldAstState { cache::write_old_ast(OldAstState {
ast: program, ast: program,
exec_state: exec_state.clone(), exec_state: exec_state.clone(),
settings: self.settings.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(); let session_data = self.engine.get_session_data();
Ok(session_data) Ok(session_data)
} }
@ -1588,12 +1635,7 @@ let w = f() + f()
ctx.run_with_caching(old_program).await.unwrap(); ctx.run_with_caching(old_program).await.unwrap();
// Get the id_generator from the first execution. // Get the id_generator from the first execution.
let id_generator = cache::read_old_ast_memory() let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
.await
.unwrap()
.exec_state
.global
.id_generator;
let code = r#"sketch001 = startSketchOn('XZ') let code = r#"sketch001 = startSketchOn('XZ')
|> startProfileAt([62.74, 206.13], %) |> startProfileAt([62.74, 206.13], %)
@ -1614,12 +1656,7 @@ let w = f() + f()
// Execute the program. // Execute the program.
ctx.run_with_caching(program).await.unwrap(); ctx.run_with_caching(program).await.unwrap();
let new_id_generator = cache::read_old_ast_memory() let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
.await
.unwrap()
.exec_state
.global
.id_generator;
assert_eq!(id_generator, new_id_generator); assert_eq!(id_generator, new_id_generator);
} }

View File

@ -9,7 +9,7 @@ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, ExecutorSettings, 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}, modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
parsing::ast::types::NonCodeValue, parsing::ast::types::NonCodeValue,
@ -27,6 +27,8 @@ pub struct ExecState {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GlobalState { pub struct GlobalState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// The stable artifact ID generator. /// The stable artifact ID generator.
pub id_generator: IdGenerator, pub id_generator: IdGenerator,
/// Map from source file absolute path to module ID. /// Map from source file absolute path to module ID.
@ -50,13 +52,9 @@ pub struct GlobalState {
pub mod_loader: ModuleLoader, pub mod_loader: ModuleLoader,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ModuleState { 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 /// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None. /// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>, pub pipe_value: Option<KclValue>,
@ -99,7 +97,11 @@ impl ExecState {
// Fields are opt-in so that we don't accidentally leak private internal // Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState. // state when we add more to ExecState.
ExecOutcome { 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, operations: self.mod_local.operations,
artifacts: self.global.artifacts, artifacts: self.global.artifacts,
artifact_commands: self.global.artifact_commands, 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 { pub fn memory(&self) -> &ProgramMemory {
&self.mod_local.memory &self.global.memory
} }
pub fn mut_memory(&mut self) -> &mut ProgramMemory { pub fn mut_memory(&mut self) -> &mut ProgramMemory {
&mut self.mod_local.memory &mut self.global.memory
} }
pub fn next_uuid(&mut self) -> Uuid { pub fn next_uuid(&mut self) -> Uuid {
@ -148,11 +166,29 @@ impl ExecState {
pub fn angle_unit(&self) -> UnitAngle { pub fn angle_unit(&self) -> UnitAngle {
self.mod_local.settings.default_angle_units 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 { impl GlobalState {
fn new(settings: &ExecutorSettings) -> Self { fn new(settings: &ExecutorSettings) -> Self {
let mut global = GlobalState { let mut global = GlobalState {
memory: ProgramMemory::new(),
id_generator: Default::default(), id_generator: Default::default(),
path_to_source_id: Default::default(), path_to_source_id: Default::default(),
module_infos: Default::default(), module_infos: Default::default(),
@ -181,8 +217,6 @@ impl GlobalState {
impl ModuleState { impl ModuleState {
pub(super) fn new(exec_settings: &ExecutorSettings) -> Self { pub(super) fn new(exec_settings: &ExecutorSettings) -> Self {
ModuleState { ModuleState {
memory: Default::default(),
dynamic_state: Default::default(),
pipe_value: Default::default(), pipe_value: Default::default(),
module_exports: Default::default(), module_exports: Default::default(),
operations: 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. /// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View File

@ -83,7 +83,9 @@ mod wasm;
pub use coredump::CoreDump; pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind}; pub use engine::{EngineManager, ExecutionKind};
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs}; 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::{ pub use lsp::{
copilot::Backend as CopilotLspBackend, copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand}, kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},

View File

@ -102,7 +102,7 @@ impl<'a> LogPerfStats<'a> {
} }
} }
/// After `cancel`ing, this object will not log its stats on drop (you can still can `log_now`). /// After `cancel`ing, this object will not log its stats on drop (you can still `log_now`).
pub fn cancel(&mut self) { pub fn cancel(&mut self) {
self.cancelled = true; self.cancelled = true;
} }

View File

@ -14,15 +14,6 @@ impl Notification for AstUpdated {
const METHOD: &'static str = "kcl/astUpdated"; const METHOD: &'static str = "kcl/astUpdated";
} }
/// A notification that the Memory has changed.
#[derive(Debug)]
pub enum MemoryUpdated {}
impl Notification for MemoryUpdated {
type Params = crate::execution::ProgramMemory;
const METHOD: &'static str = "kcl/memoryUpdated";
}
/// Text documents are identified using a URI. On the protocol level, URIs are passed as strings. /// Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, ts_rs::TS)] #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, ts_rs::TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View File

@ -102,8 +102,6 @@ pub struct Backend {
pub(super) token_map: DashMap<String, TokenStream>, pub(super) token_map: DashMap<String, TokenStream>,
/// AST maps. /// AST maps.
pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>, pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>,
/// Memory maps.
pub memory_map: DashMap<String, crate::execution::ProgramMemory>,
/// Current code. /// Current code.
pub code_map: DashMap<String, Vec<u8>>, pub code_map: DashMap<String, Vec<u8>>,
/// Diagnostics. /// Diagnostics.
@ -181,7 +179,6 @@ impl Backend {
workspace_folders: Default::default(), workspace_folders: Default::default(),
token_map: Default::default(), token_map: Default::default(),
ast_map: Default::default(), ast_map: Default::default(),
memory_map: Default::default(),
code_map: Default::default(), code_map: Default::default(),
diagnostics_map: Default::default(), diagnostics_map: Default::default(),
symbols_map: Default::default(), symbols_map: Default::default(),
@ -193,7 +190,6 @@ impl Backend {
fn remove_from_ast_maps(&self, filename: &str) { fn remove_from_ast_maps(&self, filename: &str) {
self.ast_map.remove(filename); self.ast_map.remove(filename);
self.symbols_map.remove(filename); self.symbols_map.remove(filename);
self.memory_map.remove(filename);
} }
} }
@ -279,13 +275,6 @@ impl crate::lsp::backend::Backend for Backend {
} }
}; };
// Try to get the memory for the current code.
let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
*memory != crate::execution::ProgramMemory::default()
} else {
false
};
// Get the previous tokens. // Get the previous tokens.
let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) { let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) {
*previous_tokens != tokens *previous_tokens != tokens
@ -293,8 +282,10 @@ impl crate::lsp::backend::Backend for Backend {
true true
}; };
let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
// Check if the tokens are the same. // Check if the tokens are the same.
if !tokens_changed && !force && has_memory && !self.has_diagnostics(params.uri.as_ref()).await { if !tokens_changed && !force && !had_diagnostics {
// We return early here because the tokens are the same. // We return early here because the tokens are the same.
return; return;
} }
@ -343,7 +334,7 @@ impl crate::lsp::backend::Backend for Backend {
None => true, None => true,
}; };
if !ast_changed && !force && has_memory { if !ast_changed && !force && !had_diagnostics {
// Return early if the ast did not change and we don't need to force. // Return early if the ast did not change and we don't need to force.
return; return;
} }
@ -680,24 +671,13 @@ impl Backend {
match executor_ctx.run_with_caching(ast.clone()).await { match executor_ctx.run_with_caching(ast.clone()).await {
Err(err) => { Err(err) => {
self.memory_map.remove(params.uri.as_str());
self.add_to_diagnostics(params, &[err.error], false).await; self.add_to_diagnostics(params, &[err.error], false).await;
// Since we already published the diagnostics we don't really care about the error // Since we already published the diagnostics we don't really care about the error
// string. // string.
Err(anyhow::anyhow!("failed to execute code")) Err(anyhow::anyhow!("failed to execute code"))
} }
Ok(outcome) => { Ok(_) => Ok(()),
let memory = outcome.memory;
self.memory_map.insert(params.uri.to_string(), memory.clone());
// Send the notification to the client that the memory was updated.
self.client
.send_notification::<custom_notifications::MemoryUpdated>(memory)
.await;
Ok(())
}
} }
} }
@ -815,8 +795,6 @@ impl Backend {
&self, &self,
params: custom_notifications::UpdateUnitsParams, params: custom_notifications::UpdateUnitsParams,
) -> RpcResult<Option<custom_notifications::UpdateUnitsResponse>> { ) -> RpcResult<Option<custom_notifications::UpdateUnitsResponse>> {
let filename = params.text_document.uri.to_string();
{ {
let mut ctx = self.executor_ctx.write().await; let mut ctx = self.executor_ctx.write().await;
// Borrow the executor context mutably. // Borrow the executor context mutably.
@ -831,16 +809,8 @@ impl Backend {
.log_message(MessageType::INFO, format!("update units: {:?}", params)) .log_message(MessageType::INFO, format!("update units: {:?}", params))
.await; .await;
// Try to get the memory for the current code.
let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
*memory != crate::execution::ProgramMemory::default()
} else {
false
};
if executor_ctx.settings.units == params.units if executor_ctx.settings.units == params.units
&& !self.has_diagnostics(params.text_document.uri.as_ref()).await && !self.has_diagnostics(params.text_document.uri.as_ref()).await
&& has_memory
{ {
// Return early the units are the same. // Return early the units are the same.
return Ok(None); return Ok(None);

View File

@ -28,7 +28,6 @@ pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
stdlib_signatures, stdlib_signatures,
token_map: Default::default(), token_map: Default::default(),
ast_map: Default::default(), ast_map: Default::default(),
memory_map: Default::default(),
code_map: Default::default(), code_map: Default::default(),
diagnostics_map: Default::default(), diagnostics_map: Default::default(),
symbols_map: Default::default(), symbols_map: Default::default(),

View File

@ -7,7 +7,6 @@ use tower_lsp::{
}; };
use crate::{ use crate::{
execution::ProgramMemory,
lsp::test_util::{copilot_lsp_server, kcl_lsp_server}, lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
parsing::ast::types::{Node, Program}, parsing::ast::types::{Node, Program},
}; };
@ -1543,13 +1542,13 @@ sphere = startSketchOn('XZ')
}, %) }, %)
|> close() |> close()
|> revolve({ axis: 'x' }, %) |> revolve({ axis: 'x' }, %)
|> patternCircular3d({ |> patternCircular3d(
axis: [0, 0, 1], axis = [0, 0, 1],
center: [0, 0, 0], center = [0, 0, 0],
repetitions: 10, repetitions = 10,
arcDegrees: 360, arcDegrees = 360,
rotateDuplicates: true rotateDuplicates = true,
}, %) )
// Sketch and revolve the outside bearing // Sketch and revolve the outside bearing
outsideRevolve = startSketchOn('XZ') outsideRevolve = startSketchOn('XZ')
@ -1644,13 +1643,13 @@ sphere = startSketchOn('XZ')
}, %) }, %)
|> close() |> close()
|> revolve({ axis = 'x' }, %) |> revolve({ axis = 'x' }, %)
|> patternCircular3d({ |> patternCircular3d(
axis = [0, 0, 1], axis = [0, 0, 1],
center = [0, 0, 0], center = [0, 0, 0],
repetitions = 10, repetitions = 10,
arcDegrees = 360, arcDegrees = 360,
rotateDuplicates = true rotateDuplicates = true,
}, %) )
// Sketch and revolve the outside bearing // Sketch and revolve the outside bearing
outsideRevolve = startSketchOn('XZ') outsideRevolve = startSketchOn('XZ')
@ -2163,9 +2162,6 @@ async fn test_kcl_lsp_on_change_update_ast() {
.await; .await;
assert!(ast != server.ast_map.get("file:///test.kcl").unwrap().clone()); assert!(ast != server.ast_map.get("file:///test.kcl").unwrap().clone());
// Make sure we never updated the memory since we aren't running the engine.
assert!(server.memory_map.get("file:///test.kcl").is_none());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2186,9 +2182,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
}) })
.await; .await;
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
// Send change file. // Send change file.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2204,9 +2197,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
}) })
.await; .await;
// Make sure the memory is the same.
assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone());
// Update the text. // Update the text.
let new_text = r#"thing = 2"#.to_string(); let new_text = r#"thing = 2"#.to_string();
// Send change file. // Send change file.
@ -2223,8 +2213,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
}], }],
}) })
.await; .await;
assert!(memory != server.memory_map.get("file:///test.kcl").unwrap().clone());
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 10)] #[tokio::test(flavor = "multi_thread", worker_threads = 10)]
@ -2265,9 +2253,6 @@ part001 = cube([0,0], 20)
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(ast.body.len(), 2); assert_eq!(ast.body.len(), 2);
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
// Send change file. // Send change file.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2283,9 +2268,6 @@ part001 = cube([0,0], 20)
}) })
.await; .await;
// Make sure the memory is the same.
assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone());
let units = server.executor_ctx.read().await.clone().unwrap().settings.units; let units = server.executor_ctx.read().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::Mm); assert_eq!(units, crate::settings::types::UnitLength::Mm);
@ -2303,9 +2285,6 @@ part001 = cube([0,0], 20)
let units = server.executor_ctx().await.clone().unwrap().settings.units; let units = server.executor_ctx().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::M); assert_eq!(units, crate::settings::types::UnitLength::M);
// Make sure it forced a memory update.
assert!(memory != server.memory_map.get("file:///test.kcl").unwrap().clone());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2323,10 +2302,6 @@ async fn kcl_test_kcl_lsp_empty_file_execute_ok() {
}, },
}) })
.await; .await;
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2454,9 +2429,6 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Send change file. // Send change file.
server server
@ -2479,9 +2451,6 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(ast, default_hashed); assert_eq!(ast, default_hashed);
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2511,9 +2480,6 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2545,11 +2511,6 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
.insert("file:///test.kcl".to_string(), Node::<Program>::default()); .insert("file:///test.kcl".to_string(), Node::<Program>::default());
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(ast, Node::<Program>::default()); assert_eq!(ast, Node::<Program>::default());
server
.memory_map
.insert("file:///test.kcl".to_string(), ProgramMemory::default());
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
@ -2569,9 +2530,6 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2604,9 +2562,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2632,13 +2587,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
// Assure we have one diagnostics. // Assure we have one diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
// Clear ONLY the memory.
server
.memory_map
.insert("file:///test.kcl".to_string(), ProgramMemory::default());
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2657,9 +2605,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2692,9 +2637,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2720,13 +2662,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
// Assure we have one diagnostics. // Assure we have one diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
// Clear ONLY the memory.
server
.memory_map
.insert("file:///test.kcl".to_string(), ProgramMemory::default());
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
let units = server.executor_ctx().await.clone().unwrap().settings.units; let units = server.executor_ctx().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::Mm); assert_eq!(units, crate::settings::types::UnitLength::Mm);
@ -2748,9 +2683,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2783,20 +2715,10 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
// Clear ONLY the memory.
server
.memory_map
.insert("file:///test.kcl".to_string(), ProgramMemory::default());
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
let units = server.executor_ctx().await.clone().unwrap().settings.units; let units = server.executor_ctx().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::Mm); assert_eq!(units, crate::settings::types::UnitLength::Mm);
@ -2818,9 +2740,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2853,20 +2772,10 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
// Clear ONLY the memory.
server
.memory_map
.insert("file:///test.kcl".to_string(), ProgramMemory::default());
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
// Update the units to the _same_ units. // Update the units to the _same_ units.
let units = server.executor_ctx().await.clone().unwrap().settings.units; let units = server.executor_ctx().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::Mm); assert_eq!(units, crate::settings::types::UnitLength::Mm);
@ -2887,20 +2796,10 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
// Clear ONLY the memory.
server
.memory_map
.insert("file:///test.kcl".to_string(), ProgramMemory::default());
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(memory, ProgramMemory::default());
assert_eq!(server.can_execute().await, true); assert_eq!(server.can_execute().await, true);
// Set that we cannot execute. // Set that we cannot execute.
@ -2933,10 +2832,6 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != default_hashed); assert!(ast != default_hashed);
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
// Now it should be the default memory.
assert!(memory == ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -2968,10 +2863,6 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
// Now it should NOT be the default memory.
assert!(memory != ProgramMemory::default());
// Assure we have no diagnostics. // Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -3219,9 +3110,6 @@ part001 = startSketchOn('XY')
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
@ -3241,9 +3129,6 @@ part001 = startSketchOn('XY')
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Assure we have diagnostics. // Assure we have diagnostics.
@ -3284,9 +3169,6 @@ part001 = startSketchOn('XY')
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
@ -3314,9 +3196,6 @@ NEW_LINT = 1"#
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert!(ast != Node::<Program>::default()); assert!(ast != Node::<Program>::default());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Assure we have diagnostics. // Assure we have diagnostics.
@ -3357,9 +3236,6 @@ part001 = startSketchOn('XY')
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl"); let ast = server.ast_map.get("file:///test.kcl");
assert!(ast.is_none()); assert!(ast.is_none());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
@ -3387,9 +3263,6 @@ NEW_LINT = 1"#
// Get the ast. // Get the ast.
let ast = server.ast_map.get("file:///test.kcl"); let ast = server.ast_map.get("file:///test.kcl");
assert!(ast.is_none()); assert!(ast.is_none());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Assure we have diagnostics. // Assure we have diagnostics.
@ -3439,10 +3312,6 @@ part001 = startSketchOn('XY')
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone(); let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
assert!(!semantic_tokens_map.is_empty()); assert!(!semantic_tokens_map.is_empty());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -3478,10 +3347,6 @@ NEW_LINT = 1"#
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone(); let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
assert!(!semantic_tokens_map.is_empty()); assert!(!semantic_tokens_map.is_empty());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Assure we have diagnostics. // Assure we have diagnostics.
// Check the diagnostics. // Check the diagnostics.
@ -3534,10 +3399,6 @@ part001 = startSketchOn('XY')
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone(); let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
assert!(!semantic_tokens_map.is_empty()); assert!(!semantic_tokens_map.is_empty());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
assert!(memory != ProgramMemory::default());
// Send change file, but the code is the same. // Send change file, but the code is the same.
server server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -3577,10 +3438,6 @@ part001 = startSketchOn('XY')
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone(); let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
assert!(!semantic_tokens_map.is_empty()); assert!(!semantic_tokens_map.is_empty());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// Assure we have diagnostics. // Assure we have diagnostics.
// Check the diagnostics. // Check the diagnostics.

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::PreImportedGeometry, execution::{EnvironmentRef, PreImportedGeometry},
fs::{FileManager, FileSystem}, fs::{FileManager, FileSystem},
parsing::ast::types::{ImportPath, Node, Program}, parsing::ast::types::{ImportPath, Node, Program},
source_range::SourceRange, source_range::SourceRange,
@ -78,8 +78,7 @@ pub(crate) fn read_std(_mod_name: &str) -> Option<&'static str> {
None None
} }
/// Info about a module. Right now, this is pretty minimal. We hope to cache /// Info about a module.
/// modules here in the future.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ModuleInfo { pub struct ModuleInfo {
/// The ID of the module. /// The ID of the module.
@ -89,12 +88,27 @@ pub struct ModuleInfo {
pub(crate) repr: ModuleRepr, pub(crate) repr: ModuleRepr,
} }
impl ModuleInfo {
pub(crate) fn take_repr(&mut self) -> ModuleRepr {
let mut result = ModuleRepr::Dummy;
std::mem::swap(&mut self.repr, &mut result);
result
}
pub(crate) fn restore_repr(&mut self, repr: ModuleRepr) {
assert!(matches!(&self.repr, ModuleRepr::Dummy));
self.repr = repr;
}
}
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ModuleRepr { pub enum ModuleRepr {
Root, Root,
Kcl(Node<Program>), // AST, memory, exported names
Kcl(Node<Program>, Option<(EnvironmentRef, Vec<String>)>),
Foreign(PreImportedGeometry), Foreign(PreImportedGeometry),
Dummy,
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]

View File

@ -3232,6 +3232,21 @@ impl FunctionExpression {
None None
} }
#[cfg(test)]
pub fn dummy() -> Box<Node<Self>> {
Box::new(Node::new(
FunctionExpression {
params: Vec::new(),
body: Node::new(Program::default(), 0, 0, ModuleId::default()),
return_type: None,
digest: None,
},
0,
0,
ModuleId::default(),
))
}
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]

View File

@ -103,7 +103,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
twenty_twenty::assert_image(format!("tests/{test_name}/rendered_model.png"), &png, 0.99); twenty_twenty::assert_image(format!("tests/{test_name}/rendered_model.png"), &png, 0.99);
} }
assert_snapshot(test_name, "Program memory after executing", || { assert_snapshot(test_name, "Program memory after executing", || {
insta::assert_json_snapshot!("program_memory", exec_state.mod_local.memory, { insta::assert_json_snapshot!("program_memory", exec_state.memory(), {
".environments[].**[].from[]" => rounded_redaction(4), ".environments[].**[].from[]" => rounded_redaction(4),
".environments[].**[].to[]" => rounded_redaction(4), ".environments[].**[].to[]" => rounded_redaction(4),
".environments[].**[].x[]" => rounded_redaction(4), ".environments[].**[].x[]" => rounded_redaction(4),
@ -187,14 +187,6 @@ fn assert_common_snapshots(
// in GitHub. // in GitHub.
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned()); insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
}); });
assert_snapshot(test_name, "Artifact graph mind map", || {
let mind_map = artifact_graph
.to_mermaid_mind_map()
.unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
// Change the snapshot suffix so that it is rendered as a Markdown file
// in GitHub.
insta::assert_binary_snapshot!("artifact_graph_mind_map.md", mind_map.as_bytes().to_owned());
});
} }
mod cube { mod cube {

View File

@ -180,12 +180,12 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// color = '#ff0000', /// color = '#ff0000',
/// metalness = 90, /// metalness = 90,
/// roughness = 90 /// roughness = 90
/// ) /// )
/// |> patternLinear3d({ /// |> patternLinear3d(
/// axis = [1, 0, 1], /// axis = [1, 0, 1],
/// instances = 7, /// instances = 7,
/// distance = 6 /// distance = 6
/// }, %) /// )
/// ``` /// ```
/// ///
/// ```no_run /// ```no_run
@ -199,16 +199,16 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// |> close() /// |> close()
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// |> patternLinear3d({ /// |> patternLinear3d(
/// axis = [1, 0, 1], /// axis = [1, 0, 1],
/// instances = 7, /// instances = 7,
/// distance = 6 /// distance = 6
/// }, %) /// )
/// |> appearance( /// |> appearance(
/// color = '#ff0000', /// color = '#ff0000',
/// metalness = 90, /// metalness = 90,
/// roughness = 90 /// roughness = 90
/// ) /// )
/// ``` /// ```
/// ///
/// ```no_run /// ```no_run
@ -219,12 +219,12 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// |> line(end = [-1, 0]) /// |> line(end = [-1, 0])
/// |> line(end = [0, -5]) /// |> line(end = [0, -5])
/// |> close() /// |> close()
/// |> patternCircular2d({ /// |> patternCircular2d(
/// center = [0, 0], /// center = [0, 0],
/// instances = 13, /// instances = 13,
/// arcDegrees = 360, /// arcDegrees = 360,
/// rotateDuplicates = true /// rotateDuplicates = true
/// }, %) /// )
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// |> appearance( /// |> appearance(

View File

@ -188,7 +188,7 @@ impl Args {
exec_state: &'e mut ExecState, exec_state: &'e mut ExecState,
tag: &'a TagIdentifier, tag: &'a TagIdentifier,
) -> Result<&'e crate::execution::TagEngineInfo, KclError> { ) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
if let KclValue::TagIdentifier(t) = exec_state.memory().get(&tag.value, self.source_range)? { if let KclValue::TagIdentifier(t) = exec_state.memory().get_from_call_stack(&tag.value, self.source_range)? {
Ok(t.info.as_ref().ok_or_else(|| { Ok(t.info.as_ref().ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have engine info", tag.value), message: format!("Tag `{}` does not have engine info", tag.value),
@ -255,11 +255,13 @@ impl Args {
ids.extend( ids.extend(
exec_state exec_state
.memory() .memory()
.find_solids_on_sketch(solid.sketch.id) .walk_call_stack()
.iter() .filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
.flat_map(|eg| eg.get_all_edge_cut_ids()), .flat_map(|v| match v {
KclValue::Solid { value } => value.get_all_edge_cut_ids(),
_ => unreachable!(),
}),
); );
ids.extend(exec_state.mod_local.dynamic_state.edge_cut_ids_on_sketch(sketch_id));
traversed_sketches.push(sketch_id); traversed_sketches.push(sketch_id);
} }
@ -440,13 +442,6 @@ impl Args {
FromArgs::from_args(self, 0) FromArgs::from_args(self, 0)
} }
pub(crate) fn get_data_and_solid_set<'a, T>(&'a self) -> Result<(T, SolidSet), KclError>
where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_solid<'a, T>(&'a self) -> Result<(T, Box<Solid>), KclError> pub(crate) fn get_data_and_solid<'a, T>(&'a self) -> Result<(T, Box<Solid>), KclError>
where where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized, T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
@ -943,72 +938,6 @@ impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
} }
} }
impl<'a> FromKclValue<'a> for super::patterns::CircularPattern3dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, instances);
let_field_of!(obj, arc_degrees "arcDegrees");
let_field_of!(obj, rotate_duplicates "rotateDuplicates");
let_field_of!(obj, axis);
let_field_of!(obj, center);
let_field_of!(obj, use_original? "useOriginal");
Some(Self {
instances,
axis,
center,
arc_degrees,
rotate_duplicates,
use_original,
})
}
}
impl<'a> FromKclValue<'a> for super::patterns::CircularPattern2dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, instances);
let_field_of!(obj, arc_degrees "arcDegrees");
let_field_of!(obj, rotate_duplicates "rotateDuplicates");
let_field_of!(obj, center);
let_field_of!(obj, use_original? "useOriginal");
Some(Self {
instances,
center,
arc_degrees,
rotate_duplicates,
use_original,
})
}
}
impl<'a> FromKclValue<'a> for super::patterns::LinearPattern3dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, distance);
let_field_of!(obj, instances);
let_field_of!(obj, axis);
Some(Self {
instances,
distance,
axis,
})
}
}
impl<'a> FromKclValue<'a> for super::patterns::LinearPattern2dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, distance);
let_field_of!(obj, instances);
let_field_of!(obj, axis);
Some(Self {
instances,
distance,
axis,
})
}
}
impl<'a> FromKclValue<'a> for super::sketch::BezierData { impl<'a> FromKclValue<'a> for super::sketch::BezierData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?; let obj = arg.as_object()?;

View File

@ -19,7 +19,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
fn_expr: f.expr, fn_expr: f.expr,
meta: meta.clone(), meta: meta.clone(),
ctx: args.ctx.clone(), ctx: args.ctx.clone(),
memory: *f.memory, memory: f.memory,
}; };
let new_array = inner_map(array, map_fn, exec_state, &args).await?; let new_array = inner_map(array, map_fn, exec_state, &args).await?;
Ok(KclValue::Array { value: new_array, meta }) Ok(KclValue::Array { value: new_array, meta })
@ -97,7 +97,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
fn_expr: f.expr, fn_expr: f.expr,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
ctx: args.ctx.clone(), ctx: args.ctx.clone(),
memory: *f.memory, memory: f.memory,
}; };
inner_reduce(array, start, reduce_fn, exec_state, &args).await inner_reduce(array, start, reduce_fn, exec_state, &args).await
} }

View File

@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::KclError, errors::KclError,
execution::{ExecState, KclValue, ProgramMemory}, execution::{EnvironmentRef, ExecState, KclValue},
parsing::ast::types::FunctionExpression, parsing::ast::types::FunctionExpression,
}; };
@ -305,5 +305,5 @@ pub enum Primitive {
pub struct FnAsArg<'a> { pub struct FnAsArg<'a> {
pub func: Option<&'a crate::execution::MemoryFunction>, pub func: Option<&'a crate::execution::MemoryFunction>,
pub expr: crate::parsing::ast::types::BoxNode<FunctionExpression>, pub expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
pub memory: Box<ProgramMemory>, pub memory: EnvironmentRef,
} }

View File

@ -29,22 +29,6 @@ use crate::{
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry"; const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Data for a linear pattern on a 2D sketch.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct LinearPattern2dData {
/// 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.
pub instances: u32,
/// The distance between each repetition. This can also be referred to as spacing.
pub distance: f64,
/// The axis of the pattern. This is a 2D vector.
pub axis: [f64; 2],
}
/// Data for a linear pattern on a 3D model. /// Data for a linear pattern on a 3D model.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
@ -72,7 +56,7 @@ pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result
fn_expr: transform.expr, fn_expr: transform.expr,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
ctx: args.ctx.clone(), ctx: args.ctx.clone(),
memory: *transform.memory, memory: transform.memory,
}, },
extr, extr,
use_original, use_original,
@ -95,7 +79,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
fn_expr: transform.expr, fn_expr: transform.expr,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
ctx: args.ctx.clone(), ctx: args.ctx.clone(),
memory: *transform.memory, memory: transform.memory,
}, },
sketch, sketch,
use_original, use_original,
@ -689,10 +673,13 @@ mod tests {
/// A linear pattern on a 2D sketch. /// A linear pattern on a 2D sketch.
pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch_set, use_original): (LinearPattern2dData, SketchSet, Option<bool>) = let sketch_set: SketchSet = args.get_unlabeled_kw_arg("sketchSet")?;
super::args::FromArgs::from_args(&args, 0)?; let instances: u32 = args.get_kw_arg("instances")?;
let distance: f64 = args.get_kw_arg("distance")?;
let axis: [f64; 2] = args.get_kw_arg("axis")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
if data.axis == [0.0, 0.0] { if axis == [0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -701,7 +688,8 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
})); }));
} }
let sketches = inner_pattern_linear_2d(data, sketch_set, use_original, exec_state, args).await?; let sketches =
inner_pattern_linear_2d(sketch_set, instances, distance, axis, use_original, exec_state, args).await?;
Ok(sketches.into()) Ok(sketches.into())
} }
@ -711,31 +699,41 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
/// ```no_run /// ```no_run
/// exampleSketch = startSketchOn('XZ') /// exampleSketch = startSketchOn('XZ')
/// |> circle({ center = [0, 0], radius = 1 }, %) /// |> circle({ center = [0, 0], radius = 1 }, %)
/// |> patternLinear2d({ /// |> patternLinear2d(
/// axis = [1, 0], /// axis = [1, 0],
/// instances = 7, /// instances = 7,
/// distance = 4 /// distance = 4
/// }, %) /// )
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternLinear2d", name = "patternLinear2d",
keywords = true,
unlabeled_first = true,
args = {
sketch_set = { docs = "The sketch(es) to duplicate" },
instances = { docs = "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." },
distance = { docs = "Distance between each repetition. Also known as 'spacing'."},
axis = { docs = "The axis of the pattern. A 2D vector." },
use_original = { docs = "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." },
}
}] }]
async fn inner_pattern_linear_2d( async fn inner_pattern_linear_2d(
data: LinearPattern2dData,
sketch_set: SketchSet, sketch_set: SketchSet,
instances: u32,
distance: f64,
axis: [f64; 2],
use_original: Option<bool>, use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Sketch>>, KclError> { ) -> Result<Vec<Box<Sketch>>, KclError> {
let axis = data.axis;
let [x, y] = axis; let [x, y] = axis;
let axis_len = f64::sqrt(x * x + y * y); let axis_len = f64::sqrt(x * x + y * y);
let normalized_axis = kcmc::shared::Point2d::from([x / axis_len, y / axis_len]); let normalized_axis = kcmc::shared::Point2d::from([x / axis_len, y / axis_len]);
let transforms: Vec<_> = (1..data.instances) let transforms: Vec<_> = (1..instances)
.map(|i| { .map(|i| {
let d = data.distance * (i as f64); let d = distance * (i as f64);
let translate = (normalized_axis * d).with_z(0.0).map(LengthUnit); let translate = (normalized_axis * d).with_z(0.0).map(LengthUnit);
vec![Transform { vec![Transform {
translate, translate,
@ -755,10 +753,13 @@ async fn inner_pattern_linear_2d(
/// A linear pattern on a 3D model. /// A linear pattern on a 3D model.
pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid_set, use_original): (LinearPattern3dData, SolidSet, Option<bool>) = let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?;
super::args::FromArgs::from_args(&args, 0)?; let instances: u32 = args.get_kw_arg("instances")?;
let distance: f64 = args.get_kw_arg("distance")?;
let axis: [f64; 3] = args.get_kw_arg("axis")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
if data.axis == [0.0, 0.0, 0.0] { if axis == [0.0, 0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -767,7 +768,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
})); }));
} }
let solids = inner_pattern_linear_3d(data, solid_set, use_original, exec_state, args).await?; let solids = inner_pattern_linear_3d(solid_set, instances, distance, axis, use_original, exec_state, args).await?;
Ok(solids.into()) Ok(solids.into())
} }
@ -783,30 +784,40 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
/// |> close() /// |> close()
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// |> patternLinear3d({ /// |> patternLinear3d(
/// axis = [1, 0, 1], /// axis = [1, 0, 1],
/// instances = 7, /// instances = 7,
/// distance = 6 /// distance = 6
/// }, %) /// )
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternLinear3d", name = "patternLinear3d",
feature_tree_operation = true, feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
solid_set = { docs = "The solid(s) to duplicate" },
instances = { docs = "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." },
distance = { docs = "Distance between each repetition. Also known as 'spacing'."},
axis = { docs = "The axis of the pattern. A 2D vector." },
use_original = { docs = "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." },
}
}] }]
async fn inner_pattern_linear_3d( async fn inner_pattern_linear_3d(
data: LinearPattern3dData,
solid_set: SolidSet, solid_set: SolidSet,
instances: u32,
distance: f64,
axis: [f64; 3],
use_original: Option<bool>, use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Solid>>, KclError> { ) -> Result<Vec<Box<Solid>>, KclError> {
let axis = data.axis;
let [x, y, z] = axis; let [x, y, z] = axis;
let axis_len = f64::sqrt(x * x + y * y + z * z); let axis_len = f64::sqrt(x * x + y * y + z * z);
let normalized_axis = kcmc::shared::Point3d::from([x / axis_len, y / axis_len, z / axis_len]); let normalized_axis = kcmc::shared::Point3d::from([x / axis_len, y / axis_len, z / axis_len]);
let transforms: Vec<_> = (1..data.instances) let transforms: Vec<_> = (1..instances)
.map(|i| { .map(|i| {
let d = data.distance * (i as f64); let d = distance * (i as f64);
let translate = (normalized_axis * d).map(LengthUnit); let translate = (normalized_axis * d).map(LengthUnit);
vec![Transform { vec![Transform {
translate, translate,
@ -828,7 +839,7 @@ async fn inner_pattern_linear_3d(
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CircularPattern2dData { struct CircularPattern2dData {
/// The number of total instances. Must be greater than or equal to 1. /// The number of total instances. Must be greater than or equal to 1.
/// This includes the original entity. For example, if instances is 2, /// This includes the original entity. For example, if instances is 2,
/// there will be two copies -- the original, and one new copy. /// there will be two copies -- the original, and one new copy.
@ -870,7 +881,7 @@ pub struct CircularPattern3dData {
pub use_original: Option<bool>, pub use_original: Option<bool>,
} }
pub enum CircularPattern { enum CircularPattern {
ThreeD(CircularPattern3dData), ThreeD(CircularPattern3dData),
TwoD(CircularPattern2dData), TwoD(CircularPattern2dData),
} }
@ -941,9 +952,24 @@ impl CircularPattern {
/// A circular pattern on a 2D sketch. /// A circular pattern on a 2D sketch.
pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch_set): (CircularPattern2dData, SketchSet) = args.get_data_and_sketch_set()?; let sketch_set: SketchSet = args.get_unlabeled_kw_arg("sketchSet")?;
let instances: u32 = args.get_kw_arg("instances")?;
let center: [f64; 2] = args.get_kw_arg("center")?;
let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?;
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let sketches = inner_pattern_circular_2d(data, sketch_set, exec_state, args).await?; let sketches = inner_pattern_circular_2d(
sketch_set,
instances,
center,
arc_degrees,
rotate_duplicates,
use_original,
exec_state,
args,
)
.await?;
Ok(sketches.into()) Ok(sketches.into())
} }
@ -959,21 +985,36 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
/// |> line(end = [-1, 0]) /// |> line(end = [-1, 0])
/// |> line(end = [0, -5]) /// |> line(end = [0, -5])
/// |> close() /// |> close()
/// |> patternCircular2d({ /// |> patternCircular2d(
/// center = [0, 0], /// center = [0, 0],
/// instances = 13, /// instances = 13,
/// arcDegrees = 360, /// arcDegrees = 360,
/// rotateDuplicates = true /// rotateDuplicates = true
/// }, %) /// )
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternCircular2d", name = "patternCircular2d",
keywords = true,
unlabeled_first = true,
args = {
sketch_set = { docs = "Which sketch(es) to pattern" },
instances = { docs = "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."},
center = { docs = "The center about which to make the pattern. This is a 2D vector."},
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0."},
rotate_duplicates= { docs = "Whether or not to rotate the duplicates as they are copied."},
use_original= { docs = "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."},
}
}] }]
#[allow(clippy::too_many_arguments)]
async fn inner_pattern_circular_2d( async fn inner_pattern_circular_2d(
data: CircularPattern2dData,
sketch_set: SketchSet, sketch_set: SketchSet,
instances: u32,
center: [f64; 2],
arc_degrees: f64,
rotate_duplicates: bool,
use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Sketch>>, KclError> { ) -> Result<Vec<Box<Sketch>>, KclError> {
@ -982,6 +1023,13 @@ async fn inner_pattern_circular_2d(
if args.ctx.context_type == crate::execution::ContextType::Mock { if args.ctx.context_type == crate::execution::ContextType::Mock {
return Ok(starting_sketches); return Ok(starting_sketches);
} }
let data = CircularPattern2dData {
instances,
center,
arc_degrees,
rotate_duplicates,
use_original,
};
let mut sketches = Vec::new(); let mut sketches = Vec::new();
for sketch in starting_sketches.iter() { for sketch in starting_sketches.iter() {
@ -1008,9 +1056,36 @@ async fn inner_pattern_circular_2d(
/// A circular pattern on a 3D model. /// A circular pattern on a 3D model.
pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid_set): (CircularPattern3dData, SolidSet) = args.get_data_and_solid_set()?; let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?;
// 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.
let instances: u32 = args.get_kw_arg("instances")?;
// The axis around which to make the pattern. This is a 3D vector.
let axis: [f64; 3] = args.get_kw_arg("axis")?;
// The center about which to make the pattern. This is a 3D vector.
let center: [f64; 3] = args.get_kw_arg("center")?;
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?;
// Whether or not to rotate the duplicates as they are copied.
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
// If the target being patterned is itself a pattern, then, should you use the original solid,
// or the pattern?
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let solids = inner_pattern_circular_3d(data, solid_set, exec_state, args).await?; let solids = inner_pattern_circular_3d(
solid_set,
instances,
axis,
center,
arc_degrees,
rotate_duplicates,
use_original,
exec_state,
args,
)
.await?;
Ok(solids.into()) Ok(solids.into())
} }
@ -1024,21 +1099,38 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
/// |> circle({ center = [0, 0], radius = 1 }, %) /// |> circle({ center = [0, 0], radius = 1 }, %)
/// ///
/// example = extrude(exampleSketch, length = -5) /// example = extrude(exampleSketch, length = -5)
/// |> patternCircular3d({ /// |> patternCircular3d(
/// axis = [1, -1, 0], /// axis = [1, -1, 0],
/// center = [10, -20, 0], /// center = [10, -20, 0],
/// instances = 11, /// instances = 11,
/// arcDegrees = 360, /// arcDegrees = 360,
/// rotateDuplicates = true /// rotateDuplicates = true
/// }, %) /// )
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternCircular3d", name = "patternCircular3d",
feature_tree_operation = true, feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
solid_set = { docs = "Which solid(s) to pattern" },
instances = { docs = "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."},
axis = { docs = "The axis around which to make the pattern. This is a 3D vector"},
center = { docs = "The center about which to make the pattern. This is a 3D vector."},
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0."},
rotate_duplicates = { docs = "Whether or not to rotate the duplicates as they are copied."},
use_original = { docs = "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."},
}
}] }]
#[allow(clippy::too_many_arguments)]
async fn inner_pattern_circular_3d( async fn inner_pattern_circular_3d(
data: CircularPattern3dData,
solid_set: SolidSet, solid_set: SolidSet,
instances: u32,
axis: [f64; 3],
center: [f64; 3],
arc_degrees: f64,
rotate_duplicates: bool,
use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Solid>>, KclError> { ) -> Result<Vec<Box<Solid>>, KclError> {
@ -1055,6 +1147,14 @@ async fn inner_pattern_circular_3d(
} }
let mut solids = Vec::new(); let mut solids = Vec::new();
let data = CircularPattern3dData {
instances,
axis,
center,
arc_degrees,
rotate_duplicates,
use_original,
};
for solid in starting_solids.iter() { for solid in starting_solids.iter() {
let geometries = pattern_circular( let geometries = pattern_circular(
CircularPattern::ThreeD(data.clone()), CircularPattern::ThreeD(data.clone()),

View File

@ -16,12 +16,17 @@ impl Program {
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
let indentation = options.get_indentation(indentation_level); let indentation = options.get_indentation(indentation_level);
let result = self let mut result = self
.shebang .shebang
.as_ref() .as_ref()
.map(|sh| format!("{}\n\n", sh.inner.content)) .map(|sh| format!("{}\n\n", sh.inner.content))
.unwrap_or_default(); .unwrap_or_default();
for start in &self.non_code_meta.start_nodes {
result.push_str(&start.recast(options, indentation_level));
}
let result = result; // Remove mutation.
let result = self let result = self
.body .body
.iter() .iter()
@ -48,17 +53,9 @@ impl Program {
}) })
.enumerate() .enumerate()
.fold(result, |mut output, (index, recast_str)| { .fold(result, |mut output, (index, recast_str)| {
let start_string = if index == 0 { let start_string = if index == 0 && self.non_code_meta.start_nodes.is_empty() {
// We need to indent. // We need to indent.
if self.non_code_meta.start_nodes.is_empty() { indentation.to_string()
indentation.to_string()
} else {
self.non_code_meta
.start_nodes
.iter()
.map(|start| start.recast(options, indentation_level))
.collect()
}
} else { } else {
// Do nothing, we already applied the indentation elsewhere. // Do nothing, we already applied the indentation elsewhere.
String::new() String::new()
@ -334,8 +331,25 @@ impl CallExpressionKw {
.iter() .iter()
.map(|arg| arg.recast(options, indentation_level, ctxt)), .map(|arg| arg.recast(options, indentation_level, ctxt)),
); );
let args = arg_list.join(", "); let args = arg_list.clone().join(", ");
format!("{indent}{name}({args})") if arg_list.len() >= 4 {
let inner_indentation = if ctxt == ExprContext::Pipe {
options.get_indentation_offset_pipe(indentation_level + 1)
} else {
options.get_indentation(indentation_level + 1)
};
let mut args = arg_list.join(&format!(",\n{inner_indentation}"));
args.push(',');
let args = args;
let end_indent = if ctxt == ExprContext::Pipe {
options.get_indentation_offset_pipe(indentation_level)
} else {
options.get_indentation(indentation_level)
};
format!("{indent}{name}(\n{inner_indentation}{args}\n{end_indent})")
} else {
format!("{indent}{name}({args})")
}
} }
} }
@ -797,6 +811,38 @@ mod tests {
use super::*; use super::*;
use crate::{parsing::ast::types::FormatOptions, ModuleId}; use crate::{parsing::ast::types::FormatOptions, ModuleId};
#[test]
fn test_recast_annotations_without_body_items() {
let input = r#"@settings(defaultLengthUnit = in)
"#;
let program = crate::parsing::top_level_parse(input).unwrap();
let output = program.recast(&Default::default(), 0);
assert_eq!(output, input);
}
#[test]
fn test_recast_annotations_in_function_body() {
let input = r#"fn myFunc() {
@meta(yes = true)
x = 2
}
"#;
let program = crate::parsing::top_level_parse(input).unwrap();
let output = program.recast(&Default::default(), 0);
assert_eq!(output, input);
}
#[test]
fn test_recast_annotations_in_function_body_without_items() {
let input = r#"fn myFunc() {
@meta(yes = true)
}
"#;
let program = crate::parsing::top_level_parse(input).unwrap();
let output = program.recast(&Default::default(), 0);
assert_eq!(output, input);
}
#[test] #[test]
fn test_recast_if_else_if_same() { fn test_recast_if_else_if_same() {
let input = r#"b = if false { let input = r#"b = if false {
@ -1031,13 +1077,13 @@ sphere = startSketchOn('XZ')
}, %) }, %)
|> close() |> close()
|> revolve({ axis: 'x' }, %) |> revolve({ axis: 'x' }, %)
|> patternCircular3d({ |> patternCircular3d(
axis = [0, 0, 1], axis = [0, 0, 1],
center = [0, 0, 0], center = [0, 0, 0],
repetitions = 10, repetitions = 10,
arcDegrees = 360, arcDegrees = 360,
rotateDuplicates = true rotateDuplicates = true
}, %) )
// Sketch and revolve the outside bearing // Sketch and revolve the outside bearing
outsideRevolve = startSketchOn('XZ') outsideRevolve = startSketchOn('XZ')
@ -1098,13 +1144,13 @@ sphere = startSketchOn('XZ')
}, %) }, %)
|> close() |> close()
|> revolve({ axis = 'x' }, %) |> revolve({ axis = 'x' }, %)
|> patternCircular3d({ |> patternCircular3d(
axis = [0, 0, 1], axis = [0, 0, 1],
center = [0, 0, 0], center = [0, 0, 0],
repetitions = 10, repetitions = 10,
arcDegrees = 360, arcDegrees = 360,
rotateDuplicates = true rotateDuplicates = true,
}, %) )
// Sketch and revolve the outside bearing // Sketch and revolve the outside bearing
outsideRevolve = startSketchOn('XZ') outsideRevolve = startSketchOn('XZ')
@ -1329,6 +1375,18 @@ part001 = startSketchOn('XY')
); );
} }
#[test]
fn test_recast_empty_function_body_with_comments() {
let input = r#"fn myFunc() {
// Yo yo my comments.
}
"#;
let program = crate::parsing::top_level_parse(input).unwrap();
let output = program.recast(&Default::default(), 0);
assert_eq!(output, input);
}
#[test] #[test]
fn test_recast_large_file() { fn test_recast_large_file() {
let some_program_string = r#"@settings(units=mm) let some_program_string = r#"@settings(units=mm)
@ -1417,11 +1475,11 @@ tabs_r = startSketchOn({
radius = hole_diam / 2 radius = hole_diam / 2
}, %), %) }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d(
axis = [0, -1, 0], axis = [0, -1, 0],
repetitions = 1, repetitions = 1,
distance = length - 10 distance = length - 10
}, %) )
// build the tabs of the mounting bracket (left side) // build the tabs of the mounting bracket (left side)
tabs_l = startSketchOn({ tabs_l = startSketchOn({
plane: { plane: {
@ -1444,11 +1502,7 @@ tabs_l = startSketchOn({
radius = hole_diam / 2 radius = hole_diam / 2
}, %), %) }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d(axis = [0, -1, 0], repetitions = 1, distance = length - 10)
axis = [0, -1, 0],
repetitions = 1,
distance = length - 10
}, %)
"#; "#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap(); let program = crate::parsing::top_level_parse(some_program_string).unwrap();
@ -1542,11 +1596,7 @@ tabs_r = startSketchOn({
radius = hole_diam / 2 radius = hole_diam / 2
}, %), %) }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d(axis = [0, -1, 0], repetitions = 1, distance = length - 10)
axis = [0, -1, 0],
repetitions = 1,
distance = length - 10
}, %)
// build the tabs of the mounting bracket (left side) // build the tabs of the mounting bracket (left side)
tabs_l = startSketchOn({ tabs_l = startSketchOn({
plane = { plane = {
@ -1569,11 +1619,7 @@ tabs_l = startSketchOn({
radius = hole_diam / 2 radius = hole_diam / 2
}, %), %) }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d(axis = [0, -1, 0], repetitions = 1, distance = length - 10)
axis = [0, -1, 0],
repetitions = 1,
distance = length - 10
}, %)
"# "#
); );
} }

View File

@ -1,6 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map add_lots.kcl
extension: md
snapshot_kind: binary
---

View File

@ -1,4 +0,0 @@
```mermaid
mindmap
root
```

View File

@ -81,37 +81,10 @@ description: Program memory after executing add_lots.kcl
"start": 4, "start": 4,
"type": "FunctionExpression" "type": "FunctionExpression"
}, },
"memory": { "memory": [
"environments": [ 0,
{ 1
"bindings": { ],
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
@ -1543,9 +1516,29 @@ description: Program memory after executing add_lots.kcl
] ]
} }
}, },
"snapshots": [
{
"parent_snapshot": null,
"data": {
"f": {
"type": "Tombstone",
"value": null,
"__meta": []
},
"x": {
"type": "Tombstone",
"value": null,
"__meta": []
}
}
}
],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

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