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>
This commit is contained in:
Nick Cameron
2025-02-12 10:22:56 +13:00
committed by GitHub
parent bc4d254297
commit 322f398049
139 changed files with 4825 additions and 8154 deletions

File diff suppressed because it is too large Load Diff

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

@ -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,8 +753,7 @@ 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,
@ -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

@ -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

@ -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'
@ -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,24 +406,60 @@ 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>,
usePrevMemory?: boolean,
path?: string,
variables?: { [key in string]?: KclValue }
): Promise<ExecState> => {
try {
if (!variables) {
variables = {}
}
if (usePrevMemory === undefined) {
usePrevMemory = true
}
const execOutcome: RustExecOutcome = await execute_mock(
JSON.stringify(node),
path,
JSON.stringify({ settings: await jsAppSettings() }),
usePrevMemory,
JSON.stringify(variables),
fileSystemManager
)
return mockExecStateFromRust(execOutcome)
} catch (e: any) {
return Promise.reject(errFromErrWithOutputs(e))
}
}
/**
* Execute a KCL program.
* @param node The AST of the program to execute.
* @param path The full path of the file being executed. Use `null` for
* expressions that don't have a file, like expressions in the command bar.
*/
export const executeWithEngine = async (
node: Node<Program>, node: Node<Program>,
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
path?: string, path?: string
programMemoryOverride: ProgramMemory | Error | null = null
): 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 {
const execOutcome: RustExecOutcome = await execute_with_engine(
JSON.stringify(node),
path,
JSON.stringify({ settings: await jsAppSettings() }),
engineCommandManager,
fileSystemManager
)
return execStateFromRust(execOutcome, node)
} catch (e: any) {
return Promise.reject(errFromErrWithOutputs(e))
}
}
const jsAppSettings = async () => {
let jsAppSettings = default_app_settings() let jsAppSettings = default_app_settings()
if (!TEST) { if (!TEST) {
const lastSettingsSnapshot = await import( const lastSettingsSnapshot = await import(
@ -617,19 +469,13 @@ export const executor = async (
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot) jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
} }
} }
const execOutcome: RustExecOutcome = await execute( return jsAppSettings
JSON.stringify(node), }
path,
JSON.stringify(programMemoryOverride?.toRaw() || null), const errFromErrWithOutputs = (e: any): KCLError => {
JSON.stringify({ settings: jsAppSettings }), console.log('execute error', e)
engineCommandManager,
fileSystemManager
)
return execStateFromRust(execOutcome, node)
} catch (e: any) {
console.log(e)
const parsed: KclErrorWithOutputs = JSON.parse(e.toString()) const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
const kclError = new KCLError( return new KCLError(
parsed.error.kind, parsed.error.kind,
parsed.error.msg, parsed.error.msg,
firstSourceRange(parsed.error), firstSourceRange(parsed.error),
@ -637,9 +483,6 @@ export const executor = async (
parsed.artifactCommands, parsed.artifactCommands,
rustArtifactGraphToMap(parsed.artifactGraph) rustArtifactGraphToMap(parsed.artifactGraph)
) )
return Promise.reject(kclError)
}
} }
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => { export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
@ -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

@ -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: string,
variables: VariableMap
) {
const calculatedResult = await getCalculatedKclExpressionValue(
value, value,
programMemory, variables
}: { )
value: string
programMemory: ProgramMemory
}) {
const calculatedResult = await getCalculatedKclExpressionValue({
value,
programMemory,
})
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

@ -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

@ -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
.memory()
.get_from(name, env_ref, source_range)
.map_err(|_err| {
KclError::Internal(KclErrorDetails { KclError::Internal(KclErrorDetails {
message: format!("{} is not defined in module (but was exported?)", name), message: format!("{} is not defined in module (but was exported?)", name),
source_ranges: vec![source_range], source_ranges: vec![source_range],
}) })
})?; })?
exec_state.mut_memory().add(name, item.clone(), 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,43 +321,84 @@ 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!(),
};
exec_state.global.module_infos[&module_id].restore_repr(repr);
result
}
async fn exec_module_for_result(
&self,
module_id: ModuleId,
exec_state: &mut ExecState,
exec_kind: ExecutionKind,
source_range: SourceRange,
) -> Result<Option<KclValue>, KclError> {
let path = exec_state.global.module_infos[&module_id].path.clone();
let repr = exec_state.global.module_infos[&module_id].take_repr();
// DON'T EARLY RETURN! We need to restore the module repr
let result = match &repr {
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
ModuleRepr::Kcl(program, _) => self
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
.await
.map(|(val, _, _)| val),
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
.await
.map(|geom| Some(KclValue::ImportedGeometry(geom))),
ModuleRepr::Dummy => unreachable!(),
};
exec_state.global.module_infos[&module_id].restore_repr(repr);
result
}
async fn exec_module_from_ast(
&self,
program: &Node<Program>,
path: &ModulePath,
exec_state: &mut ExecState,
exec_kind: ExecutionKind,
source_range: SourceRange,
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
let old_units = exec_state.length_unit();
let mut local_state = ModuleState::new(&self.settings); let mut local_state = ModuleState::new(&self.settings);
exec_state.global.mod_loader.enter_module(&info.path); exec_state.global.mod_loader.enter_module(path);
std::mem::swap(&mut exec_state.mod_local, &mut local_state); 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 original_execution = self.engine.replace_execution_kind(exec_kind);
let result = self let result = self
@ -341,13 +407,15 @@ impl ExecutorContext {
let new_units = exec_state.length_unit(); let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state); std::mem::swap(&mut exec_state.mod_local, &mut local_state);
exec_state.global.mod_loader.leave_module(&info.path); 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 { if !exec_kind.is_isolated() && new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?; self.engine.set_units(old_units.into(), Default::default()).await?;
} }
self.engine.replace_execution_kind(original_execution); self.engine.replace_execution_kind(original_execution);
let result = result.map_err(|err| { result
.map_err(|err| {
if let KclError::ImportCycle(_) = err { if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message. // It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range]) err.override_source_ranges(vec![source_range])
@ -355,21 +423,14 @@ impl ExecutorContext {
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
"Error loading imported file. Open it to view more details. {}: {}", "Error loading imported file. Open it to view more details. {}: {}",
info.path, path,
err.message() err.message()
), ),
source_ranges: vec![source_range], source_ranges: vec![source_range],
}) })
} }
})?; })
.map(|result| (result, env_ref, local_state.module_exports))
Ok((result, local_state.memory, local_state.module_exports))
}
ModuleRepr::Foreign(geom) => {
let geom = super::import::send_to_engine(geom.clone(), self).await?;
Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new()))
}
}
} }
#[async_recursion] #[async_recursion]
@ -387,10 +448,9 @@ 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.
@ -409,17 +469,12 @@ impl ExecutorContext {
} }
} }
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
// over variables. Variables defined lexically later shouldn't
// be available to the function body.
KclValue::Function {
expression: function_expression.clone(), expression: function_expression.clone(),
meta: vec![metadata.to_owned()], meta: vec![metadata.to_owned()],
func: None, func: None,
memory: Box::new(exec_state.memory().clone()), memory: exec_state.mut_memory().snapshot(),
} },
}
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 =
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
let result = func
.call_fn_kw(args, exec_state, ctx.clone(), callsite) .call_fn_kw(args, exec_state, ctx.clone(), callsite)
.await .await
.map_err(|e| { .map_err(|e| {
// Add the call expression to the source ranges. // Add the call expression to the source ranges.
// TODO currently ignored by the frontend // TODO currently ignored by the frontend
e.add_source_ranges(vec![source_range]) 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 =
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
// Add the call expression to the source ranges. // Add the call expression to the source ranges.
// TODO currently ignored by the frontend // TODO currently ignored by the frontend
e.add_source_ranges(vec![source_range]) 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 previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
let result = ctx let result = ctx
.exec_program(&function_expression.body, exec_state, BodyType::Block) .exec_program(&function_expression.body, exec_state, BodyType::Block)
.await; .await;
let result = result.map(|_| {
exec_state
.memory()
.get(memory::RETURN_NAME, function_expression.as_source_range())
.ok()
.cloned()
});
// Restore the previous memory. // Restore the previous memory.
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, 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 previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
let result = ctx let result = ctx
.exec_program(&function_expression.body, exec_state, BodyType::Block) .exec_program(&function_expression.body, exec_state, BodyType::Block)
.await; .await;
let result = result.map(|_| {
exec_state
.memory()
.get(memory::RETURN_NAME, function_expression.as_source_range())
.ok()
.cloned()
});
// Restore the previous memory. // Restore the previous memory.
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, 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},
}; };
@ -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),

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);
} }

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

@ -72,7 +72,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 +95,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,

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": []
} }

View File

@ -438,9 +438,13 @@ description: Program memory after executing angled_line.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -160,9 +160,13 @@ description: Program memory after executing array_elem_pop.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl/src/simulation_tests.rs
assertion_line: 92
description: Program memory after executing array_elem_push.kcl description: Program memory after executing array_elem_push.kcl
snapshot_kind: text
--- ---
{ {
"environments": [ "environments": [
@ -227,9 +225,13 @@ snapshot_kind: text
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl/src/simulation_tests.rs
assertion_line: 92
description: Program memory after executing array_range_expr.kcl description: Program memory after executing array_range_expr.kcl
snapshot_kind: text
--- ---
{ {
"environments": [ "environments": [
@ -384,9 +382,13 @@ snapshot_kind: text
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl/src/simulation_tests.rs
assertion_line: 92
description: Program memory after executing array_range_negative_expr.kcl description: Program memory after executing array_range_negative_expr.kcl
snapshot_kind: text
--- ---
{ {
"environments": [ "environments": [
@ -186,9 +184,13 @@ snapshot_kind: text
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -1969,9 +1969,13 @@ description: Program memory after executing artifact_graph_example_code1.kcl
} }
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -625,9 +625,13 @@ description: Program memory after executing artifact_graph_example_code_no_3d.kc
} }
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -216,9 +216,13 @@ snapshot_kind: text
} }
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -4909,9 +4909,13 @@ description: Program memory after executing artifact_graph_sketch_on_face_etc.kc
} }
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -650,9 +650,13 @@ description: Program memory after executing basic_fillet_cube_close_opposite.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -521,9 +521,13 @@ description: Program memory after executing basic_fillet_cube_end.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -772,9 +772,13 @@ description: Program memory after executing basic_fillet_cube_next_adjacent.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -772,9 +772,13 @@ description: Program memory after executing basic_fillet_cube_previous_adjacent.
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -521,9 +521,13 @@ description: Program memory after executing basic_fillet_cube_start.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -345,9 +345,13 @@ description: Program memory after executing big_number_angle_to_match_length_x.k
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -345,9 +345,13 @@ description: Program memory after executing big_number_angle_to_match_length_y.k
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -159,9 +159,13 @@ description: Program memory after executing boolean_logical_and.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -121,9 +121,13 @@ description: Program memory after executing boolean_logical_multiple.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -159,9 +159,13 @@ description: Program memory after executing boolean_logical_or.kcl
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -159,9 +159,13 @@ snapshot_kind: text
} }
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -63466,9 +63466,13 @@ snapshot_kind: text
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -1,7 +1,6 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl/src/simulation_tests.rs
description: Program memory after executing comparisons.kcl description: Program memory after executing comparisons.kcl
snapshot_kind: text
--- ---
{ {
"environments": [ "environments": [
@ -28,9 +27,13 @@ snapshot_kind: text
"__meta": [] "__meta": []
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl/src/simulation_tests.rs
assertion_line: 92
description: Program memory after executing computed_var.kcl description: Program memory after executing computed_var.kcl
snapshot_kind: text
--- ---
{ {
"environments": [ "environments": [
@ -187,9 +185,13 @@ snapshot_kind: text
] ]
} }
}, },
"snapshots": [],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -696,37 +696,10 @@ description: Program memory after executing cube.kcl
"start": 7, "start": 7,
"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": [
@ -976,9 +949,29 @@ description: Program memory after executing cube.kcl
} }
} }
}, },
"snapshots": [
{
"parent_snapshot": null,
"data": {
"cube": {
"type": "Tombstone",
"value": null,
"__meta": []
},
"myCube": {
"type": "Tombstone",
"value": null,
"__meta": []
}
}
}
],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

View File

@ -81,37 +81,10 @@ description: Program memory after executing double_map_fn.kcl
"start": 12, "start": 12,
"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": [
@ -271,9 +244,34 @@ description: Program memory after executing double_map_fn.kcl
] ]
} }
}, },
"snapshots": [
{
"parent_snapshot": null,
"data": {
"increment": {
"type": "Tombstone",
"value": null,
"__meta": []
},
"xs": {
"type": "Tombstone",
"value": null,
"__meta": []
},
"ys": {
"type": "Tombstone",
"value": null,
"__meta": []
}
}
}
],
"parent": null "parent": null
} }
], ],
"currentEnv": 0, "currentEnv": [
"return": null 0,
0
],
"callStack": []
} }

File diff suppressed because it is too large Load Diff

View File

@ -394,37 +394,10 @@ description: Program memory after executing function_sketch.kcl
"start": 6, "start": 6,
"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": [
@ -654,9 +627,29 @@ description: Program memory after executing function_sketch.kcl
} }
} }
}, },
"snapshots": [
{
"parent_snapshot": null,
"data": {
"box": {
"type": "Tombstone",
"value": null,
"__meta": []
},
"fnBox": {
"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