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 |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -351,6 +351,23 @@ Data for an imported geometry.
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Tombstone`| | No |
| `value` |`null`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise, programMemoryInit } from './wasm'
import { assertParse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import path from 'node:path'
@ -72,7 +72,7 @@ describe('Test KCL Samples from public Github repository', () => {
const ast = assertParse(code)
await enginelessExecutor(
ast,
programMemoryInit(),
false,
file.pathFromProjectDirectoryToFirstFile
)
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ it('can execute parsed AST', async () => {
expect(pResult.program).not.toEqual(null)
const execState = await enginelessExecutor(pResult.program as Node<Program>)
expect(err(execState)).toEqual(false)
expect(execState.memory.get('x')?.value).toEqual(1)
expect(execState.variables['x']?.value).toEqual(1)
})
it('formats numbers with units', () => {

View File

@ -3,7 +3,8 @@ import {
parse_wasm,
recast_wasm,
format_number,
execute,
execute_with_engine,
execute_mock,
kcl_lint,
modify_ast_for_sketch_wasm,
is_points_ccw,
@ -281,6 +282,8 @@ export const assertParse = (code: string): Node<Program> => {
return result.program
}
export type VariableMap = { [key in string]?: KclValue }
export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = (
@ -290,7 +293,7 @@ export const isPathToNodeNumber = (
}
export interface ExecState {
memory: ProgramMemory
variables: { [key in string]?: KclValue }
operations: Operation[]
artifacts: { [key in ArtifactId]?: RustArtifact }
artifactCommands: ArtifactCommand[]
@ -303,7 +306,7 @@ export interface ExecState {
*/
export function emptyExecState(): ExecState {
return {
memory: ProgramMemory.empty(),
variables: {},
operations: [],
artifacts: {},
artifactCommands: [],
@ -328,7 +331,7 @@ function execStateFromRust(
}
return {
memory: ProgramMemory.fromRaw(execOutcome.memory),
variables: execOutcome.variables,
operations: execOutcome.operations,
artifacts: execOutcome.artifacts,
artifactCommands: execOutcome.artifactCommands,
@ -336,6 +339,16 @@ function execStateFromRust(
}
}
function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
return {
variables: execOutcome.variables,
operations: execOutcome.operations,
artifacts: execOutcome.artifacts,
artifactCommands: execOutcome.artifactCommands,
artifactGraph: new Map<ArtifactId, Artifact>(),
}
}
export type ArtifactGraph = Map<ArtifactId, Artifact>
function rustArtifactGraphToMap(
@ -354,203 +367,6 @@ export function defaultArtifactGraph(): ArtifactGraph {
return new Map()
}
interface Memory {
[key: string]: KclValue | undefined
}
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
function emptyEnvironment(): Environment {
return { bindings: {}, parent: null }
}
function emptyRootEnvironment(): Environment {
return {
// This is dumb this is copied from rust.
bindings: {
ZERO: { type: 'Number', value: 0.0, __meta: [] },
QUARTER_TURN: { type: 'Number', value: 90.0, __meta: [] },
HALF_TURN: { type: 'Number', value: 180.0, __meta: [] },
THREE_QUARTER_TURN: { type: 'Number', value: 270.0, __meta: [] },
},
parent: null,
}
}
/**
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals
* isolated from the rest of the TypeScript code so that we can move it to Rust
* in the future.
*/
export class ProgramMemory {
private environments: Environment[]
private currentEnv: EnvironmentRef
private return: KclValue | null
/**
* Empty memory doesn't include prelude definitions.
*/
static empty(): ProgramMemory {
return new ProgramMemory()
}
static fromRaw(raw: RawProgramMemory): ProgramMemory {
return new ProgramMemory(raw.environments, raw.currentEnv, raw.return)
}
constructor(
environments: Environment[] = [emptyRootEnvironment()],
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
returnVal: KclValue | null = null
) {
this.environments = environments
this.currentEnv = currentEnv
this.return = returnVal
}
/**
* Returns a deep copy.
*/
clone(): ProgramMemory {
return ProgramMemory.fromRaw(structuredClone(this.toRaw()))
}
has(name: string): boolean {
let envRef = this.currentEnv
while (true) {
const env = this.environments[envRef]
if (env.bindings.hasOwnProperty(name)) {
return true
}
if (!env.parent) {
break
}
envRef = env.parent
}
return false
}
get(name: string): KclValue | null {
let envRef = this.currentEnv
while (true) {
const env = this.environments[envRef]
if (env.bindings.hasOwnProperty(name)) {
return env.bindings[name] ?? null
}
if (!env.parent) {
break
}
envRef = env.parent
}
return null
}
set(name: string, value: KclValue): Error | null {
if (this.environments.length === 0) {
return new Error('No environment to set memory in')
}
const env = this.environments[this.currentEnv]
env.bindings[name] = value
return null
}
/**
* Returns a new ProgramMemory with only `KclValue`s that pass the
* predicate. Values are deep copied.
*
* Note: Return value of the returned ProgramMemory is always null.
*/
filterVariables(
keepPrelude: boolean,
predicate: (value: KclValue) => boolean
): ProgramMemory | Error {
const environments: Environment[] = []
for (const [i, env] of this.environments.entries()) {
let bindings: Memory
if (i === ROOT_ENVIRONMENT_REF && keepPrelude) {
// Get prelude definitions. Create these first so that they're always
// first in iteration order.
const memoryOrError = programMemoryInit()
if (err(memoryOrError)) return memoryOrError
bindings = memoryOrError.environments[0].bindings
} else {
bindings = emptyEnvironment().bindings
}
for (const [name, value] of Object.entries(env.bindings)) {
if (value === undefined) continue
// Check the predicate.
if (!predicate(value)) {
continue
}
// Deep copy.
bindings[name] = structuredClone(value)
}
environments.push({ bindings, parent: env.parent })
}
return new ProgramMemory(environments, this.currentEnv, null)
}
numEnvironments(): number {
return this.environments.length
}
numVariables(envRef: EnvironmentRef): number {
return Object.keys(this.environments[envRef]).length
}
/**
* Returns all variable entries in memory that are visible, in a flat
* structure. If variables are shadowed, they're not visible, and therefore,
* not included.
*
* This should only be used to display in the MemoryPane UI.
*/
visibleEntries(): Map<string, KclValue> {
const map = new Map<string, KclValue>()
let envRef = this.currentEnv
while (true) {
const env = this.environments[envRef]
for (const [name, value] of Object.entries(env.bindings)) {
if (value === undefined) continue
// Don't include shadowed variables.
if (!map.has(name)) {
map.set(name, value)
}
}
if (!env.parent) {
break
}
envRef = env.parent
}
return map
}
/**
* Returns true if any visible variables are a Sketch or Solid.
*/
hasSketchOrSolid(): boolean {
for (const node of this.visibleEntries().values()) {
if (node.type === 'Solid' || node.type === 'Sketch') {
return true
}
}
return false
}
/**
* Return the representation that can be serialized to JSON. This should only
* be used within this module.
*/
toRaw(): RawProgramMemory {
return {
environments: this.environments,
currentEnv: this.currentEnv,
return: this.return,
}
}
}
// TODO: In the future, make the parameter be a KclValue.
export function sketchFromKclValueOptional(
obj: any,
@ -590,58 +406,85 @@ export function sketchFromKclValue(
* @param node The AST of the program to execute.
* @param path The full path of the file being executed. Use `null` for
* expressions that don't have a file, like expressions in the command bar.
* @param programMemoryOverride If this is not `null`, this will be used as the
* initial program memory, and the execution will be engineless (AKA mock
* execution).
*/
export const executor = async (
export const executeMock = async (
node: Node<Program>,
engineCommandManager: EngineCommandManager,
usePrevMemory?: boolean,
path?: string,
programMemoryOverride: ProgramMemory | Error | null = null
variables?: { [key in string]?: KclValue }
): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
try {
let jsAppSettings = default_app_settings()
if (!TEST) {
const lastSettingsSnapshot = await import(
'components/SettingsAuthProvider'
).then((module) => module.lastSettingsContextSnapshot)
if (lastSettingsSnapshot) {
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
}
if (!variables) {
variables = {}
}
const execOutcome: RustExecOutcome = await execute(
if (usePrevMemory === undefined) {
usePrevMemory = true
}
const execOutcome: RustExecOutcome = await execute_mock(
JSON.stringify(node),
path,
JSON.stringify(programMemoryOverride?.toRaw() || null),
JSON.stringify({ settings: jsAppSettings }),
JSON.stringify({ settings: await jsAppSettings() }),
usePrevMemory,
JSON.stringify(variables),
fileSystemManager
)
return mockExecStateFromRust(execOutcome)
} catch (e: any) {
return Promise.reject(errFromErrWithOutputs(e))
}
}
/**
* Execute a KCL program.
* @param node The AST of the program to execute.
* @param path The full path of the file being executed. Use `null` for
* expressions that don't have a file, like expressions in the command bar.
*/
export const executeWithEngine = async (
node: Node<Program>,
engineCommandManager: EngineCommandManager,
path?: string
): Promise<ExecState> => {
try {
const execOutcome: RustExecOutcome = await execute_with_engine(
JSON.stringify(node),
path,
JSON.stringify({ settings: await jsAppSettings() }),
engineCommandManager,
fileSystemManager
)
return execStateFromRust(execOutcome, node)
} catch (e: any) {
console.log(e)
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.error.kind,
parsed.error.msg,
firstSourceRange(parsed.error),
parsed.operations,
parsed.artifactCommands,
rustArtifactGraphToMap(parsed.artifactGraph)
)
return Promise.reject(kclError)
return Promise.reject(errFromErrWithOutputs(e))
}
}
const jsAppSettings = async () => {
let jsAppSettings = default_app_settings()
if (!TEST) {
const lastSettingsSnapshot = await import(
'components/SettingsAuthProvider'
).then((module) => module.lastSettingsContextSnapshot)
if (lastSettingsSnapshot) {
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
}
}
return jsAppSettings
}
const errFromErrWithOutputs = (e: any): KCLError => {
console.log('execute error', e)
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
return new KCLError(
parsed.error.kind,
parsed.error.msg,
firstSourceRange(parsed.error),
parsed.operations,
parsed.artifactCommands,
rustArtifactGraphToMap(parsed.artifactGraph)
)
}
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
try {
const discovered_findings: Array<Discovered> = await kcl_lint(
@ -707,7 +550,6 @@ export const modifyAstForSketch = async (
defaultArtifactGraph()
)
console.log(kclError)
return Promise.reject(kclError)
}
}
@ -755,31 +597,6 @@ export function getTangentialArcToInfo({
}
}
/**
* Returns new ProgramMemory with prelude definitions.
*/
export function programMemoryInit(): ProgramMemory | Error {
try {
const memory: RawProgramMemory = program_memory_init()
return new ProgramMemory(
memory.environments,
memory.currentEnv,
memory.return
)
} catch (e: any) {
console.log(e)
const parsed: RustKclError = JSON.parse(e.toString())
return new KCLError(
parsed.kind,
parsed.msg,
firstSourceRange(parsed),
[],
[],
defaultArtifactGraph()
)
}
}
export async function coreDump(
coreDumpManager: CoreDumpManager,
openGithubIssue: boolean = false

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import {
Program,
ProgramMemory,
executor,
executeMock,
SourceRange,
ExecState,
VariableMap,
} from '../lang/wasm'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { EngineCommand } from 'lang/std/artifactGraph'
@ -80,18 +80,9 @@ class MockEngineCommandManager {
export async function enginelessExecutor(
ast: Node<Program>,
pmo: ProgramMemory | Error = ProgramMemory.empty(),
path?: string
usePrevMemory?: boolean,
path?: string,
variables?: VariableMap
): Promise<ExecState> {
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {},
setMediaStream: () => {},
}) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession()
const execState = await executor(ast, mockEngineCommandManager, path, pmo)
await mockEngineCommandManager.waitForAllCommands()
return execState
return await executeMock(ast, usePrevMemory, path, variables)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -564,29 +564,8 @@ pub struct Solid {
}
impl Solid {
pub(crate) fn get_all_edge_cut_ids(&self) -> Vec<uuid::Uuid> {
self.edge_cuts.iter().map(|foc| foc.id()).collect()
}
}
/// An solid ID and its fillet and chamfer IDs. This is needed for lazy
/// fillet evaluation.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct SolidLazyIds {
pub solid_id: uuid::Uuid,
pub sketch_id: uuid::Uuid,
/// Chamfers or fillets on this solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cuts: Vec<uuid::Uuid>,
}
impl From<&Solid> for SolidLazyIds {
fn from(eg: &Solid) -> Self {
Self {
solid_id: eg.id,
sketch_id: eg.sketch.id,
edge_cuts: eg.edge_cuts.iter().map(|foc| foc.id()).collect(),
}
pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
self.edge_cuts.iter().map(|foc| foc.id())
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,8 @@
use std::{path::PathBuf, sync::Arc};
use anyhow::Result;
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
pub use cache::bust_cache;
use cache::OldAstState;
pub use cad_op::Operation;
pub use exec_ast::FunctionParam;
pub use geometry::*;
pub(crate) use import::{
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
};
use indexmap::IndexMap;
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
use kcmc::{
each_cmd as mcmd,
ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
@ -21,10 +12,8 @@ use kcmc::{
ImageFormat, ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;
pub use memory::ProgramMemory;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use state::{ExecState, IdGenerator, MetaSettings};
use crate::{
engine::EngineManager,
@ -41,6 +30,18 @@ use crate::{
ExecError, KclErrorWithOutputs,
};
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
pub use cache::{bust_cache, clear_mem_cache};
pub use cad_op::Operation;
pub use exec_ast::FunctionParam;
pub use geometry::*;
pub(crate) use import::{
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
};
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
pub use memory::{EnvironmentRef, ProgramMemory};
pub use state::{ExecState, IdGenerator, MetaSettings};
pub(crate) mod annotations;
mod artifact;
pub(crate) mod cache;
@ -53,12 +54,12 @@ mod memory;
mod state;
/// Outcome of executing a program. This is used in TS.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExecOutcome {
/// Program variable bindings of the top-level module.
pub memory: ProgramMemory,
/// Variables in the top-level of the root module. Note that functions will have an invalid env ref.
pub variables: IndexMap<String, KclValue>,
/// Operations that have been performed in execution order, for display in
/// the Feature Tree.
pub operations: Vec<Operation>,
@ -133,7 +134,7 @@ impl std::hash::Hash for TagIdentifier {
pub type MemoryFunction =
fn(
s: Vec<Arg>,
memory: ProgramMemory,
memory: EnvironmentRef,
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
metadata: Vec<Metadata>,
exec_state: &ExecState,
@ -160,7 +161,6 @@ pub struct TagEngineInfo {
#[serde(rename_all = "camelCase")]
pub enum BodyType {
Root,
Sketch,
Block,
}
@ -496,17 +496,56 @@ impl ExecutorContext {
pub async fn run_mock(
&self,
program: crate::Program,
program_memory_override: Option<ProgramMemory>,
use_prev_memory: bool,
variables: IndexMap<String, KclValue>,
) -> Result<ExecOutcome, KclErrorWithOutputs> {
assert!(self.is_mock());
let mut exec_state = ExecState::new(&self.settings);
if let Some(program_memory_override) = program_memory_override {
exec_state.mod_local.memory = program_memory_override;
let mut mem = if use_prev_memory {
cache::read_old_memory()
.await
.unwrap_or_else(|| exec_state.memory().clone())
} else {
exec_state.memory().clone()
};
// Add any extra variables to memory
let mut to_restore = Vec::new();
for (k, v) in variables {
crate::log::log(format!("add var: {k}"));
to_restore.push((k.clone(), mem.get(&k, SourceRange::default()).ok().cloned()));
mem.add(k, v, SourceRange::synthetic())
.map_err(KclErrorWithOutputs::no_outputs)?;
}
// Push a scope so that old variables can be overwritten (since we might be re-executing some
// part of the scene).
mem.push_new_env_for_scope();
*exec_state.mut_memory() = mem;
self.inner_run(&program.ast, &mut exec_state).await?;
Ok(exec_state.to_wasm_outcome())
// Restore any temporary variables, then save any newly created variables back to
// memory in case another run wants to use them. Note this is just saved to the preserved
// memory, not to the exec_state which is not cached for mock execution.
let mut mem = exec_state.memory().clone();
let top = mem.pop_and_preserve_env();
for (k, v) in to_restore {
match v {
Some(v) => mem.insert_or_update(k, v),
None => mem.clear(k),
}
}
mem.squash_env(top);
cache::write_old_memory(mem).await;
let outcome = exec_state.to_mock_wasm_outcome();
crate::log::log(format!("return mock {:#?}", outcome.variables));
Ok(outcome)
}
pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
@ -516,7 +555,7 @@ impl ExecutorContext {
ast: old_ast,
exec_state: old_state,
settings: old_settings,
}) = cache::read_old_ast_memory().await
}) = cache::read_old_ast().await
{
let old = CacheInformation {
ast: &old_ast,
@ -595,7 +634,7 @@ impl ExecutorContext {
result?;
// Save this as the last successful execution to the cache.
cache::write_old_ast_memory(OldAstState {
cache::write_old_ast(OldAstState {
ast: program,
exec_state: exec_state.clone(),
settings: self.settings.clone(),
@ -661,6 +700,14 @@ impl ExecutorContext {
)
})?;
if !self.is_mock() {
cache::write_old_memory(exec_state.memory().clone()).await;
}
crate::log::log(format!(
"Post interpretation KCL memory stats: {:#?}",
exec_state.memory().stats
));
let session_data = self.engine.get_session_data();
Ok(session_data)
}
@ -1588,12 +1635,7 @@ let w = f() + f()
ctx.run_with_caching(old_program).await.unwrap();
// Get the id_generator from the first execution.
let id_generator = cache::read_old_ast_memory()
.await
.unwrap()
.exec_state
.global
.id_generator;
let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
let code = r#"sketch001 = startSketchOn('XZ')
|> startProfileAt([62.74, 206.13], %)
@ -1614,12 +1656,7 @@ let w = f() + f()
// Execute the program.
ctx.run_with_caching(program).await.unwrap();
let new_id_generator = cache::read_old_ast_memory()
.await
.unwrap()
.exec_state
.global
.id_generator;
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
assert_eq!(id_generator, new_id_generator);
}

View File

@ -9,7 +9,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{
annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, ExecutorSettings,
KclValue, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen,
KclValue, Operation, ProgramMemory, UnitAngle, UnitLen,
},
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
parsing::ast::types::NonCodeValue,
@ -27,6 +27,8 @@ pub struct ExecState {
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GlobalState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// The stable artifact ID generator.
pub id_generator: IdGenerator,
/// Map from source file absolute path to module ID.
@ -50,13 +52,9 @@ pub struct GlobalState {
pub mod_loader: ModuleLoader,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// Dynamic state that follows dynamic flow of the program.
pub dynamic_state: DynamicState,
/// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>,
@ -99,7 +97,11 @@ impl ExecState {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
memory: self.mod_local.memory,
variables: self
.memory()
.find_all_in_current_env(|_| true)
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
operations: self.mod_local.operations,
artifacts: self.global.artifacts,
artifact_commands: self.global.artifact_commands,
@ -107,12 +109,28 @@ impl ExecState {
}
}
pub fn to_mock_wasm_outcome(self) -> ExecOutcome {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
variables: self
.memory()
.find_all_in_current_env(|_| true)
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
operations: Default::default(),
artifacts: Default::default(),
artifact_commands: Default::default(),
artifact_graph: Default::default(),
}
}
pub fn memory(&self) -> &ProgramMemory {
&self.mod_local.memory
&self.global.memory
}
pub fn mut_memory(&mut self) -> &mut ProgramMemory {
&mut self.mod_local.memory
&mut self.global.memory
}
pub fn next_uuid(&mut self) -> Uuid {
@ -148,11 +166,29 @@ impl ExecState {
pub fn angle_unit(&self) -> UnitAngle {
self.mod_local.settings.default_angle_units
}
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
self.global
.mod_loader
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
path,
),
source_ranges: vec![source_range],
})
}
}
impl GlobalState {
fn new(settings: &ExecutorSettings) -> Self {
let mut global = GlobalState {
memory: ProgramMemory::new(),
id_generator: Default::default(),
path_to_source_id: Default::default(),
module_infos: Default::default(),
@ -181,8 +217,6 @@ impl GlobalState {
impl ModuleState {
pub(super) fn new(exec_settings: &ExecutorSettings) -> Self {
ModuleState {
memory: Default::default(),
dynamic_state: Default::default(),
pipe_value: Default::default(),
module_exports: Default::default(),
operations: Default::default(),
@ -239,46 +273,6 @@ impl MetaSettings {
}
}
/// Dynamic state that depends on the dynamic flow of the program, like the call
/// stack. If the language had exceptions, for example, you could store the
/// stack of exception handlers here.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct DynamicState {
pub solid_ids: Vec<SolidLazyIds>,
}
impl DynamicState {
#[must_use]
pub(super) fn merge(&self, memory: &ProgramMemory) -> Self {
let mut merged = self.clone();
merged.append(memory);
merged
}
fn append(&mut self, memory: &ProgramMemory) {
for env in &memory.environments {
for item in env.bindings.values() {
if let KclValue::Solid { value } = item {
self.solid_ids.push(SolidLazyIds::from(value.as_ref()));
}
}
}
}
pub(crate) fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<uuid::Uuid> {
self.solid_ids
.iter()
.flat_map(|eg| {
if eg.sketch_id == sketch_id {
eg.edge_cuts.clone()
} else {
Vec::new()
}
})
.collect::<Vec<_>>()
}
}
/// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]

View File

@ -83,7 +83,9 @@ mod wasm;
pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind};
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
pub use execution::{bust_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d};
pub use execution::{
bust_cache, clear_mem_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
};
pub use lsp::{
copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},

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) {
self.cancelled = true;
}

View File

@ -14,15 +14,6 @@ impl Notification for 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.
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, ts_rs::TS)]
#[serde(rename_all = "camelCase")]

View File

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

View File

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

View File

@ -7,7 +7,6 @@ use tower_lsp::{
};
use crate::{
execution::ProgramMemory,
lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
parsing::ast::types::{Node, Program},
};
@ -2163,9 +2162,6 @@ async fn test_kcl_lsp_on_change_update_ast() {
.await;
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")]
@ -2186,9 +2182,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
})
.await;
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
// Send change file.
server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2204,9 +2197,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
})
.await;
// Make sure the memory is the same.
assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone());
// Update the text.
let new_text = r#"thing = 2"#.to_string();
// Send change file.
@ -2223,8 +2213,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
}],
})
.await;
assert!(memory != server.memory_map.get("file:///test.kcl").unwrap().clone());
}
#[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();
assert_eq!(ast.body.len(), 2);
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
// Send change file.
server
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
@ -2283,9 +2268,6 @@ part001 = cube([0,0], 20)
})
.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;
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;
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")]
@ -2323,10 +2302,6 @@ async fn kcl_test_kcl_lsp_empty_file_execute_ok() {
},
})
.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")]
@ -2454,9 +2429,6 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
server
@ -2479,9 +2451,6 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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")]
@ -2511,9 +2480,6 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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());
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
server
@ -2569,9 +2530,6 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
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.
server
.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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
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;
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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;
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
let units = server.executor_ctx().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::Mm);
@ -2887,20 +2796,10 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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);
// Set that we cannot execute.
@ -2933,10 +2832,6 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
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.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
@ -3219,9 +3110,6 @@ part001 = startSketchOn('XY')
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
server
@ -3241,9 +3129,6 @@ part001 = startSketchOn('XY')
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
@ -3284,9 +3169,6 @@ part001 = startSketchOn('XY')
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
server
@ -3314,9 +3196,6 @@ NEW_LINT = 1"#
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
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.
@ -3357,9 +3236,6 @@ part001 = startSketchOn('XY')
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl");
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.
server
@ -3387,9 +3263,6 @@ NEW_LINT = 1"#
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl");
assert!(ast.is_none());
// Get the memory.
let memory = server.memory_map.get("file:///test.kcl");
assert!(memory.is_none());
// 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();
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.
server
.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();
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.
// Check the diagnostics.
@ -3534,10 +3399,6 @@ part001 = startSketchOn('XY')
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
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.
server
.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();
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.
// Check the diagnostics.

View File

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

View File

@ -3232,6 +3232,21 @@ impl FunctionExpression {
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)]

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);
}
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[].**[].to[]" => rounded_redaction(4),
".environments[].**[].x[]" => rounded_redaction(4),

View File

@ -188,7 +188,7 @@ impl Args {
exec_state: &'e mut ExecState,
tag: &'a TagIdentifier,
) -> 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(|| {
KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have engine info", tag.value),
@ -255,11 +255,13 @@ impl Args {
ids.extend(
exec_state
.memory()
.find_solids_on_sketch(solid.sketch.id)
.iter()
.flat_map(|eg| eg.get_all_edge_cut_ids()),
.walk_call_stack()
.filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
.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);
}

View File

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

View File

@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize};
use crate::{
docs::StdLibFn,
errors::KclError,
execution::{ExecState, KclValue, ProgramMemory},
execution::{EnvironmentRef, ExecState, KclValue},
parsing::ast::types::FunctionExpression,
};
@ -305,5 +305,5 @@ pub enum Primitive {
pub struct FnAsArg<'a> {
pub func: Option<&'a crate::execution::MemoryFunction>,
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,
meta: vec![args.source_range.into()],
ctx: args.ctx.clone(),
memory: *transform.memory,
memory: transform.memory,
},
extr,
use_original,
@ -95,7 +95,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
fn_expr: transform.expr,
meta: vec![args.source_range.into()],
ctx: args.ctx.clone(),
memory: *transform.memory,
memory: transform.memory,
},
sketch,
use_original,

View File

@ -81,37 +81,10 @@ description: Program memory after executing add_lots.kcl
"start": 4,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"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
},
"memory": [
0,
1
],
"__meta": [
{
"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
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
0,
0
],
"callStack": []
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -521,9 +521,13 @@ description: Program memory after executing basic_fillet_cube_start.kcl
]
}
},
"snapshots": [],
"parent": null
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
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
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
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
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
0,
0
],
"callStack": []
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -696,37 +696,10 @@ description: Program memory after executing cube.kcl
"start": 7,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"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
},
"memory": [
0,
1
],
"__meta": [
{
"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
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
0,
0
],
"callStack": []
}

View File

@ -81,37 +81,10 @@ description: Program memory after executing double_map_fn.kcl
"start": 12,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"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
},
"memory": [
0,
1
],
"__meta": [
{
"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
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
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,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"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
},
"memory": [
0,
1
],
"__meta": [
{
"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
}
],
"currentEnv": 0,
"return": null
"currentEnv": [
0,
0
],
"callStack": []
}

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