Compare commits
5 Commits
pierremtb/
...
jtran/y-co
Author | SHA1 | Date | |
---|---|---|---|
b4fb903bd0 | |||
1b8688f274 | |||
397839da84 | |||
ac120838e5 | |||
e6a2ac9c4a |
@ -47,7 +47,6 @@ import {
|
|||||||
PipeExpression,
|
PipeExpression,
|
||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
programMemoryInit,
|
|
||||||
recast,
|
recast,
|
||||||
SketchGroup,
|
SketchGroup,
|
||||||
ExtrudeGroup,
|
ExtrudeGroup,
|
||||||
@ -130,7 +129,7 @@ export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
|
|||||||
export class SceneEntities {
|
export class SceneEntities {
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
scene: Scene
|
scene: Scene
|
||||||
sceneProgramMemory: ProgramMemory = { root: {}, return: null }
|
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
activeSegments: { [key: string]: Group } = {}
|
activeSegments: { [key: string]: Group } = {}
|
||||||
intersectionPlane: Mesh | null = null
|
intersectionPlane: Mesh | null = null
|
||||||
axisGroup: Group | null = null
|
axisGroup: Group | null = null
|
||||||
@ -550,9 +549,9 @@ export class SceneEntities {
|
|||||||
const variableDeclarationName =
|
const variableDeclarationName =
|
||||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||||
|
|
||||||
const sg = kclManager.programMemory.root[
|
const sg = kclManager.programMemory.get(
|
||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
] as SketchGroup
|
) as SketchGroup
|
||||||
const lastSeg = sg.value.slice(-1)[0] || sg.start
|
const lastSeg = sg.value.slice(-1)[0] || sg.start
|
||||||
|
|
||||||
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||||
@ -768,9 +767,9 @@ export class SceneEntities {
|
|||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
})
|
})
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
const sketchGroup = programMemory.root[
|
const sketchGroup = programMemory.get(
|
||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
] as SketchGroup
|
) as SketchGroup
|
||||||
const sgPaths = sketchGroup.value
|
const sgPaths = sketchGroup.value
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
@ -820,9 +819,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
// Prepare to update the THREEjs scene
|
// Prepare to update the THREEjs scene
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
const sketchGroup = programMemory.root[
|
const sketchGroup = programMemory.get(
|
||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
] as SketchGroup
|
) as SketchGroup
|
||||||
const sgPaths = sketchGroup.value
|
const sgPaths = sketchGroup.value
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
@ -1081,9 +1080,9 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
|
|
||||||
const maybeSketchGroup = programMemory.root[variableDeclarationName]
|
const maybeSketchGroup = programMemory.get(variableDeclarationName)
|
||||||
let sketchGroup = undefined
|
let sketchGroup = undefined
|
||||||
if (maybeSketchGroup.type === 'SketchGroup') {
|
if (maybeSketchGroup?.type === 'SketchGroup') {
|
||||||
sketchGroup = maybeSketchGroup
|
sketchGroup = maybeSketchGroup
|
||||||
} else if ((maybeSketchGroup as ExtrudeGroup).sketchGroup) {
|
} else if ((maybeSketchGroup as ExtrudeGroup).sketchGroup) {
|
||||||
sketchGroup = (maybeSketchGroup as ExtrudeGroup).sketchGroup
|
sketchGroup = (maybeSketchGroup as ExtrudeGroup).sketchGroup
|
||||||
@ -1773,7 +1772,7 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
if (err(_node)) return _node
|
if (err(_node)) return _node
|
||||||
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
||||||
const lastSeg = (
|
const lastSeg = (
|
||||||
programMemory.root[variableDeclarationName] as SketchGroup
|
programMemory.get(variableDeclarationName) as SketchGroup
|
||||||
).value.slice(-1)[0]
|
).value.slice(-1)[0]
|
||||||
if (draftSegment) {
|
if (draftSegment) {
|
||||||
// truncatedAst needs to setup with another segment at the end
|
// truncatedAst needs to setup with another segment at the end
|
||||||
@ -1824,33 +1823,27 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
..._ast,
|
..._ast,
|
||||||
body: [JSON.parse(JSON.stringify(_ast.body[bodyIndex]))],
|
body: [JSON.parse(JSON.stringify(_ast.body[bodyIndex]))],
|
||||||
}
|
}
|
||||||
const programMemoryOverride = programMemoryInit()
|
|
||||||
if (err(programMemoryOverride)) return programMemoryOverride
|
|
||||||
|
|
||||||
// Grab all the TagDeclarators and TagIdentifiers from memory.
|
// Grab all the TagDeclarators and TagIdentifiers from memory.
|
||||||
let start = _node.node.start
|
let start = _node.node.start
|
||||||
for (const key in programMemory.root) {
|
const programMemoryOverride = programMemory.filterVariables(true, (value) => {
|
||||||
const value = programMemory.root[key]
|
|
||||||
if (!('__meta' in value)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
|
!('__meta' in value) ||
|
||||||
value.__meta === undefined ||
|
value.__meta === undefined ||
|
||||||
value.__meta.length === 0 ||
|
value.__meta.length === 0 ||
|
||||||
value.__meta[0].sourceRange === undefined
|
value.__meta[0].sourceRange === undefined
|
||||||
) {
|
) {
|
||||||
continue
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.__meta[0].sourceRange[0] >= start) {
|
if (value.__meta[0].sourceRange[0] >= start) {
|
||||||
// We only want things before our start point.
|
// We only want things before our start point.
|
||||||
continue
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.type === 'TagIdentifier') {
|
return value.type === 'TagIdentifier'
|
||||||
programMemoryOverride.root[key] = JSON.parse(JSON.stringify(value))
|
})
|
||||||
}
|
if (err(programMemoryOverride)) return programMemoryOverride
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < bodyIndex; i++) {
|
for (let i = 0; i < bodyIndex; i++) {
|
||||||
const node = _ast.body[i]
|
const node = _ast.body[i]
|
||||||
@ -1858,12 +1851,15 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const name = node.declarations[0].id.name
|
const name = node.declarations[0].id.name
|
||||||
// const memoryItem = kclManager.programMemory.root[name]
|
const memoryItem = programMemory.get(name)
|
||||||
const memoryItem = programMemory.root[name]
|
|
||||||
if (!memoryItem) {
|
if (!memoryItem) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
programMemoryOverride.root[name] = JSON.parse(JSON.stringify(memoryItem))
|
const error = programMemoryOverride.set(
|
||||||
|
name,
|
||||||
|
JSON.parse(JSON.stringify(memoryItem))
|
||||||
|
)
|
||||||
|
if (err(error)) return error
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
@ -1900,7 +1896,7 @@ export function sketchGroupFromPathToNode({
|
|||||||
)
|
)
|
||||||
if (err(_varDec)) return _varDec
|
if (err(_varDec)) return _varDec
|
||||||
const varDec = _varDec.node
|
const varDec = _varDec.node
|
||||||
const result = programMemory.root[varDec?.id?.name || '']
|
const result = programMemory.get(varDec?.id?.name || '')
|
||||||
if (result?.type === 'ExtrudeGroup') {
|
if (result?.type === 'ExtrudeGroup') {
|
||||||
return result.sketchGroup
|
return result.sketchGroup
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState, useRef } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import { parse, BinaryPart, Value } from '../lang/wasm'
|
import { parse, BinaryPart, Value, ProgramMemory } from '../lang/wasm'
|
||||||
import {
|
import {
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
@ -120,8 +120,7 @@ export function useCalc({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const allVarNames = Object.keys(programMemory.root)
|
if (programMemory.has(newVariableName)) {
|
||||||
if (allVarNames.includes(newVariableName)) {
|
|
||||||
setIsNewVariableNameUnique(false)
|
setIsNewVariableNameUnique(false)
|
||||||
} else {
|
} else {
|
||||||
setIsNewVariableNameUnique(true)
|
setIsNewVariableNameUnique(true)
|
||||||
@ -143,17 +142,20 @@ export function useCalc({
|
|||||||
const code = `const __result__ = ${value}`
|
const code = `const __result__ = ${value}`
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
if (trap(ast)) return
|
if (trap(ast)) return
|
||||||
const _programMem: any = { root: {}, return: null }
|
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
for (const { key, value } of availableVarInfo.variables) {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
const error = _programMem.set(key, {
|
||||||
})
|
type: 'UserVal',
|
||||||
|
value,
|
||||||
|
__meta: [],
|
||||||
|
})
|
||||||
|
if (trap(error)) return
|
||||||
|
}
|
||||||
executeAst({
|
executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
programMemoryOverride: JSON.parse(
|
programMemoryOverride: kclManager.programMemory.clone(),
|
||||||
JSON.stringify(kclManager.programMemory)
|
|
||||||
),
|
|
||||||
}).then(({ programMemory }) => {
|
}).then(({ programMemory }) => {
|
||||||
const resultDeclaration = ast.body.find(
|
const resultDeclaration = ast.body.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
@ -163,7 +165,7 @@ export function useCalc({
|
|||||||
const init =
|
const init =
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
resultDeclaration?.declarations?.[0]?.init
|
resultDeclaration?.declarations?.[0]?.init
|
||||||
const result = programMemory?.root?.__result__?.value
|
const result = programMemory?.get('__result__')?.value
|
||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
init && setValueNode(init)
|
init && setValueNode(init)
|
||||||
})
|
})
|
||||||
|
@ -163,7 +163,7 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|||||||
effects: StateEffect.reconfigure.of(targetExtensions),
|
effects: StateEffect.reconfigure.of(targetExtensions),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [targetExtensions])
|
}, [targetExtensions, view, isFirstRender])
|
||||||
|
|
||||||
return { view, setView, container, setContainer, state, setState }
|
return { view, setView, container, setContainer, state, setState }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { processMemory } from './MemoryPane'
|
import { processMemory } from './MemoryPane'
|
||||||
import { enginelessExecutor } from '../../../lib/testHelpers'
|
import { enginelessExecutor } from '../../../lib/testHelpers'
|
||||||
import { initPromise, parse } from '../../../lang/wasm'
|
import { initPromise, parse, ProgramMemory } from '../../../lang/wasm'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -29,10 +29,7 @@ describe('processMemory', () => {
|
|||||||
|> lineTo([2.15, 4.32], %)
|
|> lineTo([2.15, 4.32], %)
|
||||||
// |> rx(90, %)`
|
// |> rx(90, %)`
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast, {
|
const programMemory = await enginelessExecutor(ast, ProgramMemory.empty())
|
||||||
root: {},
|
|
||||||
return: null,
|
|
||||||
})
|
|
||||||
const output = processMemory(programMemory)
|
const output = processMemory(programMemory)
|
||||||
expect(output.myVar).toEqual(5)
|
expect(output.myVar).toEqual(5)
|
||||||
expect(output.otherVar).toEqual(3)
|
expect(output.otherVar).toEqual(3)
|
||||||
|
@ -82,8 +82,7 @@ export const MemoryPane = () => {
|
|||||||
|
|
||||||
export const processMemory = (programMemory: ProgramMemory) => {
|
export const processMemory = (programMemory: ProgramMemory) => {
|
||||||
const processedMemory: any = {}
|
const processedMemory: any = {}
|
||||||
Object.keys(programMemory?.root || {}).forEach((key) => {
|
for (const [key, val] of programMemory?.visibleEntries()) {
|
||||||
const val = programMemory.root[key]
|
|
||||||
if (typeof val.value !== 'function') {
|
if (typeof val.value !== 'function') {
|
||||||
if (val.type === 'SketchGroup') {
|
if (val.type === 'SketchGroup') {
|
||||||
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
|
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||||
@ -103,6 +102,6 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
|||||||
} else if (key !== 'log') {
|
} else if (key !== 'log') {
|
||||||
processedMemory[key] = '__function__'
|
processedMemory[key] = '__function__'
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
return processedMemory
|
return processedMemory
|
||||||
}
|
}
|
||||||
|
@ -179,6 +179,8 @@ export const Stream = () => {
|
|||||||
videoElement: videoRef.current,
|
videoElement: videoRef.current,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
}, [mediaStream])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
|
@ -14,9 +14,7 @@ import {
|
|||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
recast,
|
recast,
|
||||||
SketchGroup,
|
|
||||||
SourceRange,
|
SourceRange,
|
||||||
ExtrudeGroup,
|
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||||
@ -33,10 +31,7 @@ export class KclManager {
|
|||||||
},
|
},
|
||||||
digest: null,
|
digest: null,
|
||||||
}
|
}
|
||||||
private _programMemory: ProgramMemory = {
|
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
root: {},
|
|
||||||
return: null,
|
|
||||||
}
|
|
||||||
private _logs: string[] = []
|
private _logs: string[] = []
|
||||||
private _kclErrors: KCLError[] = []
|
private _kclErrors: KCLError[] = []
|
||||||
private _isExecuting = false
|
private _isExecuting = false
|
||||||
@ -505,10 +500,7 @@ function defaultSelectionFilter(
|
|||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
) {
|
) {
|
||||||
const firstSketchOrExtrudeGroup = Object.values(programMemory.root).find(
|
programMemory.hasSketchOrExtrudeGroup() &&
|
||||||
(node) => node.type === 'ExtrudeGroup' || node.type === 'SketchGroup'
|
|
||||||
) as SketchGroup | ExtrudeGroup
|
|
||||||
firstSketchOrExtrudeGroup &&
|
|
||||||
engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
|
@ -16,7 +16,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
// |> rx(45, %)`
|
// |> rx(45, %)`
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = programMemory?.root?.mySketch001
|
const sketch001 = programMemory?.get('mySketch001')
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'SketchGroup',
|
type: 'SketchGroup',
|
||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
@ -66,7 +66,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> extrude(2, %)`
|
|> extrude(2, %)`
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = programMemory?.root?.mySketch001
|
const sketch001 = programMemory?.get('mySketch001')
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
@ -146,7 +146,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
`
|
`
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
|
const geos = [programMemory?.get('theExtrude'), programMemory?.get('sk2')]
|
||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
{
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
|
@ -12,25 +12,25 @@ describe('test executor', () => {
|
|||||||
it('test assigning two variables, the second summing with the first', async () => {
|
it('test assigning two variables, the second summing with the first', async () => {
|
||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const newVar = myVar + 1`
|
const newVar = myVar + 1`
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(5)
|
expect(mem.get('myVar')?.value).toBe(5)
|
||||||
expect(root.newVar.value).toBe(6)
|
expect(mem.get('newVar')?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('test assigning a var with a string', async () => {
|
it('test assigning a var with a string', async () => {
|
||||||
const code = `const myVar = "a str"`
|
const code = `const myVar = "a str"`
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe('a str')
|
expect(mem.get('myVar')?.value).toBe('a str')
|
||||||
})
|
})
|
||||||
it('test assigning a var by cont concatenating two strings string execute', async () => {
|
it('test assigning a var by cont concatenating two strings string execute', async () => {
|
||||||
const code = fs.readFileSync(
|
const code = fs.readFileSync(
|
||||||
'./src/lang/testExamples/variableDeclaration.cado',
|
'./src/lang/testExamples/variableDeclaration.cado',
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe('a str another str')
|
expect(mem.get('myVar')?.value).toBe('a str another str')
|
||||||
})
|
})
|
||||||
it('fn funcN = () => {} execute', async () => {
|
it('fn funcN = () => {} execute', async () => {
|
||||||
const { root } = await exe(
|
const mem = await exe(
|
||||||
[
|
[
|
||||||
'fn funcN = (a, b) => {',
|
'fn funcN = (a, b) => {',
|
||||||
' return a + b',
|
' return a + b',
|
||||||
@ -39,8 +39,8 @@ const newVar = myVar + 1`
|
|||||||
'const magicNum = funcN(9, theVar)',
|
'const magicNum = funcN(9, theVar)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
)
|
)
|
||||||
expect(root.theVar.value).toBe(60)
|
expect(mem.get('theVar')?.value).toBe(60)
|
||||||
expect(root.magicNum.value).toBe(69)
|
expect(mem.get('magicNum')?.value).toBe(69)
|
||||||
})
|
})
|
||||||
it('sketch declaration', async () => {
|
it('sketch declaration', async () => {
|
||||||
let code = `const mySketch = startSketchOn('XY')
|
let code = `const mySketch = startSketchOn('XY')
|
||||||
@ -50,9 +50,9 @@ const newVar = myVar + 1`
|
|||||||
|> lineTo([5,-1], %, "rightPath")
|
|> lineTo([5,-1], %, "rightPath")
|
||||||
// |> close(%)
|
// |> close(%)
|
||||||
`
|
`
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
// geo is three js buffer geometry and is very bloated to have in tests
|
// geo is three js buffer geometry and is very bloated to have in tests
|
||||||
const minusGeo = root.mySketch.value
|
const minusGeo = mem.get('mySketch')?.value
|
||||||
expect(minusGeo).toEqual([
|
expect(minusGeo).toEqual([
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
@ -104,8 +104,8 @@ const newVar = myVar + 1`
|
|||||||
'fn myFn = (a) => { return a + 1 }',
|
'fn myFn = (a) => { return a + 1 }',
|
||||||
'const myVar = 5 + 1 |> myFn(%)',
|
'const myVar = 5 + 1 |> myFn(%)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(7)
|
expect(mem.get('myVar')?.value).toBe(7)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
@ -117,16 +117,16 @@ const newVar = myVar + 1`
|
|||||||
// ' |> lineTo([1, 1], %)',
|
// ' |> lineTo([1, 1], %)',
|
||||||
// 'const rotated = rx(90, mySk1)',
|
// 'const rotated = rx(90, mySk1)',
|
||||||
// ].join('\n')
|
// ].join('\n')
|
||||||
// const { root } = await exe(code)
|
// const mem = await exe(code)
|
||||||
// expect(root.mySk1.value).toHaveLength(3)
|
// expect(mem.get('mySk1')?.value).toHaveLength(3)
|
||||||
// expect(root?.rotated?.type).toBe('SketchGroup')
|
// expect(mem.get('rotated')?.type).toBe('SketchGroup')
|
||||||
// if (
|
// if (
|
||||||
// root?.mySk1?.type !== 'SketchGroup' ||
|
// mem.get('mySk1')?.type !== 'SketchGroup' ||
|
||||||
// root?.rotated?.type !== 'SketchGroup'
|
// mem.get('rotated')?.type !== 'SketchGroup'
|
||||||
// )
|
// )
|
||||||
// throw new Error('not a sketch group')
|
// throw new Error('not a sketch group')
|
||||||
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
|
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1])
|
||||||
// expect(root.rotated.rotation.map((a) => a.toFixed(4))).toEqual([
|
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([
|
||||||
// '0.7071',
|
// '0.7071',
|
||||||
// '0.0000',
|
// '0.0000',
|
||||||
// '0.0000',
|
// '0.0000',
|
||||||
@ -144,8 +144,8 @@ const newVar = myVar + 1`
|
|||||||
' |> lineTo([1,1], %)',
|
' |> lineTo([1,1], %)',
|
||||||
// ' |> rx(90, %)',
|
// ' |> rx(90, %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.mySk1).toEqual({
|
expect(mem.get('mySk1')).toEqual({
|
||||||
type: 'SketchGroup',
|
type: 'SketchGroup',
|
||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
start: {
|
start: {
|
||||||
@ -214,36 +214,37 @@ const newVar = myVar + 1`
|
|||||||
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||||
'\n'
|
'\n'
|
||||||
)
|
)
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||||
expect(root).toEqual({
|
expect(mem.get('three')).toEqual({
|
||||||
three: {
|
type: 'UserVal',
|
||||||
type: 'UserVal',
|
value: 3,
|
||||||
value: 3,
|
__meta: [
|
||||||
__meta: [
|
{
|
||||||
{
|
sourceRange: [14, 15],
|
||||||
sourceRange: [14, 15],
|
},
|
||||||
},
|
],
|
||||||
],
|
|
||||||
},
|
|
||||||
yo: {
|
|
||||||
type: 'UserVal',
|
|
||||||
value: [1, '2', 3, 9],
|
|
||||||
__meta: [
|
|
||||||
{
|
|
||||||
sourceRange: [27, 49],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
expect(mem.get('yo')).toEqual({
|
||||||
|
type: 'UserVal',
|
||||||
|
value: [1, '2', 3, 9],
|
||||||
|
__meta: [
|
||||||
|
{
|
||||||
|
sourceRange: [27, 49],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
// Check that there are no other variables or environments.
|
||||||
|
expect(mem.numEnvironments()).toBe(1)
|
||||||
|
expect(mem.numVariables(0)).toBe(2)
|
||||||
})
|
})
|
||||||
it('execute object expression', async () => {
|
it('execute object expression', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const three = 3',
|
'const three = 3',
|
||||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.yo).toEqual({
|
expect(mem.get('yo')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'UserVal',
|
||||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
||||||
__meta: [
|
__meta: [
|
||||||
@ -257,8 +258,8 @@ const newVar = myVar + 1`
|
|||||||
const code = ["const yo = {a: {b: '123'}}", "const myVar = yo.a['b']"].join(
|
const code = ["const yo = {a: {b: '123'}}", "const myVar = yo.a['b']"].join(
|
||||||
'\n'
|
'\n'
|
||||||
)
|
)
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar).toEqual({
|
expect(mem.get('myVar')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'UserVal',
|
||||||
value: '123',
|
value: '123',
|
||||||
__meta: [
|
__meta: [
|
||||||
@ -273,81 +274,81 @@ const newVar = myVar + 1`
|
|||||||
describe('testing math operators', () => {
|
describe('testing math operators', () => {
|
||||||
it('can sum', async () => {
|
it('can sum', async () => {
|
||||||
const code = ['const myVar = 1 + 2'].join('\n')
|
const code = ['const myVar = 1 + 2'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(3)
|
expect(mem.get('myVar')?.value).toBe(3)
|
||||||
})
|
})
|
||||||
it('can subtract', async () => {
|
it('can subtract', async () => {
|
||||||
const code = ['const myVar = 1 - 2'].join('\n')
|
const code = ['const myVar = 1 - 2'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(-1)
|
expect(mem.get('myVar')?.value).toBe(-1)
|
||||||
})
|
})
|
||||||
it('can multiply', async () => {
|
it('can multiply', async () => {
|
||||||
const code = ['const myVar = 1 * 2'].join('\n')
|
const code = ['const myVar = 1 * 2'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(2)
|
expect(mem.get('myVar')?.value).toBe(2)
|
||||||
})
|
})
|
||||||
it('can divide', async () => {
|
it('can divide', async () => {
|
||||||
const code = ['const myVar = 1 / 2'].join('\n')
|
const code = ['const myVar = 1 / 2'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(0.5)
|
expect(mem.get('myVar')?.value).toBe(0.5)
|
||||||
})
|
})
|
||||||
it('can modulus', async () => {
|
it('can modulus', async () => {
|
||||||
const code = ['const myVar = 5 % 2'].join('\n')
|
const code = ['const myVar = 5 % 2'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(1)
|
expect(mem.get('myVar')?.value).toBe(1)
|
||||||
})
|
})
|
||||||
it('can do multiple operations', async () => {
|
it('can do multiple operations', async () => {
|
||||||
const code = ['const myVar = 1 + 2 * 3'].join('\n')
|
const code = ['const myVar = 1 + 2 * 3'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(7)
|
expect(mem.get('myVar')?.value).toBe(7)
|
||||||
})
|
})
|
||||||
it('big example with parans', async () => {
|
it('big example with parans', async () => {
|
||||||
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
|
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(7.4)
|
expect(mem.get('myVar')?.value).toBe(7.4)
|
||||||
})
|
})
|
||||||
it('with identifier', async () => {
|
it('with identifier', async () => {
|
||||||
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
|
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(3)
|
expect(mem.get('myVar')?.value).toBe(3)
|
||||||
})
|
})
|
||||||
it('with lots of testing', async () => {
|
it('with lots of testing', async () => {
|
||||||
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
|
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(12.5)
|
expect(mem.get('myVar')?.value).toBe(12.5)
|
||||||
})
|
})
|
||||||
it('with callExpression at start', async () => {
|
it('with callExpression at start', async () => {
|
||||||
const code = 'const myVar = min(4, 100) + 2'
|
const code = 'const myVar = min(4, 100) + 2'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(6)
|
expect(mem.get('myVar')?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('with callExpression at end', async () => {
|
it('with callExpression at end', async () => {
|
||||||
const code = 'const myVar = 2 + min(4, 100)'
|
const code = 'const myVar = 2 + min(4, 100)'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(6)
|
expect(mem.get('myVar')?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('with nested callExpression', async () => {
|
it('with nested callExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(6)
|
expect(mem.get('myVar')?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('with unaryExpression', async () => {
|
it('with unaryExpression', async () => {
|
||||||
const code = 'const myVar = -min(100, 3)'
|
const code = 'const myVar = -min(100, 3)'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(-3)
|
expect(mem.get('myVar')?.value).toBe(-3)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in callExpression', async () => {
|
it('with unaryExpression in callExpression', async () => {
|
||||||
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
||||||
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
const { root: root2 } = await exe(code2)
|
const mem2 = await exe(code2)
|
||||||
expect(root.myVar.value).toBe(-3)
|
expect(mem.get('myVar')?.value).toBe(-3)
|
||||||
expect(root.myVar.value).toBe(root2.myVar.value)
|
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in ArrayExpression', async () => {
|
it('with unaryExpression in ArrayExpression', async () => {
|
||||||
const code = 'const myVar = [1,-legLen(5, 4)]'
|
const code = 'const myVar = [1,-legLen(5, 4)]'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toEqual([1, -3])
|
expect(mem.get('myVar')?.value).toEqual([1, -3])
|
||||||
})
|
})
|
||||||
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
@ -355,8 +356,8 @@ describe('testing math operators', () => {
|
|||||||
' |> startProfileAt([0, 0], %)',
|
' |> startProfileAt([0, 0], %)',
|
||||||
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = mem.get('part001')
|
||||||
// result of `-legLen(5, min(3, 999))` should be -4
|
// result of `-legLen(5, min(3, 999))` should be -4
|
||||||
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
|
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
|
||||||
expect(yVal).toBe(-4)
|
expect(yVal).toBe(-4)
|
||||||
@ -373,8 +374,8 @@ describe('testing math operators', () => {
|
|||||||
`], %)`,
|
`], %)`,
|
||||||
``,
|
``,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = mem.get('part001')
|
||||||
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
||||||
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
||||||
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
||||||
@ -382,18 +383,18 @@ describe('testing math operators', () => {
|
|||||||
`-legLen(segLen('seg01', %), myVar)`,
|
`-legLen(segLen('seg01', %), myVar)`,
|
||||||
`legLen(segLen('seg01', %), myVar)`
|
`legLen(segLen('seg01', %), myVar)`
|
||||||
)
|
)
|
||||||
const { root: removedUnaryExpRoot } = await exe(removedUnaryExp)
|
const removedUnaryExpMem = await exe(removedUnaryExp)
|
||||||
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
const removedUnaryExpMemSketch = removedUnaryExpMem.get('part001')
|
||||||
|
|
||||||
// without the minus sign, the y value should be 8
|
// without the minus sign, the y value should be 8
|
||||||
expect((removedUnaryExpRootSketch as SketchGroup).value?.[1]?.to).toEqual([
|
expect((removedUnaryExpMemSketch as SketchGroup).value?.[1]?.to).toEqual([
|
||||||
6, 8,
|
6, 8,
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
it('with nested callExpression and binaryExpression', async () => {
|
it('with nested callExpression and binaryExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||||
const { root } = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(root.myVar.value).toBe(5)
|
expect(mem.get('myVar')?.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -421,7 +422,7 @@ const theExtrude = startSketchOn('XY')
|
|||||||
|
|
||||||
async function exe(
|
async function exe(
|
||||||
code: string,
|
code: string,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null }
|
programMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
) {
|
) {
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
|
|
||||||
|
@ -79,20 +79,14 @@ export async function executeAst({
|
|||||||
return {
|
return {
|
||||||
errors: [e],
|
errors: [e],
|
||||||
logs: [],
|
logs: [],
|
||||||
programMemory: {
|
programMemory: ProgramMemory.empty(),
|
||||||
root: {},
|
|
||||||
return: null,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
return {
|
return {
|
||||||
logs: [e],
|
logs: [e],
|
||||||
errors: [],
|
errors: [],
|
||||||
programMemory: {
|
programMemory: ProgramMemory.empty(),
|
||||||
root: {},
|
|
||||||
return: null,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -983,7 +983,7 @@ export async function deleteFromSelection(
|
|||||||
if (err(parent)) {
|
if (err(parent)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sketchToPreserve = programMemory.root[sketchName] as SketchGroup
|
const sketchToPreserve = programMemory.get(sketchName) as SketchGroup
|
||||||
console.log('sketchName', sketchName)
|
console.log('sketchName', sketchName)
|
||||||
// Can't kick off multiple requests at once as getFaceDetails
|
// Can't kick off multiple requests at once as getFaceDetails
|
||||||
// is three engine calls in one and they conflict
|
// is three engine calls in one and they conflict
|
||||||
|
@ -130,8 +130,14 @@ function moreNodePathFromSourceRange(
|
|||||||
|
|
||||||
const isInRange = _node.start <= start && _node.end >= end
|
const isInRange = _node.start <= start && _node.end >= end
|
||||||
|
|
||||||
if ((_node.type === 'Identifier' || _node.type === 'Literal') && isInRange)
|
if (
|
||||||
|
(_node.type === 'Identifier' ||
|
||||||
|
_node.type === 'Literal' ||
|
||||||
|
_node.type === 'TagDeclarator') &&
|
||||||
|
isInRange
|
||||||
|
) {
|
||||||
return path
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
if (_node.type === 'CallExpression' && isInRange) {
|
if (_node.type === 'CallExpression' && isInRange) {
|
||||||
const { callee, arguments: args } = _node
|
const { callee, arguments: args } = _node
|
||||||
@ -277,6 +283,15 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'ReturnStatement' && isInRange) {
|
||||||
|
const { argument } = _node
|
||||||
|
if (argument.start <= start && argument.end >= end) {
|
||||||
|
path.push(['argument', 'ReturnStatement'])
|
||||||
|
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
if (_node.type === 'MemberExpression' && isInRange) {
|
if (_node.type === 'MemberExpression' && isInRange) {
|
||||||
const { object, property } = _node
|
const { object, property } = _node
|
||||||
@ -459,8 +474,8 @@ export function findAllPreviousVariablesPath(
|
|||||||
bodyItems?.forEach?.((item) => {
|
bodyItems?.forEach?.((item) => {
|
||||||
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
||||||
const varName = item.declarations[0].id.name
|
const varName = item.declarations[0].id.name
|
||||||
const varValue = programMemory?.root[varName]
|
const varValue = programMemory?.get(varName)
|
||||||
if (typeof varValue?.value !== type) return
|
if (!varValue || typeof varValue?.value !== type) return
|
||||||
variables.push({
|
variables.push({
|
||||||
key: varName,
|
key: varName,
|
||||||
value: varValue.value,
|
value: varValue.value,
|
||||||
@ -640,7 +655,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
if (err(_varDec)) return _varDec
|
if (err(_varDec)) return _varDec
|
||||||
const varDec = _varDec.node
|
const varDec = _varDec.node
|
||||||
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
|
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
|
||||||
const path = programMemory?.root[varName] as SketchGroup
|
const path = programMemory?.get(varName) as SketchGroup
|
||||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||||
path,
|
path,
|
||||||
primaryLine.range
|
primaryLine.range
|
||||||
@ -687,7 +702,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
constraintType === 'angle' || constraintLevel === 'full'
|
constraintType === 'angle' || constraintLevel === 'full'
|
||||||
|
|
||||||
// get the previous segment
|
// get the previous segment
|
||||||
const prevSegment = (programMemory.root[varName] as SketchGroup).value[
|
const prevSegment = (programMemory.get(varName) as SketchGroup).value[
|
||||||
secondaryIndex - 1
|
secondaryIndex - 1
|
||||||
]
|
]
|
||||||
const prevSourceRange = prevSegment.__geoMeta.sourceRange
|
const prevSourceRange = prevSegment.__geoMeta.sourceRange
|
||||||
@ -757,7 +772,7 @@ export function hasExtrudeSketchGroup({
|
|||||||
const varDec = varDecMeta.node
|
const varDec = varDecMeta.node
|
||||||
if (varDec.type !== 'VariableDeclaration') return false
|
if (varDec.type !== 'VariableDeclaration') return false
|
||||||
const varName = varDec.declarations[0].id.name
|
const varName = varDec.declarations[0].id.name
|
||||||
const varValue = programMemory?.root[varName]
|
const varValue = programMemory?.get(varName)
|
||||||
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1009,8 +1009,8 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
const { node: varDec } = nodeMeta2
|
const { node: varDec } = nodeMeta2
|
||||||
|
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
const sketch = previousProgramMemory?.get(variableName)
|
||||||
if (sketch.type !== 'SketchGroup') {
|
if (!sketch || sketch.type !== 'SketchGroup') {
|
||||||
return new Error('not a SketchGroup')
|
return new Error('not a SketchGroup')
|
||||||
}
|
}
|
||||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
@ -1105,8 +1105,8 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
if (err(nodeMeta2)) return nodeMeta2
|
if (err(nodeMeta2)) return nodeMeta2
|
||||||
const { node: varDec } = nodeMeta2
|
const { node: varDec } = nodeMeta2
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
const sketch = previousProgramMemory?.get(variableName)
|
||||||
if (sketch.type !== 'SketchGroup') {
|
if (!sketch || sketch.type !== 'SketchGroup') {
|
||||||
return new Error('not a SketchGroup')
|
return new Error('not a SketchGroup')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1443,7 +1443,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
|
|
||||||
const { node: varDec } = nodeMeta2
|
const { node: varDec } = nodeMeta2
|
||||||
const varName = varDec.declarations[0].id.name
|
const varName = varDec.declarations[0].id.name
|
||||||
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
const sketchGroup = previousProgramMemory.get(varName) as SketchGroup
|
||||||
const intersectPath = sketchGroup.value.find(
|
const intersectPath = sketchGroup.value.find(
|
||||||
({ tag }: Path) => tag && tag.value === intersectTagName
|
({ tag }: Path) => tag && tag.value === intersectTagName
|
||||||
)
|
)
|
||||||
|
@ -363,7 +363,7 @@ const part001 = startSketchOn('XY')
|
|||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// normal-segment') - 7
|
const index = code.indexOf('// normal-segment') - 7
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.get('part001') as SketchGroup,
|
||||||
[index, index]
|
[index, index]
|
||||||
)
|
)
|
||||||
if (err(_segment)) throw _segment
|
if (err(_segment)) throw _segment
|
||||||
@ -379,7 +379,7 @@ const part001 = startSketchOn('XY')
|
|||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// segment-in-start') - 7
|
const index = code.indexOf('// segment-in-start') - 7
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.get('part001') as SketchGroup,
|
||||||
[index, index]
|
[index, index]
|
||||||
)
|
)
|
||||||
if (err(_segment)) throw _segment
|
if (err(_segment)) throw _segment
|
||||||
|
@ -1636,8 +1636,8 @@ export function transformAstSketchLines({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const varName = varDec.node.id.name
|
const varName = varDec.node.id.name
|
||||||
let sketchGroup = programMemory.root?.[varName]
|
let sketchGroup = programMemory.get(varName)
|
||||||
if (sketchGroup.type === 'ExtrudeGroup') {
|
if (sketchGroup?.type === 'ExtrudeGroup') {
|
||||||
sketchGroup = sketchGroup.sketchGroup
|
sketchGroup = sketchGroup.sketchGroup
|
||||||
}
|
}
|
||||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
||||||
|
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
|
|||||||
offset: ${offset},
|
offset: ${offset},
|
||||||
}, %, "yo2")
|
}, %, "yo2")
|
||||||
const intersect = segEndX('yo2', part001)`
|
const intersect = segEndX('yo2', part001)`
|
||||||
const { root } = await enginelessExecutor(parse(code('-1')))
|
const mem = await enginelessExecutor(parse(code('-1')))
|
||||||
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
expect(mem.get('intersect')?.value).toBe(1 + Math.sqrt(2))
|
||||||
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
|
const noOffset = await enginelessExecutor(parse(code('0')))
|
||||||
expect(noOffset.intersect.value).toBeCloseTo(1)
|
expect(noOffset.get('intersect')?.value).toBeCloseTo(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
211
src/lang/wasm.ts
211
src/lang/wasm.ts
@ -143,14 +143,200 @@ interface Memory {
|
|||||||
[key: string]: MemoryItem
|
[key: string]: MemoryItem
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProgramMemory {
|
type EnvironmentRef = number
|
||||||
root: Memory
|
|
||||||
|
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
|
||||||
|
|
||||||
|
interface Environment {
|
||||||
|
bindings: Memory
|
||||||
|
parent: EnvironmentRef | null
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyEnvironment(): Environment {
|
||||||
|
return { bindings: {}, parent: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawProgramMemory {
|
||||||
|
environments: Environment[]
|
||||||
|
currentEnv: EnvironmentRef
|
||||||
return: ProgramReturn | null
|
return: ProgramReturn | 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: ProgramReturn | 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[] = [emptyEnvironment()],
|
||||||
|
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
|
||||||
|
returnVal: ProgramReturn | null = null
|
||||||
|
) {
|
||||||
|
this.environments = environments
|
||||||
|
this.currentEnv = currentEnv
|
||||||
|
this.return = returnVal
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a deep copy.
|
||||||
|
*/
|
||||||
|
clone(): ProgramMemory {
|
||||||
|
return ProgramMemory.fromRaw(JSON.parse(JSON.stringify(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): MemoryItem | null {
|
||||||
|
let envRef = this.currentEnv
|
||||||
|
while (true) {
|
||||||
|
const env = this.environments[envRef]
|
||||||
|
if (env.bindings.hasOwnProperty(name)) {
|
||||||
|
return env.bindings[name]
|
||||||
|
}
|
||||||
|
if (!env.parent) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
envRef = env.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
set(name: string, value: MemoryItem): 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 `MemoryItem`s that pass the
|
||||||
|
* predicate. Values are deep copied.
|
||||||
|
*
|
||||||
|
* Note: Return value of the returned ProgramMemory is always null.
|
||||||
|
*/
|
||||||
|
filterVariables(
|
||||||
|
keepPrelude: boolean,
|
||||||
|
predicate: (value: MemoryItem) => 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)) {
|
||||||
|
// Check the predicate.
|
||||||
|
if (!predicate(value)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Deep copy.
|
||||||
|
bindings[name] = JSON.parse(JSON.stringify(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, MemoryItem> {
|
||||||
|
const map = new Map<string, MemoryItem>()
|
||||||
|
let envRef = this.currentEnv
|
||||||
|
while (true) {
|
||||||
|
const env = this.environments[envRef]
|
||||||
|
for (const [name, value] of Object.entries(env.bindings)) {
|
||||||
|
// 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 SketchGroup or ExtrudeGroup.
|
||||||
|
*/
|
||||||
|
hasSketchOrExtrudeGroup(): boolean {
|
||||||
|
for (const node of this.visibleEntries().values()) {
|
||||||
|
if (node.type === 'ExtrudeGroup' || node.type === 'SketchGroup') {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const executor = async (
|
export const executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory | Error = { root: {}, return: null },
|
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
isMock: boolean = false
|
isMock: boolean = false
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
@ -171,7 +357,7 @@ export const executor = async (
|
|||||||
|
|
||||||
export const _executor = async (
|
export const _executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory | Error = { root: {}, return: null },
|
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
isMock: boolean
|
isMock: boolean
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
@ -186,15 +372,15 @@ export const _executor = async (
|
|||||||
baseUnit =
|
baseUnit =
|
||||||
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
|
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
|
||||||
}
|
}
|
||||||
const memory: ProgramMemory = await execute_wasm(
|
const memory: RawProgramMemory = await execute_wasm(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
JSON.stringify(programMemory),
|
JSON.stringify(programMemory.toRaw()),
|
||||||
baseUnit,
|
baseUnit,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
fileSystemManager,
|
fileSystemManager,
|
||||||
isMock
|
isMock
|
||||||
)
|
)
|
||||||
return memory
|
return ProgramMemory.fromRaw(memory)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
@ -329,10 +515,17 @@ export function getTangentialArcToInfo({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new ProgramMemory with prelude definitions.
|
||||||
|
*/
|
||||||
export function programMemoryInit(): ProgramMemory | Error {
|
export function programMemoryInit(): ProgramMemory | Error {
|
||||||
try {
|
try {
|
||||||
const memory: ProgramMemory = program_memory_init()
|
const memory: RawProgramMemory = program_memory_init()
|
||||||
return memory
|
return new ProgramMemory(
|
||||||
|
memory.environments,
|
||||||
|
memory.currentEnv,
|
||||||
|
memory.return
|
||||||
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
|
@ -75,7 +75,7 @@ class MockEngineCommandManager {
|
|||||||
|
|
||||||
export async function enginelessExecutor(
|
export async function enginelessExecutor(
|
||||||
ast: Program | Error,
|
ast: Program | Error,
|
||||||
pm: ProgramMemory | Error = { root: {}, return: null }
|
pm: ProgramMemory | Error = ProgramMemory.empty()
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
if (err(ast)) return Promise.reject(ast)
|
if (err(ast)) return Promise.reject(ast)
|
||||||
if (err(pm)) return Promise.reject(pm)
|
if (err(pm)) return Promise.reject(pm)
|
||||||
@ -93,7 +93,7 @@ export async function enginelessExecutor(
|
|||||||
|
|
||||||
export async function executor(
|
export async function executor(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
pm: ProgramMemory = { root: {}, return: null }
|
pm: ProgramMemory = ProgramMemory.empty()
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
const engineCommandManager = new EngineCommandManager()
|
const engineCommandManager = new EngineCommandManager()
|
||||||
engineCommandManager.start({
|
engineCommandManager.start({
|
||||||
|
@ -3,7 +3,7 @@ import { kclManager, engineCommandManager } from 'lib/singletons'
|
|||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { findUniqueName } from 'lang/modifyAst'
|
import { findUniqueName } from 'lang/modifyAst'
|
||||||
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||||
import { Value, parse } from 'lang/wasm'
|
import { ProgramMemory, Value, parse } from 'lang/wasm'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { executeAst } from 'lang/langHelpers'
|
import { executeAst } from 'lang/langHelpers'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
@ -60,9 +60,8 @@ export function useCalculateKclExpression({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const allVarNames = Object.keys(programMemory.root)
|
|
||||||
if (
|
if (
|
||||||
allVarNames.includes(newVariableName) ||
|
programMemory.has(newVariableName) ||
|
||||||
newVariableName === '' ||
|
newVariableName === '' ||
|
||||||
!isValidVariableName(newVariableName)
|
!isValidVariableName(newVariableName)
|
||||||
) {
|
) {
|
||||||
@ -89,17 +88,20 @@ export function useCalculateKclExpression({
|
|||||||
if (err(ast)) return
|
if (err(ast)) return
|
||||||
if (trap(ast, { suppress: true })) return
|
if (trap(ast, { suppress: true })) return
|
||||||
|
|
||||||
const _programMem: any = { root: {}, return: null }
|
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
for (const { key, value } of availableVarInfo.variables) {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
const error = _programMem.set(key, {
|
||||||
})
|
type: 'UserVal',
|
||||||
|
value,
|
||||||
|
__meta: [],
|
||||||
|
})
|
||||||
|
if (trap(error, { suppress: true })) return
|
||||||
|
}
|
||||||
const { programMemory } = await executeAst({
|
const { programMemory } = await executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
programMemoryOverride: JSON.parse(
|
programMemoryOverride: kclManager.programMemory.clone(),
|
||||||
JSON.stringify(kclManager.programMemory)
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
const resultDeclaration = ast.body.find(
|
const resultDeclaration = ast.body.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
@ -109,7 +111,7 @@ export function useCalculateKclExpression({
|
|||||||
const init =
|
const init =
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
resultDeclaration?.declarations?.[0]?.init
|
resultDeclaration?.declarations?.[0]?.init
|
||||||
const result = programMemory?.root?.__result__?.value
|
const result = programMemory?.get('__result__')?.value
|
||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
init && setValueNode(init)
|
init && setValueNode(init)
|
||||||
}
|
}
|
||||||
|
@ -1139,8 +1139,8 @@ export const modelingMachine = createMachine(
|
|||||||
)
|
)
|
||||||
if (err(varDecNode)) return
|
if (err(varDecNode)) return
|
||||||
const sketchVar = varDecNode.node.declarations[0].id.name
|
const sketchVar = varDecNode.node.declarations[0].id.name
|
||||||
const sketchGroup = kclManager.programMemory.root[sketchVar]
|
const sketchGroup = kclManager.programMemory.get(sketchVar)
|
||||||
if (sketchGroup.type !== 'SketchGroup') return
|
if (sketchGroup?.type !== 'SketchGroup') return
|
||||||
const idArtifact = engineCommandManager.artifactMap[sketchGroup.id]
|
const idArtifact = engineCommandManager.artifactMap[sketchGroup.id]
|
||||||
if (idArtifact.commandType !== 'start_path') return
|
if (idArtifact.commandType !== 'start_path') return
|
||||||
const extrusionArtifactId = (idArtifact as any)?.extrusions?.[0]
|
const extrusionArtifactId = (idArtifact as any)?.extrusions?.[0]
|
||||||
|
@ -1388,7 +1388,7 @@ impl CallExpression {
|
|||||||
}
|
}
|
||||||
FunctionKind::UserDefined => {
|
FunctionKind::UserDefined => {
|
||||||
let func = memory.get(&fn_name, self.into())?;
|
let func = memory.get(&fn_name, self.into())?;
|
||||||
let result = func.call_fn(fn_args, memory.clone(), ctx.clone()).await.map_err(|e| {
|
let result = func.call_fn(fn_args, ctx.clone()).await.map_err(|e| {
|
||||||
// Add the call expression to the source ranges.
|
// Add the call expression to the source ranges.
|
||||||
e.add_source_ranges(vec![self.into()])
|
e.add_source_ranges(vec![self.into()])
|
||||||
})?;
|
})?;
|
||||||
@ -2880,6 +2880,30 @@ impl BinaryExpression {
|
|||||||
pipe_info: &PipeInfo,
|
pipe_info: &PipeInfo,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
|
// First check if we are doing short-circuiting logical operator.
|
||||||
|
if self.operator == BinaryOperator::LogicalOr {
|
||||||
|
let left_json_value = self.left.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
|
let left = json_to_bool(&left_json_value);
|
||||||
|
if left {
|
||||||
|
// Short-circuit.
|
||||||
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(left),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let right_json_value = self.right.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
|
let right = json_to_bool(&right_json_value);
|
||||||
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(right),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let left_json_value = self.left.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
let left_json_value = self.left.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
let right_json_value = self.right.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
let right_json_value = self.right.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
|
|
||||||
@ -2909,6 +2933,9 @@ impl BinaryExpression {
|
|||||||
BinaryOperator::Div => (left / right).into(),
|
BinaryOperator::Div => (left / right).into(),
|
||||||
BinaryOperator::Mod => (left % right).into(),
|
BinaryOperator::Mod => (left % right).into(),
|
||||||
BinaryOperator::Pow => (left.powf(right)).into(),
|
BinaryOperator::Pow => (left.powf(right)).into(),
|
||||||
|
BinaryOperator::LogicalOr => {
|
||||||
|
unreachable!("LogicalOr should have been handled above")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal(UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
@ -2950,6 +2977,27 @@ pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn json_to_bool(j: &serde_json::Value) -> bool {
|
||||||
|
match j {
|
||||||
|
JValue::Null => false,
|
||||||
|
JValue::Bool(b) => *b,
|
||||||
|
JValue::Number(n) => {
|
||||||
|
if let Some(n) = n.as_u64() {
|
||||||
|
n != 0
|
||||||
|
} else if let Some(n) = n.as_i64() {
|
||||||
|
n != 0
|
||||||
|
} else if let Some(x) = n.as_f64() {
|
||||||
|
x != 0.0 && !x.is_nan()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JValue::String(s) => !s.is_empty(),
|
||||||
|
JValue::Array(a) => !a.is_empty(),
|
||||||
|
JValue::Object(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake)]
|
||||||
#[databake(path = kcl_lib::ast::types)]
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -2980,6 +3028,10 @@ pub enum BinaryOperator {
|
|||||||
#[serde(rename = "^")]
|
#[serde(rename = "^")]
|
||||||
#[display("^")]
|
#[display("^")]
|
||||||
Pow,
|
Pow,
|
||||||
|
/// Logical OR.
|
||||||
|
#[serde(rename = "||")]
|
||||||
|
#[display("||")]
|
||||||
|
LogicalOr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mathematical associativity.
|
/// Mathematical associativity.
|
||||||
@ -3008,6 +3060,7 @@ impl BinaryOperator {
|
|||||||
BinaryOperator::Div => *b"div",
|
BinaryOperator::Div => *b"div",
|
||||||
BinaryOperator::Mod => *b"mod",
|
BinaryOperator::Mod => *b"mod",
|
||||||
BinaryOperator::Pow => *b"pow",
|
BinaryOperator::Pow => *b"pow",
|
||||||
|
BinaryOperator::LogicalOr => *b"lor",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3018,6 +3071,7 @@ impl BinaryOperator {
|
|||||||
BinaryOperator::Add | BinaryOperator::Sub => 11,
|
BinaryOperator::Add | BinaryOperator::Sub => 11,
|
||||||
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
|
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
|
||||||
BinaryOperator::Pow => 6,
|
BinaryOperator::Pow => 6,
|
||||||
|
BinaryOperator::LogicalOr => 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3025,7 +3079,7 @@ impl BinaryOperator {
|
|||||||
/// Taken from <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table>
|
/// Taken from <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table>
|
||||||
pub fn associativity(&self) -> Associativity {
|
pub fn associativity(&self) -> Associativity {
|
||||||
match self {
|
match self {
|
||||||
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod => Associativity::Left,
|
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod | Self::LogicalOr => Associativity::Left,
|
||||||
Self::Pow => Associativity::Right,
|
Self::Pow => Associativity::Right,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3089,6 +3143,21 @@ impl UnaryExpression {
|
|||||||
pipe_info: &PipeInfo,
|
pipe_info: &PipeInfo,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
|
if self.operator == UnaryOperator::Not {
|
||||||
|
let value = self
|
||||||
|
.argument
|
||||||
|
.get_result(memory, pipe_info, ctx)
|
||||||
|
.await?
|
||||||
|
.get_json_value()?;
|
||||||
|
let negated = !json_to_bool(&value);
|
||||||
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(negated),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let num = parse_json_number_as_f64(
|
let num = parse_json_number_as_f64(
|
||||||
&self
|
&self
|
||||||
.argument
|
.argument
|
||||||
|
@ -23,7 +23,8 @@ use crate::{
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ProgramMemory {
|
pub struct ProgramMemory {
|
||||||
pub root: HashMap<String, MemoryItem>,
|
pub environments: Vec<Environment>,
|
||||||
|
pub current_env: EnvironmentRef,
|
||||||
#[serde(rename = "return")]
|
#[serde(rename = "return")]
|
||||||
pub return_: Option<ProgramReturn>,
|
pub return_: Option<ProgramReturn>,
|
||||||
}
|
}
|
||||||
@ -31,7 +32,105 @@ pub struct ProgramMemory {
|
|||||||
impl ProgramMemory {
|
impl ProgramMemory {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: HashMap::from([
|
environments: vec![Environment::root()],
|
||||||
|
current_env: EnvironmentRef::root(),
|
||||||
|
return_: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_env_for_call(&mut self, parent: EnvironmentRef) -> EnvironmentRef {
|
||||||
|
let new_env_ref = EnvironmentRef(self.environments.len());
|
||||||
|
let new_env = Environment::new(parent);
|
||||||
|
self.environments.push(new_env);
|
||||||
|
new_env_ref
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add to the program memory in the current scope.
|
||||||
|
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
||||||
|
if self.environments[self.current_env.index()].contains_key(key) {
|
||||||
|
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
||||||
|
message: format!("Cannot redefine `{}`", key),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.environments[self.current_env.index()].insert(key.to_string(), value);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a value from the program memory.
|
||||||
|
/// Return Err if not found.
|
||||||
|
pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
||||||
|
let mut env_ref = self.current_env;
|
||||||
|
loop {
|
||||||
|
let env = &self.environments[env_ref.index()];
|
||||||
|
if let Some(item) = env.bindings.get(var) {
|
||||||
|
return Ok(item);
|
||||||
|
}
|
||||||
|
if let Some(parent) = env.parent {
|
||||||
|
env_ref = parent;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("memory item key `{}` is not defined", var),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find all extrude groups in the memory that are on a specific sketch group id.
|
||||||
|
/// This does not look inside closures. But as long as we do not allow
|
||||||
|
/// mutation of variables in KCL, closure memory should be a subset of this.
|
||||||
|
pub fn find_extrude_groups_on_sketch_group(&self, sketch_group_id: uuid::Uuid) -> Vec<Box<ExtrudeGroup>> {
|
||||||
|
self.environments
|
||||||
|
.iter()
|
||||||
|
.flat_map(|env| {
|
||||||
|
env.bindings
|
||||||
|
.values()
|
||||||
|
.filter_map(|item| match item {
|
||||||
|
MemoryItem::ExtrudeGroup(eg) if eg.sketch_group.id == sketch_group_id => Some(eg.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProgramMemory {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An index pointing to an environment.
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
pub struct EnvironmentRef(usize);
|
||||||
|
|
||||||
|
impl EnvironmentRef {
|
||||||
|
pub fn root() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
pub struct Environment {
|
||||||
|
bindings: HashMap<String, MemoryItem>,
|
||||||
|
parent: Option<EnvironmentRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
pub fn root() -> Self {
|
||||||
|
Self {
|
||||||
|
// Prelude
|
||||||
|
bindings: HashMap::from([
|
||||||
(
|
(
|
||||||
"ZERO".to_string(),
|
"ZERO".to_string(),
|
||||||
MemoryItem::UserVal(UserVal {
|
MemoryItem::UserVal(UserVal {
|
||||||
@ -61,28 +160,19 @@ impl ProgramMemory {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
return_: None,
|
parent: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add to the program memory.
|
pub fn new(parent: EnvironmentRef) -> Self {
|
||||||
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
Self {
|
||||||
if self.root.contains_key(key) {
|
bindings: HashMap::new(),
|
||||||
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
parent: Some(parent),
|
||||||
message: format!("Cannot redefine `{}`", key),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.root.insert(key.to_string(), value);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a value from the program memory.
|
|
||||||
/// Return Err if not found.
|
|
||||||
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
||||||
self.root.get(key).ok_or_else(|| {
|
self.bindings.get(key).ok_or_else(|| {
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("memory item key `{}` is not defined", key),
|
message: format!("memory item key `{}` is not defined", key),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
@ -90,21 +180,12 @@ impl ProgramMemory {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find all extrude groups in the memory that are on a specific sketch group id.
|
pub fn insert(&mut self, key: String, value: MemoryItem) {
|
||||||
pub fn find_extrude_groups_on_sketch_group(&self, sketch_group_id: uuid::Uuid) -> Vec<Box<ExtrudeGroup>> {
|
self.bindings.insert(key, value);
|
||||||
self.root
|
|
||||||
.values()
|
|
||||||
.filter_map(|item| match item {
|
|
||||||
MemoryItem::ExtrudeGroup(eg) if eg.sketch_group.id == sketch_group_id => Some(eg.clone()),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ProgramMemory {
|
pub fn contains_key(&self, key: &str) -> bool {
|
||||||
fn default() -> Self {
|
self.bindings.contains_key(key)
|
||||||
Self::new()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +242,7 @@ pub enum MemoryItem {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
func: Option<MemoryFunction>,
|
func: Option<MemoryFunction>,
|
||||||
expression: Box<FunctionExpression>,
|
expression: Box<FunctionExpression>,
|
||||||
|
memory: Box<ProgramMemory>,
|
||||||
#[serde(rename = "__meta")]
|
#[serde(rename = "__meta")]
|
||||||
meta: Vec<Metadata>,
|
meta: Vec<Metadata>,
|
||||||
},
|
},
|
||||||
@ -620,7 +702,7 @@ impl MemoryItem {
|
|||||||
.map(Some)
|
.map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_user_val(&self) -> Option<&UserVal> {
|
pub fn as_user_val(&self) -> Option<&UserVal> {
|
||||||
if let MemoryItem::UserVal(x) = self {
|
if let MemoryItem::UserVal(x) = self {
|
||||||
Some(x)
|
Some(x)
|
||||||
} else {
|
} else {
|
||||||
@ -642,27 +724,21 @@ impl MemoryItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// If this value is of type function, return it.
|
/// If this value is of type function, return it.
|
||||||
pub fn get_function(&self, source_ranges: Vec<SourceRange>) -> Result<FnAsArg<'_>, KclError> {
|
pub fn get_function(&self) -> Option<FnAsArg<'_>> {
|
||||||
let MemoryItem::Function {
|
let MemoryItem::Function {
|
||||||
func,
|
func,
|
||||||
expression,
|
expression,
|
||||||
|
memory,
|
||||||
meta: _,
|
meta: _,
|
||||||
} = &self
|
} = &self
|
||||||
else {
|
else {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return None;
|
||||||
message: "not an in-memory function".to_string(),
|
|
||||||
source_ranges,
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
let func = func.as_ref().ok_or_else(|| {
|
let func = func.as_ref()?;
|
||||||
KclError::Semantic(KclErrorDetails {
|
Some(FnAsArg {
|
||||||
message: format!("Not an in-memory function: {:?}", expression),
|
|
||||||
source_ranges,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
Ok(FnAsArg {
|
|
||||||
func,
|
func,
|
||||||
expr: expression.to_owned(),
|
expr: expression.to_owned(),
|
||||||
|
memory: memory.to_owned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,10 +812,15 @@ impl MemoryItem {
|
|||||||
pub async fn call_fn(
|
pub async fn call_fn(
|
||||||
&self,
|
&self,
|
||||||
args: Vec<MemoryItem>,
|
args: Vec<MemoryItem>,
|
||||||
memory: ProgramMemory,
|
|
||||||
ctx: ExecutorContext,
|
ctx: ExecutorContext,
|
||||||
) -> Result<Option<ProgramReturn>, KclError> {
|
) -> Result<Option<ProgramReturn>, KclError> {
|
||||||
let MemoryItem::Function { func, expression, meta } = &self else {
|
let MemoryItem::Function {
|
||||||
|
func,
|
||||||
|
expression,
|
||||||
|
memory: closure_memory,
|
||||||
|
meta,
|
||||||
|
} = &self
|
||||||
|
else {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: "not a in memory function".to_string(),
|
message: "not a in memory function".to_string(),
|
||||||
source_ranges: vec![],
|
source_ranges: vec![],
|
||||||
@ -751,7 +832,14 @@ impl MemoryItem {
|
|||||||
source_ranges: vec![],
|
source_ranges: vec![],
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
func(args, memory, expression.clone(), meta.clone(), ctx).await
|
func(
|
||||||
|
args,
|
||||||
|
closure_memory.as_ref().clone(),
|
||||||
|
expression.clone(),
|
||||||
|
meta.clone(),
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1560,16 +1648,13 @@ impl ExecutorContext {
|
|||||||
memory.return_ = result.return_;
|
memory.return_ = result.return_;
|
||||||
}
|
}
|
||||||
FunctionKind::UserDefined => {
|
FunctionKind::UserDefined => {
|
||||||
if let Some(func) = memory.clone().root.get(&fn_name) {
|
// TODO: Why do we change the source range to
|
||||||
let result = func.call_fn(args.clone(), memory.clone(), self.clone()).await?;
|
// the call expression instead of keeping the
|
||||||
|
// range of the callee?
|
||||||
|
let func = memory.get(&fn_name, call_expr.into())?;
|
||||||
|
let result = func.call_fn(args.clone(), self.clone()).await?;
|
||||||
|
|
||||||
memory.return_ = result;
|
memory.return_ = result;
|
||||||
} else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("No such name {} defined", fn_name),
|
|
||||||
source_ranges: vec![call_expr.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1680,7 +1765,15 @@ impl ExecutorContext {
|
|||||||
_metadata: Vec<Metadata>,
|
_metadata: Vec<Metadata>,
|
||||||
ctx: ExecutorContext| {
|
ctx: ExecutorContext| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut fn_memory = assign_args_to_params(&function_expression, args, memory.clone())?;
|
// 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 closure_env = memory.current_env;
|
||||||
|
let body_env = body_memory.new_env_for_call(closure_env);
|
||||||
|
body_memory.current_env = body_env;
|
||||||
|
let mut fn_memory = assign_args_to_params(&function_expression, args, body_memory)?;
|
||||||
|
|
||||||
let result = ctx
|
let result = ctx
|
||||||
.inner_execute(&function_expression.body, &mut fn_memory, BodyType::Block)
|
.inner_execute(&function_expression.body, &mut fn_memory, BodyType::Block)
|
||||||
@ -1690,10 +1783,14 @@ impl ExecutorContext {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// 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.
|
||||||
MemoryItem::Function {
|
MemoryItem::Function {
|
||||||
expression: function_expression.clone(),
|
expression: function_expression.clone(),
|
||||||
meta: vec![metadata.to_owned()],
|
meta: vec![metadata.to_owned()],
|
||||||
func: Some(mem_func),
|
func: Some(mem_func),
|
||||||
|
memory: Box::new(memory.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, self).await?,
|
Value::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, self).await?,
|
||||||
@ -1796,7 +1893,8 @@ fn assign_args_to_params(
|
|||||||
return Err(err_wrong_number_args);
|
return Err(err_wrong_number_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the arguments to the memory.
|
// Add the arguments to the memory. A new call frame should have already
|
||||||
|
// been created.
|
||||||
for (index, param) in function_expression.params.iter().enumerate() {
|
for (index, param) in function_expression.params.iter().enumerate() {
|
||||||
if let Some(arg) = args.get(index) {
|
if let Some(arg) = args.get(index) {
|
||||||
// Argument was provided.
|
// Argument was provided.
|
||||||
@ -1862,11 +1960,19 @@ const newVar = myVar + 1"#;
|
|||||||
let memory = parse_execute(ast).await.unwrap();
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(5),
|
serde_json::json!(5),
|
||||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("myVar", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(6.0),
|
serde_json::json!(6.0),
|
||||||
memory.root.get("newVar").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("newVar", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1891,13 +1997,21 @@ const intersect = segEndX('yo2', part001)"#,
|
|||||||
let memory = parse_execute(&ast_fn("-1")).await.unwrap();
|
let memory = parse_execute(&ast_fn("-1")).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(1.0 + 2.0f64.sqrt()),
|
serde_json::json!(1.0 + 2.0f64.sqrt()),
|
||||||
memory.root.get("intersect").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("intersect", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let memory = parse_execute(&ast_fn("0")).await.unwrap();
|
let memory = parse_execute(&ast_fn("0")).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(1.0000000000000002),
|
serde_json::json!(1.0000000000000002),
|
||||||
memory.root.get("intersect").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("intersect", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2215,13 +2329,252 @@ const thisBox = box([[0,0], 6, 10, 3])
|
|||||||
parse_execute(ast).await.unwrap();
|
parse_execute(ast).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_function_cannot_access_future_definitions() {
|
||||||
|
let ast = r#"
|
||||||
|
fn returnX = () => {
|
||||||
|
// x shouldn't be defined yet.
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = 5
|
||||||
|
|
||||||
|
const answer = returnX()"#;
|
||||||
|
|
||||||
|
let result = parse_execute(ast).await;
|
||||||
|
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: "memory item key `x` is not defined".to_owned(),
|
||||||
|
source_ranges: vec![SourceRange([64, 65]), SourceRange([97, 106])],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_pattern_transform_function_cannot_access_future_definitions() {
|
||||||
|
let ast = r#"
|
||||||
|
fn transform = (replicaId) => {
|
||||||
|
// x shouldn't be defined yet.
|
||||||
|
let scale = x
|
||||||
|
return {
|
||||||
|
translate: [0, 0, replicaId * 10],
|
||||||
|
scale: [scale, 1, 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layer = () => {
|
||||||
|
return startSketchOn("XY")
|
||||||
|
|> circle([0, 0], 1, %, 'tag1')
|
||||||
|
|> extrude(10, %)
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = 5
|
||||||
|
|
||||||
|
// The 10 layers are replicas of each other, with a transform applied to each.
|
||||||
|
let shape = layer() |> patternTransform(10, transform, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = parse_execute(ast).await;
|
||||||
|
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: "memory item key `x` is not defined".to_owned(),
|
||||||
|
source_ranges: vec![SourceRange([80, 81])],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_function_with_parameter_redefined_outside() {
|
||||||
|
let ast = r#"
|
||||||
|
fn myIdentity = (x) => {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = 33
|
||||||
|
|
||||||
|
const two = myIdentity(2)"#;
|
||||||
|
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(2),
|
||||||
|
memory
|
||||||
|
.get("two", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(33),
|
||||||
|
memory
|
||||||
|
.get("x", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_function_referencing_variable_in_parent_scope() {
|
||||||
|
let ast = r#"
|
||||||
|
const x = 22
|
||||||
|
const y = 3
|
||||||
|
|
||||||
|
fn add = (x) => {
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
const answer = add(2)"#;
|
||||||
|
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(5.0),
|
||||||
|
memory
|
||||||
|
.get("answer", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(22),
|
||||||
|
memory
|
||||||
|
.get("x", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_function_redefining_variable_in_parent_scope() {
|
||||||
|
let ast = r#"
|
||||||
|
const x = 1
|
||||||
|
|
||||||
|
fn foo = () => {
|
||||||
|
const x = 2
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
const answer = foo()"#;
|
||||||
|
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(2),
|
||||||
|
memory
|
||||||
|
.get("answer", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(1),
|
||||||
|
memory
|
||||||
|
.get("x", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_pattern_transform_function_redefining_variable_in_parent_scope() {
|
||||||
|
let ast = r#"
|
||||||
|
const scale = 100
|
||||||
|
fn transform = (replicaId) => {
|
||||||
|
// Redefine same variable as in parent scope.
|
||||||
|
const scale = 2
|
||||||
|
return {
|
||||||
|
translate: [0, 0, replicaId * 10],
|
||||||
|
scale: [scale, 1, 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layer = () => {
|
||||||
|
return startSketchOn("XY")
|
||||||
|
|> circle([0, 0], 1, %, 'tag1')
|
||||||
|
|> extrude(10, %)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 10 layers are replicas of each other, with a transform applied to each.
|
||||||
|
let shape = layer() |> patternTransform(10, transform, %)"#;
|
||||||
|
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
// TODO: Assert that scale 2 was used.
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(100),
|
||||||
|
memory
|
||||||
|
.get("scale", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_ycombinator_is_even() {
|
||||||
|
let ast = r#"
|
||||||
|
// Heavily inspired by: https://raganwald.com/2018/09/10/why-y.html
|
||||||
|
fn why = (f) => {
|
||||||
|
fn inner = (maker) => {
|
||||||
|
fn inner2 = (x) => {
|
||||||
|
return f(maker(maker), x)
|
||||||
|
}
|
||||||
|
return inner2
|
||||||
|
}
|
||||||
|
|
||||||
|
return inner(
|
||||||
|
(maker) => {
|
||||||
|
fn inner2 = (x) => {
|
||||||
|
return f(maker(maker), x)
|
||||||
|
}
|
||||||
|
return inner2
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn innerIsEven = (self, n) => {
|
||||||
|
return !n || !self(n - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEven = why(innerIsEven)
|
||||||
|
|
||||||
|
const two = isEven(2)
|
||||||
|
const three = isEven(3)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(true),
|
||||||
|
memory
|
||||||
|
.get("two", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(false),
|
||||||
|
memory
|
||||||
|
.get("three", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_math_execute_with_functions() {
|
async fn test_math_execute_with_functions() {
|
||||||
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
||||||
let memory = parse_execute(ast).await.unwrap();
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(5.0),
|
serde_json::json!(5.0),
|
||||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("myVar", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2231,7 +2584,11 @@ const thisBox = box([[0,0], 6, 10, 3])
|
|||||||
let memory = parse_execute(ast).await.unwrap();
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(7.4),
|
serde_json::json!(7.4),
|
||||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("myVar", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2241,7 +2598,11 @@ const thisBox = box([[0,0], 6, 10, 3])
|
|||||||
let memory = parse_execute(ast).await.unwrap();
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(1.0),
|
serde_json::json!(1.0),
|
||||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("myVar", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2251,7 +2612,11 @@ const thisBox = box([[0,0], 6, 10, 3])
|
|||||||
let memory = parse_execute(ast).await.unwrap();
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(std::f64::consts::TAU),
|
serde_json::json!(std::f64::consts::TAU),
|
||||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("myVar", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2261,7 +2626,11 @@ const thisBox = box([[0,0], 6, 10, 3])
|
|||||||
let memory = parse_execute(ast).await.unwrap();
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::json!(7.4),
|
serde_json::json!(7.4),
|
||||||
memory.root.get("thing").unwrap().get_json_value().unwrap()
|
memory
|
||||||
|
.get("thing", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2391,7 +2760,9 @@ const bracket = startSketchOn('XY')
|
|||||||
fn additional_program_memory(items: &[(String, MemoryItem)]) -> ProgramMemory {
|
fn additional_program_memory(items: &[(String, MemoryItem)]) -> ProgramMemory {
|
||||||
let mut program_memory = ProgramMemory::new();
|
let mut program_memory = ProgramMemory::new();
|
||||||
for (name, item) in items {
|
for (name, item) in items {
|
||||||
program_memory.root.insert(name.to_string(), item.clone());
|
program_memory
|
||||||
|
.add(name.as_str(), item.clone(), SourceRange::default())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
program_memory
|
program_memory
|
||||||
}
|
}
|
||||||
|
@ -299,6 +299,7 @@ fn binary_operator(i: TokenSlice) -> PResult<BinaryOperator> {
|
|||||||
"*" => BinaryOperator::Mul,
|
"*" => BinaryOperator::Mul,
|
||||||
"%" => BinaryOperator::Mod,
|
"%" => BinaryOperator::Mod,
|
||||||
"^" => BinaryOperator::Pow,
|
"^" => BinaryOperator::Pow,
|
||||||
|
"||" => BinaryOperator::LogicalOr,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: token.as_source_ranges(),
|
source_ranges: token.as_source_ranges(),
|
||||||
@ -1136,11 +1137,11 @@ fn unary_expression(i: TokenSlice) -> PResult<UnaryExpression> {
|
|||||||
let (operator, op_token) = any
|
let (operator, op_token) = any
|
||||||
.try_map(|token: Token| match token.token_type {
|
.try_map(|token: Token| match token.token_type {
|
||||||
TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
|
TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
|
||||||
// TODO: negation. Original parser doesn't support `not` yet.
|
|
||||||
TokenType::Operator => Err(KclError::Syntax(KclErrorDetails {
|
TokenType::Operator => Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: token.as_source_ranges(),
|
source_ranges: token.as_source_ranges(),
|
||||||
message: format!("{EXPECTED} but found {} which is an operator, but not a unary one (unary operators apply to just a single operand, your operator applies to two or more operands)", token.value.as_str(),),
|
message: format!("{EXPECTED} but found {} which is an operator, but not a unary one (unary operators apply to just a single operand, your operator applies to two or more operands)", token.value.as_str(),),
|
||||||
})),
|
})),
|
||||||
|
TokenType::Bang => Ok((UnaryOperator::Not, token)),
|
||||||
other => Err(KclError::Syntax(KclErrorDetails { source_ranges: token.as_source_ranges(), message: format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) })),
|
other => Err(KclError::Syntax(KclErrorDetails { source_ranges: token.as_source_ranges(), message: format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) })),
|
||||||
})
|
})
|
||||||
.context(expected("a unary expression, e.g. -x or -3"))
|
.context(expected("a unary expression, e.g. -x or -3"))
|
||||||
|
@ -79,7 +79,7 @@ impl From<ParseError<&[Token], ContextError>> for KclError {
|
|||||||
// See https://github.com/KittyCAD/modeling-app/issues/784
|
// See https://github.com/KittyCAD/modeling-app/issues/784
|
||||||
KclError::Syntax(KclErrorDetails {
|
KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: bad_token.as_source_ranges(),
|
source_ranges: bad_token.as_source_ranges(),
|
||||||
message: "Unexpected token".to_string(),
|
message: format!("Unexpected token: {}", bad_token.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,7 @@ use crate::{
|
|||||||
ast::types::FunctionExpression,
|
ast::types::FunctionExpression,
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::KclError,
|
errors::KclError,
|
||||||
executor::{MemoryItem, SketchGroup, SketchSurface},
|
executor::{MemoryItem, ProgramMemory, SketchGroup, SketchSurface},
|
||||||
std::kcl_stdlib::KclStdLibFn,
|
std::kcl_stdlib::KclStdLibFn,
|
||||||
};
|
};
|
||||||
pub use args::Args;
|
pub use args::Args;
|
||||||
@ -281,6 +281,7 @@ pub enum Primitive {
|
|||||||
pub struct FnAsArg<'a> {
|
pub struct FnAsArg<'a> {
|
||||||
pub func: &'a crate::executor::MemoryFunction,
|
pub func: &'a crate::executor::MemoryFunction,
|
||||||
pub expr: Box<FunctionExpression>,
|
pub expr: Box<FunctionExpression>,
|
||||||
|
pub memory: Box<ProgramMemory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -87,7 +87,7 @@ pub async fn pattern_transform(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
fn_expr: transform.expr,
|
fn_expr: transform.expr,
|
||||||
meta: vec![args.source_range.into()],
|
meta: vec![args.source_range.into()],
|
||||||
ctx: args.ctx.clone(),
|
ctx: args.ctx.clone(),
|
||||||
memory: args.current_program_memory.clone(),
|
memory: *transform.memory,
|
||||||
},
|
},
|
||||||
extr,
|
extr,
|
||||||
&args,
|
&args,
|
||||||
|
@ -90,7 +90,7 @@ fn word(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
|
|
||||||
fn operator(i: &mut Located<&str>) -> PResult<Token> {
|
fn operator(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let (value, range) = alt((
|
let (value, range) = alt((
|
||||||
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "|", "^",
|
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "||", "|", "^",
|
||||||
))
|
))
|
||||||
.with_span()
|
.with_span()
|
||||||
.parse_next(i)?;
|
.parse_next(i)?;
|
||||||
|
@ -1308,7 +1308,7 @@ async fn serial_test_stdlib_kcl_error_right_code_path() {
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([157, 175])], message: "Expected a SketchGroup or SketchSurface as the third argument, found `[UserVal(UserVal { value: Array [Number(2), Number(2)], meta: [Metadata { source_range: SourceRange([164, 170]) }] }), UserVal(UserVal { value: Number(0.5), meta: [Metadata { source_range: SourceRange([172, 174]) }] })]`" }"#
|
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([157, 175])], message: "Expected an argument at index 2" }"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1406,7 +1406,7 @@ const part = rectShape([0, 0], 20, 20)
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([891, 940])], message: "Expected a [number, number] as the first argument, found `[UserVal(UserVal { value: String(\"XY\"), meta: [Metadata { source_range: SourceRange([898, 902]) }] }), UserVal(UserVal { value: Array [Number(-6.0), Number(6)], meta: [Metadata { source_range: SourceRange([904, 927]) }] }), UserVal(UserVal { value: Number(1), meta: [Metadata { source_range: SourceRange([760, 761]) }] })]`" }"#
|
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([891, 940])], message: "Argument at index 0 was supposed to be type [f64; 2] but wasn't" }"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1784,31 +1784,31 @@ const part002 = startSketchOn(part001, 'end')
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_plumbus_fillets() {
|
async fn serial_test_plumbus_fillets() {
|
||||||
let code = r#"fn make_circle = (ext, face, tag ,pos, radius) => {
|
let code = r#"fn make_circle = (ext, face, pos, radius) => {
|
||||||
const sg = startSketchOn(ext, face)
|
const sg = startSketchOn(ext, face)
|
||||||
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
||||||
|> arc({
|
|> arc({
|
||||||
angle_end: 360,
|
angle_end: 360,
|
||||||
angle_start: 0,
|
angle_start: 0,
|
||||||
radius: radius
|
radius: radius
|
||||||
}, %, tag)
|
}, %, $arc1)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
|
||||||
return sg
|
return sg
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pentagon = (len, taga, tagb, tagc) => {
|
fn pentagon = (len) => {
|
||||||
const sg = startSketchOn('XY')
|
const sg = startSketchOn('XY')
|
||||||
|> startProfileAt([-len / 2, -len / 2], %)
|
|> startProfileAt([-len / 2, -len / 2], %)
|
||||||
|> angledLine({ angle: 0, length: len }, %,taga)
|
|> angledLine({ angle: 0, length: len }, %, $a)
|
||||||
|> angledLine({
|
|> angledLine({
|
||||||
angle: segAng(a, %) + 180 - 108,
|
angle: segAng(a, %) + 180 - 108,
|
||||||
length: len
|
length: len
|
||||||
}, %, tagb)
|
}, %, $b)
|
||||||
|> angledLine({
|
|> angledLine({
|
||||||
angle: segAng(b, %) + 180 - 108,
|
angle: segAng(b, %) + 180 - 108,
|
||||||
length: len
|
length: len
|
||||||
}, %,tagc)
|
}, %, $c)
|
||||||
|> angledLine({
|
|> angledLine({
|
||||||
angle: segAng(c, %) + 180 - 108,
|
angle: segAng(c, %) + 180 - 108,
|
||||||
length: len
|
length: len
|
||||||
@ -1821,21 +1821,23 @@ fn pentagon = (len, taga, tagb, tagc) => {
|
|||||||
return sg
|
return sg
|
||||||
}
|
}
|
||||||
|
|
||||||
const p = pentagon(32, $a, $b, $c)
|
const p = pentagon(32)
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|
|
||||||
const plumbus0 = make_circle(p,a, $arc_a, [0, 0], 2.5)
|
const circle0 = make_circle(p, p.sketchGroup.tags.a, [0, 0], 2.5)
|
||||||
|
const plumbus0 = circle0
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|> fillet({
|
|> fillet({
|
||||||
radius: 0.5,
|
radius: 0.5,
|
||||||
tags: [arc_a, getOppositeEdge(arc_a, %)]
|
tags: [circle0.tags.arc1, getOppositeEdge(circle0.tags.arc1, %)]
|
||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
const plumbus1 = make_circle(p, b,$arc_b, [0, 0], 2.5)
|
const circle1 = make_circle(p, p.sketchGroup.tags.b, [0, 0], 2.5)
|
||||||
|
const plumbus1 = circle1
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|> fillet({
|
|> fillet({
|
||||||
radius: 0.5,
|
radius: 0.5,
|
||||||
tags: [arc_b, getOppositeEdge(arc_b, %)]
|
tags: [circle1.tags.arc1, getOppositeEdge(circle1.tags.arc1, %)]
|
||||||
}, %)
|
}, %)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
|
|||||||
|
|
||||||
// We need to get the sketch ID.
|
// We need to get the sketch ID.
|
||||||
// Get the sketch group ID from memory.
|
// Get the sketch group ID from memory.
|
||||||
let MemoryItem::SketchGroup(sketch_group) = memory.root.get(name).unwrap() else {
|
let MemoryItem::SketchGroup(sketch_group) = memory.get(name, SourceRange::default()).unwrap() else {
|
||||||
anyhow::bail!("part001 not found in memory: {:?}", memory);
|
anyhow::bail!("part001 not found in memory: {:?}", memory);
|
||||||
};
|
};
|
||||||
let sketch_id = sketch_group.id;
|
let sketch_id = sketch_group.id;
|
||||||
|
Reference in New Issue
Block a user