Compare commits
20 Commits
pierremtb/
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
34272b872d | |||
668e2afb99 | |||
548c664db0 | |||
d3a3f4410c | |||
22eb343171 | |||
f2cfa4d5cf | |||
3f1f40eeba | |||
ff2d161606 | |||
210c78029d | |||
e27840219b | |||
c943a3f192 | |||
6aa588f09f | |||
59a6333aad | |||
403f1507ae | |||
eac7b83504 | |||
667500d1b9 | |||
b15aac9f48 | |||
54153aa646 | |||
943cf21d34 | |||
5a6728c45a |
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
|
|||||||
```js
|
```js
|
||||||
r = 10 // radius
|
r = 10 // radius
|
||||||
// Call `map`, using an anonymous function instead of a named one.
|
// Call `map`, using an anonymous function instead of a named one.
|
||||||
circles = map([1..3], (id) {
|
circles = map([1..3], fn(id) {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
|||||||
// an anonymous `add` function as its parameter, instead of declaring a
|
// an anonymous `add` function as its parameter, instead of declaring a
|
||||||
// named function outside.
|
// named function outside.
|
||||||
arr = [1, 2, 3]
|
arr = [1, 2, 3]
|
||||||
sum = reduce(arr, 0, (i, result_so_far) {
|
sum = reduce(arr, 0, fn(i, result_so_far) {
|
||||||
return i + result_so_far
|
return i + result_so_far
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ fn decagon(radius) {
|
|||||||
// Use a `reduce` to draw the remaining decagon sides.
|
// Use a `reduce` to draw the remaining decagon sides.
|
||||||
// For each number in the array 1..10, run the given function,
|
// For each number in the array 1..10, run the given function,
|
||||||
// which takes a partially-sketched decagon and adds one more edge to it.
|
// which takes a partially-sketched decagon and adds one more edge to it.
|
||||||
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
|
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||||
// Draw one edge of the decagon.
|
// Draw one edge of the decagon.
|
||||||
x = cos(stepAngle * i) * radius
|
x = cos(stepAngle * i) * radius
|
||||||
y = sin(stepAngle * i) * radius
|
y = sin(stepAngle * i) * radius
|
||||||
|
@ -100436,7 +100436,7 @@
|
|||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
"r = 10 // radius\nfn drawCircle(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], drawCircle)",
|
"r = 10 // radius\nfn drawCircle(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], drawCircle)",
|
||||||
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], (id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n})"
|
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], fn(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n})"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -146129,8 +146129,8 @@
|
|||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n let sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n let sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||||
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, (i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||||
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n let stepAngle = (1/10) * tau()\n let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n let partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n let x = cos(stepAngle * i) * radius\n let y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
|
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n let stepAngle = (1/10) * tau()\n let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n let partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n let x = cos(stepAngle * i) * radius\n let y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ export class ToolbarFixture {
|
|||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
loftButton!: Locator
|
loftButton!: Locator
|
||||||
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
@ -28,6 +29,7 @@ export class ToolbarFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
this.lineBtn = page.getByTestId('line')
|
this.lineBtn = page.getByTestId('line')
|
||||||
|
@ -768,3 +768,168 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const shellPointAndClickCapCases = [
|
||||||
|
{ shouldPreselect: true },
|
||||||
|
{ shouldPreselect: false },
|
||||||
|
]
|
||||||
|
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
||||||
|
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
||||||
|
app,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
|
extrude001 = extrude(30, sketch001)
|
||||||
|
`
|
||||||
|
await app.initialise(initialCode)
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 575, y: 200 }
|
||||||
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const shellDeclaration =
|
||||||
|
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
||||||
|
|
||||||
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
|
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!shouldPreselect) {
|
||||||
|
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnCap()
|
||||||
|
await app.page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await test.step(`Preselect the cap`, async () => {
|
||||||
|
await clickOnCap()
|
||||||
|
await app.page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await editor.expectEditor.toContain(shellDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [shellDeclaration],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Shell point-and-click wall', async ({
|
||||||
|
app,
|
||||||
|
page,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-20, 20], %)
|
||||||
|
|> xLine(40, %)
|
||||||
|
|> yLine(-60, %)
|
||||||
|
|> xLine(-40, %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(40, sketch001)
|
||||||
|
`
|
||||||
|
await app.initialise(initialCode)
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 580, y: 180 }
|
||||||
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||||
|
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||||
|
const shellDeclaration =
|
||||||
|
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
||||||
|
const formattedOutLastLine = '}, extrude001)'
|
||||||
|
|
||||||
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
|
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnCap()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await clickOnWall()
|
||||||
|
await app.page.waitForTimeout(500)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap, 1 face',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await editor.expectEditor.toContain(mutatedCode)
|
||||||
|
await editor.expectEditor.toContain(shellDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [formattedOutLastLine],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@ -26,7 +26,17 @@ test.describe('Testing constraints', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// constants and locators
|
||||||
|
const lengthValue = {
|
||||||
|
old: '20',
|
||||||
|
new: '25',
|
||||||
|
}
|
||||||
|
const cmdBarKclInput = page
|
||||||
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.getByRole('textbox')
|
||||||
|
const cmdBarSubmitButton = page.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
@ -36,26 +46,26 @@ test.describe('Testing constraints', () => {
|
|||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// Click the line of code for line.
|
// Click the line of code for line.
|
||||||
await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
// TODO remove this and reinstate `await topHorzSegmentClick()`
|
||||||
|
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(500) // wait for animation
|
await page.waitForTimeout(500) // wait for animation
|
||||||
|
|
||||||
const startXPx = 500
|
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.mouse.click(834, 244)
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'dimension Length', exact: true })
|
.getByRole('button', { name: 'dimension Length', exact: true })
|
||||||
.click()
|
.click()
|
||||||
await page.getByText('Add constraining value').click()
|
await expect(cmdBarKclInput).toHaveText('20')
|
||||||
|
await cmdBarKclInput.fill(lengthValue.new)
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Can't calculate`),
|
||||||
|
`Something went wrong with the KCL expression evaluation`
|
||||||
|
).not.toBeVisible()
|
||||||
|
await cmdBarSubmitButton.click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Make sure we didn't pop out of sketch mode.
|
// Make sure we didn't pop out of sketch mode.
|
||||||
@ -66,7 +76,6 @@ test.describe('Testing constraints', () => {
|
|||||||
await page.waitForTimeout(500) // wait for animation
|
await page.waitForTimeout(500) // wait for animation
|
||||||
|
|
||||||
// Exit sketch
|
// Exit sketch
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
@ -524,7 +533,7 @@ part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test Angle/Length constraint single selection', () => {
|
test.describe('Test Angle constraint single selection', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Angle - Add variable',
|
testName: 'Angle - Add variable',
|
||||||
@ -538,18 +547,6 @@ part002 = startSketchOn('XZ')
|
|||||||
constraint: 'angle',
|
constraint: 'angle',
|
||||||
value: '83, 78.33',
|
value: '83, 78.33',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
testName: 'Length - Add variable',
|
|
||||||
addVariable: true,
|
|
||||||
constraint: 'length',
|
|
||||||
value: '83, length001',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: 'Length - No variable',
|
|
||||||
addVariable: false,
|
|
||||||
constraint: 'length',
|
|
||||||
value: '83, 78.33',
|
|
||||||
},
|
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${testName}`, async ({ page }) => {
|
test(`${testName}`, async ({ page }) => {
|
||||||
@ -608,6 +605,90 @@ part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
test.describe('Test Length constraint single selection', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testName: 'Length - Add variable',
|
||||||
|
addVariable: true,
|
||||||
|
constraint: 'length',
|
||||||
|
value: '83, length001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Length - No variable',
|
||||||
|
addVariable: false,
|
||||||
|
constraint: 'length',
|
||||||
|
value: '83, 78.33',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
|
test(`${testName}`, async ({ page }) => {
|
||||||
|
// constants and locators
|
||||||
|
const cmdBarKclInput = page
|
||||||
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.getByRole('textbox')
|
||||||
|
const cmdBarKclVariableNameInput =
|
||||||
|
page.getByPlaceholder('Variable name')
|
||||||
|
const cmdBarSubmitButton = page.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`yo = 5
|
||||||
|
part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
|> line([51.19, 48.97], %)
|
||||||
|
part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const line3 = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="${2}"]`
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.getByTestId('dropdown-constraint-' + constraint).click()
|
||||||
|
|
||||||
|
if (!addVariable) {
|
||||||
|
await test.step(`Clear the variable input`, async () => {
|
||||||
|
await cmdBarKclVariableNameInput.clear()
|
||||||
|
await cmdBarKclVariableNameInput.press('Backspace')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await expect(cmdBarKclInput).toHaveText('78.33')
|
||||||
|
await cmdBarSubmitButton.click()
|
||||||
|
|
||||||
|
const changedCode = `|> angledLine([${value}], %)`
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(changedCode)
|
||||||
|
// checking active assures the cursor is where it should be
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
test.describe('Many segments - no modal constraints', () => {
|
test.describe('Many segments - no modal constraints', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
@ -868,6 +949,15 @@ part002 = startSketchOn('XZ')
|
|||||||
|> line([3.13, -2.4], %)`
|
|> line([3.13, -2.4], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// constants and locators
|
||||||
|
const cmdBarKclInput = page
|
||||||
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.getByRole('textbox')
|
||||||
|
const cmdBarSubmitButton = page.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -928,8 +1018,8 @@ part002 = startSketchOn('XZ')
|
|||||||
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByTestId('dropdown-constraint-length').click()
|
await page.getByTestId('dropdown-constraint-length').click()
|
||||||
|
|
||||||
await page.getByLabel('length Value').fill('10')
|
await cmdBarKclInput.fill('10')
|
||||||
await page.getByRole('button', { name: 'Add constraining value' }).click()
|
await cmdBarSubmitButton.click()
|
||||||
|
|
||||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||||
|
@ -91,7 +91,14 @@ test.describe('Testing segment overlays', () => {
|
|||||||
await page.getByTestId('constraint-symbol-popover').count()
|
await page.getByTestId('constraint-symbol-popover').count()
|
||||||
).toBeGreaterThan(0)
|
).toBeGreaterThan(0)
|
||||||
await unconstrainedLocator.click()
|
await unconstrainedLocator.click()
|
||||||
await page.getByText('Add variable').click()
|
await expect(
|
||||||
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
|
).toBeFocused()
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
await expect(page.locator('.cm-content')).toContainText(expectFinal)
|
await expect(page.locator('.cm-content')).toContainText(expectFinal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +158,14 @@ test.describe('Testing segment overlays', () => {
|
|||||||
await page.getByTestId('constraint-symbol-popover').count()
|
await page.getByTestId('constraint-symbol-popover').count()
|
||||||
).toBeGreaterThan(0)
|
).toBeGreaterThan(0)
|
||||||
await unconstrainedLocator.click()
|
await unconstrainedLocator.click()
|
||||||
await page.getByText('Add variable').click()
|
await expect(
|
||||||
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
|
).toBeFocused()
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
expectAfterUnconstrained
|
expectAfterUnconstrained
|
||||||
)
|
)
|
||||||
|
@ -200,7 +200,10 @@ function CoreDump() {
|
|||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
useHotkeyWrapper(['mod + shift + .'], () => {
|
// TODO: revisit once progress is made on upstream issue
|
||||||
|
// https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1064
|
||||||
|
// const hotkey = process.platform !== 'linux' ? 'mod + shift + .' : 'mod + shift + >'
|
||||||
|
useHotkeyWrapper(['mod + shift + .', 'mod + shift + >'], () => {
|
||||||
toast
|
toast
|
||||||
.promise(
|
.promise(
|
||||||
coreDump(coreDumpManager, true),
|
coreDump(coreDumpManager, true),
|
||||||
|
@ -505,7 +505,8 @@ const ConstraintSymbol = ({
|
|||||||
constrainInfo: ConstrainInfo
|
constrainInfo: ConstrainInfo
|
||||||
verticalPosition: 'top' | 'bottom'
|
verticalPosition: 'top' | 'bottom'
|
||||||
}) => {
|
}) => {
|
||||||
const { context, send } = useModelingContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
|
const { context } = useModelingContext()
|
||||||
const varNameMap: {
|
const varNameMap: {
|
||||||
[key in ConstrainInfo['type']]: {
|
[key in ConstrainInfo['type']]: {
|
||||||
varName: string
|
varName: string
|
||||||
@ -624,11 +625,18 @@ const ConstraintSymbol = ({
|
|||||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||||
onClick={toSync(async () => {
|
onClick={toSync(async () => {
|
||||||
if (!isConstrained) {
|
if (!isConstrained) {
|
||||||
send({
|
commandBarSend({
|
||||||
type: 'Convert to variable',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
pathToNode,
|
name: 'Constrain with named value',
|
||||||
variableName: varName,
|
groupId: 'modeling',
|
||||||
|
argDefaultValues: {
|
||||||
|
currentValue: {
|
||||||
|
pathToNode,
|
||||||
|
variableName: varName,
|
||||||
|
valueText: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else if (isConstrained) {
|
} else if (isConstrained) {
|
||||||
|
@ -8,11 +8,16 @@ import { getSystemTheme } from 'lib/theme'
|
|||||||
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
|
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
import { varMentions } from 'lib/varCompletionExtension'
|
import { varMentions } from 'lib/varCompletionExtension'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import styles from './CommandBarKclInput.module.css'
|
import styles from './CommandBarKclInput.module.css'
|
||||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
|
||||||
|
const machineContextSelector = (snapshot?: {
|
||||||
|
context: Record<string, unknown>
|
||||||
|
}) => snapshot?.context
|
||||||
|
|
||||||
function CommandBarKclInput({
|
function CommandBarKclInput({
|
||||||
arg,
|
arg,
|
||||||
@ -31,12 +36,44 @@ function CommandBarKclInput({
|
|||||||
arg.name
|
arg.name
|
||||||
] as KclCommandValue | undefined
|
] as KclCommandValue | undefined
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const defaultValue = (arg.defaultValue as string) || ''
|
const argMachineContext = useSelector(
|
||||||
|
arg.machineActor,
|
||||||
|
machineContextSelector
|
||||||
|
)
|
||||||
|
const defaultValue = useMemo(
|
||||||
|
() =>
|
||||||
|
arg.defaultValue
|
||||||
|
? arg.defaultValue instanceof Function
|
||||||
|
? arg.defaultValue(commandBarState.context, argMachineContext)
|
||||||
|
: arg.defaultValue
|
||||||
|
: '',
|
||||||
|
[arg.defaultValue, commandBarState.context, argMachineContext]
|
||||||
|
)
|
||||||
|
const initialVariableName = useMemo(() => {
|
||||||
|
// Use the configured variable name if it exists
|
||||||
|
if (arg.variableName !== undefined) {
|
||||||
|
return arg.variableName instanceof Function
|
||||||
|
? arg.variableName(commandBarState.context, argMachineContext)
|
||||||
|
: arg.variableName
|
||||||
|
}
|
||||||
|
// or derive it from the previously set value or the argument name
|
||||||
|
return previouslySetValue && 'variableName' in previouslySetValue
|
||||||
|
? previouslySetValue.variableName
|
||||||
|
: arg.name
|
||||||
|
}, [
|
||||||
|
arg.variableName,
|
||||||
|
commandBarState.context,
|
||||||
|
argMachineContext,
|
||||||
|
arg.name,
|
||||||
|
previouslySetValue,
|
||||||
|
])
|
||||||
const [value, setValue] = useState(
|
const [value, setValue] = useState(
|
||||||
previouslySetValue?.valueText || defaultValue || ''
|
previouslySetValue?.valueText || defaultValue || ''
|
||||||
)
|
)
|
||||||
const [createNewVariable, setCreateNewVariable] = useState(
|
const [createNewVariable, setCreateNewVariable] = useState(
|
||||||
previouslySetValue && 'variableName' in previouslySetValue
|
(previouslySetValue && 'variableName' in previouslySetValue) ||
|
||||||
|
arg.createVariableByDefault ||
|
||||||
|
false
|
||||||
)
|
)
|
||||||
const [canSubmit, setCanSubmit] = useState(true)
|
const [canSubmit, setCanSubmit] = useState(true)
|
||||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||||
@ -52,10 +89,7 @@ function CommandBarKclInput({
|
|||||||
isNewVariableNameUnique,
|
isNewVariableNameUnique,
|
||||||
} = useCalculateKclExpression({
|
} = useCalculateKclExpression({
|
||||||
value,
|
value,
|
||||||
initialVariableName:
|
initialVariableName,
|
||||||
previouslySetValue && 'variableName' in previouslySetValue
|
|
||||||
? previouslySetValue.variableName
|
|
||||||
: arg.name,
|
|
||||||
})
|
})
|
||||||
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
||||||
label: v.key,
|
label: v.key,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { APP_VERSION } from 'routes/Settings'
|
import { APP_VERSION, RELEASE_URL } from 'routes/Settings'
|
||||||
import { CustomIcon } from 'components/CustomIcon'
|
import { CustomIcon } from 'components/CustomIcon'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
@ -72,10 +72,8 @@ export function LowerRightControls({
|
|||||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||||
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
||||||
<a
|
<a
|
||||||
onClick={openExternalBrowserIfDesktop(
|
onClick={openExternalBrowserIfDesktop(RELEASE_URL)}
|
||||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
href={RELEASE_URL}
|
||||||
)}
|
|
||||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
|
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
|
||||||
|
@ -41,7 +41,10 @@ import {
|
|||||||
angleBetweenInfo,
|
angleBetweenInfo,
|
||||||
applyConstraintAngleBetween,
|
applyConstraintAngleBetween,
|
||||||
} from './Toolbar/SetAngleBetween'
|
} from './Toolbar/SetAngleBetween'
|
||||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
import {
|
||||||
|
applyConstraintAngleLength,
|
||||||
|
applyConstraintLength,
|
||||||
|
} from './Toolbar/setAngleLength'
|
||||||
import {
|
import {
|
||||||
canSweepSelection,
|
canSweepSelection,
|
||||||
handleSelectionBatch,
|
handleSelectionBatch,
|
||||||
@ -51,6 +54,8 @@ import {
|
|||||||
Selections,
|
Selections,
|
||||||
updateSelections,
|
updateSelections,
|
||||||
canLoftSelection,
|
canLoftSelection,
|
||||||
|
canRevolveSelection,
|
||||||
|
canShellSelection,
|
||||||
} from 'lib/selections'
|
} from 'lib/selections'
|
||||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
@ -62,13 +67,15 @@ import {
|
|||||||
getSketchOrientationDetails,
|
getSketchOrientationDetails,
|
||||||
} from 'clientSideScene/sceneEntities'
|
} from 'clientSideScene/sceneEntities'
|
||||||
import {
|
import {
|
||||||
moveValueIntoNewVariablePath,
|
insertNamedConstant,
|
||||||
|
replaceValueAtNodePath,
|
||||||
sketchOnExtrudedFace,
|
sketchOnExtrudedFace,
|
||||||
sketchOnOffsetPlane,
|
sketchOnOffsetPlane,
|
||||||
startSketchOnDefault,
|
startSketchOnDefault,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
|
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
|
doesSceneHaveExtrudedSketch,
|
||||||
doesSceneHaveSweepableSketch,
|
doesSceneHaveSweepableSketch,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
isSingleCursorInPipe,
|
isSingleCursorInPipe,
|
||||||
@ -79,7 +86,6 @@ import toast from 'react-hot-toast'
|
|||||||
import { EditorSelection, Transaction } from '@codemirror/state'
|
import { EditorSelection, Transaction } from '@codemirror/state'
|
||||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
|
|
||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, reportRejection, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { modelingMachineEvent } from 'editor/manager'
|
import { modelingMachineEvent } from 'editor/manager'
|
||||||
@ -570,6 +576,26 @@ export const ModelingMachineProvider = ({
|
|||||||
if (err(canSweep)) return false
|
if (err(canSweep)) return false
|
||||||
return canSweep
|
return canSweep
|
||||||
},
|
},
|
||||||
|
'has valid revolve selection': ({ context: { selectionRanges } }) => {
|
||||||
|
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
||||||
|
// TODO: I believe this guard only allows for extruding a single face at a time
|
||||||
|
const hasNoSelection =
|
||||||
|
selectionRanges.graphSelections.length === 0 ||
|
||||||
|
isRangeBetweenCharacters(selectionRanges) ||
|
||||||
|
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||||
|
|
||||||
|
if (hasNoSelection) {
|
||||||
|
// they have no selection, we should enable the button
|
||||||
|
// so they can select the face through the cmdbar
|
||||||
|
// BUT only if there's extrudable geometry
|
||||||
|
return doesSceneHaveSweepableSketch(kclManager.ast)
|
||||||
|
}
|
||||||
|
if (!isSketchPipe(selectionRanges)) return false
|
||||||
|
|
||||||
|
const canSweep = canRevolveSelection(selectionRanges)
|
||||||
|
if (err(canSweep)) return false
|
||||||
|
return canSweep
|
||||||
|
},
|
||||||
'has valid loft selection': ({ context: { selectionRanges } }) => {
|
'has valid loft selection': ({ context: { selectionRanges } }) => {
|
||||||
const hasNoSelection =
|
const hasNoSelection =
|
||||||
selectionRanges.graphSelections.length === 0 ||
|
selectionRanges.graphSelections.length === 0 ||
|
||||||
@ -585,6 +611,24 @@ export const ModelingMachineProvider = ({
|
|||||||
if (err(canLoft)) return false
|
if (err(canLoft)) return false
|
||||||
return canLoft
|
return canLoft
|
||||||
},
|
},
|
||||||
|
'has valid shell selection': ({
|
||||||
|
context: { selectionRanges },
|
||||||
|
event,
|
||||||
|
}) => {
|
||||||
|
const hasNoSelection =
|
||||||
|
selectionRanges.graphSelections.length === 0 ||
|
||||||
|
isRangeBetweenCharacters(selectionRanges) ||
|
||||||
|
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||||
|
|
||||||
|
if (hasNoSelection) {
|
||||||
|
return doesSceneHaveExtrudedSketch(kclManager.ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canShell = canShellSelection(selectionRanges)
|
||||||
|
console.log('canShellSelection', canShellSelection(selectionRanges))
|
||||||
|
if (err(canShell)) return false
|
||||||
|
return canShell
|
||||||
|
},
|
||||||
'has valid selection for deletion': ({
|
'has valid selection for deletion': ({
|
||||||
context: { selectionRanges },
|
context: { selectionRanges },
|
||||||
}) => {
|
}) => {
|
||||||
@ -869,12 +913,18 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Get length info': fromPromise(
|
astConstrainLength: fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
async ({
|
||||||
const { modifiedAst, pathToNodeMap } =
|
input: { selectionRanges, sketchDetails, lengthValue },
|
||||||
await applyConstraintAngleLength({
|
}) => {
|
||||||
selectionRanges,
|
if (!lengthValue)
|
||||||
})
|
return Promise.reject(new Error('No length value'))
|
||||||
|
const constraintResult = await applyConstraintLength({
|
||||||
|
selectionRanges,
|
||||||
|
length: lengthValue,
|
||||||
|
})
|
||||||
|
if (err(constraintResult)) return Promise.reject(constraintResult)
|
||||||
|
const { modifiedAst, pathToNodeMap } = constraintResult
|
||||||
const pResult = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
if (trap(pResult) || !resultIsOk(pResult))
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
return Promise.reject(new Error('Unexpected compilation error'))
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
@ -1043,38 +1093,88 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Get convert to variable info': fromPromise(
|
'Apply named value constraint': fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails, data } }) => {
|
async ({ input: { selectionRanges, sketchDetails, data } }) => {
|
||||||
if (!sketchDetails)
|
if (!sketchDetails) {
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const { variableName } = await getVarNameModal({
|
}
|
||||||
valueName: data?.variableName || 'var',
|
if (!data) {
|
||||||
})
|
return Promise.reject(new Error('No data from command flow'))
|
||||||
|
}
|
||||||
let pResult = parse(recast(kclManager.ast))
|
let pResult = parse(recast(kclManager.ast))
|
||||||
if (trap(pResult) || !resultIsOk(pResult))
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
return Promise.reject(new Error('Unexpected compilation error'))
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
let parsed = pResult.program
|
let parsed = pResult.program
|
||||||
|
|
||||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
let result: {
|
||||||
moveValueIntoNewVariablePath(
|
modifiedAst: Node<Program>
|
||||||
parsed,
|
pathToReplaced: PathToNode | null
|
||||||
kclManager.programMemory,
|
} = {
|
||||||
data?.pathToNode || [],
|
modifiedAst: parsed,
|
||||||
variableName
|
pathToReplaced: null,
|
||||||
|
}
|
||||||
|
// If the user provided a constant name,
|
||||||
|
// we need to insert the named constant
|
||||||
|
// and then replace the node with the constant's name.
|
||||||
|
if ('variableName' in data.namedValue) {
|
||||||
|
const astAfterReplacement = replaceValueAtNodePath({
|
||||||
|
ast: parsed,
|
||||||
|
pathToNode: data.currentValue.pathToNode,
|
||||||
|
newExpressionString: data.namedValue.variableName,
|
||||||
|
})
|
||||||
|
if (trap(astAfterReplacement)) {
|
||||||
|
return Promise.reject(astAfterReplacement)
|
||||||
|
}
|
||||||
|
const parseResultAfterInsertion = parse(
|
||||||
|
recast(
|
||||||
|
insertNamedConstant({
|
||||||
|
node: astAfterReplacement.modifiedAst,
|
||||||
|
newExpression: data.namedValue,
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
pResult = parse(recast(_modifiedAst))
|
if (
|
||||||
|
trap(parseResultAfterInsertion) ||
|
||||||
|
!resultIsOk(parseResultAfterInsertion)
|
||||||
|
)
|
||||||
|
return Promise.reject(parseResultAfterInsertion)
|
||||||
|
result = {
|
||||||
|
modifiedAst: parseResultAfterInsertion.program,
|
||||||
|
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||||
|
}
|
||||||
|
} else if ('valueText' in data.namedValue) {
|
||||||
|
// If they didn't provide a constant name,
|
||||||
|
// just replace the node with the value.
|
||||||
|
const astAfterReplacement = replaceValueAtNodePath({
|
||||||
|
ast: parsed,
|
||||||
|
pathToNode: data.currentValue.pathToNode,
|
||||||
|
newExpressionString: data.namedValue.valueText,
|
||||||
|
})
|
||||||
|
if (trap(astAfterReplacement)) {
|
||||||
|
return Promise.reject(astAfterReplacement)
|
||||||
|
}
|
||||||
|
// The `replacer` function returns a pathToNode that assumes
|
||||||
|
// an identifier is also being inserted into the AST, creating an off-by-one error.
|
||||||
|
// This corrects that error, but TODO we should fix this upstream
|
||||||
|
// to avoid this kind of error in the future.
|
||||||
|
astAfterReplacement.pathToReplaced[1][0] =
|
||||||
|
(astAfterReplacement.pathToReplaced[1][0] as number) - 1
|
||||||
|
result = astAfterReplacement
|
||||||
|
}
|
||||||
|
|
||||||
|
pResult = parse(recast(result.modifiedAst))
|
||||||
if (trap(pResult) || !resultIsOk(pResult))
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
return Promise.reject(new Error('Unexpected compilation error'))
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
parsed = pResult.program
|
parsed = pResult.program
|
||||||
|
|
||||||
if (trap(parsed)) return Promise.reject(parsed)
|
if (trap(parsed)) return Promise.reject(parsed)
|
||||||
parsed = parsed as Node<Program>
|
parsed = parsed as Node<Program>
|
||||||
if (!pathToReplacedNode)
|
if (!result.pathToReplaced)
|
||||||
return Promise.reject(new Error('No path to replaced node'))
|
return Promise.reject(new Error('No path to replaced node'))
|
||||||
|
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
pathToReplacedNode || [],
|
result.pathToReplaced || [],
|
||||||
parsed,
|
parsed,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -1087,7 +1187,7 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
{ 0: pathToReplacedNode },
|
{ 0: result.pathToReplaced },
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
updatedAst.newAst
|
updatedAst.newAst
|
||||||
)
|
)
|
||||||
@ -1095,7 +1195,7 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode: pathToReplacedNode,
|
updatedPathToNode: result.pathToReplaced,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -76,7 +76,7 @@ export const ModelingPane = ({
|
|||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
{...props}
|
{...props}
|
||||||
title={title && typeof title === 'string' ? title : ''}
|
aria-label={title && typeof title === 'string' ? title : ''}
|
||||||
data-testid={detailsTestId}
|
data-testid={detailsTestId}
|
||||||
id={id}
|
id={id}
|
||||||
className={
|
className={
|
||||||
|
@ -10,7 +10,7 @@ interface AllKeybindingsFieldsProps {}
|
|||||||
|
|
||||||
export const AllKeybindingsFields = forwardRef(
|
export const AllKeybindingsFields = forwardRef(
|
||||||
(
|
(
|
||||||
props: AllKeybindingsFieldsProps,
|
_props: AllKeybindingsFieldsProps,
|
||||||
scrollRef: ForwardedRef<HTMLDivElement>
|
scrollRef: ForwardedRef<HTMLDivElement>
|
||||||
) => {
|
) => {
|
||||||
// This is how we will get the interaction map from the context
|
// This is how we will get the interaction map from the context
|
||||||
@ -25,7 +25,7 @@ export const AllKeybindingsFields = forwardRef(
|
|||||||
.map(([category, categoryItems]) => (
|
.map(([category, categoryItems]) => (
|
||||||
<div className="flex flex-col gap-4 px-2 pr-4">
|
<div className="flex flex-col gap-4 px-2 pr-4">
|
||||||
<h2
|
<h2
|
||||||
id={`category-${category}`}
|
id={`category-${category.replaceAll(/\s/g, '-')}`}
|
||||||
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
|
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
|
||||||
>
|
>
|
||||||
{category}
|
{category}
|
||||||
|
@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
|
|||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import { SettingsFieldInput } from './SettingsFieldInput'
|
import { SettingsFieldInput } from './SettingsFieldInput'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
|
import { APP_VERSION, IS_NIGHTLY, RELEASE_URL } from 'routes/Settings'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import {
|
import {
|
||||||
createAndOpenNewTutorialProject,
|
createAndOpenNewTutorialProject,
|
||||||
@ -246,10 +246,8 @@ export const AllSettingsFields = forwardRef(
|
|||||||
to inject the version from package.json */}
|
to inject the version from package.json */}
|
||||||
App version {APP_VERSION}.{' '}
|
App version {APP_VERSION}.{' '}
|
||||||
<a
|
<a
|
||||||
onClick={openExternalBrowserIfDesktop(
|
onClick={openExternalBrowserIfDesktop(RELEASE_URL)}
|
||||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
href={RELEASE_URL}
|
||||||
)}
|
|
||||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@ -271,7 +269,7 @@ export const AllSettingsFields = forwardRef(
|
|||||||
, and start a discussion if you don't see it! Your feedback will
|
, and start a discussion if you don't see it! Your feedback will
|
||||||
help us prioritize what to build next.
|
help us prioritize what to build next.
|
||||||
</p>
|
</p>
|
||||||
{PACKAGE_NAME.indexOf('-nightly') === -1 && (
|
{!IS_NIGHTLY && (
|
||||||
<p className="max-w-2xl mt-6">
|
<p className="max-w-2xl mt-6">
|
||||||
Want to experience the latest and (hopefully) greatest from our
|
Want to experience the latest and (hopefully) greatest from our
|
||||||
main development branch?{' '}
|
main development branch?{' '}
|
||||||
|
@ -19,7 +19,7 @@ export function KeybindingsSectionsList({
|
|||||||
key={category}
|
key={category}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
scrollRef.current
|
scrollRef.current
|
||||||
?.querySelector(`#category-${category}`)
|
?.querySelector(`#category-${category.replaceAll(/\s/g, '-')}`)
|
||||||
?.scrollIntoView({
|
?.scrollIntoView({
|
||||||
block: 'center',
|
block: 'center',
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
|
@ -22,6 +22,7 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
|||||||
import { normaliseAngle } from '../../lib/utils'
|
import { normaliseAngle } from '../../lib/utils'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { kclManager } from 'lib/singletons'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
|
|
||||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||||
|
|
||||||
@ -63,6 +64,57 @@ export function angleLengthInfo({
|
|||||||
return { enabled, transforms }
|
return { enabled, transforms }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function applyConstraintLength({
|
||||||
|
length,
|
||||||
|
selectionRanges,
|
||||||
|
}: {
|
||||||
|
length: KclCommandValue
|
||||||
|
selectionRanges: Selections
|
||||||
|
}) {
|
||||||
|
const ast = kclManager.ast
|
||||||
|
const angleLength = angleLengthInfo({ selectionRanges })
|
||||||
|
if (err(angleLength)) return angleLength
|
||||||
|
const { transforms } = angleLength
|
||||||
|
|
||||||
|
let distanceExpression: Expr = length.valueAst
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be "constrained", the value must be a binary expression, a named value, or a function call.
|
||||||
|
* If it has a variable name, we need to insert a variable declaration at the correct index.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
'variableName' in length &&
|
||||||
|
length.variableName &&
|
||||||
|
length.insertIndex !== undefined
|
||||||
|
) {
|
||||||
|
const newBody = [...ast.body]
|
||||||
|
newBody.splice(length.insertIndex, 0, length.variableDeclarationAst)
|
||||||
|
ast.body = newBody
|
||||||
|
distanceExpression = createIdentifier(length.variableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isExprBinaryPart(distanceExpression)) {
|
||||||
|
return new Error('Invalid valueNode, is not a BinaryPart')
|
||||||
|
}
|
||||||
|
|
||||||
|
const retval = transformAstSketchLines({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos: transforms,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
referenceSegName: '',
|
||||||
|
forceValueUsedInTransform: distanceExpression,
|
||||||
|
})
|
||||||
|
if (err(retval)) return Promise.reject(retval)
|
||||||
|
|
||||||
|
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst: _modifiedAst,
|
||||||
|
pathToNodeMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function applyConstraintAngleLength({
|
export async function applyConstraintAngleLength({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
angleOrLength = 'setLength',
|
angleOrLength = 'setLength',
|
||||||
|
@ -24,6 +24,8 @@ export function useConvertToVariable(range?: SourceRange) {
|
|||||||
}, [enable])
|
}, [enable])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Return early if there are no selection ranges for whatever reason
|
||||||
|
if (!context.selectionRanges) return
|
||||||
const parsed = ast
|
const parsed = ast
|
||||||
|
|
||||||
const meta = isNodeSafeToReplace(
|
const meta = isNodeSafeToReplace(
|
||||||
|
@ -45,6 +45,7 @@ import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||||
|
|
||||||
export function startSketchOnDefault(
|
export function startSketchOnDefault(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
@ -590,6 +591,25 @@ export function addOffsetPlane({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a modified clone of an AST with a named constant inserted into the body
|
||||||
|
*/
|
||||||
|
export function insertNamedConstant({
|
||||||
|
node,
|
||||||
|
newExpression,
|
||||||
|
}: {
|
||||||
|
node: Node<Program>
|
||||||
|
newExpression: KclExpressionWithVariable
|
||||||
|
}): Node<Program> {
|
||||||
|
const ast = structuredClone(node)
|
||||||
|
ast.body.splice(
|
||||||
|
newExpression.insertIndex,
|
||||||
|
0,
|
||||||
|
newExpression.variableDeclarationAst
|
||||||
|
)
|
||||||
|
return ast
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modify the AST to create a new sketch using the variable declaration
|
* Modify the AST to create a new sketch using the variable declaration
|
||||||
* of an offset plane. The new sketch just has to come after the offset
|
* of an offset plane. The new sketch just has to come after the offset
|
||||||
@ -933,6 +953,31 @@ export function giveSketchFnCallTag(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a
|
||||||
|
*/
|
||||||
|
export function replaceValueAtNodePath({
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
newExpressionString,
|
||||||
|
}: {
|
||||||
|
ast: Node<Program>
|
||||||
|
pathToNode: PathToNode
|
||||||
|
newExpressionString: string
|
||||||
|
}) {
|
||||||
|
const replaceCheckResult = isNodeSafeToReplacePath(ast, pathToNode)
|
||||||
|
if (err(replaceCheckResult)) {
|
||||||
|
return replaceCheckResult
|
||||||
|
}
|
||||||
|
const { isSafe, value, replacer } = replaceCheckResult
|
||||||
|
|
||||||
|
if (!isSafe || value.type === 'Identifier') {
|
||||||
|
return new Error('Not safe to replace')
|
||||||
|
}
|
||||||
|
|
||||||
|
return replacer(ast, newExpressionString)
|
||||||
|
}
|
||||||
|
|
||||||
export function moveValueIntoNewVariablePath(
|
export function moveValueIntoNewVariablePath(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||||
import { createLiteral } from 'lang/modifyAst'
|
import { createLiteral } from 'lang/modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
@ -118,13 +118,8 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
|||||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||||
true,
|
true,
|
||||||
]
|
]
|
||||||
const selection: Selections = {
|
const selection: Selection = {
|
||||||
graphSelections: [
|
codeRef: codeRefFromRange(segmentRange, ast),
|
||||||
{
|
|
||||||
codeRef: codeRefFromRange(segmentRange, ast),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
otherSelections: [],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeAst and artifactGraph
|
// executeAst and artifactGraph
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
sketchLineHelperMap,
|
sketchLineHelperMap,
|
||||||
} from '../std/sketch'
|
} from '../std/sketch'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import { KclCommandValue } from 'lib/commandTypes'
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
import {
|
import {
|
||||||
Artifact,
|
Artifact,
|
||||||
@ -99,14 +99,9 @@ export function modifyAstWithEdgeTreatmentAndTag(
|
|||||||
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
||||||
|
|
||||||
for (const selection of selections.graphSelections) {
|
for (const selection of selections.graphSelections) {
|
||||||
const singleSelection = {
|
|
||||||
graphSelections: [selection],
|
|
||||||
otherSelections: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = getPathToExtrudeForSegmentSelection(
|
const result = getPathToExtrudeForSegmentSelection(
|
||||||
clonedAstForGetExtrude,
|
clonedAstForGetExtrude,
|
||||||
singleSelection,
|
selection,
|
||||||
artifactGraph
|
artifactGraph
|
||||||
)
|
)
|
||||||
if (err(result)) return result
|
if (err(result)) return result
|
||||||
@ -259,12 +254,12 @@ function insertParametersIntoAst(
|
|||||||
|
|
||||||
export function getPathToExtrudeForSegmentSelection(
|
export function getPathToExtrudeForSegmentSelection(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
selection: Selections,
|
selection: Selection,
|
||||||
artifactGraph: ArtifactGraph
|
artifactGraph: ArtifactGraph
|
||||||
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
||||||
const pathToSegmentNode = getNodePathFromSourceRange(
|
const pathToSegmentNode = getNodePathFromSourceRange(
|
||||||
ast,
|
ast,
|
||||||
selection.graphSelections[0]?.codeRef?.range
|
selection.codeRef?.range
|
||||||
)
|
)
|
||||||
|
|
||||||
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
||||||
@ -308,7 +303,7 @@ async function updateAstAndFocus(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mutateAstWithTagForSketchSegment(
|
export function mutateAstWithTagForSketchSegment(
|
||||||
astClone: Node<Program>,
|
astClone: Node<Program>,
|
||||||
pathToSegmentNode: PathToNode
|
pathToSegmentNode: PathToNode
|
||||||
): { modifiedAst: Program; tag: string } | Error {
|
): { modifiedAst: Program; tag: string } | Error {
|
||||||
@ -340,7 +335,7 @@ function mutateAstWithTagForSketchSegment(
|
|||||||
return { modifiedAst: astClone, tag }
|
return { modifiedAst: astClone, tag }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEdgeTagCall(
|
export function getEdgeTagCall(
|
||||||
tag: string,
|
tag: string,
|
||||||
artifact: Artifact
|
artifact: Artifact
|
||||||
): Node<Identifier | CallExpression> {
|
): Node<Identifier | CallExpression> {
|
||||||
|
154
src/lang/modifyAst/addRevolve.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { err } from 'lib/trap'
|
||||||
|
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||||
|
import {
|
||||||
|
Program,
|
||||||
|
PathToNode,
|
||||||
|
Expr,
|
||||||
|
CallExpression,
|
||||||
|
PipeExpression,
|
||||||
|
VariableDeclarator,
|
||||||
|
} from 'lang/wasm'
|
||||||
|
import { Selections } from 'lib/selections'
|
||||||
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
import {
|
||||||
|
createLiteral,
|
||||||
|
createCallExpressionStdLib,
|
||||||
|
createObjectExpression,
|
||||||
|
createIdentifier,
|
||||||
|
createPipeExpression,
|
||||||
|
findUniqueName,
|
||||||
|
createVariableDeclaration,
|
||||||
|
} from 'lang/modifyAst'
|
||||||
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import {
|
||||||
|
mutateAstWithTagForSketchSegment,
|
||||||
|
getEdgeTagCall,
|
||||||
|
} from 'lang/modifyAst/addEdgeTreatment'
|
||||||
|
export function revolveSketch(
|
||||||
|
ast: Node<Program>,
|
||||||
|
pathToSketchNode: PathToNode,
|
||||||
|
shouldPipe = false,
|
||||||
|
angle: Expr = createLiteral(4),
|
||||||
|
axis: Selections
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
modifiedAst: Node<Program>
|
||||||
|
pathToSketchNode: PathToNode
|
||||||
|
pathToRevolveArg: PathToNode
|
||||||
|
}
|
||||||
|
| Error {
|
||||||
|
const clonedAst = structuredClone(ast)
|
||||||
|
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
||||||
|
if (err(sketchNode)) return sketchNode
|
||||||
|
|
||||||
|
// testing code
|
||||||
|
const pathToAxisSelection = getNodePathFromSourceRange(
|
||||||
|
clonedAst,
|
||||||
|
axis.graphSelections[0]?.codeRef.range
|
||||||
|
)
|
||||||
|
|
||||||
|
const lineNode = getNodeFromPath<CallExpression>(
|
||||||
|
clonedAst,
|
||||||
|
pathToAxisSelection,
|
||||||
|
'CallExpression'
|
||||||
|
)
|
||||||
|
if (err(lineNode)) return lineNode
|
||||||
|
|
||||||
|
// TODO Kevin: What if |> close(%)?
|
||||||
|
// TODO Kevin: What if opposite edge
|
||||||
|
// TODO Kevin: What if the edge isn't planar to the sketch?
|
||||||
|
// TODO Kevin: add a tag.
|
||||||
|
const tagResult = mutateAstWithTagForSketchSegment(
|
||||||
|
clonedAst,
|
||||||
|
pathToAxisSelection
|
||||||
|
)
|
||||||
|
|
||||||
|
// Have the tag whether it is already created or a new one is generated
|
||||||
|
if (err(tagResult)) return tagResult
|
||||||
|
const { tag } = tagResult
|
||||||
|
|
||||||
|
/* Original Code */
|
||||||
|
const { node: sketchExpression } = sketchNode
|
||||||
|
|
||||||
|
// determine if sketchExpression is in a pipeExpression or not
|
||||||
|
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
|
||||||
|
clonedAst,
|
||||||
|
pathToSketchNode,
|
||||||
|
'PipeExpression'
|
||||||
|
)
|
||||||
|
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
|
||||||
|
const { node: sketchPipeExpression } = sketchPipeExpressionNode
|
||||||
|
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
|
||||||
|
|
||||||
|
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
|
||||||
|
clonedAst,
|
||||||
|
pathToSketchNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
|
||||||
|
const {
|
||||||
|
node: sketchVariableDeclarator,
|
||||||
|
shallowPath: sketchPathToDecleration,
|
||||||
|
} = sketchVariableDeclaratorNode
|
||||||
|
|
||||||
|
const axisSelection = axis?.graphSelections[0]?.artifact
|
||||||
|
|
||||||
|
if (!axisSelection) return new Error('Axis selection is missing.')
|
||||||
|
|
||||||
|
const revolveCall = createCallExpressionStdLib('revolve', [
|
||||||
|
createObjectExpression({
|
||||||
|
angle: angle,
|
||||||
|
axis: getEdgeTagCall(tag, axisSelection),
|
||||||
|
}),
|
||||||
|
createIdentifier(sketchVariableDeclarator.id.name),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (shouldPipe) {
|
||||||
|
const pipeChain = createPipeExpression(
|
||||||
|
isInPipeExpression
|
||||||
|
? [...sketchPipeExpression.body, revolveCall]
|
||||||
|
: [sketchExpression as any, revolveCall]
|
||||||
|
)
|
||||||
|
|
||||||
|
sketchVariableDeclarator.init = pipeChain
|
||||||
|
const pathToRevolveArg: PathToNode = [
|
||||||
|
...sketchPathToDecleration,
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
['body', ''],
|
||||||
|
[pipeChain.body.length - 1, 'index'],
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst: clonedAst,
|
||||||
|
pathToSketchNode,
|
||||||
|
pathToRevolveArg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're not creating a pipe expression,
|
||||||
|
// but rather a separate constant for the extrusion
|
||||||
|
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
|
||||||
|
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
|
||||||
|
const sketchIndexInPathToNode =
|
||||||
|
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
|
||||||
|
if (typeof sketchIndexInBody !== 'number')
|
||||||
|
return new Error('expected sketchIndexInBody to be a number')
|
||||||
|
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||||
|
|
||||||
|
const pathToRevolveArg: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[sketchIndexInBody + 1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
modifiedAst: clonedAst,
|
||||||
|
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
|
||||||
|
pathToRevolveArg,
|
||||||
|
}
|
||||||
|
}
|
123
src/lang/modifyAst/addShell.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { ArtifactGraph } from 'lang/std/artifactGraph'
|
||||||
|
import { Selections } from 'lib/selections'
|
||||||
|
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
|
||||||
|
import { Program } from 'wasm-lib/kcl/bindings/Program'
|
||||||
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||||
|
import {
|
||||||
|
getPathToExtrudeForSegmentSelection,
|
||||||
|
mutateAstWithTagForSketchSegment,
|
||||||
|
} from './addEdgeTreatment'
|
||||||
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import { err } from 'lib/trap'
|
||||||
|
import {
|
||||||
|
createLiteral,
|
||||||
|
createIdentifier,
|
||||||
|
findUniqueName,
|
||||||
|
createCallExpressionStdLib,
|
||||||
|
createObjectExpression,
|
||||||
|
createArrayExpression,
|
||||||
|
createVariableDeclaration,
|
||||||
|
} from 'lang/modifyAst'
|
||||||
|
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||||
|
|
||||||
|
export function addShell({
|
||||||
|
node,
|
||||||
|
selection,
|
||||||
|
artifactGraph,
|
||||||
|
thickness,
|
||||||
|
}: {
|
||||||
|
node: Node<Program>
|
||||||
|
selection: Selections
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
thickness: Expr
|
||||||
|
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||||
|
const modifiedAst = structuredClone(node)
|
||||||
|
|
||||||
|
// Look up the corresponding extrude
|
||||||
|
const clonedAstForGetExtrude = structuredClone(modifiedAst)
|
||||||
|
|
||||||
|
const expressions: Expr[] = []
|
||||||
|
let pathToExtrudeNode: PathToNode | undefined = undefined
|
||||||
|
for (const graphSelection of selection.graphSelections) {
|
||||||
|
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
|
||||||
|
clonedAstForGetExtrude,
|
||||||
|
graphSelection,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
|
if (err(extrudeLookupResult)) {
|
||||||
|
return new Error("Couldn't find extrude")
|
||||||
|
}
|
||||||
|
|
||||||
|
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
|
||||||
|
// Get the sketch ref from the selection
|
||||||
|
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
|
||||||
|
// We must find a technique for these situations that is robust to intermediate declarations
|
||||||
|
const sketchNode = getNodeFromPath<VariableDeclarator>(
|
||||||
|
modifiedAst,
|
||||||
|
graphSelection.codeRef.pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(sketchNode)) {
|
||||||
|
return sketchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedArtifact = graphSelection.artifact
|
||||||
|
if (!selectedArtifact) {
|
||||||
|
return new Error('Bad artifact')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check on the selection, and handle the wall vs cap casees
|
||||||
|
let expr: Expr
|
||||||
|
if (selectedArtifact.type === 'cap') {
|
||||||
|
expr = createLiteral(selectedArtifact.subType)
|
||||||
|
} else if (selectedArtifact.type === 'wall') {
|
||||||
|
const tagResult = mutateAstWithTagForSketchSegment(
|
||||||
|
modifiedAst,
|
||||||
|
extrudeLookupResult.pathToSegmentNode
|
||||||
|
)
|
||||||
|
if (err(tagResult)) return tagResult
|
||||||
|
const { tag } = tagResult
|
||||||
|
expr = createIdentifier(tag)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expressions.push(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pathToExtrudeNode) return new Error('No extrude found')
|
||||||
|
|
||||||
|
const extrudeNode = getNodeFromPath<VariableDeclarator>(
|
||||||
|
modifiedAst,
|
||||||
|
pathToExtrudeNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(extrudeNode)) {
|
||||||
|
return extrudeNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
|
||||||
|
const shell = createCallExpressionStdLib('shell', [
|
||||||
|
createObjectExpression({
|
||||||
|
faces: createArrayExpression(expressions),
|
||||||
|
thickness,
|
||||||
|
}),
|
||||||
|
createIdentifier(extrudeNode.node.id.name),
|
||||||
|
])
|
||||||
|
const declaration = createVariableDeclaration(name, shell)
|
||||||
|
|
||||||
|
// TODO: check if we should append at the end like here or right after the extrude
|
||||||
|
modifiedAst.body.push(declaration)
|
||||||
|
const pathToNode: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[modifiedAst.body.length - 1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
modifiedAst,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ import {
|
|||||||
doesSceneHaveSweepableSketch,
|
doesSceneHaveSweepableSketch,
|
||||||
traverse,
|
traverse,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
|
doesSceneHaveExtrudedSketch,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import {
|
import {
|
||||||
@ -654,6 +655,38 @@ extrude001 = extrude(10, sketch001)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Testing doesSceneHaveExtrudedSketch', () => {
|
||||||
|
it('finds extruded sketch as variable', async () => {
|
||||||
|
const exampleCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
|
extrude001 = extrude(1, sketch001)
|
||||||
|
`
|
||||||
|
const ast = assertParse(exampleCode)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||||
|
expect(extrudable).toBeTruthy()
|
||||||
|
})
|
||||||
|
it('finds extruded sketch in pipe', async () => {
|
||||||
|
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
|
|> extrude(1, %)
|
||||||
|
`
|
||||||
|
const ast = assertParse(exampleCode)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||||
|
expect(extrudable).toBeTruthy()
|
||||||
|
})
|
||||||
|
it('finds no extrusion with sketch only', async () => {
|
||||||
|
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
|
`
|
||||||
|
const ast = assertParse(exampleCode)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||||
|
expect(extrudable).toBeFalsy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Testing traverse and pathToNode', () => {
|
describe('Testing traverse and pathToNode', () => {
|
||||||
it.each([
|
it.each([
|
||||||
['basic', '2.73'],
|
['basic', '2.73'],
|
||||||
|
@ -1064,6 +1064,35 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
|
|||||||
return Object.keys(theMap).length >= count
|
return Object.keys(theMap).length >= count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doesSceneHaveExtrudedSketch(ast: Node<Program>) {
|
||||||
|
const theMap: any = {}
|
||||||
|
traverse(ast as any, {
|
||||||
|
enter(node) {
|
||||||
|
if (
|
||||||
|
node.type === 'VariableDeclarator' &&
|
||||||
|
node.init?.type === 'PipeExpression'
|
||||||
|
) {
|
||||||
|
for (const pipe of node.init.body) {
|
||||||
|
if (
|
||||||
|
pipe.type === 'CallExpression' &&
|
||||||
|
pipe.callee.name === 'extrude'
|
||||||
|
) {
|
||||||
|
theMap[node.id.name] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
node.type === 'CallExpression' &&
|
||||||
|
node.callee.name === 'extrude' &&
|
||||||
|
node.arguments[1]?.type === 'Identifier'
|
||||||
|
) {
|
||||||
|
theMap[node.moduleId] = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return Object.keys(theMap).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
export function getObjExprProperty(
|
export function getObjExprProperty(
|
||||||
node: ObjectExpression,
|
node: ObjectExpression,
|
||||||
propName: string
|
propName: string
|
||||||
|
@ -871,3 +871,15 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
|
|||||||
pathToNode: getNodePathFromSourceRange(ast, range),
|
pathToNode: getNodePathFromSourceRange(ast, range),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSolid2D(artifact: Artifact): artifact is solid2D {
|
||||||
|
return (artifact as solid2D).pathId !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSegment(artifact: Artifact): artifact is SegmentArtifact {
|
||||||
|
return (artifact as SegmentArtifact).pathId !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSweep(artifact: Artifact): artifact is SweepArtifact {
|
||||||
|
return (artifact as SweepArtifact).pathId !== undefined
|
||||||
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { parse, ParseResult } from './wasm'
|
import { initPromise, parse, ParseResult } from './wasm'
|
||||||
import { enginelessExecutor } from 'lib/testHelpers'
|
import { enginelessExecutor } from 'lib/testHelpers'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { Program } from '../wasm-lib/kcl/bindings/Program'
|
import { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
it('can execute parsed AST', async () => {
|
it('can execute parsed AST', async () => {
|
||||||
const code = `x = 1
|
const code = `x = 1
|
||||||
// A comment.`
|
// A comment.`
|
||||||
|
@ -32,8 +32,14 @@ export function mouseControlsToCameraSystem(
|
|||||||
mouseControl: MouseControlType | undefined
|
mouseControl: MouseControlType | undefined
|
||||||
): CameraSystem | undefined {
|
): CameraSystem | undefined {
|
||||||
switch (mouseControl) {
|
switch (mouseControl) {
|
||||||
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
|
// @ts-ignore: TS2678
|
||||||
|
case 'kittycad':
|
||||||
case 'kitty_cad':
|
case 'kitty_cad':
|
||||||
return 'KittyCAD'
|
return 'KittyCAD'
|
||||||
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
|
// @ts-ignore: TS2678
|
||||||
|
case 'onshape':
|
||||||
case 'on_shape':
|
case 'on_shape':
|
||||||
return 'OnShape'
|
return 'OnShape'
|
||||||
case 'trackpad_friendly':
|
case 'trackpad_friendly':
|
||||||
@ -44,6 +50,9 @@ export function mouseControlsToCameraSystem(
|
|||||||
return 'NX'
|
return 'NX'
|
||||||
case 'creo':
|
case 'creo':
|
||||||
return 'Creo'
|
return 'Creo'
|
||||||
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
|
// @ts-ignore: TS2678
|
||||||
|
case 'autocad':
|
||||||
case 'auto_cad':
|
case 'auto_cad':
|
||||||
return 'AutoCAD'
|
return 'AutoCAD'
|
||||||
default:
|
default:
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
|
||||||
|
import { transformAstSketchLines } from 'lang/std/sketchcombos'
|
||||||
|
import { PathToNode } from 'lang/wasm'
|
||||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
||||||
import { components } from 'lib/machine-api'
|
import { components } from 'lib/machine-api'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { kclManager } from 'lib/singletons'
|
||||||
|
import { err } from 'lib/trap'
|
||||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||||
|
import { revolveAxisValidator } from './validators'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
type OutputTypeKey = OutputFormat['type']
|
type OutputTypeKey = OutputFormat['type']
|
||||||
@ -34,9 +40,14 @@ export type ModelingCommandSchema = {
|
|||||||
Loft: {
|
Loft: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
}
|
}
|
||||||
|
Shell: {
|
||||||
|
selection: Selections
|
||||||
|
thickness: KclCommandValue
|
||||||
|
}
|
||||||
Revolve: {
|
Revolve: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
angle: KclCommandValue
|
angle: KclCommandValue
|
||||||
|
axis: Selections
|
||||||
}
|
}
|
||||||
Fillet: {
|
Fillet: {
|
||||||
// todo
|
// todo
|
||||||
@ -50,6 +61,18 @@ export type ModelingCommandSchema = {
|
|||||||
'change tool': {
|
'change tool': {
|
||||||
tool: SketchTool
|
tool: SketchTool
|
||||||
}
|
}
|
||||||
|
'Constrain length': {
|
||||||
|
selection: Selections
|
||||||
|
length: KclCommandValue
|
||||||
|
}
|
||||||
|
'Constrain with named value': {
|
||||||
|
currentValue: {
|
||||||
|
valueText: string
|
||||||
|
pathToNode: PathToNode
|
||||||
|
variableName: string
|
||||||
|
}
|
||||||
|
namedValue: KclCommandValue
|
||||||
|
}
|
||||||
'Text-to-CAD': {
|
'Text-to-CAD': {
|
||||||
prompt: string
|
prompt: string
|
||||||
}
|
}
|
||||||
@ -277,6 +300,25 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Shell: {
|
||||||
|
description: 'Hollow out a 3D solid.',
|
||||||
|
icon: 'shell',
|
||||||
|
needsReview: true,
|
||||||
|
args: {
|
||||||
|
selection: {
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['cap', 'wall'],
|
||||||
|
multiple: true,
|
||||||
|
required: true,
|
||||||
|
skip: false,
|
||||||
|
},
|
||||||
|
thickness: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
defaultValue: KCL_DEFAULT_LENGTH,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
||||||
Revolve: {
|
Revolve: {
|
||||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||||
@ -290,6 +332,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
},
|
},
|
||||||
|
axis: {
|
||||||
|
required: true,
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||||
|
multiple: false,
|
||||||
|
validation: revolveAxisValidator,
|
||||||
|
},
|
||||||
angle: {
|
angle: {
|
||||||
inputType: 'kcl',
|
inputType: 'kcl',
|
||||||
defaultValue: KCL_DEFAULT_DEGREE,
|
defaultValue: KCL_DEFAULT_DEGREE,
|
||||||
@ -337,6 +386,88 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'Constrain length': {
|
||||||
|
description: 'Constrain the length of one or more segments.',
|
||||||
|
icon: 'dimension',
|
||||||
|
args: {
|
||||||
|
selection: {
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['segment'],
|
||||||
|
multiple: false,
|
||||||
|
required: true,
|
||||||
|
skip: true,
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
required: true,
|
||||||
|
createVariableByDefault: true,
|
||||||
|
defaultValue(_, machineContext) {
|
||||||
|
const selectionRanges = machineContext?.selectionRanges
|
||||||
|
if (!selectionRanges) return KCL_DEFAULT_LENGTH
|
||||||
|
const angleLength = angleLengthInfo({
|
||||||
|
selectionRanges,
|
||||||
|
angleOrLength: 'setLength',
|
||||||
|
})
|
||||||
|
if (err(angleLength)) return KCL_DEFAULT_LENGTH
|
||||||
|
const { transforms } = angleLength
|
||||||
|
|
||||||
|
// QUESTION: is it okay to reference kclManager here? will its state be up to date?
|
||||||
|
const sketched = transformAstSketchLines({
|
||||||
|
ast: structuredClone(kclManager.ast),
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos: transforms,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
referenceSegName: '',
|
||||||
|
})
|
||||||
|
if (err(sketched)) return KCL_DEFAULT_LENGTH
|
||||||
|
const { valueUsedInTransform } = sketched
|
||||||
|
return valueUsedInTransform?.toString() || KCL_DEFAULT_LENGTH
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Constrain with named value': {
|
||||||
|
description: 'Constrain a value by making it a named constant.',
|
||||||
|
icon: 'make-variable',
|
||||||
|
args: {
|
||||||
|
currentValue: {
|
||||||
|
description:
|
||||||
|
'Path to the node in the AST to constrain. This is never shown to the user.',
|
||||||
|
inputType: 'text',
|
||||||
|
required: false,
|
||||||
|
skip: true,
|
||||||
|
},
|
||||||
|
namedValue: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
required: true,
|
||||||
|
createVariableByDefault: true,
|
||||||
|
variableName(commandBarContext, machineContext) {
|
||||||
|
const { currentValue } = commandBarContext.argumentsToSubmit
|
||||||
|
if (
|
||||||
|
!currentValue ||
|
||||||
|
!(currentValue instanceof Object) ||
|
||||||
|
!('variableName' in currentValue) ||
|
||||||
|
typeof currentValue.variableName !== 'string'
|
||||||
|
) {
|
||||||
|
return 'value'
|
||||||
|
}
|
||||||
|
return currentValue.variableName
|
||||||
|
},
|
||||||
|
defaultValue: (commandBarContext) => {
|
||||||
|
const { currentValue } = commandBarContext.argumentsToSubmit
|
||||||
|
if (
|
||||||
|
!currentValue ||
|
||||||
|
!(currentValue instanceof Object) ||
|
||||||
|
!('valueText' in currentValue) ||
|
||||||
|
typeof currentValue.valueText !== 'string'
|
||||||
|
) {
|
||||||
|
return KCL_DEFAULT_LENGTH
|
||||||
|
}
|
||||||
|
return currentValue.valueText
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
'Text-to-CAD': {
|
'Text-to-CAD': {
|
||||||
description: 'Use the Zoo Text-to-CAD API to generate part starters.',
|
description: 'Use the Zoo Text-to-CAD API to generate part starters.',
|
||||||
icon: 'chat',
|
icon: 'chat',
|
||||||
|
107
src/lib/commandBarConfigs/validators.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
|
import { Selections } from 'lib/selections'
|
||||||
|
import { isSolid2D, isSegment, isSweep } from 'lang/std/artifactGraph'
|
||||||
|
|
||||||
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
|
for (let tries = 0; tries < numberOfRetries; tries++) {
|
||||||
|
try {
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'disable_dry_run' },
|
||||||
|
})
|
||||||
|
// Exit out since the command was successful
|
||||||
|
return
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
console.error('disable_dry_run failed. This is bad!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a callback function and wraps it around enable_dry_run and disable_dry_run
|
||||||
|
export const dryRunWrapper = async (callback: () => Promise<any>) => {
|
||||||
|
// Gotcha: What about race conditions?
|
||||||
|
try {
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'enable_dry_run' },
|
||||||
|
})
|
||||||
|
const result = await callback()
|
||||||
|
return result
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
await disableDryRunWithRetry(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelections(selections: unknown): selections is Selections {
|
||||||
|
return (
|
||||||
|
(selections as Selections).graphSelections !== undefined &&
|
||||||
|
(selections as Selections).otherSelections !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const revolveAxisValidator = async ({
|
||||||
|
data,
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
data: { [key: string]: Selections }
|
||||||
|
context: CommandBarContext
|
||||||
|
}): Promise<boolean | string> => {
|
||||||
|
if (!isSelections(context.argumentsToSubmit.selection)) {
|
||||||
|
return 'Unable to revolve, selections are missing'
|
||||||
|
}
|
||||||
|
const artifact =
|
||||||
|
context.argumentsToSubmit.selection.graphSelections[0].artifact
|
||||||
|
|
||||||
|
if (!artifact) {
|
||||||
|
return 'Unable to revolve, sketch not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(isSolid2D(artifact) || isSegment(artifact) || isSweep(artifact))) {
|
||||||
|
return 'Unable to revolve, sketch has no path'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketchSelection = artifact.pathId
|
||||||
|
let edgeSelection = data.axis.graphSelections[0].artifact?.id
|
||||||
|
|
||||||
|
if (!sketchSelection) {
|
||||||
|
return 'Unable to revolve, sketch is missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!edgeSelection) {
|
||||||
|
return 'Unable to revolve, edge is missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
const angleInDegrees: Models['Angle_type'] = {
|
||||||
|
unit: 'degrees',
|
||||||
|
value: 360,
|
||||||
|
}
|
||||||
|
|
||||||
|
const revolveAboutEdgeCommand = async () => {
|
||||||
|
return await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'revolve_about_edge',
|
||||||
|
angle: angleInDegrees,
|
||||||
|
edge_id: edgeSelection,
|
||||||
|
target: sketchSelection,
|
||||||
|
tolerance: 0.0001,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
|
||||||
|
if (attemptRevolve?.success) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// return error message for the toast
|
||||||
|
return 'Unable to revolve with selected axis'
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { ReactNode } from 'react'
|
|||||||
import { MachineManager } from 'components/MachineManagerProvider'
|
import { MachineManager } from 'components/MachineManagerProvider'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { Artifact } from 'lang/std/artifactGraph'
|
import { Artifact } from 'lang/std/artifactGraph'
|
||||||
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
type Icon = CustomIconName
|
type Icon = CustomIconName
|
||||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
||||||
const INPUT_TYPES = [
|
const INPUT_TYPES = [
|
||||||
@ -147,8 +147,30 @@ export type CommandArgumentConfig<
|
|||||||
inputType: 'selection'
|
inputType: 'selection'
|
||||||
selectionTypes: Artifact['type'][]
|
selectionTypes: Artifact['type'][]
|
||||||
multiple: boolean
|
multiple: boolean
|
||||||
|
validation?: ({
|
||||||
|
data,
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
data: any
|
||||||
|
context: CommandBarContext
|
||||||
|
}) => Promise<boolean | string>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
inputType: 'kcl'
|
||||||
|
createVariableByDefault?: boolean
|
||||||
|
variableName?:
|
||||||
|
| string
|
||||||
|
| ((
|
||||||
|
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||||
|
machineContext?: C
|
||||||
|
) => string)
|
||||||
|
defaultValue?:
|
||||||
|
| string
|
||||||
|
| ((
|
||||||
|
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||||
|
machineContext?: C
|
||||||
|
) => string)
|
||||||
}
|
}
|
||||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
|
||||||
| {
|
| {
|
||||||
inputType: 'string'
|
inputType: 'string'
|
||||||
defaultValue?:
|
defaultValue?:
|
||||||
@ -221,8 +243,30 @@ export type CommandArgument<
|
|||||||
inputType: 'selection'
|
inputType: 'selection'
|
||||||
selectionTypes: Artifact['type'][]
|
selectionTypes: Artifact['type'][]
|
||||||
multiple: boolean
|
multiple: boolean
|
||||||
|
validation?: ({
|
||||||
|
data,
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
data: any
|
||||||
|
context: CommandBarContext
|
||||||
|
}) => Promise<boolean | string>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
inputType: 'kcl'
|
||||||
|
createVariableByDefault?: boolean
|
||||||
|
variableName?:
|
||||||
|
| string
|
||||||
|
| ((
|
||||||
|
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||||
|
machineContext?: ContextFrom<T>
|
||||||
|
) => string)
|
||||||
|
defaultValue?:
|
||||||
|
| string
|
||||||
|
| ((
|
||||||
|
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||||
|
machineContext?: ContextFrom<T>
|
||||||
|
) => string)
|
||||||
}
|
}
|
||||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
|
|
||||||
| {
|
| {
|
||||||
inputType: 'string'
|
inputType: 'string'
|
||||||
defaultValue?:
|
defaultValue?:
|
||||||
|
@ -53,6 +53,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
|||||||
SKETCH: 'sketch',
|
SKETCH: 'sketch',
|
||||||
EXTRUDE: 'extrude',
|
EXTRUDE: 'extrude',
|
||||||
LOFT: 'loft',
|
LOFT: 'loft',
|
||||||
|
SHELL: 'shell',
|
||||||
SEGMENT: 'seg',
|
SEGMENT: 'seg',
|
||||||
REVOLVE: 'revolve',
|
REVOLVE: 'revolve',
|
||||||
PLANE: 'plane',
|
PLANE: 'plane',
|
||||||
@ -110,3 +111,10 @@ export const KCL_SAMPLES_MANIFEST_URLS = {
|
|||||||
|
|
||||||
/** Toast id for the app auto-updater toast */
|
/** Toast id for the app auto-updater toast */
|
||||||
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'
|
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'
|
||||||
|
|
||||||
|
/** Local sketch axis values in KCL for operations, it could either be 'X' or 'Y' */
|
||||||
|
export const KCL_AXIS_X = 'X'
|
||||||
|
export const KCL_AXIS_Y = 'Y'
|
||||||
|
export const KCL_AXIS_NEG_X = '-X'
|
||||||
|
export const KCL_AXIS_NEG_Y = '-Y'
|
||||||
|
export const KCL_DEFAULT_AXIS = 'X'
|
||||||
|
@ -155,6 +155,8 @@ export function buildCommandArgument<
|
|||||||
context: ContextFrom<T>,
|
context: ContextFrom<T>,
|
||||||
machineActor: Actor<T>
|
machineActor: Actor<T>
|
||||||
): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
|
): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
|
||||||
|
// GOTCHA: modelingCommandConfig is not a 1:1 mapping to this baseCommandArgument
|
||||||
|
// You need to manually add key/value pairs here.
|
||||||
const baseCommandArgument = {
|
const baseCommandArgument = {
|
||||||
description: arg.description,
|
description: arg.description,
|
||||||
required: arg.required,
|
required: arg.required,
|
||||||
@ -181,10 +183,13 @@ export function buildCommandArgument<
|
|||||||
...baseCommandArgument,
|
...baseCommandArgument,
|
||||||
multiple: arg.multiple,
|
multiple: arg.multiple,
|
||||||
selectionTypes: arg.selectionTypes,
|
selectionTypes: arg.selectionTypes,
|
||||||
|
validation: arg.validation,
|
||||||
} satisfies CommandArgument<O, T> & { inputType: 'selection' }
|
} satisfies CommandArgument<O, T> & { inputType: 'selection' }
|
||||||
} else if (arg.inputType === 'kcl') {
|
} else if (arg.inputType === 'kcl') {
|
||||||
return {
|
return {
|
||||||
inputType: arg.inputType,
|
inputType: arg.inputType,
|
||||||
|
createVariableByDefault: arg.createVariableByDefault,
|
||||||
|
variableName: arg.variableName,
|
||||||
defaultValue: arg.defaultValue,
|
defaultValue: arg.defaultValue,
|
||||||
...baseCommandArgument,
|
...baseCommandArgument,
|
||||||
} satisfies CommandArgument<O, T> & { inputType: 'kcl' }
|
} satisfies CommandArgument<O, T> & { inputType: 'kcl' }
|
||||||
|
@ -569,6 +569,17 @@ export function canSweepSelection(selection: Selections) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canRevolveSelection(selection: Selections) {
|
||||||
|
const commonNodes = selection.graphSelections.map((_, i) =>
|
||||||
|
buildCommonNodeFromSelection(selection, i)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
!!isSketchPipe(selection) &&
|
||||||
|
(commonNodes.every((n) => nodeHasClose(n)) ||
|
||||||
|
commonNodes.every((n) => nodeHasCircle(n)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function canLoftSelection(selection: Selections) {
|
export function canLoftSelection(selection: Selections) {
|
||||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
const commonNodes = selection.graphSelections.map((_, i) =>
|
||||||
buildCommonNodeFromSelection(selection, i)
|
buildCommonNodeFromSelection(selection, i)
|
||||||
@ -585,6 +596,17 @@ export function canLoftSelection(selection: Selections) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canShellSelection(selection: Selections) {
|
||||||
|
const commonNodes = selection.graphSelections.map((_, i) =>
|
||||||
|
buildCommonNodeFromSelection(selection, i)
|
||||||
|
)
|
||||||
|
return commonNodes.every(
|
||||||
|
(n) =>
|
||||||
|
n.selection.artifact?.type === 'cap' ||
|
||||||
|
n.selection.artifact?.type === 'wall'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// This accounts for non-geometry selections under "other"
|
// This accounts for non-geometry selections under "other"
|
||||||
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
||||||
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
||||||
@ -619,12 +641,29 @@ export function getSelectionCountByType(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
selection.graphSelections.forEach((selection) => {
|
selection.graphSelections.forEach((graphSelection) => {
|
||||||
if (!selection.artifact) {
|
if (!graphSelection.artifact) {
|
||||||
incrementOrInitializeSelectionType('other')
|
/**
|
||||||
return
|
* TODO: remove this heuristic-based selection type detection.
|
||||||
|
* Currently, if you've created a sketch and have not left sketch mode,
|
||||||
|
* the selection will be a segment selection with no artifact.
|
||||||
|
* This is because the mock execution does not update the artifact graph.
|
||||||
|
* Once we move the artifactGraph creation to WASM, we can remove this,
|
||||||
|
* as the artifactGraph will always be up-to-date.
|
||||||
|
*/
|
||||||
|
if (isSingleCursorInPipe(selection, kclManager.ast)) {
|
||||||
|
incrementOrInitializeSelectionType('segment')
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
'Selection is outside of a sketch but has no artifact. Sketch segment selections are the only kind that can have a valid selection with no artifact.',
|
||||||
|
JSON.stringify(graphSelection)
|
||||||
|
)
|
||||||
|
incrementOrInitializeSelectionType('other')
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
incrementOrInitializeSelectionType(selection.artifact.type)
|
incrementOrInitializeSelectionType(graphSelection.artifact.type)
|
||||||
})
|
})
|
||||||
|
|
||||||
return selectionsByType
|
return selectionsByType
|
||||||
|
@ -12,7 +12,7 @@ export type InteractionMapItem = {
|
|||||||
* Controls both the available names for interaction map categories
|
* Controls both the available names for interaction map categories
|
||||||
* and the order in which they are displayed.
|
* and the order in which they are displayed.
|
||||||
*/
|
*/
|
||||||
export const interactionMapCategories = [
|
const interactionMapCategories = [
|
||||||
'Sketching',
|
'Sketching',
|
||||||
'Modeling',
|
'Modeling',
|
||||||
'Command Palette',
|
'Command Palette',
|
||||||
|
@ -190,9 +190,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'shell',
|
id: 'shell',
|
||||||
onClick: () => console.error('Shell not yet implemented'),
|
onClick: ({ commandBarSend }) => {
|
||||||
|
commandBarSend({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Shell', groupId: 'modeling' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
disabled: (state) => !state.can({ type: 'Shell' }),
|
||||||
icon: 'shell',
|
icon: 'shell',
|
||||||
status: 'kcl-only',
|
status: 'available',
|
||||||
title: 'Shell',
|
title: 'Shell',
|
||||||
description: 'Hollow out a 3D solid.',
|
description: 'Hollow out a 3D solid.',
|
||||||
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/shell' }],
|
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/shell' }],
|
||||||
@ -534,13 +540,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'constraint-length',
|
id: 'constraint-length',
|
||||||
disabled: (state) =>
|
disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }),
|
||||||
!(
|
onClick: ({ commandBarSend }) =>
|
||||||
state.matches({ Sketch: 'SketchIdle' }) &&
|
commandBarSend({
|
||||||
state.can({ type: 'Constrain length' })
|
type: 'Find and select command',
|
||||||
),
|
data: {
|
||||||
onClick: ({ modelingSend }) =>
|
name: 'Constrain length',
|
||||||
modelingSend({ type: 'Constrain length' }),
|
groupId: 'modeling',
|
||||||
|
},
|
||||||
|
}),
|
||||||
icon: 'dimension',
|
icon: 'dimension',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Length',
|
title: 'Length',
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import { Selections__old } from 'lib/selections'
|
import { Selections__old } from 'lib/selections'
|
||||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
||||||
import { MachineManager } from 'components/MachineManagerProvider'
|
import { MachineManager } from 'components/MachineManagerProvider'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
export type CommandBarContext = {
|
export type CommandBarContext = {
|
||||||
commands: Command[]
|
commands: Command[]
|
||||||
@ -247,14 +248,69 @@ export const commandBarMachine = setup({
|
|||||||
'All arguments are skippable': () => false,
|
'All arguments are skippable': () => false,
|
||||||
},
|
},
|
||||||
actors: {
|
actors: {
|
||||||
'Validate argument': fromPromise(({ input }) => {
|
'Validate argument': fromPromise(
|
||||||
return new Promise((resolve, reject) => {
|
({
|
||||||
// TODO: figure out if we should validate argument data here or in the form itself,
|
input,
|
||||||
// and if we should support people configuring a argument's validation function
|
}: {
|
||||||
|
input: {
|
||||||
|
context: CommandBarContext | undefined
|
||||||
|
event: CommandBarMachineEvent | undefined
|
||||||
|
}
|
||||||
|
}) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!input || input?.event?.type !== 'Submit argument') {
|
||||||
|
toast.error(`Unable to validate, wrong event type.`)
|
||||||
|
return reject(`Unable to validate, wrong event type`)
|
||||||
|
}
|
||||||
|
|
||||||
resolve(input)
|
const context = input?.context
|
||||||
})
|
|
||||||
}),
|
if (!context) {
|
||||||
|
toast.error(`Unable to validate, wrong argument.`)
|
||||||
|
return reject(`Unable to validate, wrong argument`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = input.event.data
|
||||||
|
const argName = context.currentArgument?.name
|
||||||
|
const args = context?.selectedCommand?.args
|
||||||
|
const argConfig = args && argName ? args[argName] : undefined
|
||||||
|
// Only do a validation check if the argument, selectedCommand, and the validation function are defined
|
||||||
|
if (
|
||||||
|
context.currentArgument &&
|
||||||
|
context.selectedCommand &&
|
||||||
|
argConfig?.inputType === 'selection' &&
|
||||||
|
argConfig?.validation
|
||||||
|
) {
|
||||||
|
argConfig
|
||||||
|
.validation({ context, data })
|
||||||
|
.then((result) => {
|
||||||
|
if (typeof result === 'boolean' && result === true) {
|
||||||
|
return resolve(data)
|
||||||
|
} else {
|
||||||
|
// validation failed
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
// The result of the validation is the error message
|
||||||
|
toast.error(result)
|
||||||
|
return reject(
|
||||||
|
`unable to validate ${argName}, Message: ${result}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Default message if there is not a custom one sent
|
||||||
|
toast.error(`Unable to validate ${argName}`)
|
||||||
|
return reject(`unable to validate ${argName}}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return reject(`unable to validate ${argName}}`)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Missing several requirements for validate argument, just bypass
|
||||||
|
return resolve(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
),
|
||||||
'Validate all arguments': fromPromise(
|
'Validate all arguments': fromPromise(
|
||||||
({ input }: { input: CommandBarContext }) => {
|
({ input }: { input: CommandBarContext }) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -449,9 +505,10 @@ export const commandBarMachine = setup({
|
|||||||
invoke: {
|
invoke: {
|
||||||
src: 'Validate argument',
|
src: 'Validate argument',
|
||||||
id: 'validateSingleArgument',
|
id: 'validateSingleArgument',
|
||||||
input: ({ event }) => {
|
input: ({ event, context }) => {
|
||||||
if (event.type !== 'Submit argument') return {}
|
if (event.type !== 'Submit argument')
|
||||||
return event.data
|
return { event: undefined, context: undefined }
|
||||||
|
return { event, context }
|
||||||
},
|
},
|
||||||
onDone: {
|
onDone: {
|
||||||
target: '#Command Bar.Checking Arguments',
|
target: '#Command Bar.Checking Arguments',
|
||||||
|
33
src/main.ts
@ -23,6 +23,15 @@ import argvFromYargs from './commandLineArgs'
|
|||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null
|
let mainWindow: BrowserWindow | null = null
|
||||||
|
|
||||||
|
// Supporting multiple instances instead of multiple applications
|
||||||
|
let cmdQPressed = false
|
||||||
|
const instances: BrowserWindow[] = []
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
if (!gotTheLock) {
|
||||||
|
app.quit()
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
// Check the command line arguments for a project path
|
// Check the command line arguments for a project path
|
||||||
const args = parseCLIArgs()
|
const args = parseCLIArgs()
|
||||||
|
|
||||||
@ -117,16 +126,34 @@ const createWindow = (filePath?: string): BrowserWindow => {
|
|||||||
|
|
||||||
newWindow.show()
|
newWindow.show()
|
||||||
|
|
||||||
|
instances.push(newWindow)
|
||||||
return newWindow
|
return newWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// before-quit with multiple instances
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
// Quit from the dock context menu should quit the application directly
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
cmdQPressed = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Quit when all windows are closed, even on macOS. There, it's common
|
// Quit when all windows are closed, even on macOS. There, it's common
|
||||||
// for applications and their menu bar to stay active until the user quits
|
// for applications and their menu bar to stay active until the user quits
|
||||||
// explicitly with Cmd + Q, but it is a really weird behavior with our app.
|
// explicitly with Cmd + Q, but it is a really weird behavior with our app.
|
||||||
|
// app.on('window-all-closed', () => {
|
||||||
|
// app.quit()
|
||||||
|
// })
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
app.quit()
|
if (cmdQPressed || process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Various actions can trigger this event, such as launching the application for the first time,
|
||||||
|
// attempting to re-launch the application when it's already running, or clicking on the application's dock or taskbar icon.
|
||||||
|
app.on('activate', () => createWindow())
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
@ -135,6 +162,10 @@ app.on('ready', (event, data) => {
|
|||||||
mainWindow = createWindow()
|
mainWindow = createWindow()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// This event will be emitted inside the primary instance of your application when a second instance
|
||||||
|
// has been executed and calls app.requestSingleInstanceLock().
|
||||||
|
app.on('second-instance', (event, argv, workingDirectory) => createWindow())
|
||||||
|
|
||||||
// For now there is no good reason to separate these out to another file(s)
|
// For now there is no good reason to separate these out to another file(s)
|
||||||
// There is just not enough code to warrant it and further abstracts everything
|
// There is just not enough code to warrant it and further abstracts everything
|
||||||
// which is already quite abstracted
|
// which is already quite abstracted
|
||||||
|
@ -30,6 +30,12 @@ export const PACKAGE_NAME = isDesktop()
|
|||||||
? window.electron.packageJson.name
|
? window.electron.packageJson.name
|
||||||
: 'zoo-modeling-app'
|
: 'zoo-modeling-app'
|
||||||
|
|
||||||
|
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
|
||||||
|
|
||||||
|
export const RELEASE_URL = `https://github.com/KittyCAD/modeling-app/releases/tag/${
|
||||||
|
IS_NIGHTLY ? 'nightly-' : ''
|
||||||
|
}v${APP_VERSION}`
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
@ -79,7 +79,10 @@ kittycad = { version = "0.3.28", default-features = false, features = ["js", "re
|
|||||||
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
|
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
|
assertions_on_result_states = "warn"
|
||||||
|
dbg_macro = "warn"
|
||||||
iter_over_hash_type = "warn"
|
iter_over_hash_type = "warn"
|
||||||
|
lossy_float_literal = "warn"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "executor"
|
name = "executor"
|
||||||
|
@ -12,8 +12,8 @@ redo-kcl-stdlib-docs-no-imgs:
|
|||||||
# Generate the stdlib image artifacts
|
# Generate the stdlib image artifacts
|
||||||
# Then run the stdlib docs generation
|
# Then run the stdlib docs generation
|
||||||
redo-kcl-stdlib-docs:
|
redo-kcl-stdlib-docs:
|
||||||
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
|
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib --no-fail-fast -- kcl_test_example
|
||||||
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
|
EXPECTORATE=overwrite {{cnr}} -p kcl-lib --no-fail-fast -- docs::gen_std_tests::test_generate_stdlib
|
||||||
|
|
||||||
# Copy a test KCL file from executor tests into a new simulation test.
|
# Copy a test KCL file from executor tests into a new simulation test.
|
||||||
copy-exec-test-into-sim-test test_name:
|
copy-exec-test-into-sim-test test_name:
|
||||||
|
@ -15,5 +15,5 @@ async fn kcl_to_core_test() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
result.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -366,9 +366,11 @@ impl Node<CallExpressionKw> {
|
|||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
let fn_name = &self.callee.name;
|
let fn_name = &self.callee.name;
|
||||||
|
let callsite: SourceRange = self.into();
|
||||||
|
|
||||||
// Build a hashmap from argument labels to the final evaluated values.
|
// Build a hashmap from argument labels to the final evaluated values.
|
||||||
let mut fn_args = HashMap::with_capacity(self.arguments.len());
|
let mut fn_args = HashMap::with_capacity(self.arguments.len());
|
||||||
|
let mut tag_declarator_args = Vec::new();
|
||||||
for arg_expr in &self.arguments {
|
for arg_expr in &self.arguments {
|
||||||
let source_range = SourceRange::from(arg_expr.arg.clone());
|
let source_range = SourceRange::from(arg_expr.arg.clone());
|
||||||
let metadata = Metadata { source_range };
|
let metadata = Metadata { source_range };
|
||||||
@ -376,8 +378,12 @@ impl Node<CallExpressionKw> {
|
|||||||
.execute_expr(&arg_expr.arg, exec_state, &metadata, StatementKind::Expression)
|
.execute_expr(&arg_expr.arg, exec_state, &metadata, StatementKind::Expression)
|
||||||
.await?;
|
.await?;
|
||||||
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
|
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
|
||||||
|
if let Expr::TagDeclarator(td) = &arg_expr.arg {
|
||||||
|
tag_declarator_args.push((td.inner.clone(), source_range));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let fn_args = fn_args; // remove mutability
|
let fn_args = fn_args; // remove mutability
|
||||||
|
let tag_declarator_args = tag_declarator_args; // remove mutability
|
||||||
|
|
||||||
// Evaluate the unlabeled first param, if any exists.
|
// Evaluate the unlabeled first param, if any exists.
|
||||||
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
||||||
@ -403,11 +409,43 @@ impl Node<CallExpressionKw> {
|
|||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
update_memory_for_tags_of_geometry(&mut result, &tag_declarator_args, exec_state)?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
FunctionKind::UserDefined => {
|
FunctionKind::UserDefined => {
|
||||||
todo!("Part of modeling-app#4600: Support keyword arguments for user-defined functions")
|
let source_range = SourceRange::from(self);
|
||||||
|
// Clone the function so that we can use a mutable reference to
|
||||||
|
// exec_state.
|
||||||
|
let func = exec_state.memory.get(fn_name, source_range)?.clone();
|
||||||
|
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
|
||||||
|
|
||||||
|
let return_value = {
|
||||||
|
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||||
|
let result = func
|
||||||
|
.call_fn_kw(args, exec_state, ctx.clone(), callsite)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
// Add the call expression to the source ranges.
|
||||||
|
// TODO currently ignored by the frontend
|
||||||
|
e.add_source_ranges(vec![source_range])
|
||||||
|
});
|
||||||
|
exec_state.dynamic_state = previous_dynamic_state;
|
||||||
|
result?
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = return_value.ok_or_else(move || {
|
||||||
|
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
||||||
|
// We want to send the source range of the original function.
|
||||||
|
if let KclValue::Function { meta, .. } = func {
|
||||||
|
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||||
|
};
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||||
|
source_ranges,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,6 +457,7 @@ impl Node<CallExpression> {
|
|||||||
let fn_name = &self.callee.name;
|
let fn_name = &self.callee.name;
|
||||||
|
|
||||||
let mut fn_args: Vec<Arg> = Vec::with_capacity(self.arguments.len());
|
let mut fn_args: Vec<Arg> = Vec::with_capacity(self.arguments.len());
|
||||||
|
let mut tag_declarator_args = Vec::new();
|
||||||
|
|
||||||
for arg_expr in &self.arguments {
|
for arg_expr in &self.arguments {
|
||||||
let metadata = Metadata {
|
let metadata = Metadata {
|
||||||
@ -428,15 +467,19 @@ impl Node<CallExpression> {
|
|||||||
.execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
|
.execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
|
||||||
.await?;
|
.await?;
|
||||||
let arg = Arg::new(value, SourceRange::from(arg_expr));
|
let arg = Arg::new(value, SourceRange::from(arg_expr));
|
||||||
|
if let Expr::TagDeclarator(td) = arg_expr {
|
||||||
|
tag_declarator_args.push((td.inner.clone(), arg.source_range));
|
||||||
|
}
|
||||||
fn_args.push(arg);
|
fn_args.push(arg);
|
||||||
}
|
}
|
||||||
|
let tag_declarator_args = tag_declarator_args; // remove mutability
|
||||||
|
|
||||||
match ctx.stdlib.get_either(fn_name) {
|
match ctx.stdlib.get_either(fn_name) {
|
||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
update_memory_for_tags_of_geometry(&mut result, &tag_declarator_args, exec_state)?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
FunctionKind::UserDefined => {
|
FunctionKind::UserDefined => {
|
||||||
@ -475,7 +518,24 @@ impl Node<CallExpression> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
|
/// `tag_declarator_args` should only contain tag declarator literals, which
|
||||||
|
/// will be defined as local variables. Non-literals that evaluate to tag
|
||||||
|
/// declarators should not be defined.
|
||||||
|
fn update_memory_for_tags_of_geometry(
|
||||||
|
result: &mut KclValue,
|
||||||
|
tag_declarator_args: &[(TagDeclarator, SourceRange)],
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
) -> Result<(), KclError> {
|
||||||
|
// Define all the tags in the memory.
|
||||||
|
for (tag_declarator, arg_sr) in tag_declarator_args {
|
||||||
|
let tag = TagIdentifier {
|
||||||
|
value: tag_declarator.name.clone(),
|
||||||
|
info: None,
|
||||||
|
meta: vec![Metadata { source_range: *arg_sr }],
|
||||||
|
};
|
||||||
|
|
||||||
|
exec_state.memory.add_tag(&tag.value, tag.clone(), *arg_sr)?;
|
||||||
|
}
|
||||||
// If the return result is a sketch or solid, we want to update the
|
// If the return result is a sketch or solid, we want to update the
|
||||||
// memory for the tags of the group.
|
// memory for the tags of the group.
|
||||||
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
||||||
@ -483,7 +543,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
|||||||
match result {
|
match result {
|
||||||
KclValue::Sketch { value: ref mut sketch } => {
|
KclValue::Sketch { value: ref mut sketch } => {
|
||||||
for (_, tag) in sketch.tags.iter() {
|
for (_, tag) in sketch.tags.iter() {
|
||||||
exec_state.memory.update_tag(&tag.value, tag.clone())?;
|
exec_state.memory.update_tag_if_defined(&tag.value, tag.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KclValue::Solid(ref mut solid) => {
|
KclValue::Solid(ref mut solid) => {
|
||||||
@ -521,7 +581,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
|||||||
info.sketch = solid.id;
|
info.sketch = solid.id;
|
||||||
t.info = Some(info);
|
t.info = Some(info);
|
||||||
|
|
||||||
exec_state.memory.update_tag(&tag.name, t.clone())?;
|
exec_state.memory.update_tag_if_defined(&tag.name, t.clone());
|
||||||
|
|
||||||
// update the sketch tags.
|
// update the sketch tags.
|
||||||
solid.sketch.tags.insert(tag.name.clone(), t);
|
solid.sketch.tags.insert(tag.name.clone(), t);
|
||||||
@ -542,22 +602,6 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node<TagDeclarator> {
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
|
||||||
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
|
||||||
value: self.name.clone(),
|
|
||||||
info: None,
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}));
|
|
||||||
|
|
||||||
exec_state.memory.add(&self.name, memory_item.clone(), self.into())?;
|
|
||||||
|
|
||||||
Ok(self.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node<ArrayExpression> {
|
impl Node<ArrayExpression> {
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
@ -72,6 +72,10 @@ pub enum KclValue {
|
|||||||
ImportedGeometry(ImportedGeometry),
|
ImportedGeometry(ImportedGeometry),
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
Function {
|
Function {
|
||||||
|
/// Adam Chalmers speculation:
|
||||||
|
/// Reference to a KCL stdlib function (written in Rust).
|
||||||
|
/// Some if the KCL value is an alias of a stdlib function,
|
||||||
|
/// None if it's a KCL function written/declared in KCL.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
func: Option<MemoryFunction>,
|
func: Option<MemoryFunction>,
|
||||||
#[schemars(skip)]
|
#[schemars(skip)]
|
||||||
@ -503,4 +507,39 @@ impl KclValue {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this is a function, call it by applying keyword arguments.
|
||||||
|
/// If it's not a function, returns an error.
|
||||||
|
pub async fn call_fn_kw(
|
||||||
|
&self,
|
||||||
|
args: crate::std::Args,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
ctx: ExecutorContext,
|
||||||
|
callsite: SourceRange,
|
||||||
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
let KclValue::Function {
|
||||||
|
func,
|
||||||
|
expression,
|
||||||
|
memory: closure_memory,
|
||||||
|
meta: _,
|
||||||
|
} = &self
|
||||||
|
else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "cannot call this because it isn't a function".to_string(),
|
||||||
|
source_ranges: vec![callsite],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
if let Some(_func) = func {
|
||||||
|
todo!("Implement calling KCL stdlib fns that are aliased. Part of https://github.com/KittyCAD/modeling-app/issues/4600");
|
||||||
|
} else {
|
||||||
|
crate::execution::call_user_defined_function_kw(
|
||||||
|
args.kw_args,
|
||||||
|
closure_memory.as_ref(),
|
||||||
|
expression.as_ref(),
|
||||||
|
exec_state,
|
||||||
|
&ctx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,10 +125,16 @@ impl ProgramMemory {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_tag(&mut self, tag: &str, value: TagIdentifier) -> Result<(), KclError> {
|
pub fn add_tag(&mut self, tag: &str, value: TagIdentifier, source_range: SourceRange) -> Result<(), KclError> {
|
||||||
self.environments[self.current_env.index()].insert(tag.to_string(), KclValue::TagIdentifier(Box::new(value)));
|
self.add(tag, KclValue::TagIdentifier(Box::new(value)), source_range)
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
pub fn update_tag_if_defined(&mut self, tag: &str, value: TagIdentifier) {
|
||||||
|
if !self.environments[self.current_env.index()].contains_key(tag) {
|
||||||
|
// Do nothing if the tag isn't defined.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.environments[self.current_env.index()].insert(tag.to_string(), KclValue::TagIdentifier(Box::new(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a value from the program memory.
|
/// Get a value from the program memory.
|
||||||
@ -845,7 +851,7 @@ impl GetTangentialInfoFromPathsResult {
|
|||||||
|
|
||||||
impl Sketch {
|
impl Sketch {
|
||||||
pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
|
pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
|
||||||
let mut tag_identifier: TagIdentifier = tag.into();
|
let mut tag_identifier = TagIdentifier::from(tag);
|
||||||
let base = current_path.get_base();
|
let base = current_path.get_base();
|
||||||
tag_identifier.info = Some(TagEngineInfo {
|
tag_identifier.info = Some(TagEngineInfo {
|
||||||
id: base.geo_meta.id,
|
id: base.geo_meta.id,
|
||||||
@ -2085,7 +2091,7 @@ impl ExecutorContext {
|
|||||||
let item = match init {
|
let item = match init {
|
||||||
Expr::None(none) => KclValue::from(none),
|
Expr::None(none) => KclValue::from(none),
|
||||||
Expr::Literal(literal) => KclValue::from(literal),
|
Expr::Literal(literal) => KclValue::from(literal),
|
||||||
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
|
Expr::TagDeclarator(tag) => KclValue::from(tag),
|
||||||
Expr::Identifier(identifier) => {
|
Expr::Identifier(identifier) => {
|
||||||
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -2247,6 +2253,59 @@ fn assign_args_to_params(
|
|||||||
Ok(fn_memory)
|
Ok(fn_memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assign_args_to_params_kw(
|
||||||
|
function_expression: NodeRef<'_, FunctionExpression>,
|
||||||
|
mut args: crate::std::args::KwArgs,
|
||||||
|
mut fn_memory: ProgramMemory,
|
||||||
|
) -> Result<ProgramMemory, KclError> {
|
||||||
|
// Add the arguments to the memory. A new call frame should have already
|
||||||
|
// been created.
|
||||||
|
let source_ranges = vec![function_expression.into()];
|
||||||
|
for param in function_expression.params.iter() {
|
||||||
|
if param.labeled {
|
||||||
|
let arg = args.labeled.get(¶m.identifier.name);
|
||||||
|
let arg_val = match arg {
|
||||||
|
Some(arg) => arg.value.clone(),
|
||||||
|
None => match param.default_value {
|
||||||
|
Some(ref default_val) => KclValue::from(default_val.clone()),
|
||||||
|
None => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: format!(
|
||||||
|
"This function requires a parameter {}, but you haven't passed it one.",
|
||||||
|
param.identifier.name
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
fn_memory.add(¶m.identifier.name, arg_val, (¶m.identifier).into())?;
|
||||||
|
} else {
|
||||||
|
let Some(unlabeled) = args.unlabeled.take() else {
|
||||||
|
let param_name = ¶m.identifier.name;
|
||||||
|
return Err(if args.labeled.contains_key(param_name) {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: "This function expects an unlabeled first parameter, but you haven't passed it one."
|
||||||
|
.to_owned(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
fn_memory.add(
|
||||||
|
¶m.identifier.name,
|
||||||
|
unlabeled.value.clone(),
|
||||||
|
(¶m.identifier).into(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(fn_memory)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn call_user_defined_function(
|
pub(crate) async fn call_user_defined_function(
|
||||||
args: Vec<Arg>,
|
args: Vec<Arg>,
|
||||||
memory: &ProgramMemory,
|
memory: &ProgramMemory,
|
||||||
@ -2277,6 +2336,36 @@ pub(crate) async fn call_user_defined_function(
|
|||||||
result.map(|_| fn_memory.return_)
|
result.map(|_| fn_memory.return_)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn call_user_defined_function_kw(
|
||||||
|
args: crate::std::args::KwArgs,
|
||||||
|
memory: &ProgramMemory,
|
||||||
|
function_expression: NodeRef<'_, FunctionExpression>,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
ctx: &ExecutorContext,
|
||||||
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
// Create a new environment to execute the function body in so that local
|
||||||
|
// variables shadow variables in the parent scope. The new environment's
|
||||||
|
// parent should be the environment of the closure.
|
||||||
|
let mut body_memory = memory.clone();
|
||||||
|
let body_env = body_memory.new_env_for_call(memory.current_env);
|
||||||
|
body_memory.current_env = body_env;
|
||||||
|
let fn_memory = assign_args_to_params_kw(function_expression, args, body_memory)?;
|
||||||
|
|
||||||
|
// Execute the function body using the memory we just created.
|
||||||
|
let (result, fn_memory) = {
|
||||||
|
let previous_memory = std::mem::replace(&mut exec_state.memory, fn_memory);
|
||||||
|
let result = ctx
|
||||||
|
.inner_execute(&function_expression.body, exec_state, BodyType::Block)
|
||||||
|
.await;
|
||||||
|
// Restore the previous memory.
|
||||||
|
let fn_memory = std::mem::replace(&mut exec_state.memory, previous_memory);
|
||||||
|
|
||||||
|
(result, fn_memory)
|
||||||
|
};
|
||||||
|
|
||||||
|
result.map(|_| fn_memory.return_)
|
||||||
|
}
|
||||||
|
|
||||||
pub enum StatementKind<'a> {
|
pub enum StatementKind<'a> {
|
||||||
Declaration { name: &'a str },
|
Declaration { name: &'a str },
|
||||||
Expression,
|
Expression,
|
||||||
@ -2888,8 +2977,10 @@ let notTagDeclarator = !myTagDeclarator";
|
|||||||
);
|
);
|
||||||
|
|
||||||
let code9 = "
|
let code9 = "
|
||||||
let myTagDeclarator = $myTag
|
sk = startSketchOn('XY')
|
||||||
let notTagIdentifier = !myTag";
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([5, 0], %, $myTag)
|
||||||
|
notTagIdentifier = !myTag";
|
||||||
let tag_identifier_err = parse_execute(code9).await.unwrap_err().downcast::<KclError>().unwrap();
|
let tag_identifier_err = parse_execute(code9).await.unwrap_err().downcast::<KclError>().unwrap();
|
||||||
// These are currently printed out as JSON objects, so we don't want to
|
// These are currently printed out as JSON objects, so we don't want to
|
||||||
// check the full error.
|
// check the full error.
|
||||||
|
@ -138,7 +138,7 @@ pub use lsp::test_util::kcl_lsp_server;
|
|||||||
impl Program {
|
impl Program {
|
||||||
pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
|
pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
|
||||||
let module_id = ModuleId::default();
|
let module_id = ModuleId::default();
|
||||||
let tokens = parsing::token::lexer(input, module_id)?;
|
let tokens = parsing::token::lex(input, module_id)?;
|
||||||
let (ast, errs) = parsing::parse_tokens(tokens).0?;
|
let (ast, errs) = parsing::parse_tokens(tokens).0?;
|
||||||
|
|
||||||
Ok((ast.map(|ast| Program { ast }), errs))
|
Ok((ast.map(|ast| Program { ast }), errs))
|
||||||
@ -146,7 +146,7 @@ impl Program {
|
|||||||
|
|
||||||
pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
|
pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
|
||||||
let module_id = ModuleId::default();
|
let module_id = ModuleId::default();
|
||||||
let tokens = parsing::token::lexer(input, module_id)?;
|
let tokens = parsing::token::lex(input, module_id)?;
|
||||||
let ast = parsing::parse_tokens(tokens).parse_errs_as_err()?;
|
let ast = parsing::parse_tokens(tokens).parse_errs_as_err()?;
|
||||||
|
|
||||||
Ok(Program { ast })
|
Ok(Program { ast })
|
||||||
|
@ -49,34 +49,31 @@ use crate::{
|
|||||||
cache::{CacheInformation, OldAstState},
|
cache::{CacheInformation, OldAstState},
|
||||||
types::{Expr, Node, VariableKind},
|
types::{Expr, Node, VariableKind},
|
||||||
},
|
},
|
||||||
token::TokenType,
|
token::TokenStream,
|
||||||
PIPE_OPERATOR,
|
PIPE_OPERATOR,
|
||||||
},
|
},
|
||||||
ModuleId, Program, SourceRange,
|
ModuleId, Program, SourceRange,
|
||||||
};
|
};
|
||||||
|
const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
|
||||||
|
SemanticTokenType::NUMBER,
|
||||||
|
SemanticTokenType::VARIABLE,
|
||||||
|
SemanticTokenType::KEYWORD,
|
||||||
|
SemanticTokenType::TYPE,
|
||||||
|
SemanticTokenType::STRING,
|
||||||
|
SemanticTokenType::OPERATOR,
|
||||||
|
SemanticTokenType::COMMENT,
|
||||||
|
SemanticTokenType::FUNCTION,
|
||||||
|
SemanticTokenType::PARAMETER,
|
||||||
|
SemanticTokenType::PROPERTY,
|
||||||
|
];
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
|
||||||
pub static ref SEMANTIC_TOKEN_TYPES: Vec<SemanticTokenType> = {
|
SemanticTokenModifier::DECLARATION,
|
||||||
// This is safe to unwrap because we know all the token types are valid.
|
SemanticTokenModifier::DEFINITION,
|
||||||
// And the test would fail if they were not.
|
SemanticTokenModifier::DEFAULT_LIBRARY,
|
||||||
let mut gen = TokenType::all_semantic_token_types().unwrap();
|
SemanticTokenModifier::READONLY,
|
||||||
gen.extend(vec![
|
SemanticTokenModifier::STATIC,
|
||||||
SemanticTokenType::PARAMETER,
|
];
|
||||||
SemanticTokenType::PROPERTY,
|
|
||||||
]);
|
|
||||||
gen
|
|
||||||
};
|
|
||||||
|
|
||||||
pub static ref SEMANTIC_TOKEN_MODIFIERS: Vec<SemanticTokenModifier> = {
|
|
||||||
vec![
|
|
||||||
SemanticTokenModifier::DECLARATION,
|
|
||||||
SemanticTokenModifier::DEFINITION,
|
|
||||||
SemanticTokenModifier::DEFAULT_LIBRARY,
|
|
||||||
SemanticTokenModifier::READONLY,
|
|
||||||
SemanticTokenModifier::STATIC,
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A subcommand for running the server.
|
/// A subcommand for running the server.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -105,7 +102,7 @@ pub struct Backend {
|
|||||||
/// The stdlib signatures for the language.
|
/// The stdlib signatures for the language.
|
||||||
pub stdlib_signatures: HashMap<String, SignatureHelp>,
|
pub stdlib_signatures: HashMap<String, SignatureHelp>,
|
||||||
/// Token maps.
|
/// Token maps.
|
||||||
pub token_map: DashMap<String, Vec<crate::parsing::token::Token>>,
|
pub(super) token_map: DashMap<String, TokenStream>,
|
||||||
/// AST maps.
|
/// AST maps.
|
||||||
pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>,
|
pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>,
|
||||||
/// Last successful execution.
|
/// Last successful execution.
|
||||||
@ -284,7 +281,7 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
|
|
||||||
// Lets update the tokens.
|
// Lets update the tokens.
|
||||||
let module_id = ModuleId::default();
|
let module_id = ModuleId::default();
|
||||||
let tokens = match crate::parsing::token::lexer(¶ms.text, module_id) {
|
let tokens = match crate::parsing::token::lex(¶ms.text, module_id) {
|
||||||
Ok(tokens) => tokens,
|
Ok(tokens) => tokens,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.add_to_diagnostics(¶ms, &[err], true).await;
|
self.add_to_diagnostics(¶ms, &[err], true).await;
|
||||||
@ -410,11 +407,11 @@ impl Backend {
|
|||||||
self.executor_ctx.read().await
|
self.executor_ctx.read().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_semantic_tokens(&self, tokens: &[crate::parsing::token::Token], params: &TextDocumentItem) {
|
async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
|
||||||
// Update the semantic tokens map.
|
// Update the semantic tokens map.
|
||||||
let mut semantic_tokens = vec![];
|
let mut semantic_tokens = vec![];
|
||||||
let mut last_position = Position::new(0, 0);
|
let mut last_position = Position::new(0, 0);
|
||||||
for token in tokens {
|
for token in tokens.as_slice() {
|
||||||
let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
|
let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
|
||||||
// We continue here because not all tokens can be converted this way, we will get
|
// We continue here because not all tokens can be converted this way, we will get
|
||||||
// the rest from the ast.
|
// the rest from the ast.
|
||||||
@ -444,8 +441,11 @@ impl Backend {
|
|||||||
let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
|
let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
|
||||||
let token_index = Arc::new(Mutex::new(token_type_index));
|
let token_index = Arc::new(Mutex::new(token_type_index));
|
||||||
let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
|
let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
|
||||||
crate::walk::walk(&ast, &|node: crate::walk::Node| {
|
crate::walk::walk(&ast, |node: crate::walk::Node| {
|
||||||
let node_range: SourceRange = (&node).into();
|
let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
|
||||||
|
return Ok(true);
|
||||||
|
};
|
||||||
|
|
||||||
if !node_range.contains(source_range.start()) {
|
if !node_range.contains(source_range.start()) {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
@ -563,7 +563,7 @@ impl Backend {
|
|||||||
let semantic_token = SemanticToken {
|
let semantic_token = SemanticToken {
|
||||||
delta_line: position.line - last_position.line + 1,
|
delta_line: position.line - last_position.line + 1,
|
||||||
delta_start: 0,
|
delta_start: 0,
|
||||||
length: token.value.len() as u32,
|
length: (token.end - token.start) as u32,
|
||||||
token_type: token_type_index,
|
token_type: token_type_index,
|
||||||
token_modifiers_bitset,
|
token_modifiers_bitset,
|
||||||
};
|
};
|
||||||
@ -582,7 +582,7 @@ impl Backend {
|
|||||||
} else {
|
} else {
|
||||||
position.character - last_position.character
|
position.character - last_position.character
|
||||||
},
|
},
|
||||||
length: token.value.len() as u32,
|
length: (token.end - token.start) as u32,
|
||||||
token_type: token_type_index,
|
token_type: token_type_index,
|
||||||
token_modifiers_bitset,
|
token_modifiers_bitset,
|
||||||
};
|
};
|
||||||
@ -963,8 +963,8 @@ impl LanguageServer for Backend {
|
|||||||
semantic_tokens_options: SemanticTokensOptions {
|
semantic_tokens_options: SemanticTokensOptions {
|
||||||
work_done_progress_options: WorkDoneProgressOptions::default(),
|
work_done_progress_options: WorkDoneProgressOptions::default(),
|
||||||
legend: SemanticTokensLegend {
|
legend: SemanticTokensLegend {
|
||||||
token_types: SEMANTIC_TOKEN_TYPES.clone(),
|
token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
|
||||||
token_modifiers: SEMANTIC_TOKEN_MODIFIERS.clone(),
|
token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
|
||||||
},
|
},
|
||||||
range: Some(false),
|
range: Some(false),
|
||||||
full: Some(SemanticTokensFullOptions::Bool(true)),
|
full: Some(SemanticTokensFullOptions::Bool(true)),
|
||||||
|
@ -1082,7 +1082,7 @@ fn myFn = (param1) => {
|
|||||||
|
|
||||||
// Get the token map.
|
// Get the token map.
|
||||||
let token_map = server.token_map.get("file:///test.kcl").unwrap().clone();
|
let token_map = server.token_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(token_map != vec![]);
|
assert!(!token_map.is_empty());
|
||||||
|
|
||||||
// Get the ast.
|
// Get the ast.
|
||||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||||
@ -2206,7 +2206,7 @@ part001 = cube([0,0], 20)
|
|||||||
|
|
||||||
// Get the tokens.
|
// Get the tokens.
|
||||||
let tokens = server.token_map.get("file:///test.kcl").unwrap().clone();
|
let tokens = server.token_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert_eq!(tokens.len(), 120);
|
assert_eq!(tokens.as_slice().len(), 120);
|
||||||
|
|
||||||
// Get the ast.
|
// Get the ast.
|
||||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||||
@ -3379,11 +3379,11 @@ part001 = startSketchOn('XY')
|
|||||||
|
|
||||||
// Get the symbols map.
|
// Get the symbols map.
|
||||||
let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
|
let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(symbols_map != vec![]);
|
assert!(!symbols_map.is_empty());
|
||||||
|
|
||||||
// Get the semantic tokens map.
|
// Get the semantic tokens map.
|
||||||
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(semantic_tokens_map != vec![]);
|
assert!(!semantic_tokens_map.is_empty());
|
||||||
|
|
||||||
// Get the memory.
|
// Get the memory.
|
||||||
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
|
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
|
||||||
@ -3422,7 +3422,7 @@ NEW_LINT = 1"#
|
|||||||
|
|
||||||
// Get the semantic tokens map.
|
// Get the semantic tokens map.
|
||||||
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(semantic_tokens_map != vec![]);
|
assert!(!semantic_tokens_map.is_empty());
|
||||||
|
|
||||||
// Get the memory.
|
// Get the memory.
|
||||||
let memory = server.memory_map.get("file:///test.kcl");
|
let memory = server.memory_map.get("file:///test.kcl");
|
||||||
@ -3466,7 +3466,7 @@ part001 = startSketchOn('XY')
|
|||||||
|
|
||||||
// Get the token map.
|
// Get the token map.
|
||||||
let token_map = server.token_map.get("file:///test.kcl").unwrap().clone();
|
let token_map = server.token_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(token_map != vec![]);
|
assert!(!token_map.is_empty());
|
||||||
|
|
||||||
// Get the ast.
|
// Get the ast.
|
||||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||||
@ -3474,11 +3474,11 @@ part001 = startSketchOn('XY')
|
|||||||
|
|
||||||
// Get the symbols map.
|
// Get the symbols map.
|
||||||
let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
|
let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(symbols_map != vec![]);
|
assert!(!symbols_map.is_empty());
|
||||||
|
|
||||||
// Get the semantic tokens map.
|
// Get the semantic tokens map.
|
||||||
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(semantic_tokens_map != vec![]);
|
assert!(!semantic_tokens_map.is_empty());
|
||||||
|
|
||||||
// Get the memory.
|
// Get the memory.
|
||||||
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
|
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
|
||||||
@ -3509,7 +3509,7 @@ part001 = startSketchOn('XY')
|
|||||||
|
|
||||||
// Get the token map.
|
// Get the token map.
|
||||||
let token_map = server.token_map.get("file:///test.kcl").unwrap().clone();
|
let token_map = server.token_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(token_map != vec![]);
|
assert!(!token_map.is_empty());
|
||||||
|
|
||||||
// Get the ast.
|
// Get the ast.
|
||||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||||
@ -3517,11 +3517,11 @@ part001 = startSketchOn('XY')
|
|||||||
|
|
||||||
// Get the symbols map.
|
// Get the symbols map.
|
||||||
let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
|
let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(symbols_map != vec![]);
|
assert!(!symbols_map.is_empty());
|
||||||
|
|
||||||
// Get the semantic tokens map.
|
// Get the semantic tokens map.
|
||||||
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
let semantic_tokens_map = server.semantic_tokens_map.get("file:///test.kcl").unwrap().clone();
|
||||||
assert!(semantic_tokens_map != vec![]);
|
assert!(!semantic_tokens_map.is_empty());
|
||||||
|
|
||||||
// Get the memory.
|
// Get the memory.
|
||||||
let memory = server.memory_map.get("file:///test.kcl");
|
let memory = server.memory_map.get("file:///test.kcl");
|
||||||
|
@ -184,7 +184,7 @@ impl Node<Program> {
|
|||||||
/// Walk the ast and get all the variables and tags as completion items.
|
/// Walk the ast and get all the variables and tags as completion items.
|
||||||
pub fn completion_items<'a>(&'a self) -> Result<Vec<CompletionItem>> {
|
pub fn completion_items<'a>(&'a self) -> Result<Vec<CompletionItem>> {
|
||||||
let completions = Arc::new(Mutex::new(vec![]));
|
let completions = Arc::new(Mutex::new(vec![]));
|
||||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||||
let mut findings = completions.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
let mut findings = completions.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
||||||
match node {
|
match node {
|
||||||
crate::walk::Node::TagDeclarator(tag) => {
|
crate::walk::Node::TagDeclarator(tag) => {
|
||||||
@ -195,7 +195,7 @@ impl Node<Program> {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok::<bool, anyhow::Error>(true)
|
||||||
})?;
|
})?;
|
||||||
let x = completions.lock().unwrap();
|
let x = completions.lock().unwrap();
|
||||||
Ok(x.clone())
|
Ok(x.clone())
|
||||||
@ -204,7 +204,7 @@ impl Node<Program> {
|
|||||||
/// Returns all the lsp symbols in the program.
|
/// Returns all the lsp symbols in the program.
|
||||||
pub fn get_lsp_symbols<'a>(&'a self, code: &str) -> Result<Vec<DocumentSymbol>> {
|
pub fn get_lsp_symbols<'a>(&'a self, code: &str) -> Result<Vec<DocumentSymbol>> {
|
||||||
let symbols = Arc::new(Mutex::new(vec![]));
|
let symbols = Arc::new(Mutex::new(vec![]));
|
||||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||||
let mut findings = symbols.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
let mut findings = symbols.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
||||||
match node {
|
match node {
|
||||||
crate::walk::Node::TagDeclarator(tag) => {
|
crate::walk::Node::TagDeclarator(tag) => {
|
||||||
@ -215,7 +215,7 @@ impl Node<Program> {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok::<bool, anyhow::Error>(true)
|
||||||
})?;
|
})?;
|
||||||
let x = symbols.lock().unwrap();
|
let x = symbols.lock().unwrap();
|
||||||
Ok(x.clone())
|
Ok(x.clone())
|
||||||
@ -227,10 +227,10 @@ impl Node<Program> {
|
|||||||
RuleT: crate::lint::Rule<'a>,
|
RuleT: crate::lint::Rule<'a>,
|
||||||
{
|
{
|
||||||
let v = Arc::new(Mutex::new(vec![]));
|
let v = Arc::new(Mutex::new(vec![]));
|
||||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
|
||||||
let mut findings = v.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
let mut findings = v.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
|
||||||
findings.append(&mut rule.check(node)?);
|
findings.append(&mut rule.check(node)?);
|
||||||
Ok(true)
|
Ok::<bool, anyhow::Error>(true)
|
||||||
})?;
|
})?;
|
||||||
let x = v.lock().unwrap();
|
let x = v.lock().unwrap();
|
||||||
Ok(x.clone())
|
Ok(x.clone())
|
||||||
@ -2810,7 +2810,8 @@ pub struct Parameter {
|
|||||||
pub identifier: Node<Identifier>,
|
pub identifier: Node<Identifier>,
|
||||||
/// The type of the parameter.
|
/// The type of the parameter.
|
||||||
/// This is optional if the user defines a type.
|
/// This is optional if the user defines a type.
|
||||||
#[serde(skip)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[ts(skip)]
|
||||||
pub type_: Option<FnArgType>,
|
pub type_: Option<FnArgType>,
|
||||||
/// Is the parameter optional?
|
/// Is the parameter optional?
|
||||||
/// If so, what is its default value?
|
/// If so, what is its default value?
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
errors::{CompilationError, KclError, KclErrorDetails},
|
errors::{CompilationError, KclError, KclErrorDetails},
|
||||||
parsing::{
|
parsing::{
|
||||||
ast::types::{Node, Program},
|
ast::types::{Node, Program},
|
||||||
token::{Token, TokenType},
|
token::TokenStream,
|
||||||
},
|
},
|
||||||
source_range::{ModuleId, SourceRange},
|
source_range::{ModuleId, SourceRange},
|
||||||
};
|
};
|
||||||
@ -34,15 +34,13 @@ pub fn top_level_parse(code: &str) -> ParseResult {
|
|||||||
|
|
||||||
/// Parse the given KCL code into an AST.
|
/// Parse the given KCL code into an AST.
|
||||||
pub fn parse_str(code: &str, module_id: ModuleId) -> ParseResult {
|
pub fn parse_str(code: &str, module_id: ModuleId) -> ParseResult {
|
||||||
let tokens = pr_try!(crate::parsing::token::lexer(code, module_id));
|
let tokens = pr_try!(crate::parsing::token::lex(code, module_id));
|
||||||
parse_tokens(tokens)
|
parse_tokens(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the supplied tokens into an AST.
|
/// Parse the supplied tokens into an AST.
|
||||||
pub fn parse_tokens(tokens: Vec<Token>) -> ParseResult {
|
pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult {
|
||||||
let (tokens, unknown_tokens): (Vec<Token>, Vec<Token>) = tokens
|
let unknown_tokens = tokens.remove_unknown();
|
||||||
.into_iter()
|
|
||||||
.partition(|token| token.token_type != TokenType::Unknown);
|
|
||||||
|
|
||||||
if !unknown_tokens.is_empty() {
|
if !unknown_tokens.is_empty() {
|
||||||
let source_ranges = unknown_tokens.iter().map(SourceRange::from).collect();
|
let source_ranges = unknown_tokens.iter().map(SourceRange::from).collect();
|
||||||
@ -69,7 +67,7 @@ pub fn parse_tokens(tokens: Vec<Token>) -> ParseResult {
|
|||||||
return Node::<Program>::default().into();
|
return Node::<Program>::default().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
parser::run_parser(&mut tokens.as_slice())
|
parser::run_parser(tokens.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of parsing.
|
/// Result of parsing.
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/parsing/parser.rs
|
||||||
|
expression: actual
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 35,
|
||||||
|
"id": {
|
||||||
|
"end": 6,
|
||||||
|
"name": "foo",
|
||||||
|
"start": 3,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 33,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 32,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
},
|
||||||
|
"end": 33,
|
||||||
|
"start": 25,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 35,
|
||||||
|
"start": 23
|
||||||
|
},
|
||||||
|
"end": 35,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 8,
|
||||||
|
"name": "x",
|
||||||
|
"start": 7,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"type_": {
|
||||||
|
"type": "Primitive",
|
||||||
|
"type": "Number"
|
||||||
|
},
|
||||||
|
"default_value": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 2.0,
|
||||||
|
"raw": "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 6,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 3,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 35,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 35,
|
||||||
|
"start": 0
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/parsing/parser.rs
|
||||||
|
expression: actual
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 27,
|
||||||
|
"id": {
|
||||||
|
"end": 6,
|
||||||
|
"name": "foo",
|
||||||
|
"start": 3,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 25,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 24,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
},
|
||||||
|
"end": 25,
|
||||||
|
"start": 17,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 27,
|
||||||
|
"start": 15
|
||||||
|
},
|
||||||
|
"end": 27,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 8,
|
||||||
|
"name": "x",
|
||||||
|
"start": 7,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"default_value": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 2.0,
|
||||||
|
"raw": "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 6,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 3,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 27,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 27,
|
||||||
|
"start": 0
|
||||||
|
}
|
@ -1,28 +1,221 @@
|
|||||||
use std::str::FromStr;
|
// Clippy does not agree with rustc here for some reason.
|
||||||
|
#![allow(clippy::needless_lifetimes)]
|
||||||
|
|
||||||
|
use std::{fmt, iter::Enumerate, num::NonZeroUsize};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use parse_display::{Display, FromStr};
|
use parse_display::Display;
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tower_lsp::lsp_types::SemanticTokenType;
|
use tower_lsp::lsp_types::SemanticTokenType;
|
||||||
use winnow::{error::ParseError, stream::ContainsToken};
|
use winnow::{
|
||||||
|
self,
|
||||||
|
error::ParseError,
|
||||||
|
stream::{ContainsToken, Stream},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::KclError,
|
errors::KclError,
|
||||||
parsing::ast::types::{ItemVisibility, VariableKind},
|
parsing::ast::types::{ItemVisibility, VariableKind},
|
||||||
source_range::{ModuleId, SourceRange},
|
source_range::{ModuleId, SourceRange},
|
||||||
};
|
};
|
||||||
|
use tokeniser::Input;
|
||||||
|
|
||||||
mod tokeniser;
|
mod tokeniser;
|
||||||
|
|
||||||
// Re-export
|
|
||||||
pub use tokeniser::Input;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) use tokeniser::RESERVED_WORDS;
|
pub(crate) use tokeniser::RESERVED_WORDS;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub(crate) struct TokenStream {
|
||||||
|
tokens: Vec<Token>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokenStream {
|
||||||
|
fn new(tokens: Vec<Token>) -> Self {
|
||||||
|
Self { tokens }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn remove_unknown(&mut self) -> Vec<Token> {
|
||||||
|
let tokens = std::mem::take(&mut self.tokens);
|
||||||
|
let (tokens, unknown_tokens): (Vec<Token>, Vec<Token>) = tokens
|
||||||
|
.into_iter()
|
||||||
|
.partition(|token| token.token_type != TokenType::Unknown);
|
||||||
|
self.tokens = tokens;
|
||||||
|
unknown_tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Token> {
|
||||||
|
self.tokens.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.tokens.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> TokenSlice {
|
||||||
|
TokenSlice::from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a TokenStream> for TokenSlice<'a> {
|
||||||
|
fn from(stream: &'a TokenStream) -> Self {
|
||||||
|
TokenSlice {
|
||||||
|
start: 0,
|
||||||
|
end: stream.tokens.len(),
|
||||||
|
stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for TokenStream {
|
||||||
|
type Item = Token;
|
||||||
|
|
||||||
|
type IntoIter = std::vec::IntoIter<Token>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.tokens.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct TokenSlice<'a> {
|
||||||
|
stream: &'a TokenStream,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::ops::Deref for TokenSlice<'a> {
|
||||||
|
type Target = [Token];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.stream.tokens[self.start..self.end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TokenSlice<'a> {
|
||||||
|
pub fn token(&self, i: usize) -> &Token {
|
||||||
|
&self.stream.tokens[i + self.start]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Token> {
|
||||||
|
(**self).iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn without_ends(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
start: self.start + 1,
|
||||||
|
end: self.end - 1,
|
||||||
|
stream: self.stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for TokenSlice<'a> {
|
||||||
|
type Item = &'a Token;
|
||||||
|
|
||||||
|
type IntoIter = std::slice::Iter<'a, Token>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.stream.tokens[self.start..self.end].iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Stream for TokenSlice<'a> {
|
||||||
|
type Token = Token;
|
||||||
|
type Slice = Self;
|
||||||
|
type IterOffsets = Enumerate<std::vec::IntoIter<Token>>;
|
||||||
|
type Checkpoint = Checkpoint;
|
||||||
|
|
||||||
|
fn iter_offsets(&self) -> Self::IterOffsets {
|
||||||
|
#[allow(clippy::unnecessary_to_owned)]
|
||||||
|
self.to_vec().into_iter().enumerate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_offset(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_token(&mut self) -> Option<Self::Token> {
|
||||||
|
let token = self.first()?.clone();
|
||||||
|
self.start += 1;
|
||||||
|
Some(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_for<P>(&self, predicate: P) -> Option<usize>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Token) -> bool,
|
||||||
|
{
|
||||||
|
self.iter().position(|b| predicate(b.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_at(&self, tokens: usize) -> Result<usize, winnow::error::Needed> {
|
||||||
|
if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
|
||||||
|
Err(winnow::error::Needed::Size(needed))
|
||||||
|
} else {
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_slice(&mut self, offset: usize) -> Self::Slice {
|
||||||
|
assert!(self.start + offset <= self.end);
|
||||||
|
|
||||||
|
let next = TokenSlice {
|
||||||
|
stream: self.stream,
|
||||||
|
start: self.start,
|
||||||
|
end: self.start + offset,
|
||||||
|
};
|
||||||
|
self.start += offset;
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkpoint(&self) -> Self::Checkpoint {
|
||||||
|
Checkpoint(self.start, self.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
|
||||||
|
self.start = checkpoint.0;
|
||||||
|
self.end = checkpoint.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw(&self) -> &dyn fmt::Debug {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> winnow::stream::Offset for TokenSlice<'a> {
|
||||||
|
fn offset_from(&self, start: &Self) -> usize {
|
||||||
|
self.start - start.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> winnow::stream::Offset<Checkpoint> for TokenSlice<'a> {
|
||||||
|
fn offset_from(&self, start: &Checkpoint) -> usize {
|
||||||
|
self.start - start.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl winnow::stream::Offset for Checkpoint {
|
||||||
|
fn offset_from(&self, start: &Self) -> usize {
|
||||||
|
self.0 - start.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> winnow::stream::StreamIsPartial for TokenSlice<'a> {
|
||||||
|
type PartialState = ();
|
||||||
|
|
||||||
|
fn complete(&mut self) -> Self::PartialState {}
|
||||||
|
|
||||||
|
fn restore_partial(&mut self, _: Self::PartialState) {}
|
||||||
|
|
||||||
|
fn is_partial_supported() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Checkpoint(usize, usize);
|
||||||
|
|
||||||
/// The types of tokens.
|
/// The types of tokens.
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize, JsonSchema, FromStr, Display)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone, Display)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[display(style = "camelCase")]
|
#[display(style = "camelCase")]
|
||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
/// A number.
|
/// A number.
|
||||||
@ -73,6 +266,8 @@ pub enum TokenType {
|
|||||||
impl TryFrom<TokenType> for SemanticTokenType {
|
impl TryFrom<TokenType> for SemanticTokenType {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
fn try_from(token_type: TokenType) -> Result<Self> {
|
fn try_from(token_type: TokenType) -> Result<Self> {
|
||||||
|
// If you return a new kind of `SemanticTokenType`, make sure to update `SEMANTIC_TOKEN_TYPES`
|
||||||
|
// in the LSP implementation.
|
||||||
Ok(match token_type {
|
Ok(match token_type {
|
||||||
TokenType::Number => Self::NUMBER,
|
TokenType::Number => Self::NUMBER,
|
||||||
TokenType::Word => Self::VARIABLE,
|
TokenType::Word => Self::VARIABLE,
|
||||||
@ -102,52 +297,6 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TokenType {
|
impl TokenType {
|
||||||
// This is for the lsp server.
|
|
||||||
// Don't call this function directly in the code use a lazy_static instead
|
|
||||||
// like we do in the lsp server.
|
|
||||||
pub fn all_semantic_token_types() -> Result<Vec<SemanticTokenType>> {
|
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
|
||||||
settings.inline_subschemas = true;
|
|
||||||
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
|
||||||
|
|
||||||
let schema = TokenType::json_schema(&mut generator);
|
|
||||||
let schemars::schema::Schema::Object(o) = &schema else {
|
|
||||||
anyhow::bail!("expected object schema: {:#?}", schema);
|
|
||||||
};
|
|
||||||
let Some(subschemas) = &o.subschemas else {
|
|
||||||
anyhow::bail!("expected subschemas: {:#?}", schema);
|
|
||||||
};
|
|
||||||
let Some(one_ofs) = &subschemas.one_of else {
|
|
||||||
anyhow::bail!("expected one_of: {:#?}", schema);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut semantic_tokens = vec![];
|
|
||||||
for one_of in one_ofs {
|
|
||||||
let schemars::schema::Schema::Object(o) = one_of else {
|
|
||||||
anyhow::bail!("expected object one_of: {:#?}", one_of);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(enum_values) = o.enum_values.as_ref() else {
|
|
||||||
anyhow::bail!("expected enum values: {:#?}", o);
|
|
||||||
};
|
|
||||||
|
|
||||||
if enum_values.len() > 1 {
|
|
||||||
anyhow::bail!("expected only one enum value: {:#?}", o);
|
|
||||||
}
|
|
||||||
|
|
||||||
if enum_values.is_empty() {
|
|
||||||
anyhow::bail!("expected at least one enum value: {:#?}", o);
|
|
||||||
}
|
|
||||||
|
|
||||||
let label = TokenType::from_str(&enum_values[0].to_string().replace('"', ""))?;
|
|
||||||
if let Ok(semantic_token_type) = SemanticTokenType::try_from(label) {
|
|
||||||
semantic_tokens.push(semantic_token_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(semantic_tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_whitespace(&self) -> bool {
|
pub fn is_whitespace(&self) -> bool {
|
||||||
matches!(self, Self::Whitespace)
|
matches!(self, Self::Whitespace)
|
||||||
}
|
}
|
||||||
@ -157,17 +306,15 @@ impl TokenType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub token_type: TokenType,
|
pub token_type: TokenType,
|
||||||
/// Offset in the source code where this token begins.
|
/// Offset in the source code where this token begins.
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
/// Offset in the source code where this token ends.
|
/// Offset in the source code where this token ends.
|
||||||
pub end: usize,
|
pub end: usize,
|
||||||
#[serde(default, skip_serializing_if = "ModuleId::is_top_level")]
|
pub(super) module_id: ModuleId,
|
||||||
pub module_id: ModuleId,
|
pub(super) value: String,
|
||||||
pub value: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainsToken<Token> for (TokenType, &str) {
|
impl ContainsToken<Token> for (TokenType, &str) {
|
||||||
@ -249,7 +396,7 @@ impl From<&Token> for SourceRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lexer(s: &str, module_id: ModuleId) -> Result<Vec<Token>, KclError> {
|
pub fn lex(s: &str, module_id: ModuleId) -> Result<TokenStream, KclError> {
|
||||||
tokeniser::lex(s, module_id).map_err(From::from)
|
tokeniser::lex(s, module_id).map_err(From::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,15 +428,3 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// We have this as a test so we can ensure it never panics with an unwrap in the server.
|
|
||||||
#[test]
|
|
||||||
fn test_token_type_to_semantic_token_type() {
|
|
||||||
let semantic_types = TokenType::all_semantic_token_types().unwrap();
|
|
||||||
assert!(!semantic_types.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -47,7 +47,7 @@ fn read(filename: &'static str, test_name: &str) -> String {
|
|||||||
|
|
||||||
fn parse(test_name: &str) {
|
fn parse(test_name: &str) {
|
||||||
let input = read("input.kcl", test_name);
|
let input = read("input.kcl", test_name);
|
||||||
let tokens = crate::parsing::token::lexer(&input, ModuleId::default()).unwrap();
|
let tokens = crate::parsing::token::lex(&input, ModuleId::default()).unwrap();
|
||||||
|
|
||||||
// Parse the tokens into an AST.
|
// Parse the tokens into an AST.
|
||||||
let parse_res = Result::<_, KclError>::Ok(crate::parsing::parse_tokens(tokens).unwrap());
|
let parse_res = Result::<_, KclError>::Ok(crate::parsing::parse_tokens(tokens).unwrap());
|
||||||
@ -1502,3 +1502,108 @@ mod kw_fn {
|
|||||||
super::execute(TEST_NAME, true).await
|
super::execute(TEST_NAME, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod tag_can_be_proxied_through_parameter {
|
||||||
|
const TEST_NAME: &str = "tag_can_be_proxied_through_parameter";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[test]
|
||||||
|
fn unparse() {
|
||||||
|
super::unparse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod tag_proxied_through_function_does_not_define_var {
|
||||||
|
const TEST_NAME: &str = "tag_proxied_through_function_does_not_define_var";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[test]
|
||||||
|
fn unparse() {
|
||||||
|
super::unparse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod kw_fn_too_few_args {
|
||||||
|
const TEST_NAME: &str = "kw_fn_too_few_args";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[test]
|
||||||
|
fn unparse() {
|
||||||
|
super::unparse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod kw_fn_unlabeled_but_has_label {
|
||||||
|
const TEST_NAME: &str = "kw_fn_unlabeled_but_has_label";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[test]
|
||||||
|
fn unparse() {
|
||||||
|
super::unparse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod kw_fn_with_defaults {
|
||||||
|
const TEST_NAME: &str = "kw_fn_with_defaults";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[test]
|
||||||
|
fn unparse() {
|
||||||
|
super::unparse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,6 +50,13 @@ pub struct KwArgs {
|
|||||||
pub labeled: HashMap<String, Arg>,
|
pub labeled: HashMap<String, Arg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KwArgs {
|
||||||
|
/// How many arguments are there?
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Positional args.
|
/// Positional args.
|
||||||
|
@ -3,10 +3,10 @@ use std::fmt::Write;
|
|||||||
use crate::parsing::{
|
use crate::parsing::{
|
||||||
ast::types::{
|
ast::types::{
|
||||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||||
CallExpressionKw, Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression, ImportSelector,
|
CallExpressionKw, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression,
|
||||||
ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression,
|
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
|
||||||
MemberObject, Node, NonCodeValue, ObjectExpression, Parameter, PipeExpression, Program, TagDeclarator,
|
MemberExpression, MemberObject, Node, NonCodeValue, ObjectExpression, Parameter, PipeExpression, Program,
|
||||||
UnaryExpression, VariableDeclaration, VariableKind,
|
TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
|
||||||
},
|
},
|
||||||
PIPE_OPERATOR,
|
PIPE_OPERATOR,
|
||||||
};
|
};
|
||||||
@ -166,7 +166,14 @@ pub(crate) enum ExprContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
pub(crate) fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
|
pub(crate) fn recast(&self, options: &FormatOptions, indentation_level: usize, mut ctxt: ExprContext) -> String {
|
||||||
|
let is_decl = matches!(ctxt, ExprContext::Decl);
|
||||||
|
if is_decl {
|
||||||
|
// Just because this expression is being bound to a variable, doesn't mean that every child
|
||||||
|
// expression is being bound. So, reset the expression context if necessary.
|
||||||
|
// This will still preserve the "::Pipe" context though.
|
||||||
|
ctxt = ExprContext::Other;
|
||||||
|
}
|
||||||
match &self {
|
match &self {
|
||||||
Expr::BinaryExpression(bin_exp) => bin_exp.recast(options),
|
Expr::BinaryExpression(bin_exp) => bin_exp.recast(options),
|
||||||
Expr::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, ctxt),
|
Expr::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, ctxt),
|
||||||
@ -175,11 +182,7 @@ impl Expr {
|
|||||||
Expr::MemberExpression(mem_exp) => mem_exp.recast(),
|
Expr::MemberExpression(mem_exp) => mem_exp.recast(),
|
||||||
Expr::Literal(literal) => literal.recast(),
|
Expr::Literal(literal) => literal.recast(),
|
||||||
Expr::FunctionExpression(func_exp) => {
|
Expr::FunctionExpression(func_exp) => {
|
||||||
let mut result = if ctxt == ExprContext::Decl {
|
let mut result = if is_decl { String::new() } else { "fn".to_owned() };
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
"fn".to_owned()
|
|
||||||
};
|
|
||||||
result += &func_exp.recast(options, indentation_level);
|
result += &func_exp.recast(options, indentation_level);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -659,15 +662,18 @@ impl FunctionExpression {
|
|||||||
|
|
||||||
impl Parameter {
|
impl Parameter {
|
||||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
let mut result = format!(
|
let at_sign = if self.labeled { "" } else { "@" };
|
||||||
"{}{}",
|
let identifier = &self.identifier.name;
|
||||||
if self.labeled { "" } else { "@" },
|
let question_mark = if self.default_value.is_some() { "?" } else { "" };
|
||||||
self.identifier.name.clone()
|
let mut result = format!("{at_sign}{identifier}{question_mark}");
|
||||||
);
|
|
||||||
if let Some(ty) = &self.type_ {
|
if let Some(ty) = &self.type_ {
|
||||||
result += ": ";
|
result += ": ";
|
||||||
result += &ty.recast(options, indentation_level);
|
result += &ty.recast(options, indentation_level);
|
||||||
}
|
}
|
||||||
|
if let Some(DefaultParamVal::Literal(ref literal)) = self.default_value {
|
||||||
|
let lit = literal.recast();
|
||||||
|
result.push_str(&format!(" = {lit}"));
|
||||||
|
};
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -2134,8 +2140,10 @@ fn f() {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let tokens = crate::parsing::token::lexer(raw, ModuleId::default()).unwrap();
|
let tokens = crate::parsing::token::lex(raw, ModuleId::default()).unwrap();
|
||||||
let literal = crate::parsing::parser::unsigned_number_literal.parse(&tokens).unwrap();
|
let literal = crate::parsing::parser::unsigned_number_literal
|
||||||
|
.parse(tokens.as_slice())
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
literal.recast(),
|
literal.recast(),
|
||||||
expected,
|
expected,
|
||||||
@ -2170,6 +2178,28 @@ sketch002 = startSketchOn({
|
|||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unparse_fn_unnamed() {
|
||||||
|
let input = r#"squares_out = reduce(arr, 0, fn(i, squares) {
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
"#;
|
||||||
|
let ast = crate::parsing::top_level_parse(input).unwrap();
|
||||||
|
let actual = ast.recast(&FormatOptions::new(), 0);
|
||||||
|
assert_eq!(actual, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unparse_fn_named() {
|
||||||
|
let input = r#"fn f(x) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let ast = crate::parsing::top_level_parse(input).unwrap();
|
||||||
|
let actual = ast.recast(&FormatOptions::new(), 0);
|
||||||
|
assert_eq!(actual, input);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recast_objects_with_comments() {
|
fn recast_objects_with_comments() {
|
||||||
use winnow::Parser;
|
use winnow::Parser;
|
||||||
@ -2191,9 +2221,9 @@ sketch002 = startSketchOn({
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
|
let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
|
||||||
crate::parsing::parser::print_tokens(&tokens);
|
crate::parsing::parser::print_tokens(tokens.as_slice());
|
||||||
let expr = crate::parsing::parser::object.parse(&tokens).unwrap();
|
let expr = crate::parsing::parser::object.parse(tokens.as_slice()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expr.recast(&FormatOptions::new(), 0, ExprContext::Other),
|
expr.recast(&FormatOptions::new(), 0, ExprContext::Other),
|
||||||
expected,
|
expected,
|
||||||
@ -2289,8 +2319,10 @@ sketch002 = startSketchOn({
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
|
let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
|
||||||
let expr = crate::parsing::parser::array_elem_by_elem.parse(&tokens).unwrap();
|
let expr = crate::parsing::parser::array_elem_by_elem
|
||||||
|
.parse(tokens.as_slice())
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expr.recast(&FormatOptions::new(), 0, ExprContext::Other),
|
expr.recast(&FormatOptions::new(), 0, ExprContext::Other),
|
||||||
expected,
|
expected,
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
|
|
||||||
/// The "Node" type wraps all the AST elements we're able to find in a KCL
|
/// The "Node" type wraps all the AST elements we're able to find in a KCL
|
||||||
/// file. Tokens we walk through will be one of these.
|
/// file. Tokens we walk through will be one of these.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Node<'a> {
|
pub enum Node<'a> {
|
||||||
Program(NodeRef<'a, types::Program>),
|
Program(NodeRef<'a, types::Program>),
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ pub enum Node<'a> {
|
|||||||
MemberExpression(NodeRef<'a, types::MemberExpression>),
|
MemberExpression(NodeRef<'a, types::MemberExpression>),
|
||||||
UnaryExpression(NodeRef<'a, types::UnaryExpression>),
|
UnaryExpression(NodeRef<'a, types::UnaryExpression>),
|
||||||
IfExpression(NodeRef<'a, types::IfExpression>),
|
IfExpression(NodeRef<'a, types::IfExpression>),
|
||||||
|
ElseIf(&'a types::ElseIf),
|
||||||
|
|
||||||
Parameter(&'a types::Parameter),
|
Parameter(&'a types::Parameter),
|
||||||
|
|
||||||
@ -38,11 +39,22 @@ pub enum Node<'a> {
|
|||||||
|
|
||||||
MemberObject(&'a types::MemberObject),
|
MemberObject(&'a types::MemberObject),
|
||||||
LiteralIdentifier(&'a types::LiteralIdentifier),
|
LiteralIdentifier(&'a types::LiteralIdentifier),
|
||||||
|
|
||||||
|
KclNone(&'a types::KclNone),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Node<'_>> for SourceRange {
|
/// Returned during source_range conversion.
|
||||||
fn from(node: &Node) -> Self {
|
#[derive(Debug)]
|
||||||
match node {
|
pub enum AstNodeError {
|
||||||
|
/// Returned if we try and [SourceRange] a [types::KclNone].
|
||||||
|
NoSourceForAKclNone,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Node<'_>> for SourceRange {
|
||||||
|
type Error = AstNodeError;
|
||||||
|
|
||||||
|
fn try_from(node: &Node) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match node {
|
||||||
Node::Program(n) => SourceRange::from(*n),
|
Node::Program(n) => SourceRange::from(*n),
|
||||||
Node::ImportStatement(n) => SourceRange::from(*n),
|
Node::ImportStatement(n) => SourceRange::from(*n),
|
||||||
Node::ExpressionStatement(n) => SourceRange::from(*n),
|
Node::ExpressionStatement(n) => SourceRange::from(*n),
|
||||||
@ -68,6 +80,62 @@ impl From<&Node<'_>> for SourceRange {
|
|||||||
Node::MemberObject(m) => SourceRange::new(m.start(), m.end(), m.module_id()),
|
Node::MemberObject(m) => SourceRange::new(m.start(), m.end(), m.module_id()),
|
||||||
Node::IfExpression(n) => SourceRange::from(*n),
|
Node::IfExpression(n) => SourceRange::from(*n),
|
||||||
Node::LiteralIdentifier(l) => SourceRange::new(l.start(), l.end(), l.module_id()),
|
Node::LiteralIdentifier(l) => SourceRange::new(l.start(), l.end(), l.module_id()),
|
||||||
|
|
||||||
|
// This is broken too
|
||||||
|
Node::ElseIf(n) => SourceRange::new(n.cond.start(), n.cond.end(), n.cond.module_id()),
|
||||||
|
|
||||||
|
// The KclNone type here isn't an actual node, so it has no
|
||||||
|
// start/end information.
|
||||||
|
Node::KclNone(_) => return Err(Self::Error::NoSourceForAKclNone),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree> From<&'tree types::BodyItem> for Node<'tree> {
|
||||||
|
fn from(node: &'tree types::BodyItem) -> Self {
|
||||||
|
match node {
|
||||||
|
types::BodyItem::ImportStatement(v) => v.as_ref().into(),
|
||||||
|
types::BodyItem::ExpressionStatement(v) => v.into(),
|
||||||
|
types::BodyItem::VariableDeclaration(v) => v.as_ref().into(),
|
||||||
|
types::BodyItem::ReturnStatement(v) => v.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree> From<&'tree types::Expr> for Node<'tree> {
|
||||||
|
fn from(node: &'tree types::Expr) -> Self {
|
||||||
|
match node {
|
||||||
|
types::Expr::Literal(lit) => lit.as_ref().into(),
|
||||||
|
types::Expr::TagDeclarator(tag) => tag.as_ref().into(),
|
||||||
|
types::Expr::Identifier(id) => id.as_ref().into(),
|
||||||
|
types::Expr::BinaryExpression(be) => be.as_ref().into(),
|
||||||
|
types::Expr::FunctionExpression(fe) => fe.as_ref().into(),
|
||||||
|
types::Expr::CallExpression(ce) => ce.as_ref().into(),
|
||||||
|
types::Expr::CallExpressionKw(ce) => ce.as_ref().into(),
|
||||||
|
types::Expr::PipeExpression(pe) => pe.as_ref().into(),
|
||||||
|
types::Expr::PipeSubstitution(ps) => ps.as_ref().into(),
|
||||||
|
types::Expr::ArrayExpression(ae) => ae.as_ref().into(),
|
||||||
|
types::Expr::ArrayRangeExpression(are) => are.as_ref().into(),
|
||||||
|
types::Expr::ObjectExpression(oe) => oe.as_ref().into(),
|
||||||
|
types::Expr::MemberExpression(me) => me.as_ref().into(),
|
||||||
|
types::Expr::UnaryExpression(ue) => ue.as_ref().into(),
|
||||||
|
types::Expr::IfExpression(e) => e.as_ref().into(),
|
||||||
|
types::Expr::None(n) => n.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
|
||||||
|
fn from(node: &'tree types::BinaryPart) -> Self {
|
||||||
|
match node {
|
||||||
|
types::BinaryPart::Literal(lit) => lit.as_ref().into(),
|
||||||
|
types::BinaryPart::Identifier(id) => id.as_ref().into(),
|
||||||
|
types::BinaryPart::BinaryExpression(be) => be.as_ref().into(),
|
||||||
|
types::BinaryPart::CallExpression(ce) => ce.as_ref().into(),
|
||||||
|
types::BinaryPart::CallExpressionKw(ce) => ce.as_ref().into(),
|
||||||
|
types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(),
|
||||||
|
types::BinaryPart::MemberExpression(me) => me.as_ref().into(),
|
||||||
|
types::BinaryPart::IfExpression(e) => e.as_ref().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,4 +184,6 @@ impl_from!(Node, ObjectProperty);
|
|||||||
impl_from_ref!(Node, Parameter);
|
impl_from_ref!(Node, Parameter);
|
||||||
impl_from_ref!(Node, MemberObject);
|
impl_from_ref!(Node, MemberObject);
|
||||||
impl_from!(Node, IfExpression);
|
impl_from!(Node, IfExpression);
|
||||||
|
impl_from!(Node, ElseIf);
|
||||||
impl_from_ref!(Node, LiteralIdentifier);
|
impl_from_ref!(Node, LiteralIdentifier);
|
||||||
|
impl_from!(Node, KclNone);
|
||||||
|
197
src/wasm-lib/kcl/src/walk/ast_visitor.rs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::walk::Node;
|
||||||
|
|
||||||
|
/// Walk-specific trait adding the ability to traverse the KCL AST.
|
||||||
|
///
|
||||||
|
/// This trait is implemented on [Node] to handle the fairly tricky bit of
|
||||||
|
/// recursing into the AST in a single place, as well as helpers for traversing
|
||||||
|
/// the tree. for callers to use.
|
||||||
|
pub trait Visitable<'tree> {
|
||||||
|
/// Return a `Vec<Node>` for all *direct* children of this AST node. This
|
||||||
|
/// should only contain direct descendants.
|
||||||
|
fn children(&self) -> Vec<Node<'tree>>;
|
||||||
|
|
||||||
|
/// Return `self` as a [Node]. Generally speaking, the [Visitable] trait
|
||||||
|
/// is only going to be implemented on [Node], so this is purely used by
|
||||||
|
/// helpers that are generic over a [Visitable] and want to deref back
|
||||||
|
/// into a [Node].
|
||||||
|
fn node(&self) -> Node<'tree>;
|
||||||
|
|
||||||
|
/// Call the provided [Visitor] in order to Visit `self`. This will
|
||||||
|
/// only be called on `self` -- the [Visitor] is responsible for
|
||||||
|
/// recursing into any children, if desired.
|
||||||
|
fn visit<VisitorT>(&self, visitor: VisitorT) -> Result<bool, VisitorT::Error>
|
||||||
|
where
|
||||||
|
VisitorT: Visitor<'tree>,
|
||||||
|
{
|
||||||
|
visitor.visit_node(self.node())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait used to enable visiting members of KCL AST.
|
||||||
|
///
|
||||||
|
/// Implementing this trait enables the implementer to be invoked over
|
||||||
|
/// members of KCL AST by using the [Visitable::visit] function on
|
||||||
|
/// a [Node].
|
||||||
|
pub trait Visitor<'tree> {
|
||||||
|
/// Error type returned by the [Self::visit] function.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Visit a KCL AST [Node].
|
||||||
|
///
|
||||||
|
/// In general, implementers likely wish to check to see if a Node is what
|
||||||
|
/// they're looking for, and either descend into that [Node]'s children (by
|
||||||
|
/// calling [Visitable::children] on [Node] to get children nodes,
|
||||||
|
/// calling [Visitable::visit] on each node of interest), or perform
|
||||||
|
/// some action.
|
||||||
|
fn visit_node(&self, node: Node<'tree>) -> Result<bool, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, FnT, ErrorT> Visitor<'a> for FnT
|
||||||
|
where
|
||||||
|
FnT: Fn(Node<'a>) -> Result<bool, ErrorT>,
|
||||||
|
{
|
||||||
|
type Error = ErrorT;
|
||||||
|
|
||||||
|
fn visit_node(&self, n: Node<'a>) -> Result<bool, ErrorT> {
|
||||||
|
self(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree> Visitable<'tree> for Node<'tree> {
|
||||||
|
fn node(&self) -> Node<'tree> {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<Node<'tree>> {
|
||||||
|
match self {
|
||||||
|
Node::Program(n) => n.body.iter().map(|node| node.into()).collect(),
|
||||||
|
Node::ExpressionStatement(n) => {
|
||||||
|
vec![(&n.expression).into()]
|
||||||
|
}
|
||||||
|
Node::BinaryExpression(n) => {
|
||||||
|
vec![(&n.left).into(), (&n.right).into()]
|
||||||
|
}
|
||||||
|
Node::FunctionExpression(n) => {
|
||||||
|
let mut children = n.params.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||||
|
children.push((&n.body).into());
|
||||||
|
children
|
||||||
|
}
|
||||||
|
Node::CallExpression(n) => {
|
||||||
|
let mut children = n.arguments.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||||
|
children.insert(0, (&n.callee).into());
|
||||||
|
children
|
||||||
|
}
|
||||||
|
Node::CallExpressionKw(n) => {
|
||||||
|
let mut children = n.unlabeled.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||||
|
|
||||||
|
// TODO: this is wrong but it's what the old walk code was doing.
|
||||||
|
// We likely need a real LabeledArg AST node, but I don't
|
||||||
|
// want to tango with it since it's a lot deeper than
|
||||||
|
// adding it to the enum.
|
||||||
|
children.extend(n.arguments.iter().map(|v| (&v.arg).into()).collect::<Vec<Node>>());
|
||||||
|
children
|
||||||
|
}
|
||||||
|
Node::PipeExpression(n) => n.body.iter().map(|v| v.into()).collect(),
|
||||||
|
Node::ArrayExpression(n) => n.elements.iter().map(|v| v.into()).collect(),
|
||||||
|
Node::ArrayRangeExpression(n) => {
|
||||||
|
vec![(&n.start_element).into(), (&n.end_element).into()]
|
||||||
|
}
|
||||||
|
Node::ObjectExpression(n) => n.properties.iter().map(|v| v.into()).collect(),
|
||||||
|
Node::MemberExpression(n) => {
|
||||||
|
vec![(&n.object).into(), (&n.property).into()]
|
||||||
|
}
|
||||||
|
Node::IfExpression(n) => {
|
||||||
|
let mut children = n.else_ifs.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||||
|
children.insert(0, n.cond.as_ref().into());
|
||||||
|
children.push(n.final_else.as_ref().into());
|
||||||
|
children
|
||||||
|
}
|
||||||
|
Node::VariableDeclaration(n) => vec![(&n.declaration).into()],
|
||||||
|
Node::ReturnStatement(n) => {
|
||||||
|
vec![(&n.argument).into()]
|
||||||
|
}
|
||||||
|
Node::VariableDeclarator(n) => {
|
||||||
|
vec![(&n.id).into(), (&n.init).into()]
|
||||||
|
}
|
||||||
|
Node::UnaryExpression(n) => {
|
||||||
|
vec![(&n.argument).into()]
|
||||||
|
}
|
||||||
|
Node::Parameter(n) => {
|
||||||
|
vec![(&n.identifier).into()]
|
||||||
|
}
|
||||||
|
Node::ObjectProperty(n) => {
|
||||||
|
vec![(&n.value).into()]
|
||||||
|
}
|
||||||
|
Node::ElseIf(n) => {
|
||||||
|
vec![(&n.cond).into(), n.then_val.as_ref().into()]
|
||||||
|
}
|
||||||
|
Node::PipeSubstitution(_)
|
||||||
|
| Node::TagDeclarator(_)
|
||||||
|
| Node::Identifier(_)
|
||||||
|
| Node::ImportStatement(_)
|
||||||
|
| Node::MemberObject(_)
|
||||||
|
| Node::LiteralIdentifier(_)
|
||||||
|
| Node::KclNone(_)
|
||||||
|
| Node::Literal(_) => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
macro_rules! kcl {
|
||||||
|
( $kcl:expr ) => {{
|
||||||
|
$crate::parsing::top_level_parse($kcl).unwrap()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_crows() {
|
||||||
|
let program = kcl!(
|
||||||
|
"\
|
||||||
|
const crow1 = 1
|
||||||
|
const crow2 = 2
|
||||||
|
|
||||||
|
fn crow3() {
|
||||||
|
const crow4 = 3
|
||||||
|
crow5()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct CountCrows {
|
||||||
|
n: Box<Mutex<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree> Visitor<'tree> for &CountCrows {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn visit_node(&self, node: Node<'tree>) -> Result<bool, Self::Error> {
|
||||||
|
if let Node::VariableDeclarator(vd) = node {
|
||||||
|
if vd.id.name.starts_with("crow") {
|
||||||
|
*self.n.lock().unwrap() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in node.children().iter() {
|
||||||
|
if !child.visit(*self)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let prog: Node = (&program).into();
|
||||||
|
let count_crows: CountCrows = Default::default();
|
||||||
|
Visitable::visit(&prog, &count_crows).unwrap();
|
||||||
|
assert_eq!(*count_crows.n.lock().unwrap(), 4);
|
||||||
|
}
|
||||||
|
}
|
@ -1,329 +1,55 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use super::ast_visitor::{Visitable, Visitor};
|
||||||
use crate::{
|
use crate::{
|
||||||
parsing::ast::types::{
|
parsing::ast::types::{NodeRef, Program},
|
||||||
BinaryPart, BodyItem, Expr, IfExpression, LiteralIdentifier, MemberExpression, MemberObject, NodeRef,
|
|
||||||
ObjectExpression, ObjectProperty, Parameter, Program, UnaryExpression, VariableDeclarator,
|
|
||||||
},
|
|
||||||
walk::Node,
|
walk::Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Walker is implemented by things that are able to walk an AST tree to
|
/// *DEPRECATED* Walk trait.
|
||||||
/// produce lints. This trait is implemented automatically for a few of the
|
///
|
||||||
/// common types, but can be manually implemented too.
|
/// This was written before [Visitor], which is the better way to traverse
|
||||||
|
/// a AST.
|
||||||
|
///
|
||||||
|
/// This trait continues to exist in order to not change all the linter
|
||||||
|
/// as we refine the walk code.
|
||||||
|
///
|
||||||
|
/// This, internally, uses the new [Visitor] trait, and is only provided as
|
||||||
|
/// a stub until we migrate all existing code off this trait.
|
||||||
pub trait Walker<'a> {
|
pub trait Walker<'a> {
|
||||||
/// Walk will visit every element of the AST.
|
/// Walk will visit every element of the AST, recursing through the
|
||||||
|
/// whole tree.
|
||||||
fn walk(&self, n: Node<'a>) -> Result<bool>;
|
fn walk(&self, n: Node<'a>) -> Result<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, FnT> Walker<'a> for FnT
|
impl<'tree, VisitorT> Walker<'tree> for VisitorT
|
||||||
where
|
where
|
||||||
FnT: Fn(Node<'a>) -> Result<bool>,
|
VisitorT: Visitor<'tree>,
|
||||||
|
VisitorT: Clone,
|
||||||
|
anyhow::Error: From<VisitorT::Error>,
|
||||||
|
VisitorT::Error: Send,
|
||||||
|
VisitorT::Error: Sync,
|
||||||
{
|
{
|
||||||
fn walk(&self, n: Node<'a>) -> Result<bool> {
|
fn walk(&self, n: Node<'tree>) -> Result<bool> {
|
||||||
self(n)
|
if !n.visit(self.clone())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
for child in n.children() {
|
||||||
|
if !Self::walk(self, child)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the Walker against all [Node]s in a [Program].
|
/// Run the Walker against all [Node]s in a [Program].
|
||||||
pub fn walk<'a, WalkT>(prog: NodeRef<'a, Program>, f: &WalkT) -> Result<bool>
|
pub fn walk<'a, WalkT>(prog: NodeRef<'a, Program>, f: WalkT) -> Result<bool>
|
||||||
where
|
where
|
||||||
WalkT: Walker<'a>,
|
WalkT: Walker<'a>,
|
||||||
{
|
{
|
||||||
if !f.walk(prog.into())? {
|
let prog: Node = prog.into();
|
||||||
return Ok(false);
|
f.walk(prog)
|
||||||
}
|
|
||||||
|
|
||||||
for bi in &prog.body {
|
|
||||||
if !walk_body_item(bi, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_variable_declarator<'a, WalkT>(node: NodeRef<'a, VariableDeclarator>, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !f.walk((&node.id).into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
walk_value(&node.init, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_parameter<'a, WalkT>(node: &'a Parameter, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
f.walk((&node.identifier).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_member_object<'a, WalkT>(node: &'a MemberObject, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
f.walk(node.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_literal_identifier<'a, WalkT>(node: &'a LiteralIdentifier, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
f.walk(node.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_member_expression<'a, WalkT>(node: NodeRef<'a, MemberExpression>, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !walk_member_object(&node.object, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
walk_literal_identifier(&node.property, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_binary_part<'a, WalkT>(node: &'a BinaryPart, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
match node {
|
|
||||||
BinaryPart::Literal(lit) => f.walk(lit.as_ref().into()),
|
|
||||||
BinaryPart::Identifier(id) => f.walk(id.as_ref().into()),
|
|
||||||
BinaryPart::BinaryExpression(be) => f.walk(be.as_ref().into()),
|
|
||||||
BinaryPart::CallExpression(ce) => f.walk(ce.as_ref().into()),
|
|
||||||
BinaryPart::CallExpressionKw(ce) => f.walk(ce.as_ref().into()),
|
|
||||||
BinaryPart::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
|
||||||
BinaryPart::MemberExpression(me) => walk_member_expression(me, f),
|
|
||||||
BinaryPart::IfExpression(e) => walk_if_expression(e, f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Rename this to walk_expr
|
|
||||||
fn walk_value<'a, WalkT>(node: &'a Expr, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
match node {
|
|
||||||
Expr::Literal(lit) => f.walk(lit.as_ref().into()),
|
|
||||||
Expr::TagDeclarator(tag) => f.walk(tag.as_ref().into()),
|
|
||||||
|
|
||||||
Expr::Identifier(id) => {
|
|
||||||
// sometimes there's a bare Identifier without a Value::Identifier.
|
|
||||||
f.walk(id.as_ref().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::BinaryExpression(be) => {
|
|
||||||
if !f.walk(be.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !walk_binary_part(&be.left, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
walk_binary_part(&be.right, f)
|
|
||||||
}
|
|
||||||
Expr::FunctionExpression(fe) => {
|
|
||||||
if !f.walk(fe.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in &fe.params {
|
|
||||||
if !walk_parameter(arg, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walk(&fe.body, f)
|
|
||||||
}
|
|
||||||
Expr::CallExpression(ce) => {
|
|
||||||
if !f.walk(ce.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.walk((&ce.callee).into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
for e in &ce.arguments {
|
|
||||||
if !walk_value::<WalkT>(e, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Expr::CallExpressionKw(ce) => {
|
|
||||||
if !f.walk(ce.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.walk((&ce.callee).into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if let Some(ref e) = ce.unlabeled {
|
|
||||||
if !walk_value::<WalkT>(e, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for e in &ce.arguments {
|
|
||||||
if !walk_value::<WalkT>(&e.arg, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Expr::PipeExpression(pe) => {
|
|
||||||
if !f.walk(pe.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
for e in &pe.body {
|
|
||||||
if !walk_value::<WalkT>(e, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Expr::PipeSubstitution(ps) => f.walk(ps.as_ref().into()),
|
|
||||||
Expr::ArrayExpression(ae) => {
|
|
||||||
if !f.walk(ae.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
for e in &ae.elements {
|
|
||||||
if !walk_value::<WalkT>(e, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Expr::ArrayRangeExpression(are) => {
|
|
||||||
if !f.walk(are.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !walk_value::<WalkT>(&are.start_element, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !walk_value::<WalkT>(&are.end_element, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Expr::ObjectExpression(oe) => walk_object_expression(oe, f),
|
|
||||||
Expr::MemberExpression(me) => walk_member_expression(me, f),
|
|
||||||
Expr::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
|
||||||
Expr::IfExpression(e) => walk_if_expression(e, f),
|
|
||||||
Expr::None(_) => Ok(true),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk through an [ObjectProperty].
|
|
||||||
fn walk_object_property<'a, WalkT>(node: NodeRef<'a, ObjectProperty>, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
walk_value(&node.value, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk through an [ObjectExpression].
|
|
||||||
fn walk_object_expression<'a, WalkT>(node: NodeRef<'a, ObjectExpression>, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
for prop in &node.properties {
|
|
||||||
if !walk_object_property(prop, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk through an [IfExpression].
|
|
||||||
fn walk_if_expression<'a, WalkT>(node: NodeRef<'a, IfExpression>, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !walk_value(&node.cond, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
for else_if in &node.else_ifs {
|
|
||||||
if !walk_value(&else_if.cond, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !walk(&else_if.then_val, f)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let final_else = &(*node.final_else);
|
|
||||||
if !f.walk(final_else.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// walk through an [UnaryExpression].
|
|
||||||
fn walk_unary_expression<'a, WalkT>(node: NodeRef<'a, UnaryExpression>, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
if !f.walk(node.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
walk_binary_part(&node.argument, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// walk through a [BodyItem].
|
|
||||||
fn walk_body_item<'a, WalkT>(node: &'a BodyItem, f: &WalkT) -> Result<bool>
|
|
||||||
where
|
|
||||||
WalkT: Walker<'a>,
|
|
||||||
{
|
|
||||||
// We don't walk a BodyItem since it's an enum itself.
|
|
||||||
|
|
||||||
match node {
|
|
||||||
BodyItem::ImportStatement(xs) => {
|
|
||||||
if !f.walk(xs.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
BodyItem::ExpressionStatement(xs) => {
|
|
||||||
if !f.walk(xs.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
walk_value(&xs.expression, f)
|
|
||||||
}
|
|
||||||
BodyItem::VariableDeclaration(vd) => {
|
|
||||||
if !f.walk(vd.as_ref().into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
walk_variable_declarator(&vd.declaration, f)
|
|
||||||
}
|
|
||||||
BodyItem::ReturnStatement(rs) => {
|
|
||||||
if !f.walk(rs.into())? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
walk_value(&rs.argument, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -345,10 +71,10 @@ const bar = 2
|
|||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
walk(&program, &|node| {
|
walk(&program, |node| {
|
||||||
if let Node::VariableDeclarator(vd) = node {
|
if let Node::VariableDeclarator(vd) = node {
|
||||||
if vd.id.name == "foo" {
|
if vd.id.name == "foo" {
|
||||||
return Ok(false);
|
return Ok::<bool, anyhow::Error>(false);
|
||||||
}
|
}
|
||||||
panic!("walk didn't stop");
|
panic!("walk didn't stop");
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod ast_node;
|
mod ast_node;
|
||||||
|
mod ast_visitor;
|
||||||
mod ast_walk;
|
mod ast_walk;
|
||||||
|
|
||||||
pub use ast_node::Node;
|
pub use ast_node::Node;
|
||||||
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 133 KiB |
@ -78,46 +78,179 @@ snapshot_kind: text
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"declaration": {
|
"declaration": {
|
||||||
"end": 55,
|
"end": 77,
|
||||||
"id": {
|
"id": {
|
||||||
"end": 40,
|
"end": 43,
|
||||||
|
"name": "add",
|
||||||
|
"start": 40,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 75,
|
||||||
|
"left": {
|
||||||
|
"end": 67,
|
||||||
|
"name": "x",
|
||||||
|
"start": 66,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 75,
|
||||||
|
"name": "delta",
|
||||||
|
"start": 70,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 66,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 75,
|
||||||
|
"start": 59,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 77,
|
||||||
|
"start": 55
|
||||||
|
},
|
||||||
|
"end": 77,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 46,
|
||||||
|
"name": "x",
|
||||||
|
"start": 45,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"labeled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 53,
|
||||||
|
"name": "delta",
|
||||||
|
"start": 48,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 43,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 40,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 77,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 37,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 97,
|
||||||
|
"id": {
|
||||||
|
"end": 82,
|
||||||
"name": "two",
|
"name": "two",
|
||||||
"start": 37,
|
"start": 79,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"init": {
|
"init": {
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"end": 54,
|
"end": 96,
|
||||||
"raw": "1",
|
"raw": "1",
|
||||||
"start": 53,
|
"start": 95,
|
||||||
"type": "Literal",
|
"type": "Literal",
|
||||||
"type": "Literal",
|
"type": "Literal",
|
||||||
"value": 1.0
|
"value": 1.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"callee": {
|
"callee": {
|
||||||
"end": 52,
|
"end": 94,
|
||||||
"name": "increment",
|
"name": "increment",
|
||||||
"start": 43,
|
"start": 85,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"end": 55,
|
"end": 97,
|
||||||
"start": 43,
|
"start": 85,
|
||||||
"type": "CallExpression",
|
"type": "CallExpression",
|
||||||
"type": "CallExpression"
|
"type": "CallExpression"
|
||||||
},
|
},
|
||||||
"start": 37,
|
"start": 79,
|
||||||
"type": "VariableDeclarator"
|
"type": "VariableDeclarator"
|
||||||
},
|
},
|
||||||
"end": 55,
|
"end": 97,
|
||||||
"kind": "const",
|
"kind": "const",
|
||||||
"start": 37,
|
"start": 79,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 122,
|
||||||
|
"id": {
|
||||||
|
"end": 103,
|
||||||
|
"name": "three",
|
||||||
|
"start": 98,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"name": "delta"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"end": 121,
|
||||||
|
"raw": "2",
|
||||||
|
"start": 120,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 109,
|
||||||
|
"name": "add",
|
||||||
|
"start": 106,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 122,
|
||||||
|
"start": 106,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": {
|
||||||
|
"end": 111,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 110,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 98,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 122,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 98,
|
||||||
"type": "VariableDeclaration",
|
"type": "VariableDeclaration",
|
||||||
"type": "VariableDeclaration"
|
"type": "VariableDeclaration"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"end": 56,
|
"end": 123,
|
||||||
"nonCodeMeta": {
|
"nonCodeMeta": {
|
||||||
"nonCodeNodes": {
|
"nonCodeNodes": {
|
||||||
"0": [
|
"0": [
|
||||||
@ -129,6 +262,16 @@ snapshot_kind: text
|
|||||||
"type": "newLine"
|
"type": "newLine"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"1": [
|
||||||
|
{
|
||||||
|
"end": 79,
|
||||||
|
"start": 77,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"startNodes": []
|
"startNodes": []
|
||||||
|
@ -2,4 +2,9 @@ fn increment(@x) {
|
|||||||
return x + 1
|
return x + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add(@x, delta) {
|
||||||
|
return x + delta
|
||||||
|
}
|
||||||
|
|
||||||
two = increment(1)
|
two = increment(1)
|
||||||
|
three = add(1, delta: 2)
|
||||||
|
@ -27,6 +27,202 @@ snapshot_kind: text
|
|||||||
"value": 0.0,
|
"value": 0.0,
|
||||||
"__meta": []
|
"__meta": []
|
||||||
},
|
},
|
||||||
|
"add": {
|
||||||
|
"type": "Function",
|
||||||
|
"expression": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 75,
|
||||||
|
"left": {
|
||||||
|
"end": 67,
|
||||||
|
"name": "x",
|
||||||
|
"start": 66,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 75,
|
||||||
|
"name": "delta",
|
||||||
|
"start": 70,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 66,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 75,
|
||||||
|
"start": 59,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 77,
|
||||||
|
"start": 55
|
||||||
|
},
|
||||||
|
"end": 77,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 46,
|
||||||
|
"name": "x",
|
||||||
|
"start": 45,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"labeled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 53,
|
||||||
|
"name": "delta",
|
||||||
|
"start": 48,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 43,
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"increment": {
|
||||||
|
"type": "Function",
|
||||||
|
"expression": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 33,
|
||||||
|
"left": {
|
||||||
|
"end": 29,
|
||||||
|
"name": "x",
|
||||||
|
"start": 28,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 33,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 32,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
},
|
||||||
|
"start": 28,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 33,
|
||||||
|
"start": 21,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 35,
|
||||||
|
"start": 17
|
||||||
|
},
|
||||||
|
"end": 35,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 15,
|
||||||
|
"name": "x",
|
||||||
|
"start": 14,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"labeled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 12,
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
12,
|
||||||
|
35,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
43,
|
||||||
|
77,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"increment": {
|
"increment": {
|
||||||
"type": "Function",
|
"type": "Function",
|
||||||
"expression": {
|
"expression": {
|
||||||
@ -121,14 +317,34 @@ snapshot_kind: text
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"three": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 3.0,
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
110,
|
||||||
|
111,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
120,
|
||||||
|
121,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"two": {
|
"two": {
|
||||||
"type": "Number",
|
"type": "Number",
|
||||||
"value": 2.0,
|
"value": 2.0,
|
||||||
"__meta": [
|
"__meta": [
|
||||||
{
|
{
|
||||||
"sourceRange": [
|
"sourceRange": [
|
||||||
53,
|
95,
|
||||||
54,
|
96,
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
153
src/wasm-lib/kcl/tests/kw_fn_too_few_args/ast.snap
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Result of parsing kw_fn_too_few_args.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 31,
|
||||||
|
"id": {
|
||||||
|
"end": 6,
|
||||||
|
"name": "add",
|
||||||
|
"start": 3,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 29,
|
||||||
|
"left": {
|
||||||
|
"end": 25,
|
||||||
|
"name": "x",
|
||||||
|
"start": 24,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 29,
|
||||||
|
"name": "y",
|
||||||
|
"start": 28,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 24,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 29,
|
||||||
|
"start": 17,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 31,
|
||||||
|
"start": 13
|
||||||
|
},
|
||||||
|
"end": 31,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 8,
|
||||||
|
"name": "x",
|
||||||
|
"start": 7,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 11,
|
||||||
|
"name": "y",
|
||||||
|
"start": 10,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 6,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 3,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 31,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 50,
|
||||||
|
"id": {
|
||||||
|
"end": 38,
|
||||||
|
"name": "three",
|
||||||
|
"start": 33,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"name": "x"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"end": 49,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 48,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 44,
|
||||||
|
"name": "add",
|
||||||
|
"start": 41,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 50,
|
||||||
|
"start": 41,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
},
|
||||||
|
"start": 33,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 50,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 33,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 51,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 33,
|
||||||
|
"start": 31,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Error from executing kw_fn_too_few_args.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
KCL Semantic error
|
||||||
|
|
||||||
|
× semantic: This function requires a parameter y, but you haven't passed
|
||||||
|
│ it one.
|
||||||
|
╭─[1:7]
|
||||||
|
1 │ ╭─▶ fn add(x, y) {
|
||||||
|
2 │ │ return x + y
|
||||||
|
3 │ ╰─▶ }
|
||||||
|
4 │
|
||||||
|
5 │ three = add(x: 1)
|
||||||
|
· ─────────
|
||||||
|
╰────
|
5
src/wasm-lib/kcl/tests/kw_fn_too_few_args/input.kcl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
fn add(x, y) {
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
three = add(x: 1)
|
146
src/wasm-lib/kcl/tests/kw_fn_unlabeled_but_has_label/ast.snap
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Result of parsing kw_fn_unlabeled_but_has_label.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 29,
|
||||||
|
"id": {
|
||||||
|
"end": 6,
|
||||||
|
"name": "add",
|
||||||
|
"start": 3,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 27,
|
||||||
|
"left": {
|
||||||
|
"end": 23,
|
||||||
|
"name": "x",
|
||||||
|
"start": 22,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 27,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 26,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
},
|
||||||
|
"start": 22,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 27,
|
||||||
|
"start": 15,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 29,
|
||||||
|
"start": 11
|
||||||
|
},
|
||||||
|
"end": 29,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 9,
|
||||||
|
"name": "x",
|
||||||
|
"start": 8,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"labeled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 6,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 3,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 29,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 46,
|
||||||
|
"id": {
|
||||||
|
"end": 34,
|
||||||
|
"name": "two",
|
||||||
|
"start": 31,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"name": "x"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"end": 45,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 44,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 40,
|
||||||
|
"name": "add",
|
||||||
|
"start": 37,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 46,
|
||||||
|
"start": 37,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
},
|
||||||
|
"start": 31,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 46,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 31,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 47,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 31,
|
||||||
|
"start": 29,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Error from executing kw_fn_unlabeled_but_has_label.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
KCL Semantic error
|
||||||
|
|
||||||
|
× semantic: The function does declare a parameter named 'x', but this
|
||||||
|
│ parameter doesn't use a label. Try removing the `x:`
|
||||||
|
╭─[1:7]
|
||||||
|
1 │ ╭─▶ fn add(@x) {
|
||||||
|
2 │ │ return x + 1
|
||||||
|
3 │ ╰─▶ }
|
||||||
|
4 │
|
||||||
|
5 │ two = add(x: 1)
|
||||||
|
· ─────────
|
||||||
|
╰────
|
@ -0,0 +1,5 @@
|
|||||||
|
fn add(@x) {
|
||||||
|
return x + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
two = add(x: 1)
|
207
src/wasm-lib/kcl/tests/kw_fn_with_defaults/ast.snap
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Result of parsing kw_fn_with_defaults.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 45,
|
||||||
|
"id": {
|
||||||
|
"end": 12,
|
||||||
|
"name": "increment",
|
||||||
|
"start": 3,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 43,
|
||||||
|
"left": {
|
||||||
|
"end": 38,
|
||||||
|
"name": "x",
|
||||||
|
"start": 37,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 43,
|
||||||
|
"name": "by",
|
||||||
|
"start": 41,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 37,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 43,
|
||||||
|
"start": 30,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 45,
|
||||||
|
"start": 26
|
||||||
|
},
|
||||||
|
"end": 45,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 15,
|
||||||
|
"name": "x",
|
||||||
|
"start": 14,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"labeled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 19,
|
||||||
|
"name": "by",
|
||||||
|
"start": 17,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"default_value": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0,
|
||||||
|
"raw": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 12,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 3,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 45,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 65,
|
||||||
|
"id": {
|
||||||
|
"end": 50,
|
||||||
|
"name": "two",
|
||||||
|
"start": 47,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 64,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 63,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 62,
|
||||||
|
"name": "increment",
|
||||||
|
"start": 53,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 65,
|
||||||
|
"start": 53,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"start": 47,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 65,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 47,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 98,
|
||||||
|
"id": {
|
||||||
|
"end": 75,
|
||||||
|
"name": "twentyOne",
|
||||||
|
"start": 66,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"name": "by"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"end": 97,
|
||||||
|
"raw": "20",
|
||||||
|
"start": 95,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 20.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 87,
|
||||||
|
"name": "increment",
|
||||||
|
"start": 78,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 98,
|
||||||
|
"start": 78,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": {
|
||||||
|
"end": 89,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 88,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 66,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 98,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 66,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 99,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 47,
|
||||||
|
"start": 45,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
6
src/wasm-lib/kcl/tests/kw_fn_with_defaults/input.kcl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
fn increment(@x, by? = 1) {
|
||||||
|
return x + by
|
||||||
|
}
|
||||||
|
|
||||||
|
two = increment(1)
|
||||||
|
twentyOne = increment(1, by: 20)
|
177
src/wasm-lib/kcl/tests/kw_fn_with_defaults/program_memory.snap
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Program memory after executing kw_fn_with_defaults.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"increment": {
|
||||||
|
"type": "Function",
|
||||||
|
"expression": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"end": 43,
|
||||||
|
"left": {
|
||||||
|
"end": 38,
|
||||||
|
"name": "x",
|
||||||
|
"start": 37,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"end": 43,
|
||||||
|
"name": "by",
|
||||||
|
"start": 41,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 37,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"end": 43,
|
||||||
|
"start": 30,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 45,
|
||||||
|
"start": 26
|
||||||
|
},
|
||||||
|
"end": 45,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 15,
|
||||||
|
"name": "x",
|
||||||
|
"start": 14,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"labeled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 19,
|
||||||
|
"name": "by",
|
||||||
|
"start": 17,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"default_value": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 1.0,
|
||||||
|
"raw": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 12,
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
12,
|
||||||
|
45,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"twentyOne": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 21.0,
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
88,
|
||||||
|
89,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
95,
|
||||||
|
97,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"two": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 2.0,
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
63,
|
||||||
|
64,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
}
|
@ -87,72 +87,6 @@ snapshot_kind: text
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"arc_tag": {
|
|
||||||
"type": "TagIdentifier",
|
|
||||||
"type": "TagIdentifier",
|
|
||||||
"value": "arc_tag",
|
|
||||||
"info": {
|
|
||||||
"type": "TagEngineInfo",
|
|
||||||
"id": "[uuid]",
|
|
||||||
"sketch": "[uuid]",
|
|
||||||
"path": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"id": "[uuid]",
|
|
||||||
"sourceRange": [
|
|
||||||
527,
|
|
||||||
549,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ccw": true,
|
|
||||||
"center": [
|
|
||||||
200.0,
|
|
||||||
100.0
|
|
||||||
],
|
|
||||||
"from": [
|
|
||||||
280.0,
|
|
||||||
100.0
|
|
||||||
],
|
|
||||||
"radius": 80.0,
|
|
||||||
"tag": {
|
|
||||||
"end": 548,
|
|
||||||
"start": 540,
|
|
||||||
"type": "TagDeclarator",
|
|
||||||
"value": "arc_tag"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
280.0,
|
|
||||||
99.99999999999999
|
|
||||||
],
|
|
||||||
"type": "Arc"
|
|
||||||
},
|
|
||||||
"surface": {
|
|
||||||
"faceId": "[uuid]",
|
|
||||||
"id": "[uuid]",
|
|
||||||
"sourceRange": [
|
|
||||||
527,
|
|
||||||
549,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"tag": {
|
|
||||||
"end": 548,
|
|
||||||
"start": 540,
|
|
||||||
"type": "TagDeclarator",
|
|
||||||
"value": "arc_tag"
|
|
||||||
},
|
|
||||||
"type": "extrudeArc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"__meta": [
|
|
||||||
{
|
|
||||||
"sourceRange": [
|
|
||||||
540,
|
|
||||||
548,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"b": {
|
"b": {
|
||||||
"type": "TagIdentifier",
|
"type": "TagIdentifier",
|
||||||
"type": "TagIdentifier",
|
"type": "TagIdentifier",
|
||||||
|
@ -0,0 +1,586 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Result of parsing tag_can_be_proxied_through_parameter.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 118,
|
||||||
|
"id": {
|
||||||
|
"end": 47,
|
||||||
|
"name": "myCircle",
|
||||||
|
"start": 39,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 106,
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"end": 87,
|
||||||
|
"key": {
|
||||||
|
"end": 83,
|
||||||
|
"name": "radius",
|
||||||
|
"start": 77,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 77,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"end": 87,
|
||||||
|
"raw": "4",
|
||||||
|
"start": 86,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 4.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 104,
|
||||||
|
"key": {
|
||||||
|
"end": 95,
|
||||||
|
"name": "center",
|
||||||
|
"start": 89,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 89,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 100,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 99,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 103,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 102,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 104,
|
||||||
|
"start": 98,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 75,
|
||||||
|
"type": "ObjectExpression",
|
||||||
|
"type": "ObjectExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 110,
|
||||||
|
"name": "sk",
|
||||||
|
"start": 108,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 115,
|
||||||
|
"name": "tag",
|
||||||
|
"start": 112,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 74,
|
||||||
|
"name": "circle",
|
||||||
|
"start": 68,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 116,
|
||||||
|
"start": 68,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"end": 116,
|
||||||
|
"start": 61,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 118,
|
||||||
|
"start": 57
|
||||||
|
},
|
||||||
|
"end": 118,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 50,
|
||||||
|
"name": "sk",
|
||||||
|
"start": 48,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 55,
|
||||||
|
"name": "tag",
|
||||||
|
"start": 52,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 47,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 39,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 118,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 36,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 223,
|
||||||
|
"id": {
|
||||||
|
"end": 122,
|
||||||
|
"name": "c1",
|
||||||
|
"start": 120,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 143,
|
||||||
|
"raw": "'XY'",
|
||||||
|
"start": 139,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": "XY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 138,
|
||||||
|
"name": "startSketchOn",
|
||||||
|
"start": 125,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 144,
|
||||||
|
"start": 125,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 167,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 166,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 170,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 169,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 171,
|
||||||
|
"start": 165,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 174,
|
||||||
|
"start": 173,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 164,
|
||||||
|
"name": "startProfileAt",
|
||||||
|
"start": 150,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 175,
|
||||||
|
"start": 150,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 191,
|
||||||
|
"start": 190,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 198,
|
||||||
|
"start": 193,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 189,
|
||||||
|
"name": "myCircle",
|
||||||
|
"start": 181,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 199,
|
||||||
|
"start": 181,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 223,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"2": [
|
||||||
|
{
|
||||||
|
"end": 223,
|
||||||
|
"start": 199,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "blockComment",
|
||||||
|
"value": "The tag can be used.",
|
||||||
|
"style": "line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 125,
|
||||||
|
"type": "PipeExpression",
|
||||||
|
"type": "PipeExpression"
|
||||||
|
},
|
||||||
|
"start": 120,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 223,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 120,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 257,
|
||||||
|
"id": {
|
||||||
|
"end": 228,
|
||||||
|
"name": "ang1",
|
||||||
|
"start": 224,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"computed": false,
|
||||||
|
"end": 256,
|
||||||
|
"object": {
|
||||||
|
"computed": false,
|
||||||
|
"end": 251,
|
||||||
|
"object": {
|
||||||
|
"end": 246,
|
||||||
|
"name": "c1",
|
||||||
|
"start": 244,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"property": {
|
||||||
|
"end": 251,
|
||||||
|
"name": "tags",
|
||||||
|
"start": 247,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 244,
|
||||||
|
"type": "MemberExpression",
|
||||||
|
"type": "MemberExpression"
|
||||||
|
},
|
||||||
|
"property": {
|
||||||
|
"end": 256,
|
||||||
|
"name": "mine",
|
||||||
|
"start": 252,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 244,
|
||||||
|
"type": "MemberExpression",
|
||||||
|
"type": "MemberExpression"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 243,
|
||||||
|
"name": "tangentToEnd",
|
||||||
|
"start": 231,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 257,
|
||||||
|
"start": 231,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"start": 224,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 257,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 224,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 384,
|
||||||
|
"id": {
|
||||||
|
"end": 307,
|
||||||
|
"name": "c2",
|
||||||
|
"start": 305,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 328,
|
||||||
|
"raw": "'XY'",
|
||||||
|
"start": 324,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": "XY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 323,
|
||||||
|
"name": "startSketchOn",
|
||||||
|
"start": 310,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 329,
|
||||||
|
"start": 310,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 352,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 351,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 355,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 354,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 356,
|
||||||
|
"start": 350,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 359,
|
||||||
|
"start": 358,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 349,
|
||||||
|
"name": "startProfileAt",
|
||||||
|
"start": 335,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 360,
|
||||||
|
"start": 335,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 376,
|
||||||
|
"start": 375,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 383,
|
||||||
|
"start": 378,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 374,
|
||||||
|
"name": "myCircle",
|
||||||
|
"start": 366,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 384,
|
||||||
|
"start": 366,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 384,
|
||||||
|
"start": 310,
|
||||||
|
"type": "PipeExpression",
|
||||||
|
"type": "PipeExpression"
|
||||||
|
},
|
||||||
|
"start": 305,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 384,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 305,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 418,
|
||||||
|
"id": {
|
||||||
|
"end": 389,
|
||||||
|
"name": "ang2",
|
||||||
|
"start": 385,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"computed": false,
|
||||||
|
"end": 417,
|
||||||
|
"object": {
|
||||||
|
"computed": false,
|
||||||
|
"end": 412,
|
||||||
|
"object": {
|
||||||
|
"end": 407,
|
||||||
|
"name": "c2",
|
||||||
|
"start": 405,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"property": {
|
||||||
|
"end": 412,
|
||||||
|
"name": "tags",
|
||||||
|
"start": 408,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 405,
|
||||||
|
"type": "MemberExpression",
|
||||||
|
"type": "MemberExpression"
|
||||||
|
},
|
||||||
|
"property": {
|
||||||
|
"end": 417,
|
||||||
|
"name": "mine",
|
||||||
|
"start": 413,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 405,
|
||||||
|
"type": "MemberExpression",
|
||||||
|
"type": "MemberExpression"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 404,
|
||||||
|
"name": "tangentToEnd",
|
||||||
|
"start": 392,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 418,
|
||||||
|
"start": 392,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"start": 385,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 418,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 385,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 419,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 120,
|
||||||
|
"start": 118,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
{
|
||||||
|
"end": 304,
|
||||||
|
"start": 257,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLineBlockComment",
|
||||||
|
"value": "The same tag declarator can be used again.",
|
||||||
|
"style": "line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": [
|
||||||
|
{
|
||||||
|
"end": 35,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "blockComment",
|
||||||
|
"value": "A function with a tag parameter.",
|
||||||
|
"style": "line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// A function with a tag parameter.
|
||||||
|
fn myCircle(sk, tag) {
|
||||||
|
return circle({ radius = 4, center = [0, 0] }, sk, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
c1 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> myCircle(%, $mine)
|
||||||
|
// The tag can be used.
|
||||||
|
ang1 = tangentToEnd(c1.tags.mine)
|
||||||
|
|
||||||
|
// The same tag declarator can be used again.
|
||||||
|
c2 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> myCircle(%, $mine)
|
||||||
|
ang2 = tangentToEnd(c2.tags.mine)
|
@ -0,0 +1,524 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Program memory after executing tag_can_be_proxied_through_parameter.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ang1": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
231,
|
||||||
|
257,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ang2": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
392,
|
||||||
|
418,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"c1": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ccw": true,
|
||||||
|
"center": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"from": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"radius": 4.0,
|
||||||
|
"tag": {
|
||||||
|
"end": 198,
|
||||||
|
"start": 193,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"type": "Circle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"on": {
|
||||||
|
"type": "plane",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"value": "XY",
|
||||||
|
"origin": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 1.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"zAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 1.0
|
||||||
|
},
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"from": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"tag": null,
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"mine": {
|
||||||
|
"type": "TagIdentifier",
|
||||||
|
"value": "mine",
|
||||||
|
"info": {
|
||||||
|
"type": "TagEngineInfo",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sketch": "[uuid]",
|
||||||
|
"path": {
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ccw": true,
|
||||||
|
"center": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"from": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"radius": 4.0,
|
||||||
|
"tag": {
|
||||||
|
"end": 198,
|
||||||
|
"start": 193,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"type": "Circle"
|
||||||
|
},
|
||||||
|
"surface": null
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
193,
|
||||||
|
198,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"c2": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ccw": true,
|
||||||
|
"center": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"from": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"radius": 4.0,
|
||||||
|
"tag": {
|
||||||
|
"end": 383,
|
||||||
|
"start": 378,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"type": "Circle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"on": {
|
||||||
|
"type": "plane",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"value": "XY",
|
||||||
|
"origin": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 1.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"zAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 1.0
|
||||||
|
},
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"from": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"tag": null,
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"mine": {
|
||||||
|
"type": "TagIdentifier",
|
||||||
|
"value": "mine",
|
||||||
|
"info": {
|
||||||
|
"type": "TagEngineInfo",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sketch": "[uuid]",
|
||||||
|
"path": {
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ccw": true,
|
||||||
|
"center": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"from": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"radius": 4.0,
|
||||||
|
"tag": {
|
||||||
|
"end": 383,
|
||||||
|
"start": 378,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
4.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"type": "Circle"
|
||||||
|
},
|
||||||
|
"surface": null
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
378,
|
||||||
|
383,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
68,
|
||||||
|
116,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"myCircle": {
|
||||||
|
"type": "Function",
|
||||||
|
"expression": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 106,
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"end": 87,
|
||||||
|
"key": {
|
||||||
|
"end": 83,
|
||||||
|
"name": "radius",
|
||||||
|
"start": 77,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 77,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"end": 87,
|
||||||
|
"raw": "4",
|
||||||
|
"start": 86,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 4.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 104,
|
||||||
|
"key": {
|
||||||
|
"end": 95,
|
||||||
|
"name": "center",
|
||||||
|
"start": 89,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 89,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 100,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 99,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 103,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 102,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 104,
|
||||||
|
"start": 98,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 75,
|
||||||
|
"type": "ObjectExpression",
|
||||||
|
"type": "ObjectExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 110,
|
||||||
|
"name": "sk",
|
||||||
|
"start": 108,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 115,
|
||||||
|
"name": "tag",
|
||||||
|
"start": 112,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 74,
|
||||||
|
"name": "circle",
|
||||||
|
"start": 68,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 116,
|
||||||
|
"start": 68,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"end": 116,
|
||||||
|
"start": 61,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 118,
|
||||||
|
"start": 57
|
||||||
|
},
|
||||||
|
"end": 118,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 50,
|
||||||
|
"name": "sk",
|
||||||
|
"start": 48,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 55,
|
||||||
|
"name": "tag",
|
||||||
|
"start": 52,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 47,
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
},
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
47,
|
||||||
|
118,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
}
|
@ -0,0 +1,400 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Result of parsing tag_proxied_through_function_does_not_define_var.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 193,
|
||||||
|
"id": {
|
||||||
|
"end": 47,
|
||||||
|
"name": "myCircle",
|
||||||
|
"start": 39,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 113,
|
||||||
|
"id": {
|
||||||
|
"end": 62,
|
||||||
|
"name": "c",
|
||||||
|
"start": 61,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 103,
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"end": 84,
|
||||||
|
"key": {
|
||||||
|
"end": 80,
|
||||||
|
"name": "radius",
|
||||||
|
"start": 74,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 74,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"end": 84,
|
||||||
|
"raw": "4",
|
||||||
|
"start": 83,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 4.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 101,
|
||||||
|
"key": {
|
||||||
|
"end": 92,
|
||||||
|
"name": "center",
|
||||||
|
"start": 86,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 86,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 97,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 96,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 100,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 99,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 101,
|
||||||
|
"start": 95,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 72,
|
||||||
|
"type": "ObjectExpression",
|
||||||
|
"type": "ObjectExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 107,
|
||||||
|
"name": "sk",
|
||||||
|
"start": 105,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 112,
|
||||||
|
"name": "tag",
|
||||||
|
"start": 109,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 71,
|
||||||
|
"name": "circle",
|
||||||
|
"start": 65,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 113,
|
||||||
|
"start": 65,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"start": 61,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 113,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 61,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 173,
|
||||||
|
"id": {
|
||||||
|
"end": 152,
|
||||||
|
"name": "ang",
|
||||||
|
"start": 149,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 172,
|
||||||
|
"name": "mine",
|
||||||
|
"start": 168,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 167,
|
||||||
|
"name": "tangentToEnd",
|
||||||
|
"start": 155,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 173,
|
||||||
|
"start": 155,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"start": 149,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 173,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 149,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 185,
|
||||||
|
"name": "c",
|
||||||
|
"start": 184,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 190,
|
||||||
|
"name": "ang",
|
||||||
|
"start": 187,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 191,
|
||||||
|
"start": 183,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
"end": 191,
|
||||||
|
"start": 176,
|
||||||
|
"type": "ReturnStatement",
|
||||||
|
"type": "ReturnStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 193,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 146,
|
||||||
|
"start": 115,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "blockComment",
|
||||||
|
"value": "This should not be allowed.",
|
||||||
|
"style": "line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 57
|
||||||
|
},
|
||||||
|
"end": 193,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 50,
|
||||||
|
"name": "sk",
|
||||||
|
"start": 48,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Parameter",
|
||||||
|
"identifier": {
|
||||||
|
"end": 55,
|
||||||
|
"name": "tag",
|
||||||
|
"start": 52,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 47,
|
||||||
|
"type": "FunctionExpression",
|
||||||
|
"type": "FunctionExpression"
|
||||||
|
},
|
||||||
|
"start": 39,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 193,
|
||||||
|
"kind": "fn",
|
||||||
|
"start": 36,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 274,
|
||||||
|
"id": {
|
||||||
|
"end": 197,
|
||||||
|
"name": "c1",
|
||||||
|
"start": 195,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 218,
|
||||||
|
"raw": "'XY'",
|
||||||
|
"start": 214,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": "XY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 213,
|
||||||
|
"name": "startSketchOn",
|
||||||
|
"start": 200,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 219,
|
||||||
|
"start": 200,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"end": 242,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 241,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 245,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 244,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 246,
|
||||||
|
"start": 240,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 249,
|
||||||
|
"start": 248,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 239,
|
||||||
|
"name": "startProfileAt",
|
||||||
|
"start": 225,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 250,
|
||||||
|
"start": 225,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"end": 266,
|
||||||
|
"start": 265,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 273,
|
||||||
|
"start": 268,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "mine"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"end": 264,
|
||||||
|
"name": "myCircle",
|
||||||
|
"start": 256,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"end": 274,
|
||||||
|
"start": 256,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 274,
|
||||||
|
"start": 200,
|
||||||
|
"type": "PipeExpression",
|
||||||
|
"type": "PipeExpression"
|
||||||
|
},
|
||||||
|
"start": 195,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 274,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 195,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 275,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 195,
|
||||||
|
"start": 193,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": [
|
||||||
|
{
|
||||||
|
"end": 35,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "blockComment",
|
||||||
|
"value": "A function with a tag parameter.",
|
||||||
|
"style": "line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Error from executing tag_proxied_through_function_does_not_define_var.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
KCL UndefinedValue error
|
||||||
|
|
||||||
|
× undefined value: memory item key `mine` is not defined
|
||||||
|
╭─[5:22]
|
||||||
|
4 │ // This should not be allowed.
|
||||||
|
5 │ ang = tangentToEnd(mine)
|
||||||
|
· ────
|
||||||
|
6 │ return [c, ang]
|
||||||
|
7 │ }
|
||||||
|
8 │
|
||||||
|
9 │ c1 = startSketchOn('XY')
|
||||||
|
10 │ |> startProfileAt([0, 0], %)
|
||||||
|
11 │ |> myCircle(%, $mine)
|
||||||
|
· ──────────────────
|
||||||
|
╰────
|
@ -0,0 +1,11 @@
|
|||||||
|
// A function with a tag parameter.
|
||||||
|
fn myCircle(sk, tag) {
|
||||||
|
c = circle({ radius = 4, center = [0, 0] }, sk, tag)
|
||||||
|
// This should not be allowed.
|
||||||
|
ang = tangentToEnd(mine)
|
||||||
|
return [c, ang]
|
||||||
|
}
|
||||||
|
|
||||||
|
c1 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> myCircle(%, $mine)
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 61 KiB |