Compare commits

..

10 Commits

276 changed files with 104228 additions and 104722 deletions

1
.gitignore vendored
View File

@ -61,7 +61,6 @@ Mac_App_Distribution.provisionprofile
*.tsbuildinfo
src/wasm-lib/pkg
.eslintcache
venv
.vite/

File diff suppressed because one or more lines are too long

View File

@ -78970,7 +78970,7 @@
"model = import(\"tests/inputs/cube.gltf\")",
"model = import(\"tests/inputs/cube.sldprt\")",
"model = import(\"tests/inputs/cube.step\")",
"import height, buildSketch from \"common.kcl\"\n\nplane = 'XZ'\nmargin = 2\ns1 = buildSketch(plane, [0, 0])\ns2 = buildSketch(plane, [0, height() + margin])"
"import height, buildSketch from 'common.kcl'\n\nplane = 'XZ'\nmargin = 2\ns1 = buildSketch(plane, [0, 0])\ns2 = buildSketch(plane, [0, height() + margin])"
]
},
{

View File

@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
/* add the following code to the editor ($ error is not a valid line)
$ error
topAng = 30
bottomAng = 25
const topAng = 30
const bottomAng = 25
*/
await u.codeLocator.click()
await page.keyboard.type('$ error')
@ -474,14 +474,12 @@ test.describe('Editor tests', () => {
await page.keyboard.type('bottomAng = 25')
await page.keyboard.press('Enter')
// error in gutter
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(
page.getByText('Tag names must not be empty').first()
).toBeVisible()
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
// select the line that's causing the error and delete it
await page.getByText('$ error').click()

View File

@ -136,335 +136,6 @@ test(
}
)
test(
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
const errorDir = join(dir, 'broken-code')
await fsp.mkdir(errorDir, { recursive: true })
await fsp.copyFile(
executorInputPath('broken-code-test.kcl'),
join(errorDir, 'main.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('broken-code')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('opening broken code project should clear the scene and show the error', async () => {
// Go back home.
await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
const emptyDir = join(dir, 'empty')
await fsp.mkdir(emptyDir, { recursive: true })
await fsp.writeFile(join(emptyDir, 'main.kcl'), '')
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('empty')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('opening empty code project should clear the scene', async () => {
// Go back home.
await expect(page.getByText('empty')).toBeVisible()
await page.getByText('empty').click()
// Ensure the code is empty.
await expect(u.codeLocator).toContainText('')
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
// planes colors means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open empty file, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
await fsp.writeFile(join(bracketDir, 'empty.kcl'), '')
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('creating a empty file should clear the scene', async () => {
// open the file pane.
await page.getByTestId('files-pane-button').click()
// OPen the other file.
const file = page.getByRole('button', { name: 'empty.kcl' })
await expect(file).toBeVisible()
await file.click()
// planes colors means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
timeout: 10_000,
})
.toBeLessThan(15)
// Ensure the code is empty.
await expect(u.codeLocator).toContainText('')
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('broken-code-test.kcl'),
join(bracketDir, 'broken-code-test.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('opening broken code file should clear the scene and show the error', async () => {
// open the file pane.
await page.getByTestId('files-pane-button').click()
// OPen the other file.
const file = page.getByRole('button', { name: 'broken-code-test.kcl' })
await expect(file).toBeVisible()
await file.click()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'when code with error first loads you get errors in console',
{ tag: '@electron' },

View File

@ -158,7 +158,6 @@
"@electron/rebuild": "^3.6.0",
"@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1",
"@nabla/vite-plugin-eslint": "^2.0.5",
"@playwright/test": "^1.46.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2",
@ -171,7 +170,7 @@
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.1",
"@types/react-dom": "^18.2.25",
"@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39",
@ -208,6 +207,7 @@
"ts-node": "^10.0.0",
"typescript": "^5.7.2",
"vite": "^5.4.6",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",

View File

@ -3,7 +3,7 @@ import { useHotKeyListener } from './hooks/useHotKeyListener'
import { Stream } from './components/Stream'
import { AppHeader } from './components/AppHeader'
import { useHotkeys } from 'react-hotkeys-hook'
import { useLoaderData, useNavigate } from 'react-router-dom'
import { useLoaderData, useLocation, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
@ -22,7 +22,14 @@ import Gizmo from 'components/Gizmo'
import { CoreDumpManager } from 'lib/coredump'
import { UnitsMenu } from 'components/UnitsMenu'
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
import { homeDefaultStatusBarItems } from 'components/statusBar/homeDefaultStatusBarItems'
import { StatusBar } from 'components/StatusBar'
import { useModelStateStatus } from 'components/ModelStateIndicator'
import { useNetworkHealthStatus } from 'components/NetworkHealthIndicator'
import { useModelingContext } from 'hooks/useModelingContext'
import { xStateValueToString } from 'lib/xStateValueToString'
import { maybeWriteToDisk } from 'lib/telemetry'
import { useNetworkMachineStatus } from 'components/NetworkMachineIndicator'
maybeWriteToDisk()
.then(() => {})
.catch(() => {})
@ -31,11 +38,10 @@ export function App() {
const { project, file } = useLoaderData() as IndexLoaderData
useRefreshSettings(PATHS.FILE + 'SETTINGS')
const navigate = useNavigate()
const location = useLocation()
const filePath = useAbsoluteFilePath()
const { onProjectOpen } = useLspContext()
// We need the ref for the outermost div so we can screenshot the app for
// the coredump.
const ref = useRef<HTMLDivElement>(null)
const { state: modelingState, streamRef } = useModelingContext()
const projectName = project?.name || null
const projectPath = project?.path || null
@ -77,21 +83,44 @@ export function App() {
useEngineConnectionSubscriptions()
return (
<div className="relative h-full flex flex-col" ref={ref}>
<AppHeader
className={'transition-opacity transition-duration-75 ' + paneOpacity}
project={{ project, file }}
enableMenu={true}
<div className="h-screen flex flex-col overflow-hidden select-none">
<div className="relative flex flex-1 flex-col" ref={streamRef}>
<AppHeader
className={'transition-opacity transition-duration-75 ' + paneOpacity}
project={{ project, file }}
enableMenu={true}
/>
<ModalContainer />
<ModelingSidebar paneOpacity={paneOpacity} />
<Stream />
{/* <CamToggle /> */}
<LowerRightControls coreDumpManager={coreDumpManager}>
<UnitsMenu />
<Gizmo />
<CameraProjectionToggle />
</LowerRightControls>
</div>
<StatusBar
globalItems={[
useNetworkHealthStatus(),
useNetworkMachineStatus(),
...homeDefaultStatusBarItems({ coreDumpManager, location }),
]}
localItems={[
{
id: 'modeling-state',
element: 'text',
label:
modelingState.value instanceof Object
? xStateValueToString(modelingState.value) ?? ''
: modelingState.value,
toolTip: {
children: 'The current state of the modeler',
},
},
useModelStateStatus(),
]}
/>
<ModalContainer />
<ModelingSidebar paneOpacity={paneOpacity} />
<Stream />
{/* <CamToggle /> */}
<LowerRightControls coreDumpManager={coreDumpManager}>
<UnitsMenu />
<Gizmo />
<CameraProjectionToggle />
</LowerRightControls>
</div>
)
}

View File

@ -701,7 +701,8 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const sg = sketchFromKclValue(
kclManager.programMemory.get(variableDeclarationName),
@ -901,9 +902,10 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const startSketchOn = _node1.node?.declaration
const startSketchOnInit = startSketchOn?.init
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
@ -911,7 +913,7 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'),
]
startSketchOn.init = createPipeExpression([
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags),
])
@ -941,7 +943,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declaration.init
const sketchInit = _node.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -990,7 +992,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declaration.init
const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type !== 'PipeExpression') {
return
@ -1056,9 +1058,10 @@ export class SceneEntities {
if (trap(_node1)) return Promise.reject(_node1)
// startSketchOn already exists
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const startSketchOn = _node1.node?.declaration
const startSketchOnInit = startSketchOn?.init
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
@ -1066,7 +1069,7 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'),
]
startSketchOn.init = createPipeExpression([
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags),
])
@ -1096,7 +1099,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declaration.init
const sketchInit = _node.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -1152,7 +1155,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declaration.init
const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch(
@ -1221,11 +1224,12 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const startSketchOn = _node1.node?.declaration
const startSketchOnInit = startSketchOn?.init
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
startSketchOn.init = createPipeExpression([
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
createCallExpressionStdLib('circle', [
createObjectExpression({
@ -1267,7 +1271,7 @@ export class SceneEntities {
)
let modded = structuredClone(truncatedAst)
if (trap(_node)) return
const sketchInit = _node.node.declaration.init
const sketchInit = _node.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
@ -1335,7 +1339,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declaration.init
const sketchInit = _node.node?.declarations?.[0]?.init
let modded = structuredClone(_ast)
if (sketchInit.type === 'PipeExpression') {
@ -2056,7 +2060,7 @@ function prepareTruncatedMemoryAndAst(
'VariableDeclaration'
)
if (err(_node)) return _node
const variableDeclarationName = _node.node?.declaration.id?.name || ''
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
const sg = sketchFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
@ -2081,7 +2085,7 @@ function prepareTruncatedMemoryAndAst(
])
}
;(
(_ast.body[bodyIndex] as VariableDeclaration).declaration
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
.init as PipeExpression
).body.push(newSegment)
// update source ranges to section we just added.
@ -2092,19 +2096,19 @@ function prepareTruncatedMemoryAndAst(
const updatedSrcRangeAst = pResult.program
const lastPipeItem = (
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
.init as PipeExpression
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
.declarations[0].init as PipeExpression
).body.slice(-1)[0]
;(
(_ast.body[bodyIndex] as VariableDeclaration).declaration
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
.init as PipeExpression
).body.slice(-1)[0].start = lastPipeItem.start
_ast.end = lastPipeItem.end
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
varDec.end = lastPipeItem.end
const declarator = varDec.declaration
const declarator = varDec.declarations[0]
declarator.end = lastPipeItem.end
const init = declarator.init as Node<PipeExpression>
init.end = lastPipeItem.end
@ -2141,7 +2145,7 @@ function prepareTruncatedMemoryAndAst(
if (node.type !== 'VariableDeclaration') {
continue
}
const name = node.declaration.id.name
const name = node.declarations[0].id.name
const memoryItem = programMemory.get(name)
if (!memoryItem) {
continue

View File

@ -169,11 +169,11 @@ export function useCalc({
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declaration.id?.name === '__result__'
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init
resultDeclaration?.declarations?.[0]?.init
const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)

View File

@ -636,6 +636,16 @@ const CustomIconMap = {
/>
</svg>
),
loading: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.5001 6.25839C11.76 5.76392 10.89 5.5 10 5.5V4.5C11.0878 4.5 12.1512 4.82257 13.0556 5.42692C13.9601 6.03126 14.6651 6.89025 15.0813 7.89524C15.4976 8.90023 15.6065 10.0061 15.3943 11.073C15.1821 12.1399 14.6583 13.1199 13.8891 13.8891C13.1199 14.6583 12.1399 15.1821 11.073 15.3943C10.0061 15.6065 8.90023 15.4976 7.89524 15.0813C6.89025 14.6651 6.03126 13.9601 5.42692 13.0556C4.82257 12.1512 4.5 11.0878 4.5 10H5.5C5.5 10.89 5.76392 11.76 6.25839 12.5001C6.75285 13.2401 7.45566 13.8169 8.27792 14.1575C9.10019 14.4981 10.005 14.5872 10.8779 14.4135C11.7508 14.2399 12.5526 13.8113 13.182 13.182C13.8113 12.5526 14.2399 11.7508 14.4135 10.8779C14.5872 10.005 14.4981 9.10019 14.1575 8.27792C13.8169 7.45566 13.2401 6.75285 12.5001 6.25839Z"
fill="currentColor"
/>
</svg>
),
lockClosed: (
<svg
viewBox="0 0 20 20"

View File

@ -266,7 +266,6 @@ const FileTreeItem = ({
// Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null)
onFileOpen(fileOrDir.path, project?.path || null)
kclManager.switchedFiles = true
// Open kcl files
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)

View File

@ -1,141 +1,14 @@
import { APP_VERSION } from 'routes/Settings'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
import { PATHS } from 'lib/paths'
import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
import { HelpMenu } from './HelpMenu'
import { Link, useLocation } from 'react-router-dom'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { coreDump } from 'lang/wasm'
import toast from 'react-hot-toast'
import { CoreDumpManager } from 'lib/coredump'
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
import { ModelStateIndicator } from './ModelStateIndicator'
import { reportRejection } from 'lib/trap'
export function LowerRightControls({
children,
coreDumpManager,
}: {
children?: React.ReactNode
coreDumpManager?: CoreDumpManager
}) {
const location = useLocation()
const filePath = useAbsoluteFilePath()
const linkOverrideClassName =
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
function reportbug(event: {
preventDefault: () => void
stopPropagation: () => void
}) {
event?.preventDefault()
event?.stopPropagation()
if (!coreDumpManager) {
// open default reporting option
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
} else {
toast
.promise(
coreDump(coreDumpManager, true),
{
loading: 'Preparing bug report...',
success: 'Bug report opened in new window',
error: 'Unable to export a core dump. Using default reporting.',
},
{
success: {
// Note: this extended duration is especially important for Playwright e2e testing
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
duration: 6000,
},
}
)
.catch((err: Error) => {
if (err) {
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
}
})
}
}
return (
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
<section className="absolute bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
{children}
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
target="_blank"
rel="noopener noreferrer"
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
>
v{APP_VERSION}
</a>
<a
onClick={reportbug}
href="https://github.com/KittyCAD/modeling-app/issues/new/choose"
target="_blank"
rel="noopener noreferrer"
>
<CustomIcon
name="bug"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<Tooltip position="top" contentClassName="text-xs">
Report a bug
</Tooltip>
</a>
<Link
to={
location.pathname.includes(PATHS.FILE)
? filePath + PATHS.TELEMETRY + '?tab=project'
: PATHS.HOME + PATHS.TELEMETRY
}
data-testid="telemetry-link"
>
<CustomIcon
name="stopwatch"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<span className="sr-only">Telemetry</span>
<Tooltip position="top" contentClassName="text-xs">
Telemetry
</Tooltip>
</Link>
<Link
to={
location.pathname.includes(PATHS.FILE)
? filePath + PATHS.SETTINGS + '?tab=project'
: PATHS.HOME + PATHS.SETTINGS
}
data-testid="settings-link"
>
<CustomIcon
name="settings"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<span className="sr-only">Settings</span>
<Tooltip position="top" contentClassName="text-xs">
Settings
</Tooltip>
</Link>
<NetworkMachineIndicator className={linkOverrideClassName} />
{!location.pathname.startsWith(PATHS.HOME) && (
<NetworkHealthIndicator />
)}
<HelpMenu />
</menu>
</section>
)
}

View File

@ -1,6 +1,39 @@
import { useEngineCommands } from './EngineCommands'
import { Spinner } from './Spinner'
import { CustomIcon } from './CustomIcon'
import { StatusBarItemType } from './statusBar/statusBarTypes'
export const useModelStateStatus = (): StatusBarItemType => {
const [commands] = useEngineCommands()
const lastCommandType = commands[commands.length - 1]?.type
let icon: StatusBarItemType['icon'] = 'loading'
const baseDataTestId = 'model-state-indicator'
let dataTestId = baseDataTestId
if (lastCommandType === 'receive-reliable') {
icon = 'checkmark'
dataTestId = `${baseDataTestId}-receive-reliable`
} else if (lastCommandType === 'execution-done') {
icon = 'checkmark'
dataTestId = `${baseDataTestId}-execution-done`
} else if (lastCommandType === 'export-done') {
icon = 'checkmark'
dataTestId = `${baseDataTestId}-export-done`
}
return {
id: 'model-state-indicator',
label: '',
icon,
toolTip: {
children: 'Model state indicator',
},
element: 'button',
onClick: () => {},
'data-testid': dataTestId,
}
}
export const ModelStateIndicator = () => {
const [commands] = useEngineCommands()

View File

@ -99,6 +99,7 @@ type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<Actor<T>, 'send'>
streamRef: React.RefObject<HTMLDivElement>
}
export const ModelingMachineContext = createContext(
@ -1205,13 +1206,10 @@ export const ModelingMachineProvider = ({
state: modelingState,
context: modelingState.context,
send: modelingSend,
streamRef,
}}
>
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
since realistically it won't ever have generic children that isn't app.tsx */}
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
{children}
</div>
{children}
</ModelingMachineContext.Provider>
)
}

View File

@ -40,9 +40,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
<Menu.Item>
<button
onClick={() => {
kclManager.format().catch(reportRejection)
}}
onClick={() => kclManager.format()}
className={styles.button}
>
<span>Format code</span>

View File

@ -6,6 +6,7 @@ import { useNetworkContext } from '../hooks/useNetworkContext'
import { NetworkHealthState } from '../hooks/useNetworkStatus'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { StatusBarItemType } from './statusBar/statusBarTypes'
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
[NetworkHealthState.Ok]: 'Connected',
@ -64,14 +65,28 @@ const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
},
}
const overallConnectionStateIcon: Record<
NetworkHealthState,
ActionIconProps['icon']
> = {
const overallConnectionStateIcon = {
[NetworkHealthState.Ok]: 'network',
[NetworkHealthState.Weak]: 'network',
[NetworkHealthState.Issue]: 'networkCrossedOut',
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
} as const
export const useNetworkHealthStatus = (): StatusBarItemType => {
const { overallState } = useNetworkContext()
return {
id: 'network-health',
label: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
hideLabel: true,
element: 'popover',
className: overallConnectionStateColor[overallState].icon,
toolTip: {
children: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
},
icon: overallConnectionStateIcon[overallState],
popoverContent: <NetworkHealthPopoverContent />,
}
}
export const NetworkHealthIndicator = () => {
@ -109,81 +124,95 @@ export const NetworkHealthIndicator = () => {
Network health ({NETWORK_HEALTH_TEXT[overallState]})
</Tooltip>
</Popover.Button>
<Popover.Panel
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
data-testid="network-popover"
>
<div
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
>
<h2 className="text-sm font-sans font-normal">Network health</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{NETWORK_HEALTH_TEXT[overallState]}
</p>
</div>
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{Object.keys(steps).map((name) => (
<li
key={name}
className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}
>
<div className="flex items-center text-left gap-1">
<p className="flex-1">{name}</p>
{internetConnected ? (
<ActionIcon
size="lg"
icon={
hasIssueToIcon[
String(issues[name as ConnectingTypeGroup])
]
}
iconClassName={
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].icon
}
bgClassName={
'rounded-sm ' +
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].bg
}
/>
) : (
<ActionIcon
icon={hasIssueToIcon.true}
bgClassName={hasIssueToIconColors.true.bg}
iconClassName={hasIssueToIconColors.true.icon}
/>
)}
</div>
{issues[name as ConnectingTypeGroup] && (
<button
onClick={toSync(async () => {
await navigator.clipboard.writeText(
JSON.stringify(error, null, 2) || ''
)
setHasCopied(true)
setTimeout(() => setHasCopied(false), 5000)
}, reportRejection)}
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
>
{hasCopied ? 'Copied' : 'Copy Error'}
<ActionIcon
size="lg"
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
iconClassName="text-inherit dark:text-inherit"
bgClassName="!bg-transparent"
/>
</button>
)}
</li>
))}
</ul>
<Popover.Panel>
<NetworkHealthPopoverContent />
</Popover.Panel>
</Popover>
)
}
const NetworkHealthPopoverContent = () => {
const {
hasIssues,
overallState,
internetConnected,
steps,
issues,
error,
setHasCopied,
hasCopied,
} = useNetworkContext()
return (
<div
className="absolute left-2 bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
data-testid="network-popover"
>
<div
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
>
<h2 className="text-sm font-sans font-normal">Network health</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{NETWORK_HEALTH_TEXT[overallState]}
</p>
</div>
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{Object.keys(steps).map((name) => (
<li key={name} className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}>
<div className="flex items-center text-left gap-1">
<p className="flex-1">{name}</p>
{internetConnected ? (
<ActionIcon
size="lg"
icon={
hasIssueToIcon[String(issues[name as ConnectingTypeGroup])]
}
iconClassName={
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].icon
}
bgClassName={
'rounded-sm ' +
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].bg
}
/>
) : (
<ActionIcon
icon={hasIssueToIcon.true}
bgClassName={hasIssueToIconColors.true.bg}
iconClassName={hasIssueToIconColors.true.icon}
/>
)}
</div>
{issues[name as ConnectingTypeGroup] && (
<button
onClick={toSync(async () => {
await navigator.clipboard.writeText(
JSON.stringify(error, null, 2) || ''
)
setHasCopied(true)
setTimeout(() => setHasCopied(false), 5000)
}, reportRejection)}
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
>
{hasCopied ? 'Copied' : 'Copy Error'}
<ActionIcon
size="lg"
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
iconClassName="text-inherit dark:text-inherit"
bgClassName="!bg-transparent"
/>
</button>
)}
</li>
))}
</ul>
</div>
)
}

View File

@ -5,6 +5,7 @@ import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import { CustomIcon } from './CustomIcon'
import { StatusBarItemType } from './statusBar/statusBarTypes'
export const NetworkMachineIndicator = ({
className,
@ -27,12 +28,7 @@ export const NetworkMachineIndicator = ({
}
data-testid="network-machine-toggle"
>
<CustomIcon name="printer3d" className="w-5 h-5" />
{machineCount > 0 && (
<p aria-hidden className="flex items-center justify-center text-xs">
{machineCount}
</p>
)}
<NetworkMachinesIcon machineCount={machineCount} />
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
Network machines ({machineCount}) {reason && `: ${reason}`}
</Tooltip>
@ -41,50 +37,92 @@ export const NetworkMachineIndicator = ({
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
data-testid="network-popover"
>
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
<h2 className="text-sm font-sans font-normal">Network machines</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{machineCount}
</p>
</div>
{machineCount > 0 && (
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{machines.map(
(machine: components['schemas']['MachineInfoResponse']) => {
return (
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
<p className="">{machine.id.toUpperCase()}</p>
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{machine.make_model.model}
</p>
{machine.extra &&
machine.extra.type === 'bambu' &&
machine.extra.nozzle_diameter && (
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
Nozzle Diameter: {machine.extra.nozzle_diameter}
</p>
)}
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{`Status: ${machine.state.state
.charAt(0)
.toUpperCase()}${machine.state.state.slice(1)}`}
{machine.state.state === 'failed' && machine.state.message
? ` (${machine.state.message})`
: ''}
{machine.state.state === 'running' && machine.progress
? ` (${Math.round(machine.progress)}%)`
: ''}
</p>
</li>
)
}
)}
</ul>
)}
<NetworkMachinesPopoverContent machines={machines} />
</Popover.Panel>
</Popover>
) : null
}
export const useNetworkMachineStatus = (): StatusBarItemType => {
const {
noMachinesReason,
machines,
machines: { length: machineCount },
} = useContext(MachineManagerContext)
const reason = noMachinesReason()
return {
id: 'network-machines',
label: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
hideLabel: true,
element: 'popover',
toolTip: {
children: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
},
icon: 'printer3d',
popoverContent: <NetworkMachinesPopoverContent machines={machines} />,
}
}
function NetworkMachinesIcon({ machineCount }: { machineCount: number }) {
return (
<>
<CustomIcon name="printer3d" className="w-5 h-5" />
{machineCount > 0 && (
<p aria-hidden className="flex items-center justify-center text-xs">
{machineCount}
</p>
)}
</>
)
}
function NetworkMachinesPopoverContent({ machines }: { machines: components['schemas']['MachineInfoResponse'][] }) {
return (
<>
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
<h2 className="text-sm font-sans font-normal">Network machines</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{machines.length}
</p>
</div>
{machines.length > 0 && (
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{machines.map(
(machine: components['schemas']['MachineInfoResponse']) => {
return (
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
<p className="">{machine.id.toUpperCase()}</p>
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{machine.make_model.model}
</p>
{machine.extra &&
machine.extra.type === 'bambu' &&
machine.extra.nozzle_diameter && (
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
Nozzle Diameter: {machine.extra.nozzle_diameter}
</p>
)}
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{`Status: ${machine.state.state
.charAt(0)
.toUpperCase()}${machine.state.state.slice(1)}`}
{machine.state.state === 'failed' && machine.state.message
? ` (${machine.state.message})`
: ''}
{machine.state.state === 'running' && machine.progress
? ` (${Math.round(machine.progress)}%)`
: ''}
</p>
</li>
)
}
)}
</ul>
)}
</>
)
}

View File

@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { engineCommandManager } from 'lib/singletons'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
@ -68,7 +68,8 @@ function AppLogoLink({
data-testid="app-logo"
onClick={() => {
onProjectClose(file || null, project?.path || null, false)
kclManager.switchedFiles = true
// Clear the scene.
engineCommandManager.clearScene()
}}
to={PATHS.HOME}
className={wrapperClassName + ' hover:before:brightness-110'}
@ -189,7 +190,8 @@ function ProjectMenuPopover({
className: !isDesktop() ? 'hidden' : '',
onClick: () => {
onProjectClose(file || null, project?.path || null, true)
kclManager.switchedFiles = true
// Clear the scene.
engineCommandManager.clearScene()
},
},
].filter(

View File

@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
import { ActionButton } from 'components/ActionButton'
import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast'
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
import { APP_VERSION } from 'lib/appVersion'
import { PATHS } from 'lib/paths'
import {
createAndOpenNewTutorialProject,
@ -25,6 +25,7 @@ import { useLspContext } from 'components/LspProvider'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { PACKAGE_NAME } from 'routes/Settings'
interface AllSettingsFieldsProps {
searchParamTab: SettingsLevel

View File

@ -0,0 +1,148 @@
import { useEffect } from 'react'
import { ActionButton } from './ActionButton'
import { StatusBarItemType } from './statusBar/statusBarTypes'
import Tooltip, { TooltipProps } from './Tooltip'
import { ActionIcon } from './ActionIcon'
import { Popover } from '@headlessui/react'
export function StatusBar({
globalItems,
localItems,
}: {
globalItems: StatusBarItemType[]
localItems: StatusBarItemType[]
}) {
return (
<footer
id="statusbar"
className="relative z-10 flex justify-between items-center bg-chalkboard-20 dark:bg-chalkboard-90 text-chalkboard-80 dark:text-chalkboard-30 border-t border-t-chalkboard-30 dark:border-t-chalkboard-80"
>
<menu id="statusbar-globals" className="flex items-stretch">
{globalItems.map((item) => (
<StatusBarItem key={item.id} {...item} position="left" />
))}
</menu>
<menu id="statusbar-locals" className="flex items-stretch">
{localItems.map((item) => (
<StatusBarItem key={item.id} {...item} position="right" />
))}
</menu>
</footer>
)
}
function StatusBarItem(
props: StatusBarItemType & { position: 'left' | 'middle' | 'right' }
) {
const defaultClassNames = `px-2 py-1 text-xs text-chalkboard-80 dark:text-chalkboard-30 rounded-none border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-80 focus:bg-chalkboard-30 dark:focus:bg-chalkboard-80 hover:text-chalkboard-100 dark:hover:text-chalkboard-10 focustext-chalkboard-100 dark:focus:text-chalkboard-10 focus:outline-none focus-visible:ring-2 focus:ring-primary focus:ring-opacity-50`
const tooltipPosition: TooltipProps['position'] =
props.position === 'middle' ? 'top' : `top-${props.position}`
switch (props.element) {
case 'button':
return (
<ActionButton
Element="button"
iconStart={
props.icon && {
icon: props.icon,
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
bgClassName: 'bg-transparent dark:bg-transparent',
}
}
className={defaultClassNames + ' ' + props.className}
data-testid={props['data-testid']}
>
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip {...props.toolTip} position={tooltipPosition} />
)}
</ActionButton>
)
case 'popover':
return (
<Popover className="relative">
<Popover.Button
as={ActionButton}
Element="button"
iconStart={
props.icon && {
icon: props.icon,
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
bgClassName: 'bg-transparent dark:bg-transparent',
}
}
className={defaultClassNames + ' ' + props.className}
data-testid={props['data-testid']}
>
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip
{...props.toolTip}
wrapperClassName={`${
props.toolTip?.wrapperClassName || ''
} ui-open:hidden`}
position={tooltipPosition}
/>
)}
</Popover.Button>
<Popover.Panel>{props.popoverContent}</Popover.Panel>
</Popover>
)
case 'text':
return (
<div
role="tooltip"
className={defaultClassNames + ' ' + props.className}
>
{props.icon && (
<ActionIcon
icon={props.icon}
iconClassName={props.icon === 'loading' ? 'animate-spin' : ''}
bgClassName="bg-transparent dark:bg-transparent"
/>
)}
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip {...props.toolTip} position={tooltipPosition} />
)}
</div>
)
default:
return (
<ActionButton
Element={props.element}
to={props.href}
iconStart={
props.icon && {
icon: props.icon,
bgClassName: 'bg-transparent dark:bg-transparent',
}
}
className={defaultClassNames + ' ' + props.className}
data-testid={props['data-testid']}
>
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip {...props.toolTip} position={tooltipPosition} />
)}
</ActionButton>
)
}
}

View File

@ -8,7 +8,7 @@ type LeftOrRight = 'left' | 'right'
type Corner = `${TopOrBottom}-${LeftOrRight}`
type TooltipPosition = TopOrBottom | LeftOrRight | Corner
interface TooltipProps extends React.PropsWithChildren {
export interface TooltipProps extends React.PropsWithChildren {
position?: TooltipPosition
wrapperClassName?: string
contentClassName?: string

View File

@ -0,0 +1,96 @@
import openWindow from 'lib/openWindow'
import { StatusBarItemType } from './statusBarTypes'
import { reportRejection } from 'lib/trap'
import { CoreDumpManager } from 'lib/coredump'
import toast from 'react-hot-toast'
import { coreDump } from 'lang/wasm'
import { APP_VERSION } from 'lib/appVersion'
import { Location } from 'react-router-dom'
import { PATHS } from 'lib/paths'
export const homeDefaultStatusBarItems = ({
coreDumpManager,
location,
}: {
coreDumpManager?: CoreDumpManager
location: Location
}): StatusBarItemType[] => [
{
id: 'version',
element: 'externalLink',
label: `v${APP_VERSION}`,
href: `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`,
toolTip: {
children: 'View the release notes on GitHub',
},
},
{
id: 'report-bug',
element: 'button',
icon: 'bug',
label: 'Report a bug',
onClick: (event) => reportBug(event, { coreDumpManager }),
toolTip: {
children: 'Send your current app state to the developers for debugging',
},
},
{
id: 'settings',
element: 'link',
icon: 'settings',
href:
'.' +
PATHS.SETTINGS +
(location.pathname.includes(PATHS.FILE) ? '?tab=project' : ''),
'data-testid': 'settings-link',
label: 'Settings',
toolTip: {
children: 'Settings',
},
},
]
function reportBug(
event: {
preventDefault: () => void
stopPropagation: () => void
},
dependencies: {
coreDumpManager: CoreDumpManager | undefined
}
) {
event?.preventDefault()
event?.stopPropagation()
const { coreDumpManager } = dependencies
if (!coreDumpManager) {
// open default reporting option
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
} else {
toast
.promise(
coreDump(coreDumpManager, true),
{
loading: 'Preparing bug report...',
success: 'Bug report opened in new window',
error: 'Unable to export a core dump. Using default reporting.',
},
{
success: {
// Note: this extended duration is especially important for Playwright e2e testing
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
duration: 6000,
},
}
)
.catch((err: Error) => {
if (err) {
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
}
})
}
}

View File

@ -0,0 +1,28 @@
import { CustomIconName } from 'components/CustomIcon'
import { TooltipProps } from 'components/Tooltip'
export type StatusBarItemType = {
id: string
label: string
icon?: CustomIconName
hideLabel?: boolean
toolTip?: Omit<TooltipProps, 'position'>
className?: string
['data-testid']?: string
} & (
| {
element: 'button'
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
}
| {
element: 'popover'
popoverContent: React.ReactNode
}
| {
element: 'link' | 'externalLink'
href: string
}
| {
element: 'text'
}
)

View File

@ -12,7 +12,6 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import {
CallExpression,
clearSceneAndBustCache,
emptyExecState,
ExecState,
initPromise,
@ -61,7 +60,6 @@ export class KclManager {
private _executeIsStale: ExecuteArgs | null = null
private _wasmInitFailed = true
private _hasErrors = false
private _switchedFiles = false
engineCommandManager: EngineCommandManager
@ -81,10 +79,6 @@ export class KclManager {
this._astCallBack(ast)
}
set switchedFiles(switchedFiles: boolean) {
this._switchedFiles = switchedFiles
}
get programMemory() {
return this._programMemory
}
@ -172,12 +166,8 @@ export class KclManager {
this.engineCommandManager = engineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.ensureWasmInit().then(async () => {
await this.safeParse(codeManager.code).then((ast) => {
if (ast) {
this.ast = ast
}
})
this.ensureWasmInit().then(() => {
this.ast = this.safeParse(codeManager.code) || this.ast
})
}
@ -221,25 +211,7 @@ export class KclManager {
}
}
// (jess) I'm not in love with this, but it ensures we clear the scene and
// bust the cache on
// errors from parsing when opening new files.
// Why not just clear the cache on all parse errors, you ask? well its actually
// really nice to keep the cache on parse errors within the same file, and
// only bust on engine errors esp if they take a long time to execute and
// you hit the wrong key!
private async checkIfSwitchedFilesShouldClear() {
// If we were switching files and we hit an error on parse we need to bust
// the cache and clear the scene.
if (this._hasErrors && this._switchedFiles) {
await clearSceneAndBustCache(this.engineCommandManager)
} else if (this._switchedFiles) {
// Reset the switched files boolean.
this._switchedFiles = false
}
}
async safeParse(code: string): Promise<Node<Program> | null> {
safeParse(code: string): Node<Program> | null {
const result = parse(code)
this.diagnostics = []
this._hasErrors = false
@ -248,8 +220,6 @@ export class KclManager {
const kclerror: KCLError = result as KCLError
this.diagnostics = kclErrorsToDiagnostics([kclerror])
this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null
}
@ -258,7 +228,6 @@ export class KclManager {
if (result.errors.length > 0) {
this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null
}
@ -384,7 +353,7 @@ export class KclManager {
console.error(newCode)
return
}
const newAst = await this.safeParse(newCode)
const newAst = this.safeParse(newCode)
if (!newAst) {
this.clearAst()
return
@ -439,7 +408,7 @@ export class KclManager {
})
}
async executeCode(zoomToFit?: boolean): Promise<void> {
const ast = await this.safeParse(codeManager.code)
const ast = this.safeParse(codeManager.code)
if (!ast) {
this.clearAst()
return
@ -447,9 +416,9 @@ export class KclManager {
this.ast = { ...ast }
return this.executeAst({ zoomToFit })
}
async format() {
format() {
const originalCode = codeManager.code
const ast = await this.safeParse(originalCode)
const ast = this.safeParse(originalCode)
if (!ast) {
this.clearAst()
return
@ -489,7 +458,7 @@ export class KclManager {
const newCode = recast(ast)
if (err(newCode)) return Promise.reject(newCode)
const astWithUpdatedSource = await this.safeParse(newCode)
const astWithUpdatedSource = this.safeParse(newCode)
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
let returnVal: Selections | undefined = undefined

View File

@ -60,7 +60,8 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['params', 'FunctionExpression'],
[0, 'index'],
@ -95,12 +96,14 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'FunctionExpression'],
['body', 'FunctionExpression'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],

View File

@ -82,11 +82,11 @@ describe('Testing createVariableDeclaration', () => {
it('should create a variable declaration', () => {
const result = createVariableDeclaration('myVar', createLiteral(5))
expect(result.type).toBe('VariableDeclaration')
expect(result.declaration.type).toBe('VariableDeclarator')
expect(result.declaration.id.type).toBe('Identifier')
expect(result.declaration.id.name).toBe('myVar')
expect(result.declaration.init.type).toBe('Literal')
expect((result.declaration.init as any).value).toBe(5)
expect(result.declarations[0].type).toBe('VariableDeclarator')
expect(result.declarations[0].id.type).toBe('Identifier')
expect(result.declarations[0].id.name).toBe('myVar')
expect(result.declarations[0].init.type).toBe('Literal')
expect((result.declarations[0].init as any).value).toBe(5)
})
})
describe('Testing createPipeExpression', () => {

View File

@ -66,7 +66,8 @@ export function startSketchOnDefault(
let pathToNode: PathToNode = [
['body', ''],
[sketchIndex, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
]
@ -93,7 +94,7 @@ export function addStartProfileAt(
return new Error('variableDeclaration.init.type !== PipeExpression')
}
const _node = { ...node }
const init = variableDeclaration.declaration.init
const init = variableDeclaration.declarations[0].init
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([
createLiteral(roundOff(at[0])),
@ -104,7 +105,7 @@ export function addStartProfileAt(
if (init.type === 'PipeExpression') {
init.body.splice(1, 0, startProfileAt)
} else {
variableDeclaration.declaration.init = createPipeExpression([
variableDeclaration.declarations[0].init = createPipeExpression([
init,
startProfileAt,
])
@ -148,7 +149,8 @@ export function addSketchTo(
let pathToNode: PathToNode = [
['body', ''],
[sketchIndex, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
]
if (axis !== 'xy') {
@ -331,7 +333,8 @@ export function extrudeSketch(
const pathToExtrudeArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -361,7 +364,8 @@ export function loftSketches(
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -456,7 +460,8 @@ export function revolveSketch(
const pathToRevolveArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -542,7 +547,8 @@ export function sketchOnExtrudedFace(
const newpathToNode: PathToNode = [
['body', ''],
[expressionIndex + 1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
]
@ -579,7 +585,8 @@ export function addOffsetPlane({
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -816,15 +823,17 @@ export function createVariableDeclaration(
end: 0,
moduleId: 0,
declaration: {
type: 'VariableDeclarator',
start: 0,
end: 0,
moduleId: 0,
declarations: [
{
type: 'VariableDeclarator',
start: 0,
end: 0,
moduleId: 0,
id: createIdentifier(varName),
init,
},
id: createIdentifier(varName),
init,
},
],
visibility,
kind,
}
@ -1111,7 +1120,7 @@ export async function deleteFromSelection(
traverse(astClone, {
enter: (node, path) => {
if (node.type === 'VariableDeclaration') {
const dec = node.declaration
const dec = node.declarations[0]
if (
dec.init.type === 'CallExpression' &&
(dec.init.callee.name === 'extrude' ||
@ -1146,7 +1155,7 @@ export async function deleteFromSelection(
enter: (node, path) => {
;(async () => {
if (node.type === 'VariableDeclaration') {
currentVariableName = node.declaration.id.name
currentVariableName = node.declarations[0].id.name
}
if (
// match startSketchOn(${extrudeNameToDelete})

View File

@ -273,7 +273,7 @@ export function getPathToExtrudeForSegmentSelection(
'VariableDeclaration'
)
if (err(varDecNode)) return varDecNode
const sketchVar = varDecNode.node.declaration.id.name
const sketchVar = varDecNode.node.declarations[0].id.name
const sketch = sketchFromKclValue(
kclManager.programMemory.get(sketchVar),
@ -367,7 +367,7 @@ function locateExtrudeDeclarator(
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
const { node: extrudeVarDecl } = nodeOfExtrudeCall
const extrudeDeclarator = extrudeVarDecl.declaration
const extrudeDeclarator = extrudeVarDecl.declarations[0]
if (!extrudeDeclarator) {
return new Error('Extrude Declarator not found.')
}

View File

@ -230,7 +230,8 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([
['body', ''],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
@ -249,7 +250,8 @@ describe('testing getNodePathFromSourceRange', () => {
const expected = [
['body', ''],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[3, 'index'],
@ -291,7 +293,8 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([
['body', ''],
[1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['cond', 'IfExpression'],
['left', 'BinaryExpression'],
@ -321,7 +324,8 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([
['body', ''],
[1, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['then_val', 'IfExpression'],
['body', 'IfExpression'],
@ -349,8 +353,7 @@ describe('testing getNodePathFromSourceRange', () => {
expect(result).toEqual([
['body', ''],
[0, 'index'],
['selector', 'ImportStatement'],
['items', 'ImportSelector'],
['items', 'ImportStatement'],
[1, 'index'],
['name', 'ImportItem'],
])

View File

@ -259,26 +259,34 @@ function moreNodePathFromSourceRange(
return moreNodePathFromSourceRange(expression, sourceRange, path)
}
if (_node.type === 'VariableDeclaration' && isInRange) {
const declaration = _node.declaration
const declarations = _node.declarations
if (declaration.start <= start && declaration.end >= end) {
path.push(['declaration', 'VariableDeclaration'])
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) {
path.push(['declarations', 'VariableDeclaration'])
path.push([decIndex, 'index'])
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
}
}
}
if (_node.type === 'VariableDeclaration' && isInRange) {
const declaration = _node.declaration
const declarations = _node.declarations
if (declaration.start <= start && declaration.end >= end) {
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['declaration', 'VariableDeclaration'])
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) {
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['declarations', 'VariableDeclaration'])
path.push([decIndex, 'index'])
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
}
}
return path
@ -372,31 +380,24 @@ function moreNodePathFromSourceRange(
}
if (_node.type === 'ImportStatement' && isInRange) {
if (_node.selector && _node.selector.type === 'List') {
path.push(['selector', 'ImportStatement'])
const { items } = _node.selector
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.start <= start && item.end >= end) {
path.push(['items', 'ImportSelector'])
path.push([i, 'index'])
if (item.name.start <= start && item.name.end >= end) {
path.push(['name', 'ImportItem'])
return path
}
if (
item.alias &&
item.alias.start <= start &&
item.alias.end >= end
) {
path.push(['alias', 'ImportItem'])
return path
}
const { items } = _node
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.start <= start && item.end >= end) {
path.push(['items', 'ImportStatement'])
path.push([i, 'index'])
if (item.name.start <= start && item.name.end >= end) {
path.push(['name', 'ImportItem'])
return path
}
if (item.alias && item.alias.start <= start && item.alias.end >= end) {
path.push(['alias', 'ImportItem'])
return path
}
return path
}
return path
}
return path
}
console.error('not implemented: ' + node.type)
@ -450,10 +451,13 @@ export function traverse(
traverse(node, option, pathToNode)
if (_node.type === 'VariableDeclaration') {
_traverse(_node.declaration, [
...pathToNode,
['declaration', 'VariableDeclaration'],
])
_node.declarations.forEach((declaration, index) =>
_traverse(declaration, [
...pathToNode,
['declarations', 'VariableDeclaration'],
[index, 'index'],
])
)
} else if (_node.type === 'VariableDeclarator') {
_traverse(_node.init, [...pathToNode, ['init', '']])
} else if (_node.type === 'PipeExpression') {
@ -563,7 +567,7 @@ export function findAllPreviousVariablesPath(
const variables: PrevVariable<any>[] = []
bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
const varName = item.declaration.id.name
const varName = item.declarations[0].id.name
const varValue = programMemory?.get(varName)
if (!varValue || typeof varValue?.value !== type) return
variables.push({
@ -757,7 +761,7 @@ export function isLinesParallelAndConstrained(
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
if (err(_varDec)) return _varDec
const varDec = _varDec.node
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange(
@ -877,7 +881,7 @@ export function hasExtrudeSketch({
}
const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declaration.id.name
const varName = varDec.declarations[0].id.name
const varValue = programMemory?.get(varName)
return (
varValue?.type === 'Solid' ||

View File

@ -1879,6 +1879,17 @@ export class EngineCommandManager extends EventTarget {
}
return JSON.stringify(this.defaultPlanes)
}
clearScene(): void {
const deleteCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'scene_clear_all',
},
}
this.clearDefaultPlanes()
this.engineConnection?.send(deleteCmd)
}
addCommandLog(message: CommandLog) {
if (this.commandLogs.length > 500) {
this.commandLogs.shift()

View File

@ -164,7 +164,8 @@ mySketch001 = startSketchOn('XY')
pathToNode: [
['body', ''],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
})
@ -188,7 +189,8 @@ mySketch001 = startSketchOn('XY')
pathToNode: [
['body', ''],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
})

View File

@ -1701,7 +1701,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2
const varName = varDec.declaration.id.name
const varName = varDec.declarations[0].id.name
const sketch = sketchFromKclValue(
previousProgramMemory.get(varName),
varName

View File

@ -111,10 +111,12 @@ export function isSketchVariablesLinked(
let nextVarDec: VariableDeclarator | undefined
for (const node of ast.body) {
if (node.type !== 'VariableDeclaration') continue
if (node.declaration.id.name === secondArg.name) {
nextVarDec = node.declaration
break
}
const found = node.declarations.find(
({ id }) => id?.name === secondArg.name
)
if (!found) continue
nextVarDec = found
break
}
if (!nextVarDec) return false
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)

View File

@ -16,7 +16,6 @@ import init, {
parse_project_settings,
default_project_settings,
base64_decode,
clear_scene_and_bust_cache,
} from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
@ -699,21 +698,6 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
return default_app_settings()
}
export async function clearSceneAndBustCache(
engineCommandManager: EngineCommandManager
): Promise<null | Error> {
try {
await clear_scene_and_bust_cache(engineCommandManager)
} catch (e: any) {
console.error('clear_scene_and_bust_cache: error', e)
return Promise.reject(
new Error(`Error on clear_scene_and_bust_cache: ${e}`)
)
}
return null
}
export function parseAppSettings(
toml: string
): DeepPartial<Configuration> | Error {

12
src/lib/appVersion.ts Normal file
View File

@ -0,0 +1,12 @@
import { NODE_ENV } from 'env'
import { isDesktop } from './isDesktop'
import { isTestEnv } from './isTestEnv'
/** Version number of the app */
export const APP_VERSION =
isTestEnv && NODE_ENV === 'development'
? '11.22.33'
: isDesktop()
? // @ts-ignore
window.electron.packageJson.version
: 'main'

View File

@ -2,7 +2,7 @@ import { CommandLog, EngineCommandManager } from 'lang/std/engineConnection'
import { WebrtcStats } from 'wasm-lib/kcl/bindings/WebrtcStats'
import { OsInfo } from 'wasm-lib/kcl/bindings/OsInfo'
import { isDesktop } from 'lib/isDesktop'
import { APP_VERSION } from 'routes/Settings'
import { APP_VERSION } from 'lib/appVersion'
import { UAParser } from 'ua-parser-js'
import screenshot from 'lib/screenshot'
import { VITE_KC_API_BASE_URL } from 'env'

View File

@ -13,6 +13,7 @@ import {
listProjects,
readAppSettingsFile,
} from './desktop'
import { engineCommandManager } from './singletons'
export const isHidden = (fileOrDir: FileEntry) =>
!!fileOrDir.name?.startsWith('.')
@ -115,6 +116,9 @@ export async function createAndOpenNewTutorialProject({
) => void
navigate: (path: string) => void
}) {
// Clear the scene.
engineCommandManager.clearScene()
// Create a new project with the onboarding project name
const configuration = await readAppSettingsFile()
const projects = await listProjects(configuration)

View File

@ -3,27 +3,27 @@ export const bracket = `// Shelf Bracket
// Define constants
sigmaAllow = 35000 // psi (6061-T6 aluminum)
width = 6 // inch
p = 300 // Force on shelf - lbs
factorOfSafety = 1.2 // FOS of 1.2
shelfMountL = 5 // inches
wallMountL = 2 // inches
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
const sigmaAllow = 35000 // psi (6061-T6 aluminum)
const width = 6 // inch
const p = 300 // Force on shelf - lbs
const factorOfSafety = 1.2 // FOS of 1.2
const shelfMountL = 5 // inches
const wallMountL = 2 // inches
const shelfDepth = 12 // Shelf is 12 inches in depth from the wall
const moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
filletRadius = .375 // inches
extFilletRadius = .25 // inches
mountingHoleDiameter = 0.5 // inches
const filletRadius = .375 // inches
const extFilletRadius = .25 // inches
const mountingHoleDiameter = 0.5 // inches
// Calculate required thickness of bracket
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
const thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
// Sketch the bracket body and fillet the inner and outer edges of the bend
bracketLeg1Sketch = startSketchOn('XY')
const bracketLeg1Sketch = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([shelfMountL - filletRadius, 0], %, $fillet1)
|> line([0, width], %, $fillet2)
@ -47,7 +47,7 @@ bracketLeg1Sketch = startSketchOn('XY')
}, %), %)
// Extrude the leg 2 bracket sketch
bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|> fillet({
radius = extFilletRadius,
tags = [
@ -57,7 +57,7 @@ bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
}, %)
// Sketch the fillet arc
filletSketch = startSketchOn('XZ')
const filletSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([0, thickness], %)
|> arc({
@ -73,10 +73,10 @@ filletSketch = startSketchOn('XZ')
}, %)
// Sketch the bend
filletExtrude = extrude(-width, filletSketch)
const filletExtrude = extrude(-width, filletSketch)
// Create a custom plane for the leg that sits on the wall
customPlane = {
const customPlane = {
plane = {
origin = { x = -filletRadius, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 },
@ -86,7 +86,7 @@ customPlane = {
}
// Create a sketch for the second leg
bracketLeg2Sketch = startSketchOn(customPlane)
const bracketLeg2Sketch = startSketchOn(customPlane)
|> startProfileAt([0, -filletRadius], %)
|> line([width, 0], %)
|> line([0, -wallMountL], %, $fillet3)
@ -102,7 +102,7 @@ bracketLeg2Sketch = startSketchOn(customPlane)
}, %), %)
// Extrude the second leg
bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
const bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|> fillet({
radius = extFilletRadius,
tags = [
@ -135,8 +135,8 @@ function findLineInExampleCode({
}
export const bracketWidthConstantLine = findLineInExampleCode({
searchText: 'width =',
searchText: 'const width',
})
export const bracketThicknessCalculationLine = findLineInExampleCode({
searchText: 'thickness =',
searchText: 'const thickness',
})

4
src/lib/isTestEnv.ts Normal file
View File

@ -0,0 +1,4 @@
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
export const isTestEnv =
globalThis.window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'

View File

@ -5,7 +5,7 @@ import { isDesktop } from './isDesktop'
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { parseProjectSettings } from 'lang/wasm'
import { err, reportRejection } from './trap'
import { err } from './trap'
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
interface OnSubmitProps {
@ -28,7 +28,7 @@ export function kclCommands(
groupId: 'code',
icon: 'code',
onSubmit: () => {
kclManager.format().catch(reportRejection)
kclManager.format()
},
},
{

View File

@ -109,11 +109,11 @@ export function useCalculateKclExpression({
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declaration.id?.name === '__result__'
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init
resultDeclaration?.declarations?.[0]?.init
const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)

View File

@ -0,0 +1,22 @@
import { AnyStateMachine, StateFrom } from 'xstate'
/**
* Convert an XState state value to a pretty string,
* with nested states separated by slashes
*/
export function xStateValueToString(
stateValue: StateFrom<AnyStateMachine>['value']
) {
const sep = ' / '
let output = ''
let remainingValues = stateValue
let isFirstStep = true
while (remainingValues instanceof Object) {
const key: keyof typeof remainingValues = Object.keys(remainingValues)[0]
output += (isFirstStep ? '' : sep) + key
remainingValues = remainingValues[key]
isFirstStep = false
}
if (typeof remainingValues === 'string' && remainingValues.trim().length)
return output + sep + remainingValues.trim()
}

View File

@ -2491,7 +2491,7 @@ export function canRectangleOrCircleTool({
// This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on.
if (err(node)) return false
return node.node?.declaration.init.type !== 'PipeExpression'
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
}
/** If the sketch contains `close` or `circle` stdlib functions it must be closed */
@ -2508,8 +2508,8 @@ export function isClosedSketch({
// This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on.
if (err(node)) return false
if (node.node?.declaration.init.type !== 'PipeExpression') return false
return node.node.declaration.init.body.some(
if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false
return node.node.declarations[0].init.body.some(
(node) =>
node.type === 'CallExpression' &&
(node.callee.name === 'close' || node.callee.name === 'circle')

View File

@ -13,18 +13,6 @@ import { AllSettingsFields } from 'components/Settings/AllSettingsFields'
import { AllKeybindingsFields } from 'components/Settings/AllKeybindingsFields'
import { KeybindingsSectionsList } from 'components/Settings/KeybindingsSectionsList'
import { isDesktop } from 'lib/isDesktop'
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
import { NODE_ENV } from 'env'
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
export const APP_VERSION =
isTestEnv && NODE_ENV === 'development'
? '11.22.33'
: isDesktop()
? // @ts-ignore
window.electron.packageJson.version
: 'main'
export const PACKAGE_NAME = isDesktop()
? window.electron.packageJson.name

View File

@ -5,11 +5,11 @@ import { Themes, getSystemTheme } from '../lib/theme'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { APP_NAME } from 'lib/constants'
import { APP_VERSION } from 'lib/appVersion'
import { CSSProperties, useCallback, useState } from 'react'
import { Logo } from 'components/Logo'
import { CustomIcon } from 'components/CustomIcon'
import { Link } from 'react-router-dom'
import { APP_VERSION } from './Settings'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'

View File

@ -395,10 +395,10 @@ fn do_stdlib_inner(
#const_struct
fn #boxed_fn_name_ident(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>> + Send + '_>,
Box<dyn std::future::Future<Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>> + Send + '_>,
> {
Box::pin(#fn_name_ident(exec_state, args))
}
@ -770,12 +770,12 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
#[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() {
let program = crate::Program::parse_no_errs(#code_block).unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
@ -785,7 +785,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
async fn #test_name() {
let code = #code_block;
// Note, `crate` must be kcl_lib
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await.unwrap();
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm).await.unwrap();
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99);
}
}

View File

@ -3,7 +3,7 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -12,7 +12,7 @@ mod test_examples_someFn {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -22,13 +22,10 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() {
let code = "someFn()";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
&result,
@ -47,12 +44,12 @@ pub(crate) struct SomeFn {}
#[doc = "Std lib function: someFn\nDocs"]
pub(crate) const SomeFn: SomeFn = SomeFn {};
fn boxed_someFn(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -3,7 +3,7 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -12,7 +12,7 @@ mod test_examples_someFn {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -22,13 +22,10 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() {
let code = "someFn()";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
&result,
@ -47,12 +44,12 @@ pub(crate) struct SomeFn {}
#[doc = "Std lib function: someFn\nDocs"]
pub(crate) const SomeFn: SomeFn = SomeFn {};
fn boxed_someFn(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
let code = "This is another code block.\nyes sirrr.\nshow";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
@ -41,7 +38,7 @@ mod test_examples_show {
async fn test_mock_example_show1() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -50,7 +47,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -60,13 +57,10 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show1() {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
&result,
@ -85,12 +79,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {};
fn boxed_show(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
@ -48,12 +45,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {};
fn boxed_show(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -5,7 +5,7 @@ mod test_examples_my_func {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc")
.unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -14,7 +14,7 @@ mod test_examples_my_func {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -24,13 +24,10 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func0() {
let code = "This is another code block.\nyes sirrr.\nmyFunc";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
&result,
@ -42,7 +39,7 @@ mod test_examples_my_func {
async fn test_mock_example_my_func1() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -51,7 +48,7 @@ mod test_examples_my_func {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -61,13 +58,10 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func1() {
let code = "This is code.\nIt does other shit.\nmyFunc";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
&result,
@ -86,12 +80,12 @@ pub(crate) struct MyFunc {}
#[doc = "Std lib function: myFunc\nThis is some function.\nIt does shit."]
pub(crate) const MyFunc: MyFunc = MyFunc {};
fn boxed_my_func(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -5,7 +5,7 @@ mod test_examples_line_to {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nlineTo")
.unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -14,7 +14,7 @@ mod test_examples_line_to {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -24,13 +24,10 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_line_to0() {
let code = "This is another code block.\nyes sirrr.\nlineTo";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
&result,
@ -42,7 +39,7 @@ mod test_examples_line_to {
async fn test_mock_example_line_to1() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nlineTo").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -51,7 +48,7 @@ mod test_examples_line_to {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -61,13 +58,10 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_line_to1() {
let code = "This is code.\nIt does other shit.\nlineTo";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
&result,
@ -86,12 +80,12 @@ pub(crate) struct LineTo {}
#[doc = "Std lib function: lineTo\nThis is some function.\nIt does shit."]
pub(crate) const LineTo: LineTo = LineTo {};
fn boxed_line_to(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_min {
async fn test_mock_example_min0() {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmin").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_min {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_min0() {
let code = "This is another code block.\nyes sirrr.\nmin";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
&result,
@ -41,7 +38,7 @@ mod test_examples_min {
async fn test_mock_example_min1() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmin").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -50,7 +47,7 @@ mod test_examples_min {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -60,13 +57,10 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_min1() {
let code = "This is code.\nIt does other shit.\nmin";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
&result,
@ -85,12 +79,12 @@ pub(crate) struct Min {}
#[doc = "Std lib function: min\nThis is some function.\nIt does shit."]
pub(crate) const Min: Min = Min {};
fn boxed_min(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
@ -48,12 +45,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {};
fn boxed_show(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_import {
async fn test_mock_example_import0() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_import {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_import {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() {
let code = "This is code.\nIt does other shit.\nimport";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
&result,
@ -48,12 +45,12 @@ pub(crate) struct Import {}
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
pub(crate) const Import: Import = Import {};
fn boxed_import(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_import {
async fn test_mock_example_import0() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_import {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_import {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() {
let code = "This is code.\nIt does other shit.\nimport";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
&result,
@ -48,12 +45,12 @@ pub(crate) struct Import {}
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
pub(crate) const Import: Import = Import {};
fn boxed_import(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_import {
async fn test_mock_example_import0() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_import {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_import {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() {
let code = "This is code.\nIt does other shit.\nimport";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
&result,
@ -48,12 +45,12 @@ pub(crate) struct Import {}
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
pub(crate) const Import: Import = Import {};
fn boxed_import(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -4,7 +4,7 @@ mod test_examples_show {
async fn test_mock_example_show0() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,7 +13,7 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -23,13 +23,10 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
@ -48,12 +45,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {};
fn boxed_show(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -3,7 +3,7 @@ mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_some_function0() {
let program = crate::Program::parse_no_errs("someFunction()").unwrap();
let ctx = crate::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -12,7 +12,7 @@ mod test_examples_some_function {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
@ -22,13 +22,10 @@ mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_some_function0() {
let code = "someFunction()";
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_some_function0"),
&result,
@ -47,12 +44,12 @@ pub(crate) struct SomeFunction {}
#[doc = "Std lib function: someFunction\nDocs"]
pub(crate) const SomeFunction: SomeFunction = SomeFunction {};
fn boxed_some_function(
exec_state: &mut crate::ExecState,
exec_state: &mut crate::executor::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,

View File

@ -15,6 +15,7 @@ redo-kcl-stdlib-docs:
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
# Copy a test KCL file from executor tests into a new simulation test.
copy-exec-test-into-sim-test test_name:
mkdir -p kcl/tests/{{test_name}}

View File

@ -18,7 +18,7 @@ pub fn bench_execute(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
// Spawn a future onto the runtime
b.iter(|| {
rt.block_on(test_server::execute_and_snapshot(s, Mm, None)).unwrap();
rt.block_on(test_server::execute_and_snapshot(s, Mm)).unwrap();
});
});
group.finish();
@ -38,7 +38,7 @@ pub fn bench_lego(c: &mut Criterion) {
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
// Spawn a future onto the runtime
b.iter(|| {
rt.block_on(test_server::execute_and_snapshot(&code, Mm, None)).unwrap();
rt.block_on(test_server::execute_and_snapshot(&code, Mm)).unwrap();
});
});
}

View File

@ -3,7 +3,7 @@ use iai::black_box;
async fn execute_server_rack_heavy() {
let code = SERVER_RACK_HEAVY_PROGRAM;
black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
.await
.unwrap(),
);
@ -12,7 +12,7 @@ async fn execute_server_rack_heavy() {
async fn execute_server_rack_lite() {
let code = SERVER_RACK_LITE_PROGRAM;
black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
.await
.unwrap(),
);

View File

@ -0,0 +1,3 @@
pub mod cache;
pub mod modify;
pub mod types;

View File

@ -22,7 +22,7 @@ use super::ExecutionKind;
use crate::{
engine::EngineManager,
errors::{KclError, KclErrorDetails},
execution::{DefaultPlanes, IdGenerator},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};

View File

@ -20,7 +20,7 @@ use kittycad_modeling_cmds::{self as kcmc};
use super::ExecutionKind;
use crate::{
errors::KclError,
execution::{DefaultPlanes, IdGenerator},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};

View File

@ -11,7 +11,7 @@ use wasm_bindgen::prelude::*;
use crate::{
engine::ExecutionKind,
errors::{KclError, KclErrorDetails},
execution::{DefaultPlanes, IdGenerator},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};

View File

@ -32,7 +32,7 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{DefaultPlanes, IdGenerator, Point3d},
executor::{DefaultPlanes, IdGenerator, Point3d},
SourceRange,
};

View File

@ -1,6 +1,6 @@
//! The executor for the AST.
use std::{path::PathBuf, sync::Arc};
use std::{collections::HashSet, sync::Arc};
use anyhow::Result;
use async_recursion::async_recursion;
@ -20,18 +20,14 @@ use serde::{Deserialize, Serialize};
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
pub use function_param::FunctionParam;
pub use kcl_value::{KclObjectFields, KclValue};
pub use crate::kcl_value::KclValue;
use crate::{
engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem},
parsing::ast::{
cache::{get_changed_program, CacheInformation},
types::{
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode,
},
types::{BodyItem, Expr, FunctionExpression, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode},
},
settings::types::UnitLength,
source_range::{ModuleId, SourceRange},
@ -39,10 +35,6 @@ use crate::{
ExecError, Program,
};
mod exec_ast;
mod function_param;
mod kcl_value;
/// State for executing a program.
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
@ -58,7 +50,7 @@ pub struct ExecState {
/// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>,
/// Identifiers that have been exported from the current module.
pub module_exports: Vec<String>,
pub module_exports: HashSet<String>,
/// The stack of import statements for detecting circular module imports.
/// If this is empty, we're not currently executing an import statement.
pub import_stack: Vec<std::path::PathBuf>,
@ -69,7 +61,7 @@ pub struct ExecState {
}
impl ExecState {
fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId {
pub fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId {
// Need to avoid borrowing self in the closure.
let new_module_id = ModuleId::from_usize(self.path_to_source_id.len());
let mut is_new = false;
@ -1506,9 +1498,6 @@ pub struct ExecutorSettings {
/// Should engine store this for replay?
/// If so, under what name?
pub replay: Option<String>,
/// The directory of the current project. This is used for resolving import
/// paths. If None is given, the current working directory is used.
pub project_directory: Option<PathBuf>,
}
impl Default for ExecutorSettings {
@ -1519,7 +1508,6 @@ impl Default for ExecutorSettings {
enable_ssao: false,
show_grid: false,
replay: None,
project_directory: None,
}
}
}
@ -1532,7 +1520,6 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid,
replay: None,
project_directory: None,
}
}
}
@ -1545,7 +1532,6 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid,
replay: None,
project_directory: None,
}
}
}
@ -1558,7 +1544,6 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
enable_ssao: modeling.enable_ssao.into(),
show_grid: modeling.show_scale_grid,
replay: None,
project_directory: None,
}
}
}
@ -1789,7 +1774,6 @@ impl ExecutorContext {
enable_ssao: false,
show_grid: false,
replay: None,
project_directory: None,
},
None,
engine_addr,
@ -1801,7 +1785,7 @@ impl ExecutorContext {
pub async fn reset_scene(
&self,
exec_state: &mut ExecState,
source_range: crate::execution::SourceRange,
source_range: crate::executor::SourceRange,
) -> Result<(), KclError> {
self.engine
.clear_scene(&mut exec_state.id_generator, source_range)
@ -1866,7 +1850,7 @@ impl ExecutorContext {
)
.await?;
self.inner_execute(&cache_result.program, exec_state, crate::execution::BodyType::Root)
self.inner_execute(&cache_result.program, exec_state, crate::executor::BodyType::Root)
.await?;
let session_data = self.engine.get_session_data();
Ok(session_data)
@ -1886,66 +1870,91 @@ impl ExecutorContext {
match statement {
BodyItem::ImportStatement(import_stmt) => {
let source_range = SourceRange::from(import_stmt);
let (module_memory, module_exports) =
self.open_module(&import_stmt.path, exec_state, source_range).await?;
let path = import_stmt.path.clone();
// Empty path is used by the top-level module.
if path.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
message: "import path cannot be empty".to_owned(),
source_ranges: vec![source_range],
}));
}
let resolved_path = std::path::PathBuf::from(&path);
if exec_state.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
resolved_path.to_string_lossy()
),
source_ranges: vec![import_stmt.into()],
}));
}
let module_id = exec_state.add_module(resolved_path.clone());
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
// TODO handle parsing errors properly
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
let (module_memory, module_exports) = {
exec_state.import_stack.push(resolved_path.clone());
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
let original_memory = std::mem::take(&mut exec_state.memory);
let original_exports = std::mem::take(&mut exec_state.module_exports);
let result = self
.inner_execute(&program, exec_state, crate::executor::BodyType::Root)
.await;
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
self.engine.replace_execution_kind(original_execution);
exec_state.import_stack.pop();
match &import_stmt.selector {
ImportSelector::List { items } => {
for import_item in items {
// Extract the item from the module.
let item =
module_memory
.get(&import_item.name.name, import_item.into())
.map_err(|_err| {
KclError::UndefinedValue(KclErrorDetails {
message: format!("{} is not defined in module", import_item.name.name),
source_ranges: vec![SourceRange::from(&import_item.name)],
})
})?;
// Check that the item is allowed to be imported.
if !module_exports.contains(&import_item.name.name) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
import_item.name.name
),
source_ranges: vec![SourceRange::from(&import_item.name)],
}));
}
// Add the item to the current module.
exec_state.memory.add(
import_item.identifier(),
item.clone(),
SourceRange::from(&import_item.name),
)?;
if let ItemVisibility::Export = import_stmt.visibility {
exec_state.module_exports.push(import_item.identifier().to_owned());
}
result.map_err(|err| {
if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
message: format!(
"Error loading imported file. Open it to view more details. {path}: {}",
err.message()
),
source_ranges: vec![source_range],
})
}
}
ImportSelector::Glob(_) => {
for name in module_exports.iter() {
let item = module_memory.get(name, source_range).map_err(|_err| {
KclError::Internal(KclErrorDetails {
message: format!("{} is not defined in module (but was exported?)", name),
source_ranges: vec![source_range],
})
})?;
exec_state.memory.add(name, item.clone(), source_range)?;
})?;
if let ItemVisibility::Export = import_stmt.visibility {
exec_state.module_exports.push(name.clone());
}
}
}
ImportSelector::None(_) => {
(module_memory, module_exports)
};
for import_item in &import_stmt.items {
// Extract the item from the module.
let item = module_memory
.get(&import_item.name.name, import_item.into())
.map_err(|_err| {
KclError::UndefinedValue(KclErrorDetails {
message: format!("{} is not defined in module", import_item.name.name),
source_ranges: vec![SourceRange::from(&import_item.name)],
})
})?;
// Check that the item is allowed to be imported.
if !module_exports.contains(&import_item.name.name) {
return Err(KclError::Semantic(KclErrorDetails {
message: "Importing whole module is not yet implemented, sorry.".to_owned(),
source_ranges: vec![source_range],
message: format!(
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
import_item.name.name
),
source_ranges: vec![SourceRange::from(&import_item.name)],
}));
}
// Add the item to the current module.
exec_state.memory.add(
import_item.identifier(),
item.clone(),
SourceRange::from(&import_item.name),
)?;
}
last_expr = None;
}
@ -1962,23 +1971,34 @@ impl ExecutorContext {
);
}
BodyItem::VariableDeclaration(variable_declaration) => {
let var_name = variable_declaration.declaration.id.name.to_string();
let source_range = SourceRange::from(&variable_declaration.declaration.init);
let metadata = Metadata { source_range };
for declaration in &variable_declaration.declarations {
let var_name = declaration.id.name.to_string();
let source_range = SourceRange::from(&declaration.init);
let metadata = Metadata { source_range };
let memory_item = self
.execute_expr(
&variable_declaration.declaration.init,
exec_state,
&metadata,
StatementKind::Declaration { name: &var_name },
)
.await?;
exec_state.memory.add(&var_name, memory_item, source_range)?;
// Track exports.
if let ItemVisibility::Export = variable_declaration.visibility {
exec_state.module_exports.push(var_name);
let memory_item = self
.execute_expr(
&declaration.init,
exec_state,
&metadata,
StatementKind::Declaration { name: &var_name },
)
.await?;
let is_function = memory_item.is_function();
exec_state.memory.add(&var_name, memory_item, source_range)?;
// Track exports.
match variable_declaration.visibility {
ItemVisibility::Export => {
if !is_function {
return Err(KclError::Semantic(KclErrorDetails {
message: "Only functions can be exported".to_owned(),
source_ranges: vec![source_range],
}));
}
exec_state.module_exports.insert(var_name);
}
ItemVisibility::Default => {}
}
}
last_expr = None;
}
@ -2013,68 +2033,6 @@ impl ExecutorContext {
Ok(last_expr)
}
async fn open_module(
&self,
path: &str,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<(ProgramMemory, Vec<String>), KclError> {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(path)
} else {
std::path::PathBuf::from(&path)
};
if exec_state.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
resolved_path.to_string_lossy()
),
source_ranges: vec![source_range],
}));
}
let module_id = exec_state.add_module(resolved_path.clone());
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
// TODO handle parsing errors properly
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
exec_state.import_stack.push(resolved_path.clone());
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
let original_memory = std::mem::take(&mut exec_state.memory);
let original_exports = std::mem::take(&mut exec_state.module_exports);
let result = self
.inner_execute(&program, exec_state, crate::execution::BodyType::Root)
.await;
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
self.engine.replace_execution_kind(original_execution);
exec_state.import_stack.pop();
result.map_err(|err| {
if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
message: format!(
"Error loading imported file. Open it to view more details. {path}: {}",
err.message()
),
source_ranges: vec![source_range],
})
}
})?;
Ok((module_memory, module_exports))
}
pub async fn execute_expr<'a>(
&self,
init: &Expr,
@ -2162,7 +2120,7 @@ impl ExecutorContext {
self.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::execution::SourceRange::default(),
crate::executor::SourceRange::default(),
ModelingCmd::from(mcmd::ZoomToFit {
object_ids: Default::default(),
animated: false,
@ -2176,7 +2134,7 @@ impl ExecutorContext {
.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::execution::SourceRange::default(),
crate::executor::SourceRange::default(),
ModelingCmd::from(mcmd::TakeSnapshot {
format: ImageFormat::Png,
}),

View File

@ -2,7 +2,7 @@ use schemars::JsonSchema;
use crate::{
errors::KclError,
execution::{
executor::{
call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
},
parsing::ast::types::FunctionExpression,

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::KclErrorDetails,
exec::{ProgramMemory, Sketch},
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
std::{args::Arg, FnAsArg},
ExecState, ExecutorContext, KclError, SourceRange,
@ -262,6 +262,9 @@ impl KclValue {
}
}
pub(crate) fn is_function(&self) -> bool {
matches!(self, KclValue::Function { .. })
}
/// Put the number into a KCL value.
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Self::Number { value: f, meta }
@ -493,7 +496,7 @@ impl KclValue {
)
.await
} else {
crate::execution::call_user_defined_function(
crate::executor::call_user_defined_function(
args,
closure_memory.as_ref(),
expression.as_ref(),

View File

@ -60,8 +60,10 @@ mod coredump;
mod docs;
mod engine;
mod errors;
mod execution;
mod executor;
mod fs;
mod function_param;
mod kcl_value;
pub mod lint;
mod log;
mod lsp;
@ -82,7 +84,7 @@ mod wasm;
pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind};
pub use errors::{CompilationError, ConnectionError, ExecError, KclError};
pub use execution::{ExecState, ExecutorContext, ExecutorSettings};
pub use executor::{ExecState, ExecutorContext, ExecutorSettings};
pub use lsp::{
copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
@ -98,7 +100,7 @@ pub use source_range::{ModuleId, SourceRange};
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
// Ideally we wouldn't export these things at all, they should only be used for testing.
pub mod exec {
pub use crate::execution::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch};
pub use crate::executor::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch};
}
#[cfg(target_arch = "wasm32")]

View File

@ -60,7 +60,11 @@ pub fn lint_variables(decl: Node) -> Result<Vec<Discovered>> {
return Ok(vec![]);
};
lint_lower_camel_case_var(&decl.declaration)
Ok(decl
.declarations
.iter()
.flat_map(|v| lint_lower_camel_case_var(v).unwrap_or_default())
.collect())
}
pub fn lint_object_properties(decl: Node) -> Result<Vec<Discovered>> {

View File

@ -19,7 +19,7 @@ impl Notification for AstUpdated {
pub enum MemoryUpdated {}
impl Notification for MemoryUpdated {
type Params = crate::execution::ProgramMemory;
type Params = crate::executor::ProgramMemory;
const METHOD: &'static str = "kcl/memoryUpdated";
}

View File

@ -115,7 +115,7 @@ pub struct Backend {
/// information.
pub last_successful_ast_state: Arc<RwLock<Option<OldAstState>>>,
/// Memory maps.
pub memory_map: DashMap<String, crate::execution::ProgramMemory>,
pub memory_map: DashMap<String, crate::executor::ProgramMemory>,
/// Current code.
pub code_map: DashMap<String, Vec<u8>>,
/// Diagnostics.
@ -129,7 +129,7 @@ pub struct Backend {
/// If we can send telemetry for this user.
pub can_send_telemetry: bool,
/// Optional executor context to use if we want to execute the code.
pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
pub executor_ctx: Arc<RwLock<Option<crate::executor::ExecutorContext>>>,
/// If we are currently allowed to execute the ast.
pub can_execute: Arc<RwLock<bool>>,
@ -140,7 +140,7 @@ impl Backend {
#[cfg(target_arch = "wasm32")]
pub fn new_wasm(
client: Client,
executor_ctx: Option<crate::execution::ExecutorContext>,
executor_ctx: Option<crate::executor::ExecutorContext>,
fs: crate::fs::wasm::FileSystemManager,
zoo_client: kittycad::Client,
can_send_telemetry: bool,
@ -157,7 +157,7 @@ impl Backend {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(
client: Client,
executor_ctx: Option<crate::execution::ExecutorContext>,
executor_ctx: Option<crate::executor::ExecutorContext>,
zoo_client: kittycad::Client,
can_send_telemetry: bool,
) -> Result<Self, String> {
@ -172,7 +172,7 @@ impl Backend {
fn with_file_manager(
client: Client,
executor_ctx: Option<crate::execution::ExecutorContext>,
executor_ctx: Option<crate::executor::ExecutorContext>,
fs: crate::fs::FileManager,
zoo_client: kittycad::Client,
can_send_telemetry: bool,
@ -297,7 +297,7 @@ impl crate::lsp::backend::Backend for Backend {
// Try to get the memory for the current code.
let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
*memory != crate::execution::ProgramMemory::default()
*memory != crate::executor::ProgramMemory::default()
} else {
false
};
@ -406,7 +406,7 @@ impl Backend {
*self.can_execute.read().await
}
pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::executor::ExecutorContext>> {
self.executor_ctx.read().await
}
@ -871,7 +871,7 @@ impl Backend {
// Try to get the memory for the current code.
let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
*memory != crate::execution::ProgramMemory::default()
*memory != crate::executor::ProgramMemory::default()
} else {
false
};

View File

@ -9,10 +9,10 @@ pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?;
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?;
let zoo_client = crate::execution::new_zoo_client(None, None)?;
let zoo_client = crate::executor::new_zoo_client(None, None)?;
let executor_ctx = if execute {
Some(crate::execution::ExecutorContext::new(&zoo_client, Default::default()).await?)
Some(crate::executor::ExecutorContext::new(&zoo_client, Default::default()).await?)
} else {
None
};

View File

@ -7,7 +7,7 @@ use tower_lsp::{
};
use crate::{
execution::ProgramMemory,
executor::ProgramMemory,
lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
parsing::ast::types::{Node, Program},
};

View File

@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
execution::ExecState,
executor::ExecState,
parsing::ast::types::{Node, Program},
};
@ -27,7 +27,7 @@ pub struct OldAstState {
/// The exec state.
pub exec_state: ExecState,
/// The last settings used for execution.
pub settings: crate::execution::ExecutorSettings,
pub settings: crate::executor::ExecutorSettings,
}
impl From<crate::Program> for CacheInformation {
@ -55,7 +55,7 @@ pub struct CacheResult {
// the cache.
pub fn get_changed_program(
info: CacheInformation,
new_settings: &crate::execution::ExecutorSettings,
new_settings: &crate::executor::ExecutorSettings,
) -> Option<CacheResult> {
let Some(old) = info.old else {
// We have no old info, we need to re-execute the whole thing.
@ -109,14 +109,14 @@ mod tests {
use super::*;
async fn execute(program: &crate::Program) -> Result<ExecState> {
let ctx = crate::execution::ExecutorContext {
let ctx = crate::executor::ExecutorContext {
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
fs: Arc::new(crate::fs::FileManager::new()),
stdlib: Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
context_type: crate::executor::ContextType::Mock,
};
let mut exec_state = crate::execution::ExecState::default();
let mut exec_state = crate::executor::ExecState::default();
ctx.run(program.clone().into(), &mut exec_state).await?;
Ok(exec_state)

View File

@ -4,10 +4,9 @@ use super::types::{DefaultParamVal, ItemVisibility, VariableKind};
use crate::parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression,
ImportItem, ImportSelector, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject,
NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
PipeSubstitution, Program, ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration,
VariableDeclarator,
ImportItem, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode,
NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program,
ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
};
/// Position-independent digest of the AST node.
@ -53,20 +52,9 @@ impl ImportItem {
impl ImportStatement {
compute_digest!(|slf, hasher| {
match &mut slf.selector {
ImportSelector::List { items } => {
for item in items {
hasher.update(item.compute_digest());
}
}
ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"),
ImportSelector::None(None) => hasher.update(b"ImportSelector::None"),
ImportSelector::None(Some(alias)) => {
hasher.update(b"ImportSelector::None");
hasher.update(alias.compute_digest());
}
for item in &mut slf.items {
hasher.update(item.compute_digest());
}
hasher.update(slf.visibility.digestable_id());
let path = slf.path.as_bytes();
hasher.update(path.len().to_ne_bytes());
hasher.update(path);
@ -282,7 +270,10 @@ impl ExpressionStatement {
impl VariableDeclaration {
compute_digest!(|slf, hasher| {
hasher.update(slf.declaration.compute_digest());
hasher.update(slf.declarations.len().to_ne_bytes());
for declarator in &mut slf.declarations {
hasher.update(declarator.compute_digest());
}
hasher.update(slf.visibility.digestable_id());
hasher.update(slf.kind.digestable_id());
});

View File

@ -4,19 +4,14 @@ use async_recursion::async_recursion;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
},
executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier},
parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
CallExpressionKw, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
},
source_range::SourceRange,
std::{
args::{Arg, KwArgs},
FunctionKind,
},
std::{args::Arg, FunctionKind},
};
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
@ -391,14 +386,7 @@ impl Node<CallExpressionKw> {
None
};
let args = crate::std::Args::new_kw(
KwArgs {
unlabeled,
labeled: fn_args,
},
self.into(),
ctx.clone(),
);
let args = crate::std::Args::new_kw(fn_args, unlabeled, self.into(), ctx.clone());
match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => {
// Attempt to call the function.
@ -819,10 +807,3 @@ impl Property {
}
}
}
impl Node<PipeExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
}
}

View File

@ -1,76 +1,6 @@
pub(crate) mod cache;
pub(crate) mod digest;
pub(crate) mod execute;
pub mod modify;
pub(crate) mod source_range;
pub mod types;
use crate::{
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
source_range::ModuleId,
};
impl BodyItem {
pub fn module_id(&self) -> ModuleId {
match self {
BodyItem::ImportStatement(stmt) => stmt.module_id,
BodyItem::ExpressionStatement(expression_statement) => expression_statement.module_id,
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.module_id,
BodyItem::ReturnStatement(return_statement) => return_statement.module_id,
}
}
}
impl Expr {
pub fn module_id(&self) -> ModuleId {
match self {
Expr::Literal(literal) => literal.module_id,
Expr::Identifier(identifier) => identifier.module_id,
Expr::TagDeclarator(tag) => tag.module_id,
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
Expr::FunctionExpression(function_expression) => function_expression.module_id,
Expr::CallExpression(call_expression) => call_expression.module_id,
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
Expr::ArrayExpression(array_expression) => array_expression.module_id,
Expr::ArrayRangeExpression(array_range) => array_range.module_id,
Expr::ObjectExpression(object_expression) => object_expression.module_id,
Expr::MemberExpression(member_expression) => member_expression.module_id,
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
Expr::IfExpression(expr) => expr.module_id,
Expr::None(none) => none.module_id,
}
}
}
impl BinaryPart {
pub fn module_id(&self) -> ModuleId {
match self {
BinaryPart::Literal(literal) => literal.module_id,
BinaryPart::Identifier(identifier) => identifier.module_id,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::IfExpression(e) => e.module_id,
}
}
}
impl MemberObject {
pub fn module_id(&self) -> ModuleId {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
MemberObject::Identifier(identifier) => identifier.module_id,
}
}
}
impl LiteralIdentifier {
pub fn module_id(&self) -> ModuleId {
match self {
LiteralIdentifier::Identifier(identifier) => identifier.module_id,
LiteralIdentifier::Literal(literal) => literal.module_id,
}
}
}

View File

@ -9,7 +9,7 @@ use kittycad_modeling_cmds as kcmc;
use crate::{
engine::EngineManager,
errors::{KclError, KclErrorDetails},
execution::Point2d,
executor::Point2d,
parsing::ast::types::{
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
PipeSubstitution, VariableDeclarator,
@ -42,7 +42,7 @@ pub async fn modify_ast_for_sketch(
// The name of the sketch.
sketch_name: &str,
// The type of plane the sketch is on. `XY` or `XZ`, etc
plane: crate::execution::PlaneType,
plane: crate::executor::PlaneType,
// The ID of the parent sketch.
sketch_id: uuid::Uuid,
) -> Result<String, KclError> {
@ -196,7 +196,7 @@ fn create_start_sketch_on(
name: &str,
start: [f64; 2],
end: [f64; 2],
plane: crate::execution::PlaneType,
plane: crate::executor::PlaneType,
additional_lines: Vec<[f64; 2]>,
) -> Result<Node<VariableDeclarator>, KclError> {
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;

View File

@ -0,0 +1,71 @@
use crate::{
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
source_range::ModuleId,
};
impl BodyItem {
pub fn module_id(&self) -> ModuleId {
match self {
BodyItem::ImportStatement(stmt) => stmt.module_id,
BodyItem::ExpressionStatement(expression_statement) => expression_statement.module_id,
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.module_id,
BodyItem::ReturnStatement(return_statement) => return_statement.module_id,
}
}
}
impl Expr {
pub fn module_id(&self) -> ModuleId {
match self {
Expr::Literal(literal) => literal.module_id,
Expr::Identifier(identifier) => identifier.module_id,
Expr::TagDeclarator(tag) => tag.module_id,
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
Expr::FunctionExpression(function_expression) => function_expression.module_id,
Expr::CallExpression(call_expression) => call_expression.module_id,
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
Expr::ArrayExpression(array_expression) => array_expression.module_id,
Expr::ArrayRangeExpression(array_range) => array_range.module_id,
Expr::ObjectExpression(object_expression) => object_expression.module_id,
Expr::MemberExpression(member_expression) => member_expression.module_id,
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
Expr::IfExpression(expr) => expr.module_id,
Expr::None(none) => none.module_id,
}
}
}
impl BinaryPart {
pub fn module_id(&self) -> ModuleId {
match self {
BinaryPart::Literal(literal) => literal.module_id,
BinaryPart::Identifier(identifier) => identifier.module_id,
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::IfExpression(e) => e.module_id,
}
}
}
impl MemberObject {
pub fn module_id(&self) -> ModuleId {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
MemberObject::Identifier(identifier) => identifier.module_id,
}
}
}
impl LiteralIdentifier {
pub fn module_id(&self) -> ModuleId {
match self {
LiteralIdentifier::Identifier(identifier) => identifier.module_id,
LiteralIdentifier::Literal(literal) => literal.module_id,
}
}
}

View File

@ -8,6 +8,7 @@ use std::{
};
use anyhow::Result;
use async_recursion::async_recursion;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -16,7 +17,7 @@ use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
};
use super::digest::Digest;
use super::{digest::Digest, execute::execute_pipe_body};
pub use crate::parsing::ast::types::{
condition::{ElseIf, IfExpression},
literal_value::LiteralValue,
@ -25,7 +26,7 @@ pub use crate::parsing::ast::types::{
use crate::{
docs::StdLibFn,
errors::KclError,
execution::{KclValue, Metadata, TagIdentifier},
executor::{ExecState, ExecutorContext, KclValue, Metadata, TagIdentifier},
parsing::PIPE_OPERATOR,
source_range::{ModuleId, SourceRange},
};
@ -465,9 +466,11 @@ impl Program {
continue;
}
BodyItem::VariableDeclaration(ref mut variable_declaration) => {
if variable_declaration.declaration.id.name == name {
variable_declaration.declaration = declarator;
return;
for declaration in &mut variable_declaration.declarations {
if declaration.id.name == name {
*declaration = declarator;
return;
}
}
}
BodyItem::ReturnStatement(_return_statement) => continue,
@ -498,16 +501,20 @@ impl Program {
for item in &self.body {
match item {
BodyItem::ImportStatement(stmt) => {
if stmt.get_variable(name) {
return Some(Definition::Import(stmt));
for import_item in &stmt.items {
if import_item.identifier() == name {
return Some(Definition::Import(stmt.as_ref()));
}
}
}
BodyItem::ExpressionStatement(_expression_statement) => {
continue;
}
BodyItem::VariableDeclaration(variable_declaration) => {
if variable_declaration.declaration.id.name == name {
return Some(Definition::Variable(&variable_declaration.declaration));
for declaration in &variable_declaration.declarations {
if declaration.id.name == name {
return Some(Definition::Variable(declaration));
}
}
}
BodyItem::ReturnStatement(_return_statement) => continue,
@ -1118,7 +1125,7 @@ impl NonCodeMeta {
pub struct ImportItem {
/// Name of the item to import.
pub name: Node<Identifier>,
/// Rename the item using an identifier after `as`.
/// Rename the item using an identifier after "as".
pub alias: Option<Node<Identifier>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -1164,71 +1171,13 @@ impl ImportItem {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
#[allow(clippy::large_enum_variant)]
pub enum ImportSelector {
/// A comma-separated list of names and possible aliases to import (may be a single item, but never zero).
/// E.g., `import bar as baz from "foo.kcl"`
List { items: NodeList<ImportItem> },
/// Import all public items from a module.
/// E.g., `import * from "foo.kcl"`
Glob(Node<()>),
/// Import the module itself (the param is an optional alias).
/// E.g., `import "foo.kcl" as bar`
None(Option<Node<Identifier>>),
}
impl ImportSelector {
pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> {
match self {
ImportSelector::List { items } => {
for item in items {
let source_range = SourceRange::from(&*item);
if source_range.contains(pos) {
let old_name = item.rename_symbol(new_name, pos);
if old_name.is_some() {
return old_name;
}
}
}
None
}
ImportSelector::Glob(_) => None,
ImportSelector::None(None) => None,
ImportSelector::None(Some(alias)) => {
let alias_source_range = SourceRange::from(&*alias);
if !alias_source_range.contains(pos) {
return None;
}
let old_name = std::mem::replace(&mut alias.name, new_name.to_owned());
Some(old_name)
}
}
}
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
match self {
ImportSelector::List { items } => {
for item in items {
item.rename_identifiers(old_name, new_name);
}
}
ImportSelector::Glob(_) => {}
ImportSelector::None(None) => {}
ImportSelector::None(Some(alias)) => alias.rename(old_name, new_name),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct ImportStatement {
pub selector: ImportSelector,
pub items: NodeList<ImportItem>,
pub path: String,
pub visibility: ItemVisibility,
pub raw_path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
@ -1236,41 +1185,6 @@ pub struct ImportStatement {
}
impl Node<ImportStatement> {
pub fn get_variable(&self, name: &str) -> bool {
match &self.selector {
ImportSelector::List { items } => {
for import_item in items {
if import_item.identifier() == name {
return true;
}
}
false
}
ImportSelector::Glob(_) => false,
ImportSelector::None(_) => name == self.module_name().unwrap(),
}
}
/// Get the name of the module object for this import.
/// Validated during parsing and guaranteed to return `Some` if the statement imports
/// the module itself (i.e., self.selector is ImportSelector::None).
pub fn module_name(&self) -> Option<String> {
if let ImportSelector::None(Some(alias)) = &self.selector {
return Some(alias.name.clone());
}
let mut parts = self.path.split('.');
let name = parts.next()?;
let ext = parts.next()?;
let rest = parts.next();
if rest.is_some() || ext != "kcl" {
return None;
}
Some(name.to_owned())
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
@ -1278,13 +1192,24 @@ impl Node<ImportStatement> {
}
pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> {
self.selector.rename_symbol(new_name, pos)
for item in &mut self.items {
let source_range = SourceRange::from(&*item);
if source_range.contains(pos) {
let old_name = item.rename_symbol(new_name, pos);
if old_name.is_some() {
return old_name;
}
}
}
None
}
}
impl ImportStatement {
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
self.selector.rename_identifiers(old_name, new_name);
for item in &mut self.items {
item.rename_identifiers(old_name, new_name);
}
}
}
@ -1546,7 +1471,7 @@ impl ItemVisibility {
#[ts(export)]
#[serde(tag = "type")]
pub struct VariableDeclaration {
pub declaration: Node<VariableDeclarator>,
pub declarations: NodeList<VariableDeclarator>,
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
pub visibility: ItemVisibility,
pub kind: VariableKind, // Change to enum if there are specific values
@ -1558,29 +1483,33 @@ pub struct VariableDeclaration {
impl From<&Node<VariableDeclaration>> for Vec<CompletionItem> {
fn from(declaration: &Node<VariableDeclaration>) -> Self {
vec![CompletionItem {
label: declaration.declaration.id.name.to_string(),
label_details: None,
kind: Some(match declaration.inner.kind {
VariableKind::Const => CompletionItemKind::CONSTANT,
VariableKind::Fn => CompletionItemKind::FUNCTION,
}),
detail: Some(declaration.inner.kind.to_string()),
documentation: None,
deprecated: None,
preselect: None,
sort_text: None,
filter_text: None,
insert_text: None,
insert_text_format: None,
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
}]
let mut completions = vec![];
for variable in &declaration.declarations {
completions.push(CompletionItem {
label: variable.id.name.to_string(),
label_details: None,
kind: Some(match declaration.inner.kind {
VariableKind::Const => CompletionItemKind::CONSTANT,
VariableKind::Fn => CompletionItemKind::FUNCTION,
}),
detail: Some(declaration.inner.kind.to_string()),
documentation: None,
deprecated: None,
preselect: None,
sort_text: None,
filter_text: None,
insert_text: None,
insert_text_format: None,
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
})
}
completions
}
}
@ -1614,11 +1543,13 @@ impl Node<VariableDeclaration> {
return None;
}
let declaration_source_range: SourceRange = self.declaration.id.clone().into();
if declaration_source_range.contains(pos) {
let old_name = self.declaration.id.name.clone();
self.declaration.id.name = new_name.to_string();
return Some(old_name);
for declaration in &mut self.declarations {
let declaration_source_range: SourceRange = declaration.id.clone().into();
if declaration_source_range.contains(pos) {
let old_name = declaration.id.name.clone();
declaration.id.name = new_name.to_string();
return Some(old_name);
}
}
None
@ -1626,9 +1557,9 @@ impl Node<VariableDeclaration> {
}
impl VariableDeclaration {
pub fn new(declaration: Node<VariableDeclarator>, visibility: ItemVisibility, kind: VariableKind) -> Self {
pub fn new(declarations: NodeList<VariableDeclarator>, visibility: ItemVisibility, kind: VariableKind) -> Self {
Self {
declaration,
declarations,
visibility,
kind,
digest: None,
@ -1636,14 +1567,18 @@ impl VariableDeclaration {
}
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
self.declaration.init.replace_value(source_range, new_value.clone());
for declaration in &mut self.declarations {
declaration.init.replace_value(source_range, new_value.clone());
}
}
/// Returns an Expr that includes the given character position.
pub fn get_expr_for_position(&self, pos: usize) -> Option<&Expr> {
let source_range: SourceRange = self.declaration.clone().into();
if source_range.contains(pos) {
return Some(&self.declaration.init);
for declaration in &self.declarations {
let source_range: SourceRange = declaration.into();
if source_range.contains(pos) {
return Some(&declaration.init);
}
}
None
@ -1651,69 +1586,77 @@ impl VariableDeclaration {
/// Returns an Expr that includes the given character position.
pub fn get_mut_expr_for_position(&mut self, pos: usize) -> Option<&mut Expr> {
let source_range: SourceRange = self.declaration.clone().into();
if source_range.contains(pos) {
return Some(&mut self.declaration.init);
for declaration in &mut self.declarations {
let source_range: SourceRange = declaration.clone().into();
if source_range.contains(pos) {
return Some(&mut declaration.init);
}
}
None
}
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
// Skip the init for the variable with the new name since it is the one we are renaming.
if self.declaration.id.name != new_name {
self.declaration.init.rename_identifiers(old_name, new_name);
for declaration in &mut self.declarations {
// Skip the init for the variable with the new name since it is the one we are renaming.
if declaration.id.name == new_name {
continue;
}
declaration.init.rename_identifiers(old_name, new_name);
}
}
pub fn get_lsp_symbols(&self, code: &str) -> Vec<DocumentSymbol> {
let source_range: SourceRange = self.declaration.clone().into();
let inner_source_range: SourceRange = self.declaration.id.clone().into();
let mut symbols = vec![];
let mut symbol_kind = match self.kind {
VariableKind::Fn => SymbolKind::FUNCTION,
VariableKind::Const => SymbolKind::CONSTANT,
};
for declaration in &self.declarations {
let source_range: SourceRange = declaration.into();
let inner_source_range: SourceRange = declaration.id.clone().into();
let children = match &self.declaration.init {
Expr::FunctionExpression(function_expression) => {
symbol_kind = SymbolKind::FUNCTION;
let mut children = vec![];
for param in &function_expression.params {
let param_source_range: SourceRange = (&param.identifier).into();
#[allow(deprecated)]
children.push(DocumentSymbol {
name: param.identifier.name.clone(),
detail: None,
kind: SymbolKind::CONSTANT,
range: param_source_range.to_lsp_range(code),
selection_range: param_source_range.to_lsp_range(code),
children: None,
tags: None,
deprecated: None,
});
let mut symbol_kind = match self.kind {
VariableKind::Fn => SymbolKind::FUNCTION,
VariableKind::Const => SymbolKind::CONSTANT,
};
let children = match &declaration.init {
Expr::FunctionExpression(function_expression) => {
symbol_kind = SymbolKind::FUNCTION;
let mut children = vec![];
for param in &function_expression.params {
let param_source_range: SourceRange = (&param.identifier).into();
#[allow(deprecated)]
children.push(DocumentSymbol {
name: param.identifier.name.clone(),
detail: None,
kind: SymbolKind::CONSTANT,
range: param_source_range.to_lsp_range(code),
selection_range: param_source_range.to_lsp_range(code),
children: None,
tags: None,
deprecated: None,
});
}
children
}
children
}
Expr::ObjectExpression(object_expression) => {
symbol_kind = SymbolKind::OBJECT;
let mut children = vec![];
for property in &object_expression.properties {
children.extend(property.get_lsp_symbols(code));
Expr::ObjectExpression(object_expression) => {
symbol_kind = SymbolKind::OBJECT;
let mut children = vec![];
for property in &object_expression.properties {
children.extend(property.get_lsp_symbols(code));
}
children
}
children
}
Expr::ArrayExpression(_) => {
symbol_kind = SymbolKind::ARRAY;
vec![]
}
_ => vec![],
};
Expr::ArrayExpression(_) => {
symbol_kind = SymbolKind::ARRAY;
vec![]
}
_ => vec![],
};
vec![
#[allow(deprecated)]
DocumentSymbol {
name: self.declaration.id.name.clone(),
symbols.push(DocumentSymbol {
name: declaration.id.name.clone(),
detail: Some(self.kind.to_string()),
kind: symbol_kind,
range: source_range.to_lsp_range(code),
@ -1721,8 +1664,10 @@ impl VariableDeclaration {
children: Some(children),
tags: None,
deprecated: None,
},
]
});
}
symbols
}
}
@ -2684,6 +2629,11 @@ impl Node<PipeExpression> {
constraint_levels.get_constraint_level(self.into())
}
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
}
}
impl PipeExpression {
@ -2833,18 +2783,6 @@ impl Parameter {
}
}
impl From<&Parameter> for SourceRange {
fn from(p: &Parameter) -> Self {
let sr = Self::from(&p.identifier);
// If it's unlabelled, the span should start 1 char earlier than the identifier,
// to include the '@' symbol.
if !p.labeled {
return Self::new(sr.start() - 1, sr.end(), sr.module_id());
}
sr
}
}
fn is_true(b: &bool) -> bool {
*b
}
@ -3305,7 +3243,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration")
};
let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else {
panic!("expected a function expression")
};
let params = &func_expr.params;
@ -3327,7 +3265,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration")
};
let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else {
panic!("expected a function expression")
};
let params = &func_expr.params;
@ -3350,7 +3288,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration")
};
let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else {
panic!("expected a function expression")
};
let params = &func_expr.params;
@ -3424,7 +3362,7 @@ const cylinder = startSketchOn('-XZ')
let BodyItem::VariableDeclaration(var_decl) = function else {
panic!("expected a variable declaration")
};
let Expr::FunctionExpression(ref func_expr) = var_decl.declaration.init else {
let Expr::FunctionExpression(ref func_expr) = var_decl.declarations.first().unwrap().init else {
panic!("expected a function expression")
};
let params = &func_expr.params;

View File

@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::Node;
use crate::{execution::KclValue, parsing::ast::types::ConstraintLevel};
use crate::{executor::KclValue, parsing::ast::types::ConstraintLevel};
const KCL_NONE_ID: &str = "KCL_NONE_ID";

View File

@ -19,11 +19,11 @@ use crate::{
ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, BoxNode,
CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ImportSelector,
ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression,
MemberObject, Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty,
Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, Shebang, TagDeclarator,
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ImportStatement,
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
PipeSubstitution, Program, ReturnStatement, Shebang, TagDeclarator, UnaryExpression, UnaryOperator,
VariableDeclaration, VariableDeclarator, VariableKind,
},
math::BinaryExpressionToken,
token::{Token, TokenType},
@ -1217,6 +1217,7 @@ fn noncode_just_after_code(i: TokenSlice) -> PResult<Node<NonCodeNode>> {
// the large_enum_variant lint below introduces a LOT of code complexity in a
// match!() that's super clean that isn't worth it for the marginal space
// savings. revisit if that's a lie.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum WithinFunction {
@ -1237,8 +1238,7 @@ fn body_items_within_function(i: TokenSlice) -> PResult<WithinFunction> {
// Any of the body item variants, each of which can optionally be followed by a comment.
// If there is a comment, it may be preceded by whitespace.
let item = dispatch! {peek(any);
token if token.visibility_keyword().is_some() => (alt((declaration.map(BodyItem::VariableDeclaration), import_stmt.map(BodyItem::ImportStatement))), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
token if token.declaration_keyword().is_some() =>
token if token.declaration_keyword().is_some() || token.visibility_keyword().is_some() =>
(declaration.map(BodyItem::VariableDeclaration), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
token if token.value == "import" && matches!(token.token_type, TokenType::Keyword) =>
(import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
@ -1407,22 +1407,7 @@ fn function_body(i: TokenSlice) -> PResult<Node<Program>> {
))
}
fn import_items(i: TokenSlice) -> PResult<NodeList<ImportItem>> {
separated(1.., import_item, comma_sep)
.parse_next(i)
.map_err(|e| e.cut())
}
fn glob(i: TokenSlice) -> PResult<Token> {
one_of((TokenType::Operator, "*"))
.context(expected("the multiple import operator, *"))
.parse_next(i)
}
fn import_stmt(i: TokenSlice) -> PResult<BoxNode<ImportStatement>> {
let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
.parse_next(i)?
.map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
let import_token = any
.try_map(|token: Token| {
if matches!(token.token_type, TokenType::Keyword) && token.value == "import" {
@ -1436,63 +1421,38 @@ fn import_stmt(i: TokenSlice) -> PResult<BoxNode<ImportStatement>> {
})
.context(expected("the 'import' keyword"))
.parse_next(i)?;
let module_id = import_token.module_id;
let start = visibility_token.unwrap_or(import_token).start;
let start = import_token.start;
require_whitespace(i)?;
let (mut selector, path) = alt((
string_literal.map(|s| (ImportSelector::None(None), Some(s))),
glob.map(|t| {
let s = t.as_source_range();
(
ImportSelector::Glob(Node::new((), s.start(), s.end(), s.module_id())),
None,
)
}),
import_items.map(|items| (ImportSelector::List { items }, None)),
))
.parse_next(i)?;
let items = separated(1.., import_item, comma_sep)
.parse_next(i)
.map_err(|e| e.cut())?;
let path = match path {
Some(path) => path,
None => {
require_whitespace(i)?;
any.try_map(|token: Token| {
if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "from" {
Ok(())
} else {
Err(CompilationError::fatal(
token.as_source_range(),
format!("{} is not the 'from' keyword", token.value.as_str()),
))
}
})
.context(expected("the 'from' keyword"))
.parse_next(i)
.map_err(|e| e.cut())?;
require_whitespace(i)?;
require_whitespace(i)?;
string_literal(i)?
any.try_map(|token: Token| {
if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "from" {
Ok(())
} else {
Err(CompilationError::fatal(
token.as_source_range(),
format!("{} is not the 'from' keyword", token.value.as_str()),
))
}
};
})
.context(expected("the 'from' keyword"))
.parse_next(i)
.map_err(|e| e.cut())?;
let mut end: usize = path.end;
require_whitespace(i)?;
let path = string_literal(i)?;
let end = path.end;
let path_string = match path.inner.value {
LiteralValue::String(s) => s,
_ => unreachable!(),
};
if path_string.is_empty() {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(path.start, path.end, path.module_id),
"import path cannot be empty",
)
.into(),
));
}
if path_string
.chars()
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.')
@ -1505,44 +1465,16 @@ fn import_stmt(i: TokenSlice) -> PResult<BoxNode<ImportStatement>> {
.into(),
));
}
if let ImportSelector::None(ref mut a) = selector {
if let Some(alias) = opt(preceded(
(whitespace, import_as_keyword, whitespace),
identifier.context(expected("an identifier to alias the import")),
))
.parse_next(i)?
{
end = alias.end;
*a = Some(alias);
}
if a.is_none()
&& (!path_string.ends_with(".kcl")
|| path_string.starts_with("_")
|| path_string.contains('-')
|| path_string[0..path_string.len() - 4].contains('.'))
{
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(path.start, path.end, path.module_id),
"import path is not a valid identifier and must be aliased.".to_owned(),
)
.into(),
));
}
}
Ok(Node::boxed(
ImportStatement {
selector,
visibility,
items,
path: path_string,
raw_path: path.inner.raw,
digest: None,
},
start,
end,
module_id,
import_token.module_id,
))
}
@ -1775,7 +1707,7 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
let end = val.end();
Ok(Box::new(Node {
inner: VariableDeclaration {
declaration: Node {
declarations: vec![Node {
start: id.start,
end,
module_id,
@ -1784,7 +1716,7 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
init: val,
digest: None,
},
},
}],
visibility,
kind,
digest: None,
@ -1855,59 +1787,22 @@ impl TryFrom<Token> for Node<TagDeclarator> {
type Error = CompilationError;
fn try_from(token: Token) -> Result<Self, Self::Error> {
match token.token_type {
TokenType::Word => {
Ok(Node::new(
TagDeclarator {
// We subtract 1 from the start because the tag starts with a `$`.
name: token.value,
digest: None,
},
token.start - 1,
token.end,
token.module_id,
))
}
TokenType::Number => Err(CompilationError::fatal(
token.as_source_range(),
format!(
"Tag names must not start with a number. Tag starts with `{}`",
token.value.as_str()
),
)),
// e.g. `line(%, $)` or `line(%, $ , 5)`
TokenType::Brace | TokenType::Whitespace | TokenType::Comma => Err(CompilationError::fatal(
token.as_source_range(),
"Tag names must not be empty".to_string(),
)),
TokenType::Type => Err(CompilationError::fatal(
if token.token_type == TokenType::Word {
Ok(Node::new(
TagDeclarator {
// We subtract 1 from the start because the tag starts with a `$`.
name: token.value,
digest: None,
},
token.start - 1,
token.end,
token.module_id,
))
} else {
Err(CompilationError::fatal(
token.as_source_range(),
format!("Cannot assign a tag to a reserved keyword: {}", token.value.as_str()),
)),
TokenType::Bang
| TokenType::At
| TokenType::Hash
| TokenType::Colon
| TokenType::Period
| TokenType::Operator
| TokenType::DoublePeriod
| TokenType::QuestionMark
| TokenType::BlockComment
| TokenType::Function
| TokenType::String
| TokenType::Dollar
| TokenType::Keyword
| TokenType::Unknown
| TokenType::LineComment => Err(CompilationError::fatal(
token.as_source_range(),
// this is `start with` because if most of these cases are in the middle, it ends
// up hitting a different error path(e.g. including a bang) or being valid(e.g. including a comment) since it will get broken up into
// multiple tokens
format!("Tag names must not start with a {}", token.token_type),
)),
))
}
}
}
@ -1931,8 +1826,7 @@ fn tag(i: TokenSlice) -> PResult<Node<TagDeclarator>> {
let tag_declarator = any
.try_map(Node::<TagDeclarator>::try_from)
.context(expected("a tag, e.g. '$seg01' or '$line01'"))
.parse_next(i)
.map_err(|e| e.cut())?;
.parse_next(i)?;
// Now that we've parsed a tag declarator, verify that it's not a stdlib
// name. If it is, stop backtracking.
tag_declarator
@ -2180,11 +2074,6 @@ fn question_mark(i: TokenSlice) -> PResult<()> {
Ok(())
}
fn at_sign(i: TokenSlice) -> PResult<()> {
TokenType::At.parse_from(i)?;
Ok(())
}
fn fun(i: TokenSlice) -> PResult<Token> {
any.try_map(|token: Token| match token.token_type {
TokenType::Keyword if token.value == "fn" => Ok(token),
@ -2257,15 +2146,13 @@ fn argument_type(i: TokenSlice) -> PResult<FnArgType> {
}
struct ParamDescription {
labeled: bool,
arg_name: Token,
type_: std::option::Option<FnArgType>,
is_optional: bool,
}
fn parameter(i: TokenSlice) -> PResult<ParamDescription> {
let (found_at_sign, arg_name, optional, _, type_) = (
opt(at_sign),
let (arg_name, optional, _, type_) = (
any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
opt(question_mark),
opt(whitespace),
@ -2273,7 +2160,6 @@ fn parameter(i: TokenSlice) -> PResult<ParamDescription> {
)
.parse_next(i)?;
Ok(ParamDescription {
labeled: found_at_sign.is_none(),
arg_name,
type_,
is_optional: optional.is_some(),
@ -2292,7 +2178,6 @@ fn parameters(i: TokenSlice) -> PResult<Vec<Parameter>> {
.into_iter()
.map(
|ParamDescription {
labeled,
arg_name,
type_,
is_optional,
@ -2308,7 +2193,7 @@ fn parameters(i: TokenSlice) -> PResult<Vec<Parameter>> {
} else {
None
},
labeled,
labeled: true,
digest: None,
})
},
@ -2316,15 +2201,6 @@ fn parameters(i: TokenSlice) -> PResult<Vec<Parameter>> {
.collect::<Result<_, _>>()
.map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?;
// Make sure the only unlabeled parameter is the first one.
if let Some(param) = params.iter().skip(1).find(|param| !param.labeled) {
let source_range = SourceRange::from(param);
return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
source_range,
"Only the first parameter can be declared unlabeled",
))));
}
// Make sure optional parameters are last.
if let Err(e) = optional_after_required(&params) {
return Err(ErrMode::Cut(ContextError::from(e)));
@ -2428,7 +2304,6 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
opt(whitespace).parse_next(i)?;
let _ = terminated(open_paren, opt(whitespace)).parse_next(i)?;
let args = arguments(i)?;
if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) {
let just_args: Vec<_> = args.iter().collect();
typecheck_all(std_fn, &just_args)?;
@ -2508,11 +2383,10 @@ mod tests {
// example, "return" is the problem.
assert!(
err.message.starts_with("Unexpected token: ")
|| err.message.starts_with("= is not")
|| err
.message
.starts_with("Cannot assign a variable to a reserved keyword: "),
"Error message is: `{}`",
"Error message is: {}",
err.message,
);
}
@ -2575,7 +2449,7 @@ mod tests {
let tokens = crate::parsing::token::lexer("x = 4", ModuleId::default()).unwrap();
let vardec = declaration(&mut tokens.as_slice()).unwrap();
assert_eq!(vardec.inner.kind, VariableKind::Const);
let vardec = &vardec.declaration;
let vardec = vardec.declarations.first().unwrap();
assert_eq!(vardec.id.name, "x");
let Expr::Literal(init_val) = &vardec.init else {
panic!("weird init value")
@ -2650,10 +2524,10 @@ const mySk1 = startSketchAt([0, 0])"#;
fn test_comment_in_pipe() {
let tokens = crate::parsing::token::lexer(r#"const x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
let mut body = program.parse(&tokens).unwrap().inner.body;
let BodyItem::VariableDeclaration(item) = body.remove(0) else {
let BodyItem::VariableDeclaration(mut item) = body.remove(0) else {
panic!("expected vardec");
};
let val = item.inner.declaration.inner.init;
let val = item.declarations.remove(0).inner.init;
let Expr::PipeExpression(pipe) = val else {
panic!("expected pipe");
};
@ -2921,14 +2795,14 @@ const mySk1 = startSketchAt([0, 0])"#;
.enumerate()
{
let tokens = crate::parsing::token::lexer(test_input, ModuleId::default()).unwrap();
let actual = match declaration.parse(&tokens) {
let mut actual = match declaration.parse(&tokens) {
Err(e) => panic!("Could not parse test {i}: {e:#?}"),
Ok(a) => a,
};
let Expr::BinaryExpression(_expr) = &actual.declaration.inner.init else {
let Expr::BinaryExpression(_expr) = actual.declarations.remove(0).inner.init else {
panic!(
"Expected test {i} to be a binary expression but it wasn't, it was {:?}",
actual.declaration
actual.declarations[0]
);
};
// TODO: check both sides are 1... probably not necessary but should do.
@ -3270,15 +3144,16 @@ const mySk1 = startSketchAt([0, 0])"#;
};
// Run the second parser, check it matches the first parser.
let actual = declaration.parse(&tokens).unwrap();
let mut actual = declaration.parse(&tokens).unwrap();
assert_eq!(expected, actual);
// Inspect its output in more detail.
assert_eq!(actual.inner.kind, VariableKind::Const);
assert_eq!(actual.start, 0);
let decl = &actual.declaration;
assert_eq!(actual.declarations.len(), 1);
let decl = actual.declarations.pop().unwrap();
assert_eq!(decl.id.name, "myVar");
let Expr::Literal(value) = &decl.inner.init else {
let Expr::Literal(value) = decl.inner.init else {
panic!("value should be a literal")
};
assert_eq!(value.end, test.len());
@ -3493,18 +3368,12 @@ const mySk1 = startSketchAt([0, 0])"#;
}
#[track_caller]
fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
fn assert_err(p: &str, msg: &str, src: [usize; 2]) {
let result = crate::parsing::top_level_parse(p);
let err = result.unwrap_errs().next().unwrap();
assert_eq!(err.message, msg);
let src_actual = [err.source_range.start(), err.source_range.end()];
assert_eq!(
src_expected,
src_actual,
"expected error would highlight {} but it actually highlighted {}",
&p[src_expected[0]..src_expected[1]],
&p[src_actual[0]..src_actual[1]],
);
assert_eq!(err.source_range.start(), src[0]);
assert_eq!(err.source_range.end(), src[1]);
}
#[track_caller]
@ -3613,20 +3482,6 @@ const secondExtrude = startSketchOn('XY')
assert_err(">!", "Unexpected token: >", [0, 1]);
}
#[test]
fn test_parse_unlabeled_param_not_allowed() {
assert_err(
"fn f(@x, @y) { return 1 }",
"Only the first parameter can be declared unlabeled",
[9, 11],
);
assert_err(
"fn f(x, @y) { return 1 }",
"Only the first parameter can be declared unlabeled",
[8, 10],
);
}
#[test]
fn test_parse_z_percent_parens() {
assert_err("z%)", "Unexpected token: %", [1, 2]);
@ -3842,46 +3697,6 @@ e
)
}
#[test]
fn bad_imports() {
assert_err(
r#"import cube from "../cube.kcl""#,
"import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported.",
[17, 30],
);
assert_err(
r#"import * as foo from "dsfs""#,
"as is not the 'from' keyword",
[9, 11],
);
assert_err(r#"import a from "dsfs" as b"#, "Unexpected token: as", [21, 23]);
assert_err(r#"import * from "dsfs" as b"#, "Unexpected token: as", [21, 23]);
assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
assert_err(
r#"import "dsfs""#,
"import path is not a valid identifier and must be aliased.",
[7, 13],
);
assert_err(
r#"import "foo.bar.kcl""#,
"import path is not a valid identifier and must be aliased.",
[7, 20],
);
assert_err(
r#"import "_foo.kcl""#,
"import path is not a valid identifier and must be aliased.",
[7, 17],
);
assert_err(
r#"import "foo-bar.kcl""#,
"import path is not a valid identifier and must be aliased.",
[7, 20],
);
}
#[test]
fn zero_param_function() {
let code = r#"
@ -4112,108 +3927,12 @@ let myBox = box([0,0], -3, -16, -10)
}
#[test]
fn test_parse_empty_tag_brace() {
fn test_parse_empty_tag() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $)
"#;
assert_err(some_program_string, "Tag names must not be empty", [69, 70]);
}
#[test]
fn test_parse_empty_tag_whitespace() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $ ,01)
"#;
assert_err(some_program_string, "Tag names must not be empty", [69, 70]);
}
#[test]
fn test_parse_empty_tag_comma() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $,)
"#;
assert_err(some_program_string, "Tag names must not be empty", [69, 70]);
}
#[test]
fn test_parse_tag_starting_with_digit() {
let some_program_string = r#"
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $01)"#;
assert_err(
some_program_string,
"Tag names must not start with a number. Tag starts with `01`",
[74, 76],
);
}
#[test]
fn test_parse_tag_including_digit() {
let some_program_string = r#"
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $var01)"#;
assert_no_err(some_program_string);
}
#[test]
fn test_parse_tag_starting_with_bang() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $!var,01)
"#;
assert_err(some_program_string, "Tag names must not start with a bang", [69, 70]);
}
#[test]
fn test_parse_tag_starting_with_dollar() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $$,01)
"#;
assert_err(some_program_string, "Tag names must not start with a dollar", [69, 70]);
}
#[test]
fn test_parse_tag_starting_with_fn() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $fn,01)
"#;
assert_err(some_program_string, "Tag names must not start with a keyword", [69, 71]);
}
#[test]
fn test_parse_tag_starting_with_a_comment() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line(%, $//
,01)
"#;
assert_err(
some_program_string,
"Tag names must not start with a lineComment",
[69, 71],
);
}
#[test]
fn test_parse_tag_starting_with_reserved_type() {
let some_program_string = r#"
startSketchOn('XY')
|> line(%, $sketch)
"#;
assert_err(
some_program_string,
"Cannot assign a tag to a reserved keyword: sketch",
[41, 47],
);
}
#[test]
fn test_parse_tag_with_reserved_in_middle_works() {
let some_program_string = r#"
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([5, 5], %, $sketching)
"#;
assert_no_err(some_program_string);
|> line([5, 5], %, $)
"#;
assert_err(some_program_string, "Unexpected token: |>", [57, 59]);
}
#[test]
@ -4516,8 +4235,6 @@ const my14 = 4 ^ 2 - 3 ^ 2 * 2
);
snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y: z)"#);
snapshot_test!(kw_function_all_named, r#"val = foo(x: a, y: b)"#);
snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
}
#[allow(unused)]

View File

@ -1,241 +1,245 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3893
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 143,
"id": {
"end": 15,
"name": "boxSketch",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"elements": [
{
"end": 34,
"raw": "0",
"start": 33,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 37,
"raw": "0",
"start": 36,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 38,
"start": 32,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 31,
"name": "startSketchAt",
"start": 18,
"type": "Identifier"
},
"end": 39,
"start": 18,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 54,
"raw": "0",
"start": 53,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 58,
"raw": "10",
"start": 56,
"type": "Literal",
"type": "Literal",
"value": 10.0
}
],
"end": 59,
"start": 52,
"type": "ArrayExpression",
"type": "ArrayExpression"
"declarations": [
{
"end": 143,
"id": {
"end": 15,
"name": "boxSketch",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"elements": [
{
"end": 34,
"raw": "0",
"start": 33,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 37,
"raw": "0",
"start": 36,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 38,
"start": 32,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 31,
"name": "startSketchAt",
"start": 18,
"type": "Identifier"
},
{
"end": 62,
"start": 61,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 51,
"name": "line",
"start": 47,
"type": "Identifier"
"end": 39,
"start": 18,
"type": "CallExpression",
"type": "CallExpression"
},
"end": 63,
"start": 47,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"argument": {
{
"arguments": [
{
"elements": [
{
"end": 54,
"raw": "0",
"start": 53,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 58,
"raw": "10",
"start": 56,
"type": "Literal",
"type": "Literal",
"value": 10.0
}
],
"end": 59,
"start": 52,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 62,
"start": 61,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 51,
"name": "line",
"start": 47,
"type": "Identifier"
},
"end": 63,
"start": 47,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"argument": {
"end": 88,
"raw": "5",
"start": 87,
"type": "Literal",
"type": "Literal",
"value": 5.0
},
"end": 88,
"operator": "-",
"start": 86,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
{
"end": 91,
"raw": "5",
"start": 87,
"start": 90,
"type": "Literal",
"type": "Literal",
"value": 5.0
}
],
"end": 92,
"start": 85,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 95,
"start": 94,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 84,
"name": "tangentialArc",
"start": 71,
"type": "Identifier"
},
"end": 96,
"start": 71,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 111,
"raw": "5",
"start": 110,
"type": "Literal",
"type": "Literal",
"value": 5.0
},
"end": 88,
"operator": "-",
"start": 86,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
{
"end": 91,
"raw": "5",
"start": 90,
"type": "Literal",
"type": "Literal",
"value": 5.0
}
],
"end": 92,
"start": 85,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 95,
"start": 94,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 84,
"name": "tangentialArc",
"start": 71,
"type": "Identifier"
},
"end": 96,
"start": 71,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 111,
"raw": "5",
"start": 110,
"type": "Literal",
"type": "Literal",
"value": 5.0
},
{
"argument": {
{
"argument": {
"end": 116,
"raw": "15",
"start": 114,
"type": "Literal",
"type": "Literal",
"value": 15.0
},
"end": 116,
"raw": "15",
"start": 114,
"type": "Literal",
"type": "Literal",
"value": 15.0
},
"end": 116,
"operator": "-",
"start": 113,
"type": "UnaryExpression",
"type": "UnaryExpression"
}
],
"end": 117,
"start": 109,
"type": "ArrayExpression",
"type": "ArrayExpression"
"operator": "-",
"start": 113,
"type": "UnaryExpression",
"type": "UnaryExpression"
}
],
"end": 117,
"start": 109,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 120,
"start": 119,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 108,
"name": "line",
"start": 104,
"type": "Identifier"
},
{
"end": 120,
"start": 119,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 108,
"name": "line",
"end": 121,
"start": 104,
"type": "Identifier"
"type": "CallExpression",
"type": "CallExpression"
},
"end": 121,
"start": 104,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 139,
"raw": "10",
"start": 137,
"type": "Literal",
"type": "Literal",
"value": 10.0
{
"arguments": [
{
"end": 139,
"raw": "10",
"start": 137,
"type": "Literal",
"type": "Literal",
"value": 10.0
},
{
"end": 142,
"start": 141,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 136,
"name": "extrude",
"start": 129,
"type": "Identifier"
},
{
"end": 142,
"start": 141,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 136,
"name": "extrude",
"end": 143,
"start": 129,
"type": "Identifier"
},
"end": 143,
"start": 129,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 143,
"start": 18,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 143,
"start": 18,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 143,
"kind": "const",
"start": 0,

View File

@ -1,35 +1,39 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3963
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 17,
"id": {
"end": 8,
"name": "sg",
"start": 6,
"type": "Identifier"
},
"init": {
"argument": {
"end": 17,
"name": "scale",
"start": 12,
"type": "Identifier",
"declarations": [
{
"end": 17,
"id": {
"end": 8,
"name": "sg",
"start": 6,
"type": "Identifier"
},
"end": 17,
"operator": "-",
"start": 11,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"init": {
"argument": {
"end": 17,
"name": "scale",
"start": 12,
"type": "Identifier",
"type": "Identifier"
},
"end": 17,
"operator": "-",
"start": 11,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 17,
"kind": "const",
"start": 0,

View File

@ -1,44 +1,48 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3965
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 23,
"id": {
"end": 13,
"name": "myArray",
"start": 6,
"type": "Identifier"
},
"init": {
"declarations": [
{
"end": 23,
"endElement": {
"end": 22,
"raw": "10",
"start": 20,
"type": "Literal",
"type": "Literal",
"value": 10.0
"id": {
"end": 13,
"name": "myArray",
"start": 6,
"type": "Identifier"
},
"endInclusive": true,
"start": 16,
"startElement": {
"end": 18,
"raw": "0",
"start": 17,
"type": "Literal",
"type": "Literal",
"value": 0.0
"init": {
"end": 23,
"endElement": {
"end": 22,
"raw": "10",
"start": 20,
"type": "Literal",
"type": "Literal",
"value": 10.0
},
"endInclusive": true,
"start": 16,
"startElement": {
"end": 18,
"raw": "0",
"start": 17,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
"type": "ArrayRangeExpression",
"type": "ArrayRangeExpression"
},
"type": "ArrayRangeExpression",
"type": "ArrayRangeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 23,
"kind": "const",
"start": 0,

View File

@ -1,48 +1,52 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3966
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 57,
"id": {
"end": 24,
"name": "firstPrimeNumber",
"start": 8,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"end": 51,
"raw": "2",
"start": 50,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"end": 51,
"start": 43,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 57,
"start": 33
},
"declarations": [
{
"end": 57,
"params": [],
"start": 27,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 8,
"type": "VariableDeclarator"
},
"id": {
"end": 24,
"name": "firstPrimeNumber",
"start": 8,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"end": 51,
"raw": "2",
"start": 50,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"end": 51,
"start": 43,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 57,
"start": 33
},
"end": 57,
"params": [],
"start": 27,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 8,
"type": "VariableDeclarator"
}
],
"end": 57,
"kind": "fn",
"start": 5,

View File

@ -1,58 +1,61 @@
---
source: kcl/src/parsing/parser.rs
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 49,
"id": {
"end": 8,
"name": "thing",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"declarations": [
{
"end": 49,
"id": {
"end": 8,
"name": "thing",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"end": 43,
"raw": "true",
"start": 39,
"type": "Literal",
"type": "Literal",
"value": true
},
"end": 43,
"raw": "true",
"start": 39,
"type": "Literal",
"type": "Literal",
"value": true
},
"end": 43,
"start": 32,
"type": "ReturnStatement",
"type": "ReturnStatement"
"start": 32,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 49,
"start": 22
},
"end": 49,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 17,
"name": "param",
"start": 12,
"type": "Identifier"
}
}
],
"end": 49,
"start": 22
"start": 11,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"end": 49,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 17,
"name": "param",
"start": 12,
"type": "Identifier"
}
}
],
"start": 11,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 3,
"type": "VariableDeclarator"
},
"start": 3,
"type": "VariableDeclarator"
}
],
"end": 49,
"kind": "fn",
"start": 0,

View File

@ -1,233 +1,237 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3981
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 165,
"id": {
"end": 14,
"name": "mySketch",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"elements": [
{
"end": 33,
"raw": "0",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 35,
"raw": "0",
"start": 34,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 36,
"start": 31,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 30,
"name": "startSketchAt",
"start": 17,
"type": "Identifier"
},
"end": 37,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 58,
"raw": "0",
"start": 57,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 61,
"raw": "1",
"start": 60,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 62,
"start": 56,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 65,
"start": 64,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
},
{
"end": 74,
"start": 67,
"type": "TagDeclarator",
"type": "TagDeclarator",
"value": "myPath"
}
],
"callee": {
"end": 55,
"name": "lineTo",
"start": 49,
"type": "Identifier"
},
"end": 75,
"start": 49,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 96,
"raw": "1",
"start": 95,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 99,
"raw": "1",
"start": 98,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 100,
"start": 94,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 103,
"start": 102,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 93,
"name": "lineTo",
"start": 87,
"type": "Identifier"
},
"end": 104,
"start": 87,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 125,
"raw": "1",
"start": 124,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 128,
"raw": "0",
"start": 127,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 129,
"start": 123,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 132,
"start": 131,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
},
{
"end": 144,
"start": 134,
"type": "TagDeclarator",
"type": "TagDeclarator",
"value": "rightPath"
}
],
"callee": {
"end": 122,
"name": "lineTo",
"start": 116,
"type": "Identifier"
},
"end": 145,
"start": 116,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 164,
"start": 163,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 162,
"name": "close",
"start": 157,
"type": "Identifier"
},
"end": 165,
"start": 157,
"type": "CallExpression",
"type": "CallExpression"
}
],
"declarations": [
{
"end": 165,
"start": 17,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"id": {
"end": 14,
"name": "mySketch",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"elements": [
{
"end": 33,
"raw": "0",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 35,
"raw": "0",
"start": 34,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 36,
"start": 31,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 30,
"name": "startSketchAt",
"start": 17,
"type": "Identifier"
},
"end": 37,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 58,
"raw": "0",
"start": 57,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 61,
"raw": "1",
"start": 60,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 62,
"start": 56,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 65,
"start": 64,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
},
{
"end": 74,
"start": 67,
"type": "TagDeclarator",
"type": "TagDeclarator",
"value": "myPath"
}
],
"callee": {
"end": 55,
"name": "lineTo",
"start": 49,
"type": "Identifier"
},
"end": 75,
"start": 49,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 96,
"raw": "1",
"start": 95,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 99,
"raw": "1",
"start": 98,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 100,
"start": 94,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 103,
"start": 102,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 93,
"name": "lineTo",
"start": 87,
"type": "Identifier"
},
"end": 104,
"start": 87,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 125,
"raw": "1",
"start": 124,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 128,
"raw": "0",
"start": 127,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 129,
"start": 123,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 132,
"start": 131,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
},
{
"end": 144,
"start": 134,
"type": "TagDeclarator",
"type": "TagDeclarator",
"value": "rightPath"
}
],
"callee": {
"end": 122,
"name": "lineTo",
"start": 116,
"type": "Identifier"
},
"end": 145,
"start": 116,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 164,
"start": 163,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 162,
"name": "close",
"start": 157,
"type": "Identifier"
},
"end": 165,
"start": 157,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 165,
"start": 17,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 165,
"kind": "const",
"start": 0,

View File

@ -1,131 +1,135 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3989
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 70,
"id": {
"end": 14,
"name": "mySketch",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"elements": [
{
"end": 33,
"raw": "0",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 35,
"raw": "0",
"start": 34,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 36,
"start": 31,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 30,
"name": "startSketchAt",
"start": 17,
"type": "Identifier"
},
"end": 37,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 50,
"raw": "1",
"start": 49,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 53,
"raw": "1",
"start": 52,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 54,
"start": 48,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 57,
"start": 56,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 47,
"name": "lineTo",
"start": 41,
"type": "Identifier"
},
"end": 58,
"start": 41,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 69,
"start": 68,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 67,
"name": "close",
"start": 62,
"type": "Identifier"
},
"end": 70,
"start": 62,
"type": "CallExpression",
"type": "CallExpression"
}
],
"declarations": [
{
"end": 70,
"start": 17,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"id": {
"end": 14,
"name": "mySketch",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"elements": [
{
"end": 33,
"raw": "0",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 35,
"raw": "0",
"start": 34,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 36,
"start": 31,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 30,
"name": "startSketchAt",
"start": 17,
"type": "Identifier"
},
"end": 37,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 50,
"raw": "1",
"start": 49,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 53,
"raw": "1",
"start": 52,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 54,
"start": 48,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 57,
"start": 56,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 47,
"name": "lineTo",
"start": 41,
"type": "Identifier"
},
"end": 58,
"start": 41,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 69,
"start": 68,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 67,
"name": "close",
"start": 62,
"type": "Identifier"
},
"end": 70,
"start": 62,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 70,
"start": 17,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 70,
"kind": "const",
"start": 0,

View File

@ -1,42 +1,46 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3993
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 30,
"id": {
"end": 11,
"name": "myBox",
"start": 6,
"type": "Identifier"
},
"init": {
"arguments": [
{
"end": 29,
"name": "p",
"start": 28,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 27,
"name": "startSketchAt",
"start": 14,
"declarations": [
{
"end": 30,
"id": {
"end": 11,
"name": "myBox",
"start": 6,
"type": "Identifier"
},
"end": 30,
"start": 14,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"init": {
"arguments": [
{
"end": 29,
"name": "p",
"start": 28,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 27,
"name": "startSketchAt",
"start": 14,
"type": "Identifier"
},
"end": 30,
"start": 14,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 30,
"kind": "const",
"start": 0,

View File

@ -1,79 +1,83 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3994
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 29,
"id": {
"end": 11,
"name": "myBox",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 17,
"raw": "1",
"start": 16,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"callee": {
"end": 15,
"name": "f",
"start": 14,
"type": "Identifier"
},
"end": 18,
"start": 14,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 25,
"raw": "2",
"start": 24,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
{
"end": 28,
"start": 27,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 23,
"name": "g",
"start": 22,
"type": "Identifier"
},
"end": 29,
"start": 22,
"type": "CallExpression",
"type": "CallExpression"
}
],
"declarations": [
{
"end": 29,
"start": 14,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"id": {
"end": 11,
"name": "myBox",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 17,
"raw": "1",
"start": 16,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"callee": {
"end": 15,
"name": "f",
"start": 14,
"type": "Identifier"
},
"end": 18,
"start": 14,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 25,
"raw": "2",
"start": 24,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
{
"end": 28,
"start": 27,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 23,
"name": "g",
"start": 22,
"type": "Identifier"
},
"end": 29,
"start": 22,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 29,
"start": 14,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 29,
"kind": "const",
"start": 0,

View File

@ -1,93 +1,97 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 3995
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 49,
"id": {
"end": 11,
"name": "myBox",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 29,
"name": "p",
"start": 28,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 27,
"name": "startSketchAt",
"start": 14,
"type": "Identifier"
},
"end": 30,
"start": 14,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 41,
"raw": "0",
"start": 40,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 44,
"name": "l",
"start": 43,
"type": "Identifier",
"type": "Identifier"
}
],
"end": 45,
"start": 39,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 48,
"start": 47,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 38,
"name": "line",
"start": 34,
"type": "Identifier"
},
"end": 49,
"start": 34,
"type": "CallExpression",
"type": "CallExpression"
}
],
"declarations": [
{
"end": 49,
"start": 14,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"id": {
"end": 11,
"name": "myBox",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 29,
"name": "p",
"start": 28,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 27,
"name": "startSketchAt",
"start": 14,
"type": "Identifier"
},
"end": 30,
"start": 14,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"elements": [
{
"end": 41,
"raw": "0",
"start": 40,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 44,
"name": "l",
"start": 43,
"type": "Identifier",
"type": "Identifier"
}
],
"end": 45,
"start": 39,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"end": 48,
"start": 47,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 38,
"name": "line",
"start": 34,
"type": "Identifier"
},
"end": 49,
"start": 34,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 49,
"start": 14,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 49,
"kind": "const",
"start": 0,

View File

@ -1,59 +1,63 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4001
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 37,
"id": {
"end": 14,
"name": "mySketch",
"start": 6,
"type": "Identifier"
},
"init": {
"arguments": [
{
"elements": [
{
"end": 33,
"raw": "0",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 35,
"raw": "0",
"start": 34,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 36,
"start": 31,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 30,
"name": "startSketchAt",
"start": 17,
"declarations": [
{
"end": 37,
"id": {
"end": 14,
"name": "mySketch",
"start": 6,
"type": "Identifier"
},
"end": 37,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"init": {
"arguments": [
{
"elements": [
{
"end": 33,
"raw": "0",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 35,
"raw": "0",
"start": 34,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 36,
"start": 31,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 30,
"name": "startSketchAt",
"start": 17,
"type": "Identifier"
},
"end": 37,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 37,
"kind": "const",
"start": 0,

View File

@ -1,161 +1,165 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4005
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 107,
"id": {
"end": 14,
"name": "cylinder",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 35,
"raw": "'XY'",
"start": 31,
"type": "Literal",
"type": "Literal",
"value": "XY"
}
],
"callee": {
"end": 30,
"name": "startSketchOn",
"start": 17,
"type": "Identifier"
},
"end": 36,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 81,
"properties": [
{
"end": 67,
"key": {
"end": 59,
"name": "center",
"start": 53,
"type": "Identifier"
},
"start": 53,
"type": "ObjectProperty",
"value": {
"elements": [
{
"end": 63,
"raw": "0",
"start": 62,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 66,
"raw": "0",
"start": 65,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 67,
"start": 61,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
},
{
"end": 79,
"key": {
"end": 75,
"name": "radius",
"start": 69,
"type": "Identifier"
},
"start": 69,
"type": "ObjectProperty",
"value": {
"end": 79,
"raw": "22",
"start": 77,
"type": "Literal",
"type": "Literal",
"value": 22.0
}
}
],
"start": 51,
"type": "ObjectExpression",
"type": "ObjectExpression"
},
{
"end": 84,
"start": 83,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 50,
"name": "circle",
"start": 44,
"type": "Identifier"
},
"end": 85,
"start": 44,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 103,
"raw": "14",
"start": 101,
"type": "Literal",
"type": "Literal",
"value": 14.0
},
{
"end": 106,
"start": 105,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 100,
"name": "extrude",
"start": 93,
"type": "Identifier"
},
"end": 107,
"start": 93,
"type": "CallExpression",
"type": "CallExpression"
}
],
"declarations": [
{
"end": 107,
"start": 17,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
},
"id": {
"end": 14,
"name": "cylinder",
"start": 6,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 35,
"raw": "'XY'",
"start": 31,
"type": "Literal",
"type": "Literal",
"value": "XY"
}
],
"callee": {
"end": 30,
"name": "startSketchOn",
"start": 17,
"type": "Identifier"
},
"end": 36,
"start": 17,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 81,
"properties": [
{
"end": 67,
"key": {
"end": 59,
"name": "center",
"start": 53,
"type": "Identifier"
},
"start": 53,
"type": "ObjectProperty",
"value": {
"elements": [
{
"end": 63,
"raw": "0",
"start": 62,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 66,
"raw": "0",
"start": 65,
"type": "Literal",
"type": "Literal",
"value": 0.0
}
],
"end": 67,
"start": 61,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
},
{
"end": 79,
"key": {
"end": 75,
"name": "radius",
"start": 69,
"type": "Identifier"
},
"start": 69,
"type": "ObjectProperty",
"value": {
"end": 79,
"raw": "22",
"start": 77,
"type": "Literal",
"type": "Literal",
"value": 22.0
}
}
],
"start": 51,
"type": "ObjectExpression",
"type": "ObjectExpression"
},
{
"end": 84,
"start": 83,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 50,
"name": "circle",
"start": 44,
"type": "Identifier"
},
"end": 85,
"start": 44,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 103,
"raw": "14",
"start": 101,
"type": "Literal",
"type": "Literal",
"value": 14.0
},
{
"end": 106,
"start": 105,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 100,
"name": "extrude",
"start": 93,
"type": "Identifier"
},
"end": 107,
"start": 93,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 107,
"start": 17,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 6,
"type": "VariableDeclarator"
}
],
"end": 107,
"kind": "const",
"start": 0,

View File

@ -1,84 +1,87 @@
---
source: kcl/src/parsing/parser.rs
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 49,
"id": {
"end": 4,
"name": "f",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"arguments": [
{
"end": 41,
"name": "angle",
"start": 36,
"type": "Identifier",
"declarations": [
{
"end": 49,
"id": {
"end": 4,
"name": "f",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"arguments": [
{
"end": 41,
"name": "angle",
"start": 36,
"type": "Identifier",
"type": "Identifier"
},
{
"end": 46,
"raw": "360",
"start": 43,
"type": "Literal",
"type": "Literal",
"value": 360.0
}
],
"callee": {
"end": 35,
"name": "default",
"start": 28,
"type": "Identifier"
},
{
"end": 46,
"raw": "360",
"start": 43,
"type": "Literal",
"type": "Literal",
"value": 360.0
}
],
"callee": {
"end": 35,
"name": "default",
"end": 47,
"start": 28,
"type": "Identifier"
"type": "CallExpression",
"type": "CallExpression"
},
"end": 47,
"start": 28,
"type": "CallExpression",
"type": "CallExpression"
"start": 21,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 49,
"start": 19
},
"end": 49,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 13,
"name": "angle",
"start": 8,
"type": "Identifier"
},
"end": 47,
"start": 21,
"type": "ReturnStatement",
"type": "ReturnStatement"
"default_value": {
"type": "KclNone",
"type": "KclNone",
"__private": "KCL_NONE_ID"
}
}
],
"end": 49,
"start": 19
"start": 7,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"end": 49,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 13,
"name": "angle",
"start": 8,
"type": "Identifier"
},
"default_value": {
"type": "KclNone",
"type": "KclNone",
"__private": "KCL_NONE_ID"
}
}
],
"start": 7,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 3,
"type": "VariableDeclarator"
},
"start": 3,
"type": "VariableDeclarator"
}
],
"end": 49,
"kind": "fn",
"start": 0,

View File

@ -1,74 +1,78 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4007
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 91,
"id": {
"end": 11,
"name": "numbers",
"start": 4,
"type": "Identifier"
},
"init": {
"elements": [
{
"end": 29,
"raw": "1",
"start": 28,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 80,
"raw": "3",
"start": 79,
"type": "Literal",
"type": "Literal",
"value": 3.0
}
],
"declarations": [
{
"end": 91,
"nonCodeMeta": {
"nonCodeNodes": {
"1": [
{
"end": 48,
"start": 43,
"type": "NonCodeNode",
"value": {
"type": "blockComment",
"value": "A,",
"style": "line"
}
}
],
"2": [
{
"end": 66,
"start": 61,
"type": "NonCodeNode",
"value": {
"type": "blockComment",
"value": "B,",
"style": "line"
}
}
]
},
"startNodes": []
"id": {
"end": 11,
"name": "numbers",
"start": 4,
"type": "Identifier"
},
"start": 14,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 4,
"type": "VariableDeclarator"
},
"init": {
"elements": [
{
"end": 29,
"raw": "1",
"start": 28,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 80,
"raw": "3",
"start": 79,
"type": "Literal",
"type": "Literal",
"value": 3.0
}
],
"end": 91,
"nonCodeMeta": {
"nonCodeNodes": {
"1": [
{
"end": 48,
"start": 43,
"type": "NonCodeNode",
"value": {
"type": "blockComment",
"value": "A,",
"style": "line"
}
}
],
"2": [
{
"end": 66,
"start": 61,
"type": "NonCodeNode",
"value": {
"type": "blockComment",
"value": "B,",
"style": "line"
}
}
]
},
"startNodes": []
},
"start": 14,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 4,
"type": "VariableDeclarator"
}
],
"end": 91,
"kind": "const",
"start": 0,

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