Merge branch 'main' into move-tests-to-electon
							
								
								
									
										22
									
								
								.github/workflows/build-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -362,6 +362,17 @@ jobs:
 | 
			
		||||
      - name: List artifacts
 | 
			
		||||
        run: "ls -R out"
 | 
			
		||||
 | 
			
		||||
      - name: Set more complete nightly release notes
 | 
			
		||||
        if: ${{ env.IS_NIGHTLY == 'true' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          # Note: prefered going this way instead of a full clone in the checkout step,
 | 
			
		||||
          # see https://github.com/actions/checkout/issues/1471
 | 
			
		||||
          git fetch --prune --unshallow --tags
 | 
			
		||||
          export TAG="nightly-${VERSION}"
 | 
			
		||||
          export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
 | 
			
		||||
          export NOTES=$(./scripts/get-nightly-changelog.sh)
 | 
			
		||||
          yarn files:set-notes
 | 
			
		||||
 | 
			
		||||
      - name: Authenticate to Google Cloud
 | 
			
		||||
        if: ${{ env.IS_NIGHTLY == 'true' }}
 | 
			
		||||
        uses: 'google-github-actions/auth@v2.1.7'
 | 
			
		||||
@ -382,3 +393,14 @@ jobs:
 | 
			
		||||
          glob: '*'
 | 
			
		||||
          parent: false
 | 
			
		||||
          destination: 'dl.kittycad.io/releases/modeling-app/nightly'
 | 
			
		||||
 | 
			
		||||
      - name: Tag nightly commit
 | 
			
		||||
        if: ${{ env.IS_NIGHTLY == 'true' }}
 | 
			
		||||
        uses: actions/github-script@v7
 | 
			
		||||
        with:
 | 
			
		||||
          script: |
 | 
			
		||||
            const { VERSION } = process.env          
 | 
			
		||||
            const { owner, repo } = context.repo
 | 
			
		||||
            const { sha } = context
 | 
			
		||||
            const ref = `refs/tags/nightly-${VERSION}`
 | 
			
		||||
            github.rest.git.createRef({ owner, repo, sha, ref })
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ export class ToolbarFixture {
 | 
			
		||||
  public page: Page
 | 
			
		||||
 | 
			
		||||
  extrudeButton!: Locator
 | 
			
		||||
  loftButton!: Locator
 | 
			
		||||
  offsetPlaneButton!: Locator
 | 
			
		||||
  startSketchBtn!: Locator
 | 
			
		||||
  lineBtn!: Locator
 | 
			
		||||
@ -26,6 +27,7 @@ export class ToolbarFixture {
 | 
			
		||||
  reConstruct = (page: Page) => {
 | 
			
		||||
    this.page = page
 | 
			
		||||
    this.extrudeButton = page.getByTestId('extrude')
 | 
			
		||||
    this.loftButton = page.getByTestId('loft')
 | 
			
		||||
    this.offsetPlaneButton = page.getByTestId('plane-offset')
 | 
			
		||||
    this.startSketchBtn = page.getByTestId('sketch')
 | 
			
		||||
    this.lineBtn = page.getByTestId('line')
 | 
			
		||||
 | 
			
		||||
@ -753,3 +753,94 @@ test(`Offset plane point-and-click`, async ({
 | 
			
		||||
    await scene.expectPixelColor([74, 74, 74], testPoint, 15)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const loftPointAndClickCases = [
 | 
			
		||||
  { shouldPreselect: true },
 | 
			
		||||
  { shouldPreselect: false },
 | 
			
		||||
]
 | 
			
		||||
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
 | 
			
		||||
  test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
 | 
			
		||||
    app,
 | 
			
		||||
    page,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
    |> circle({ center = [0, 0], radius = 30 }, %)
 | 
			
		||||
    plane001 = offsetPlane('XZ', 50)
 | 
			
		||||
    sketch002 = startSketchOn(plane001)
 | 
			
		||||
    |> circle({ center = [0, 0], radius = 20 }, %)
 | 
			
		||||
`
 | 
			
		||||
    await app.initialise(initialCode)
 | 
			
		||||
 | 
			
		||||
    // One dumb hardcoded screen pixel value
 | 
			
		||||
    const testPoint = { x: 575, y: 200 }
 | 
			
		||||
    const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
 | 
			
		||||
    const [clickOnSketch2] = scene.makeMouseHelpers(
 | 
			
		||||
      testPoint.x,
 | 
			
		||||
      testPoint.y + 80
 | 
			
		||||
    )
 | 
			
		||||
    const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
 | 
			
		||||
 | 
			
		||||
    await test.step(`Look for the white of the sketch001 shape`, async () => {
 | 
			
		||||
      await scene.expectPixelColor([254, 254, 254], testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    async function selectSketches() {
 | 
			
		||||
      await clickOnSketch1()
 | 
			
		||||
      await page.keyboard.down('Shift')
 | 
			
		||||
      await clickOnSketch2()
 | 
			
		||||
      await app.page.waitForTimeout(500)
 | 
			
		||||
      await page.keyboard.up('Shift')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!shouldPreselect) {
 | 
			
		||||
      await test.step(`Go through the command bar flow without preselected sketches`, async () => {
 | 
			
		||||
        await toolbar.loftButton.click()
 | 
			
		||||
        await cmdBar.expectState({
 | 
			
		||||
          stage: 'arguments',
 | 
			
		||||
          currentArgKey: 'selection',
 | 
			
		||||
          currentArgValue: '',
 | 
			
		||||
          headerArguments: { Selection: '' },
 | 
			
		||||
          highlightedHeaderArg: 'selection',
 | 
			
		||||
          commandName: 'Loft',
 | 
			
		||||
        })
 | 
			
		||||
        await selectSketches()
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
        await cmdBar.expectState({
 | 
			
		||||
          stage: 'review',
 | 
			
		||||
          headerArguments: { Selection: '2 faces' },
 | 
			
		||||
          commandName: 'Loft',
 | 
			
		||||
        })
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      await test.step(`Preselect the two sketches`, async () => {
 | 
			
		||||
        await selectSketches()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step(`Go through the command bar flow with preselected sketches`, async () => {
 | 
			
		||||
        await toolbar.loftButton.click()
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
        await cmdBar.expectState({
 | 
			
		||||
          stage: 'review',
 | 
			
		||||
          headerArguments: { Selection: '2 faces' },
 | 
			
		||||
          commandName: 'Loft',
 | 
			
		||||
        })
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
 | 
			
		||||
      await editor.expectEditor.toContain(loftDeclaration)
 | 
			
		||||
      await editor.expectState({
 | 
			
		||||
        diagnostics: [],
 | 
			
		||||
        activeLines: [loftDeclaration],
 | 
			
		||||
        highlightedCode: '',
 | 
			
		||||
      })
 | 
			
		||||
      await scene.expectPixelColor([89, 89, 89], testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -563,7 +563,7 @@ extrude001 = extrude(50, sketch001)
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
 | 
			
		||||
    // Constants and locators
 | 
			
		||||
    const planeColor: [number, number, number] = [170, 220, 170]
 | 
			
		||||
    const planeColor: [number, number, number] = [161, 220, 155]
 | 
			
		||||
    const bgColor: [number, number, number] = [27, 27, 27]
 | 
			
		||||
    const middlePixelIsColor = async (color: [number, number, number]) => {
 | 
			
		||||
      return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,8 @@ try {
 | 
			
		||||
    .split('\n')
 | 
			
		||||
    .filter((line) => line && line.length > 1)
 | 
			
		||||
    .forEach((line) => {
 | 
			
		||||
      // Allow line comments.
 | 
			
		||||
      if (line.trimStart().startsWith('#')) return
 | 
			
		||||
      const [key, value] = line.split('=')
 | 
			
		||||
      // prefer env vars over secrets file
 | 
			
		||||
      secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB  | 
| 
		 Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB  | 
| 
		 Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB  | 
| 
		 Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB  | 
| 
		 Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB  | 
| 
		 Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB  | 
| 
		 Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB  | 
| 
		 Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB  | 
@ -13,7 +13,7 @@ test.describe('Test toggling perspective', () => {
 | 
			
		||||
      y: screenHeight * 0.2,
 | 
			
		||||
    }
 | 
			
		||||
    const backgroundColor: [number, number, number] = [29, 29, 29]
 | 
			
		||||
    const xzPlaneColor: [number, number, number] = [50, 50, 99]
 | 
			
		||||
    const xzPlaneColor: [number, number, number] = [82, 55, 96]
 | 
			
		||||
    const locationToHaveColor = async (color: [number, number, number]) => {
 | 
			
		||||
      return u.getGreatestPixDiff(checkedScreenLocation, color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								scripts/get-nightly-changelog.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						@ -0,0 +1,5 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
echo "## What's Changed"
 | 
			
		||||
git log ${PREVIOUS_TAG}..HEAD --oneline --pretty=format:%s | grep -v Bump | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
 | 
			
		||||
echo ""
 | 
			
		||||
echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${PREVIOUS_TAG}...${TAG}"
 | 
			
		||||
@ -273,14 +273,26 @@ export class CameraControls {
 | 
			
		||||
        camSettings.center.y,
 | 
			
		||||
        camSettings.center.z
 | 
			
		||||
      )
 | 
			
		||||
      const quat = new Quaternion(
 | 
			
		||||
      const orientation = new Quaternion(
 | 
			
		||||
        camSettings.orientation.x,
 | 
			
		||||
        camSettings.orientation.y,
 | 
			
		||||
        camSettings.orientation.z,
 | 
			
		||||
        camSettings.orientation.w
 | 
			
		||||
      ).invert()
 | 
			
		||||
 | 
			
		||||
      this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat))
 | 
			
		||||
      const newUp = new Vector3(
 | 
			
		||||
        camSettings.up.x,
 | 
			
		||||
        camSettings.up.y,
 | 
			
		||||
        camSettings.up.z
 | 
			
		||||
      )
 | 
			
		||||
      this.camera.quaternion.set(
 | 
			
		||||
        orientation.x,
 | 
			
		||||
        orientation.y,
 | 
			
		||||
        orientation.z,
 | 
			
		||||
        orientation.w
 | 
			
		||||
      )
 | 
			
		||||
      this.camera.up.copy(newUp)
 | 
			
		||||
      this.camera.updateProjectionMatrix()
 | 
			
		||||
      if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
 | 
			
		||||
        this.useOrthographicCamera()
 | 
			
		||||
      }
 | 
			
		||||
@ -1164,7 +1176,7 @@ export class CameraControls {
 | 
			
		||||
      this.camera.updateProjectionMatrix()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.syncDirection === 'clientToEngine' || forceUpdate)
 | 
			
		||||
    if (this.syncDirection === 'clientToEngine' || forceUpdate) {
 | 
			
		||||
      this.throttledUpdateEngineCamera({
 | 
			
		||||
        quaternion: this.camera.quaternion,
 | 
			
		||||
        position: this.camera.position,
 | 
			
		||||
@ -1172,6 +1184,7 @@ export class CameraControls {
 | 
			
		||||
        isPerspective: this.isPerspective,
 | 
			
		||||
        target: this.target,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    this.deferReactUpdate(this.reactCameraProperties)
 | 
			
		||||
    Object.values(this._camChangeCallbacks).forEach((cb) => cb())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ import {
 | 
			
		||||
  isSketchPipe,
 | 
			
		||||
  Selections,
 | 
			
		||||
  updateSelections,
 | 
			
		||||
  canLoftSelection,
 | 
			
		||||
} from 'lib/selections'
 | 
			
		||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
 | 
			
		||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
 | 
			
		||||
@ -569,6 +570,21 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
          if (err(canSweep)) return false
 | 
			
		||||
          return canSweep
 | 
			
		||||
        },
 | 
			
		||||
        'has valid loft selection': ({ context: { selectionRanges } }) => {
 | 
			
		||||
          const hasNoSelection =
 | 
			
		||||
            selectionRanges.graphSelections.length === 0 ||
 | 
			
		||||
            isRangeBetweenCharacters(selectionRanges) ||
 | 
			
		||||
            isSelectionLastLine(selectionRanges, codeManager.code)
 | 
			
		||||
 | 
			
		||||
          if (hasNoSelection) {
 | 
			
		||||
            const count = 2
 | 
			
		||||
            return doesSceneHaveSweepableSketch(kclManager.ast, count)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const canLoft = canLoftSelection(selectionRanges)
 | 
			
		||||
          if (err(canLoft)) return false
 | 
			
		||||
          return canLoft
 | 
			
		||||
        },
 | 
			
		||||
        'has valid selection for deletion': ({
 | 
			
		||||
          context: { selectionRanges },
 | 
			
		||||
        }) => {
 | 
			
		||||
 | 
			
		||||
@ -346,6 +346,37 @@ export function extrudeSketch(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function loftSketches(
 | 
			
		||||
  node: Node<Program>,
 | 
			
		||||
  declarators: VariableDeclarator[]
 | 
			
		||||
): {
 | 
			
		||||
  modifiedAst: Node<Program>
 | 
			
		||||
  pathToNode: PathToNode
 | 
			
		||||
} {
 | 
			
		||||
  const modifiedAst = structuredClone(node)
 | 
			
		||||
  const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
 | 
			
		||||
  const elements = declarators.map((d) => createIdentifier(d.id.name))
 | 
			
		||||
  const loft = createCallExpressionStdLib('loft', [
 | 
			
		||||
    createArrayExpression(elements),
 | 
			
		||||
  ])
 | 
			
		||||
  const declaration = createVariableDeclaration(name, loft)
 | 
			
		||||
  modifiedAst.body.push(declaration)
 | 
			
		||||
  const pathToNode: PathToNode = [
 | 
			
		||||
    ['body', ''],
 | 
			
		||||
    [modifiedAst.body.length - 1, 'index'],
 | 
			
		||||
    ['declarations', 'VariableDeclaration'],
 | 
			
		||||
    ['0', 'index'],
 | 
			
		||||
    ['init', 'VariableDeclarator'],
 | 
			
		||||
    ['arguments', 'CallExpression'],
 | 
			
		||||
    [0, 'index'],
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    modifiedAst,
 | 
			
		||||
    pathToNode,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function revolveSketch(
 | 
			
		||||
  node: Node<Program>,
 | 
			
		||||
  pathToNode: PathToNode,
 | 
			
		||||
 | 
			
		||||
@ -628,6 +628,18 @@ sketch002 = startSketchOn(extrude001, $seg01)
 | 
			
		||||
    const extrudable = doesSceneHaveSweepableSketch(ast)
 | 
			
		||||
    expect(extrudable).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
  it('finds sketch001 and sketch002 pipes to be lofted', async () => {
 | 
			
		||||
    const exampleCode = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> circle({ center = [0, 0], radius = 1 }, %)
 | 
			
		||||
plane001 = offsetPlane('XZ', 2)
 | 
			
		||||
sketch002 = startSketchOn(plane001)
 | 
			
		||||
  |> circle({ center = [0, 0], radius = 3 }, %)
 | 
			
		||||
`
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const extrudable = doesSceneHaveSweepableSketch(ast, 2)
 | 
			
		||||
    expect(extrudable).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
  it('find sketch002 NOT pipe to be extruded', async () => {
 | 
			
		||||
    const exampleCode = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([3.29, 7.86], %)
 | 
			
		||||
 | 
			
		||||
@ -173,6 +173,30 @@ function moreNodePathFromSourceRange(
 | 
			
		||||
    }
 | 
			
		||||
    return path
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_node.type === 'CallExpressionKw' && isInRange) {
 | 
			
		||||
    const { callee, arguments: args } = _node
 | 
			
		||||
    if (
 | 
			
		||||
      callee.type === 'Identifier' &&
 | 
			
		||||
      callee.start <= start &&
 | 
			
		||||
      callee.end >= end
 | 
			
		||||
    ) {
 | 
			
		||||
      path.push(['callee', 'CallExpressionKw'])
 | 
			
		||||
      return path
 | 
			
		||||
    }
 | 
			
		||||
    if (args.length > 0) {
 | 
			
		||||
      for (let argIndex = 0; argIndex < args.length; argIndex++) {
 | 
			
		||||
        const arg = args[argIndex].arg
 | 
			
		||||
        if (arg.start <= start && arg.end >= end) {
 | 
			
		||||
          path.push(['arguments', 'CallExpressionKw'])
 | 
			
		||||
          path.push([argIndex, 'index'])
 | 
			
		||||
          return moreNodePathFromSourceRange(arg, sourceRange, path)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return path
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_node.type === 'BinaryExpression' && isInRange) {
 | 
			
		||||
    const { left, right } = _node
 | 
			
		||||
    if (left.start <= start && left.end >= end) {
 | 
			
		||||
@ -975,7 +999,9 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
 | 
			
		||||
        if (
 | 
			
		||||
          node.type === 'CallExpression' &&
 | 
			
		||||
          node.callee.type === 'Identifier' &&
 | 
			
		||||
          (node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
 | 
			
		||||
          (node.callee.name === 'extrude' ||
 | 
			
		||||
            node.callee.name === 'revolve' ||
 | 
			
		||||
            node.callee.name === 'loft') &&
 | 
			
		||||
          node.arguments?.[1]?.type === 'Identifier' &&
 | 
			
		||||
          node.arguments[1].name === varDec.id.name
 | 
			
		||||
        ) {
 | 
			
		||||
@ -988,7 +1014,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** File must contain at least one sketch that has not been extruded already */
 | 
			
		||||
export function doesSceneHaveSweepableSketch(ast: Node<Program>) {
 | 
			
		||||
export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
 | 
			
		||||
  const theMap: any = {}
 | 
			
		||||
  traverse(ast as any, {
 | 
			
		||||
    enter(node) {
 | 
			
		||||
@ -1037,7 +1063,7 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>) {
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  return Object.keys(theMap).length > 0
 | 
			
		||||
  return Object.keys(theMap).length >= count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getObjExprProperty(
 | 
			
		||||
 | 
			
		||||
@ -11,8 +11,8 @@ Map {
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      "range": [
 | 
			
		||||
        37,
 | 
			
		||||
        64,
 | 
			
		||||
        12,
 | 
			
		||||
        31,
 | 
			
		||||
        0,
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,9 @@ export type ModelingCommandSchema = {
 | 
			
		||||
    // result: (typeof EXTRUSION_RESULTS)[number]
 | 
			
		||||
    distance: KclCommandValue
 | 
			
		||||
  }
 | 
			
		||||
  Loft: {
 | 
			
		||||
    selection: Selections
 | 
			
		||||
  }
 | 
			
		||||
  Revolve: {
 | 
			
		||||
    selection: Selections
 | 
			
		||||
    angle: KclCommandValue
 | 
			
		||||
@ -260,6 +263,20 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  Loft: {
 | 
			
		||||
    description: 'Create a 3D body by blending between two or more sketches',
 | 
			
		||||
    icon: 'loft',
 | 
			
		||||
    needsReview: true,
 | 
			
		||||
    args: {
 | 
			
		||||
      selection: {
 | 
			
		||||
        inputType: 'selection',
 | 
			
		||||
        selectionTypes: ['solid2D'],
 | 
			
		||||
        multiple: true,
 | 
			
		||||
        required: true,
 | 
			
		||||
        skip: false,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  // TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
 | 
			
		||||
  Revolve: {
 | 
			
		||||
    description: 'Create a 3D body by rotating a sketch region about an axis.',
 | 
			
		||||
 | 
			
		||||
@ -52,6 +52,7 @@ export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
 | 
			
		||||
export const KCL_DEFAULT_CONSTANT_PREFIXES = {
 | 
			
		||||
  SKETCH: 'sketch',
 | 
			
		||||
  EXTRUDE: 'extrude',
 | 
			
		||||
  LOFT: 'loft',
 | 
			
		||||
  SEGMENT: 'seg',
 | 
			
		||||
  REVOLVE: 'revolve',
 | 
			
		||||
  PLANE: 'plane',
 | 
			
		||||
 | 
			
		||||
@ -529,6 +529,10 @@ function nodeHasExtrude(node: CommonASTNode) {
 | 
			
		||||
    doesPipeHaveCallExp({
 | 
			
		||||
      calleeName: 'revolve',
 | 
			
		||||
      ...node,
 | 
			
		||||
    }) ||
 | 
			
		||||
    doesPipeHaveCallExp({
 | 
			
		||||
      calleeName: 'loft',
 | 
			
		||||
      ...node,
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -559,6 +563,22 @@ export function canSweepSelection(selection: Selections) {
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function canLoftSelection(selection: Selections) {
 | 
			
		||||
  const commonNodes = selection.graphSelections.map((_, i) =>
 | 
			
		||||
    buildCommonNodeFromSelection(selection, i)
 | 
			
		||||
  )
 | 
			
		||||
  return (
 | 
			
		||||
    !!isCursorInSketchCommandRange(
 | 
			
		||||
      engineCommandManager.artifactGraph,
 | 
			
		||||
      selection
 | 
			
		||||
    ) &&
 | 
			
		||||
    commonNodes.length > 1 &&
 | 
			
		||||
    commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
 | 
			
		||||
    commonNodes.every((n) => nodeHasClose(n) || nodeHasCircle(n)) &&
 | 
			
		||||
    commonNodes.every((n) => !nodeHasExtrude(n))
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This accounts for non-geometry selections under "other"
 | 
			
		||||
export type ResolvedSelectionType = Artifact['type'] | 'other'
 | 
			
		||||
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
 | 
			
		||||
 | 
			
		||||
@ -139,9 +139,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'loft',
 | 
			
		||||
        onClick: () => console.error('Loft not yet implemented'),
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Loft', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
        disabled: (state) => !state.can({ type: 'Loft' }),
 | 
			
		||||
        icon: 'loft',
 | 
			
		||||
        status: 'kcl-only',
 | 
			
		||||
        status: 'available',
 | 
			
		||||
        title: 'Loft',
 | 
			
		||||
        hotkey: 'L',
 | 
			
		||||
        description:
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,7 @@ import {
 | 
			
		||||
  addOffsetPlane,
 | 
			
		||||
  deleteFromSelection,
 | 
			
		||||
  extrudeSketch,
 | 
			
		||||
  loftSketches,
 | 
			
		||||
  revolveSketch,
 | 
			
		||||
} from 'lang/modifyAst'
 | 
			
		||||
import {
 | 
			
		||||
@ -256,6 +257,7 @@ export type ModelingMachineEvent =
 | 
			
		||||
  | { type: 'Export'; data: ModelingCommandSchema['Export'] }
 | 
			
		||||
  | { type: 'Make'; data: ModelingCommandSchema['Make'] }
 | 
			
		||||
  | { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
 | 
			
		||||
  | { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
 | 
			
		||||
  | { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
 | 
			
		||||
  | { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
 | 
			
		||||
  | { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
 | 
			
		||||
@ -387,6 +389,7 @@ export const modelingMachine = setup({
 | 
			
		||||
  guards: {
 | 
			
		||||
    'Selection is on face': () => false,
 | 
			
		||||
    'has valid sweep selection': () => false,
 | 
			
		||||
    'has valid loft selection': () => false,
 | 
			
		||||
    'has valid edge treatment selection': () => false,
 | 
			
		||||
    'Has exportable geometry': () => false,
 | 
			
		||||
    'has valid selection for deletion': () => false,
 | 
			
		||||
@ -1529,6 +1532,50 @@ export const modelingMachine = setup({
 | 
			
		||||
          updateAstResult.newAst
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (updateAstResult?.selections) {
 | 
			
		||||
          editorManager.selectRange(updateAstResult?.selections)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ),
 | 
			
		||||
    loftAstMod: fromPromise(
 | 
			
		||||
      async ({
 | 
			
		||||
        input,
 | 
			
		||||
      }: {
 | 
			
		||||
        input: ModelingCommandSchema['Loft'] | undefined
 | 
			
		||||
      }) => {
 | 
			
		||||
        if (!input) return new Error('No input provided')
 | 
			
		||||
        // Extract inputs
 | 
			
		||||
        const ast = kclManager.ast
 | 
			
		||||
        const { selection } = input
 | 
			
		||||
        const declarators = selection.graphSelections.flatMap((s) => {
 | 
			
		||||
          const path = getNodePathFromSourceRange(ast, s?.codeRef.range)
 | 
			
		||||
          const nodeFromPath = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
            ast,
 | 
			
		||||
            path,
 | 
			
		||||
            'VariableDeclarator'
 | 
			
		||||
          )
 | 
			
		||||
          return err(nodeFromPath) ? [] : nodeFromPath.node
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        // TODO: add better validation on selection
 | 
			
		||||
        if (!(declarators && declarators.length > 1)) {
 | 
			
		||||
          trap('Not enough sketches selected')
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Perform the loft
 | 
			
		||||
        const loftSketchesRes = loftSketches(ast, declarators)
 | 
			
		||||
        const updateAstResult = await kclManager.updateAst(
 | 
			
		||||
          loftSketchesRes.modifiedAst,
 | 
			
		||||
          true,
 | 
			
		||||
          {
 | 
			
		||||
            focusPath: [loftSketchesRes.pathToNode],
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        await codeManager.updateEditorWithAstAndWriteToFile(
 | 
			
		||||
          updateAstResult.newAst
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (updateAstResult?.selections) {
 | 
			
		||||
          editorManager.selectRange(updateAstResult?.selections)
 | 
			
		||||
        }
 | 
			
		||||
@ -1570,6 +1617,11 @@ export const modelingMachine = setup({
 | 
			
		||||
          reenter: false,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        Loft: {
 | 
			
		||||
          target: 'Applying loft',
 | 
			
		||||
          reenter: true,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        Fillet: {
 | 
			
		||||
          target: 'idle',
 | 
			
		||||
          guard: 'has valid edge treatment selection',
 | 
			
		||||
@ -2318,6 +2370,19 @@ export const modelingMachine = setup({
 | 
			
		||||
        onError: ['idle'],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'Applying loft': {
 | 
			
		||||
      invoke: {
 | 
			
		||||
        src: 'loftAstMod',
 | 
			
		||||
        id: 'loftAstMod',
 | 
			
		||||
        input: ({ event }) => {
 | 
			
		||||
          if (event.type !== 'Loft') return undefined
 | 
			
		||||
          return event.data
 | 
			
		||||
        },
 | 
			
		||||
        onDone: ['idle'],
 | 
			
		||||
        onError: ['idle'],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  initial: 'idle',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										57
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@ -316,7 +316,7 @@ dependencies = [
 | 
			
		||||
 "bitvec",
 | 
			
		||||
 "chrono",
 | 
			
		||||
 "hex",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "rand 0.8.5",
 | 
			
		||||
@ -746,7 +746,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "derive-docs"
 | 
			
		||||
version = "0.1.31"
 | 
			
		||||
version = "0.1.32"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "Inflector",
 | 
			
		||||
 "anyhow",
 | 
			
		||||
@ -1136,7 +1136,7 @@ dependencies = [
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "futures-sink",
 | 
			
		||||
 "http 1.1.0",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "slab",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tokio-util",
 | 
			
		||||
@ -1182,9 +1182,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hashbrown"
 | 
			
		||||
version = "0.15.0"
 | 
			
		||||
version = "0.15.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
 | 
			
		||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "heck"
 | 
			
		||||
@ -1584,12 +1584,12 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "2.6.0"
 | 
			
		||||
version = "2.7.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
 | 
			
		||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "equivalent",
 | 
			
		||||
 "hashbrown 0.15.0",
 | 
			
		||||
 "hashbrown 0.15.2",
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -1706,7 +1706,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
version = "0.2.27"
 | 
			
		||||
version = "0.2.28"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "approx 0.5.1",
 | 
			
		||||
@ -1732,7 +1732,7 @@ dependencies = [
 | 
			
		||||
 "http 1.1.0",
 | 
			
		||||
 "iai",
 | 
			
		||||
 "image",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "insta",
 | 
			
		||||
 "itertools 0.13.0",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
@ -1773,7 +1773,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
version = "0.1.17"
 | 
			
		||||
version = "0.1.18"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "hyper 0.14.30",
 | 
			
		||||
@ -1790,7 +1790,7 @@ version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "kcl-lib",
 | 
			
		||||
 "kittycad",
 | 
			
		||||
 "kittycad-modeling-cmds",
 | 
			
		||||
@ -1800,9 +1800,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad"
 | 
			
		||||
version = "0.3.25"
 | 
			
		||||
version = "0.3.28"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f6359cc0a1bbccbcf78775eea17a033cf2aa89d3fe6a9784f8ce94e5f882c185"
 | 
			
		||||
checksum = "933cb5f77624386c87d296e3fd493daf50156d1cbfa03b9f333a6d4da2896369"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -1831,7 +1831,7 @@ dependencies = [
 | 
			
		||||
 "serde_bytes",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "serde_urlencoded",
 | 
			
		||||
 "thiserror 1.0.68",
 | 
			
		||||
 "thiserror 2.0.0",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tracing",
 | 
			
		||||
 "url",
 | 
			
		||||
@ -2921,9 +2921,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "reqwest-conditional-middleware"
 | 
			
		||||
version = "0.3.0"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b1663d9d4fbb6e3900f91455d6d7833301c91ae3c7fc6e116fd7acd40e478a93"
 | 
			
		||||
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "async-trait",
 | 
			
		||||
 "http 1.1.0",
 | 
			
		||||
@ -2933,9 +2933,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "reqwest-middleware"
 | 
			
		||||
version = "0.3.3"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04"
 | 
			
		||||
checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -2948,9 +2948,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "reqwest-retry"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
version = "0.7.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a83df1aaec00176d0fabb65dea13f832d2a446ca99107afc17c5d2d4981221d0"
 | 
			
		||||
checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -2962,6 +2962,7 @@ dependencies = [
 | 
			
		||||
 "reqwest",
 | 
			
		||||
 "reqwest-middleware",
 | 
			
		||||
 "retry-policies",
 | 
			
		||||
 "thiserror 1.0.68",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tracing",
 | 
			
		||||
 "wasm-timer",
 | 
			
		||||
@ -2969,9 +2970,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "reqwest-tracing"
 | 
			
		||||
version = "0.5.3"
 | 
			
		||||
version = "0.5.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bfdd9bfa64c72233d8dd99ab7883efcdefe9e16d46488ecb9228b71a2e2ceb45"
 | 
			
		||||
checksum = "ff82cf5730a1311fb9413b0bc2b8e743e0157cd73f010ab4ec374a923873b6a2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -3157,7 +3158,7 @@ dependencies = [
 | 
			
		||||
 "chrono",
 | 
			
		||||
 "dyn-clone",
 | 
			
		||||
 "indexmap 1.9.3",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "schemars_derive",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
@ -3258,7 +3259,7 @@ version = "1.0.133"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "ryu",
 | 
			
		||||
@ -3846,7 +3847,7 @@ version = "0.22.22"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_spanned",
 | 
			
		||||
 "toml_datetime",
 | 
			
		||||
@ -4012,7 +4013,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "chrono",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "thiserror 1.0.68",
 | 
			
		||||
@ -4769,7 +4770,7 @@ dependencies = [
 | 
			
		||||
 "crc32fast",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
 "displaydoc",
 | 
			
		||||
 "indexmap 2.6.0",
 | 
			
		||||
 "indexmap 2.7.0",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "thiserror 1.0.68",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -74,8 +74,11 @@ members = [
 | 
			
		||||
 | 
			
		||||
[workspace.dependencies]
 | 
			
		||||
http = "1"
 | 
			
		||||
kittycad = { version = "0.3.25", default-features = false, features = ["js", "requests"] }
 | 
			
		||||
kittycad-modeling-cmds = { version = "0.2.76", features = ["websocket"] }
 | 
			
		||||
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
 | 
			
		||||
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
 | 
			
		||||
 | 
			
		||||
[workspace.lints.clippy]
 | 
			
		||||
iter_over_hash_type = "warn"
 | 
			
		||||
 | 
			
		||||
[[test]]
 | 
			
		||||
name = "executor"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "derive-docs"
 | 
			
		||||
description = "A tool for generating documentation from Rust derive macros"
 | 
			
		||||
version = "0.1.31"
 | 
			
		||||
version = "0.1.32"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
@ -27,3 +27,6 @@ anyhow = "1.0.93"
 | 
			
		||||
expectorate = "1.1.0"
 | 
			
		||||
pretty_assertions = "1.4.1"
 | 
			
		||||
rustfmt-wrapper = "0.2.1"
 | 
			
		||||
 | 
			
		||||
[lints]
 | 
			
		||||
workspace = true
 | 
			
		||||
 | 
			
		||||
@ -23,17 +23,30 @@ use unbox::unbox;
 | 
			
		||||
struct StdlibMetadata {
 | 
			
		||||
    /// The name of the function in the API.
 | 
			
		||||
    name: String,
 | 
			
		||||
 | 
			
		||||
    /// Tags for the function.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    tags: Vec<String>,
 | 
			
		||||
 | 
			
		||||
    /// Whether the function is unpublished.
 | 
			
		||||
    /// Then docs will not be generated.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    unpublished: bool,
 | 
			
		||||
 | 
			
		||||
    /// Whether the function is deprecated.
 | 
			
		||||
    /// Then specific docs detailing that this is deprecated will be generated.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    deprecated: bool,
 | 
			
		||||
 | 
			
		||||
    /// If true, expects keyword arguments.
 | 
			
		||||
    /// If false, expects positional arguments.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    keywords: bool,
 | 
			
		||||
 | 
			
		||||
    /// If true, the first argument is unlabeled.
 | 
			
		||||
    /// If false, all arguments require labels.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    unlabeled_first: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[proc_macro_attribute]
 | 
			
		||||
@ -171,7 +184,7 @@ fn do_stdlib_inner(
 | 
			
		||||
            code_blocks.iter().map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            }).collect::<Vec<String>>()
 | 
			
		||||
@ -225,6 +238,12 @@ fn do_stdlib_inner(
 | 
			
		||||
        quote! { false }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let uses_keyword_arguments = if metadata.keywords {
 | 
			
		||||
        quote! { true }
 | 
			
		||||
    } else {
 | 
			
		||||
        quote! { false }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let docs_crate = get_crate(None);
 | 
			
		||||
 | 
			
		||||
    // When the user attaches this proc macro to a function with the wrong type
 | 
			
		||||
@ -233,7 +252,7 @@ fn do_stdlib_inner(
 | 
			
		||||
    // of the various parameters. We do this by calling dummy functions that
 | 
			
		||||
    // require a type that satisfies SharedExtractor or ExclusiveExtractor.
 | 
			
		||||
    let mut arg_types = Vec::new();
 | 
			
		||||
    for arg in ast.sig.inputs.iter() {
 | 
			
		||||
    for (i, arg) in ast.sig.inputs.iter().enumerate() {
 | 
			
		||||
        // Get the name of the argument.
 | 
			
		||||
        let arg_name = match arg {
 | 
			
		||||
            syn::FnArg::Receiver(pat) => {
 | 
			
		||||
@ -263,7 +282,7 @@ fn do_stdlib_inner(
 | 
			
		||||
 | 
			
		||||
        let ty_string = rust_type_to_openapi_type(&ty_string);
 | 
			
		||||
        let required = !ty_ident.to_string().starts_with("Option <");
 | 
			
		||||
 | 
			
		||||
        let label_required = !(i == 0 && metadata.unlabeled_first);
 | 
			
		||||
        if ty_string != "ExecState" && ty_string != "Args" {
 | 
			
		||||
            let schema = quote! {
 | 
			
		||||
               generator.root_schema_for::<#ty_ident>()
 | 
			
		||||
@ -274,6 +293,7 @@ fn do_stdlib_inner(
 | 
			
		||||
                    type_: #ty_string.to_string(),
 | 
			
		||||
                    schema: #schema,
 | 
			
		||||
                    required: #required,
 | 
			
		||||
                    label_required: #label_required,
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@ -334,6 +354,7 @@ fn do_stdlib_inner(
 | 
			
		||||
                type_: #ret_ty_string.to_string(),
 | 
			
		||||
                schema,
 | 
			
		||||
                required: true,
 | 
			
		||||
                label_required: true,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
@ -400,6 +421,10 @@ fn do_stdlib_inner(
 | 
			
		||||
                vec![#(#tags),*]
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fn keyword_arguments(&self) -> bool {
 | 
			
		||||
                #uses_keyword_arguments
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
 | 
			
		||||
                let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
                // We set this to false so we can recurse them later.
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
            type_: "Foo".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Foo>(),
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
            type_: "i32".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
            type_: "string".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<str>(),
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
            type_: "i32".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for SomeFn {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -117,6 +121,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "[number]".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<[f64; 2usize]>(),
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -130,6 +135,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -150,7 +156,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<f64>(),
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for MyFunc {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -117,6 +121,7 @@ impl crate::docs::StdLibFn for MyFunc {
 | 
			
		||||
            type_: "kittycad::types::InputFormat".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
 | 
			
		||||
            required: false,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -130,6 +135,7 @@ impl crate::docs::StdLibFn for MyFunc {
 | 
			
		||||
            type_: "[Sketch]".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -150,7 +156,7 @@ impl crate::docs::StdLibFn for MyFunc {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for LineTo {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -118,12 +122,14 @@ impl crate::docs::StdLibFn for LineTo {
 | 
			
		||||
                type_: "LineToData".to_string(),
 | 
			
		||||
                schema: generator.root_schema_for::<LineToData>(),
 | 
			
		||||
                required: true,
 | 
			
		||||
                label_required: true,
 | 
			
		||||
            },
 | 
			
		||||
            crate::docs::StdLibFnArg {
 | 
			
		||||
                name: "sketch".to_string(),
 | 
			
		||||
                type_: "Sketch".to_string(),
 | 
			
		||||
                schema: generator.root_schema_for::<Sketch>(),
 | 
			
		||||
                required: true,
 | 
			
		||||
                label_required: true,
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
@ -138,6 +144,7 @@ impl crate::docs::StdLibFn for LineTo {
 | 
			
		||||
            type_: "Sketch".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -158,7 +165,7 @@ impl crate::docs::StdLibFn for LineTo {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for Min {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -117,6 +121,7 @@ impl crate::docs::StdLibFn for Min {
 | 
			
		||||
            type_: "[number]".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Vec<f64>>(),
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -130,6 +135,7 @@ impl crate::docs::StdLibFn for Min {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -150,7 +156,7 @@ impl crate::docs::StdLibFn for Min {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Option<f64>>(),
 | 
			
		||||
            required: false,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            type_: "kittycad::types::InputFormat".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
 | 
			
		||||
            required: false,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            type_: "number".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            type_: "kittycad::types::InputFormat".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
 | 
			
		||||
            required: false,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            type_: "[Sketch]".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            type_: "kittycad::types::InputFormat".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
 | 
			
		||||
            required: false,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            type_: "[Sketch]".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for Import {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "[number]".to_string(),
 | 
			
		||||
            schema: generator.root_schema_for::<Vec<f64>>(),
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            type_: "()".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +119,7 @@ impl crate::docs::StdLibFn for Show {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for SomeFunction {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keyword_arguments(&self) -> bool {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = inline_subschemas;
 | 
			
		||||
@ -91,6 +95,7 @@ impl crate::docs::StdLibFn for SomeFunction {
 | 
			
		||||
            type_: "i32".to_string(),
 | 
			
		||||
            schema,
 | 
			
		||||
            required: true,
 | 
			
		||||
            label_required: true,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -108,7 +113,7 @@ impl crate::docs::StdLibFn for SomeFunction {
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cb| {
 | 
			
		||||
                let program = crate::Program::parse(cb).unwrap();
 | 
			
		||||
                let mut options: crate::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
 | 
			
		||||
                options.insert_final_newline = false;
 | 
			
		||||
                program.ast.recast(&options, 0)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
description = "A test server for KCL"
 | 
			
		||||
version = "0.1.17"
 | 
			
		||||
version = "0.1.18"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
 | 
			
		||||
@ -13,3 +13,6 @@ pico-args = "0.5.0"
 | 
			
		||||
serde = { version = "1.0.214", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.128"
 | 
			
		||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
 | 
			
		||||
 | 
			
		||||
[lints]
 | 
			
		||||
workspace = true
 | 
			
		||||
 | 
			
		||||
@ -15,9 +15,12 @@ path = "src/tool.rs"
 | 
			
		||||
[dependencies]
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
async-trait = "0.1.81"
 | 
			
		||||
indexmap = "2.6.0"
 | 
			
		||||
indexmap = "2.7.0"
 | 
			
		||||
kcl-lib = { path = "../kcl" }
 | 
			
		||||
kittycad = { workspace = true, features = ["clap"] }
 | 
			
		||||
kittycad-modeling-cmds = { workspace = true }
 | 
			
		||||
tokio = { version = "1.41", features = ["full", "time", "rt", "tracing"] }
 | 
			
		||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
 | 
			
		||||
 | 
			
		||||
[lints]
 | 
			
		||||
workspace = true
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
description = "KittyCAD Language implementation and tools"
 | 
			
		||||
version = "0.2.27"
 | 
			
		||||
version = "0.2.28"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
@ -23,7 +23,7 @@ clap = { version = "4.5.21", default-features = false, optional = true, features
 | 
			
		||||
convert_case = "0.6.0"
 | 
			
		||||
dashmap = "6.1.0"
 | 
			
		||||
databake = { version = "0.1.8", features = ["derive"] }
 | 
			
		||||
derive-docs = { version = "0.1.29", path = "../derive-docs" }
 | 
			
		||||
derive-docs = { version = "0.1.32", path = "../derive-docs" }
 | 
			
		||||
dhat = { version = "0.3", optional = true }
 | 
			
		||||
fnv = "1.0.7"
 | 
			
		||||
form_urlencoded = "1.2.1"
 | 
			
		||||
@ -32,7 +32,7 @@ git_rev = "0.1.0"
 | 
			
		||||
gltf-json = "1.4.1"
 | 
			
		||||
http = { workspace = true }
 | 
			
		||||
image = { version = "0.25.5", default-features = false, features = ["png"] }
 | 
			
		||||
indexmap = { version = "2.6.0", features = ["serde"] }
 | 
			
		||||
indexmap = { version = "2.7.0", features = ["serde"] }
 | 
			
		||||
kittycad = { workspace = true }
 | 
			
		||||
kittycad-modeling-cmds = { workspace = true }
 | 
			
		||||
lazy_static = "1.5.0"
 | 
			
		||||
@ -121,6 +121,9 @@ pretty_assertions = "1.4.1"
 | 
			
		||||
tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
 | 
			
		||||
twenty-twenty = "0.8.0"
 | 
			
		||||
 | 
			
		||||
[lints]
 | 
			
		||||
workspace = true
 | 
			
		||||
 | 
			
		||||
[[bench]]
 | 
			
		||||
name = "compiler_benchmark_criterion"
 | 
			
		||||
harness = false
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,9 @@ members = ["."]
 | 
			
		||||
[profile.release]
 | 
			
		||||
debug = 1
 | 
			
		||||
 | 
			
		||||
[lints]
 | 
			
		||||
workspace = true
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "parser"
 | 
			
		||||
path = "fuzz_targets/parser.rs"
 | 
			
		||||
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
pub mod modify;
 | 
			
		||||
pub mod types;
 | 
			
		||||
@ -1,9 +1,10 @@
 | 
			
		||||
use std::collections::{BTreeMap, HashMap};
 | 
			
		||||
use std::collections::BTreeMap;
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use base64::Engine;
 | 
			
		||||
use convert_case::Casing;
 | 
			
		||||
use handlebars::Renderable;
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
use itertools::Itertools;
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
 | 
			
		||||
@ -271,7 +272,7 @@ fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
 | 
			
		||||
    Ok(hbs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn generate_index(combined: &HashMap<String, Box<dyn StdLibFn>>) -> Result<()> {
 | 
			
		||||
fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>) -> Result<()> {
 | 
			
		||||
    let hbs = init_handlebars()?;
 | 
			
		||||
 | 
			
		||||
    let mut functions = Vec::new();
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,8 @@ pub struct StdLibFnData {
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    /// The tags of the function.
 | 
			
		||||
    pub tags: Vec<String>,
 | 
			
		||||
    /// If this function uses keyword arguments, or positional arguments.
 | 
			
		||||
    pub keyword_arguments: bool,
 | 
			
		||||
    /// The args of the function.
 | 
			
		||||
    pub args: Vec<StdLibFnArg>,
 | 
			
		||||
    /// The return value of the function.
 | 
			
		||||
@ -55,6 +57,18 @@ pub struct StdLibFnArg {
 | 
			
		||||
    pub schema: schemars::schema::RootSchema,
 | 
			
		||||
    /// If the argument is required.
 | 
			
		||||
    pub required: bool,
 | 
			
		||||
    /// Even in functions that use keyword arguments, not every parameter requires a label (most do though).
 | 
			
		||||
    /// Some functions allow one unlabeled parameter, which has to be first in the
 | 
			
		||||
    /// argument list.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This field is ignored for functions that still use positional arguments.
 | 
			
		||||
    /// Defaults to true.
 | 
			
		||||
    #[serde(default = "its_true")]
 | 
			
		||||
    pub label_required: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn its_true() -> bool {
 | 
			
		||||
    true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl StdLibFnArg {
 | 
			
		||||
@ -120,6 +134,9 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
 | 
			
		||||
    /// The description of the function.
 | 
			
		||||
    fn description(&self) -> String;
 | 
			
		||||
 | 
			
		||||
    /// Does this use keyword arguments, or positional?
 | 
			
		||||
    fn keyword_arguments(&self) -> bool;
 | 
			
		||||
 | 
			
		||||
    /// The tags of the function.
 | 
			
		||||
    fn tags(&self) -> Vec<String>;
 | 
			
		||||
 | 
			
		||||
@ -151,6 +168,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
 | 
			
		||||
            summary: self.summary(),
 | 
			
		||||
            description: self.description(),
 | 
			
		||||
            tags: self.tags(),
 | 
			
		||||
            keyword_arguments: self.keyword_arguments(),
 | 
			
		||||
            args: self.args(false),
 | 
			
		||||
            return_value: self.return_value(false),
 | 
			
		||||
            unpublished: self.unpublished(),
 | 
			
		||||
@ -797,7 +815,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_serialize_function() {
 | 
			
		||||
        let some_function = crate::ast::types::Function::StdLib {
 | 
			
		||||
        let some_function = crate::parsing::ast::types::Function::StdLib {
 | 
			
		||||
            func: Box::new(crate::std::sketch::Line),
 | 
			
		||||
        };
 | 
			
		||||
        let serialized = serde_json::to_string(&some_function).unwrap();
 | 
			
		||||
@ -806,12 +824,12 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_deserialize_function() {
 | 
			
		||||
        let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{},"schemaDefinitions":{}},"args":[],"unpublished":false,"deprecated":false, "examples": []}}"#;
 | 
			
		||||
        let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
 | 
			
		||||
        let some_function_string = r#"{"type":"StdLib","func":{"name":"line","keywordArguments":false,"summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{},"schemaDefinitions":{}},"args":[],"unpublished":false,"deprecated":false, "examples": []}}"#;
 | 
			
		||||
        let some_function: crate::parsing::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            some_function,
 | 
			
		||||
            crate::ast::types::Function::StdLib {
 | 
			
		||||
            crate::parsing::ast::types::Function::StdLib {
 | 
			
		||||
                func: Box::new(crate::std::sketch::Line)
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
@ -213,7 +213,12 @@ impl EngineConnection {
 | 
			
		||||
                            WebSocketResponse::Success(SuccessWebSocketResponse {
 | 
			
		||||
                                resp: OkWebSocketResponseData::ModelingBatch { responses },
 | 
			
		||||
                                ..
 | 
			
		||||
                            }) => {
 | 
			
		||||
                            }) =>
 | 
			
		||||
                            {
 | 
			
		||||
                                #[expect(
 | 
			
		||||
                                    clippy::iter_over_hash_type,
 | 
			
		||||
                                    reason = "modeling command uses a HashMap and keys are random, so we don't really have a choice"
 | 
			
		||||
                                )]
 | 
			
		||||
                                for (resp_id, batch_response) in responses {
 | 
			
		||||
                                    let id: uuid::Uuid = (*resp_id).into();
 | 
			
		||||
                                    match batch_response {
 | 
			
		||||
 | 
			
		||||
@ -342,92 +342,80 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        let plane_settings: HashMap<PlaneName, (Uuid, Point3d, Point3d, Option<Color>)> = HashMap::from([
 | 
			
		||||
        let plane_settings: Vec<(PlaneName, Uuid, Point3d, Point3d, Option<Color>)> = vec![
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::Xy,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d { x: 1.0, y: 0.0, z: 0.0 },
 | 
			
		||||
                    Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                    Some(Color {
 | 
			
		||||
                        r: 0.7,
 | 
			
		||||
                        g: 0.28,
 | 
			
		||||
                        b: 0.28,
 | 
			
		||||
                        a: 0.4,
 | 
			
		||||
                    }),
 | 
			
		||||
                ),
 | 
			
		||||
                id_generator.next_uuid(),
 | 
			
		||||
                Point3d { x: 1.0, y: 0.0, z: 0.0 },
 | 
			
		||||
                Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                Some(Color {
 | 
			
		||||
                    r: 0.7,
 | 
			
		||||
                    g: 0.28,
 | 
			
		||||
                    b: 0.28,
 | 
			
		||||
                    a: 0.4,
 | 
			
		||||
                }),
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::Yz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                    Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                    Some(Color {
 | 
			
		||||
                        r: 0.28,
 | 
			
		||||
                        g: 0.7,
 | 
			
		||||
                        b: 0.28,
 | 
			
		||||
                        a: 0.4,
 | 
			
		||||
                    }),
 | 
			
		||||
                ),
 | 
			
		||||
                id_generator.next_uuid(),
 | 
			
		||||
                Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                Some(Color {
 | 
			
		||||
                    r: 0.28,
 | 
			
		||||
                    g: 0.7,
 | 
			
		||||
                    b: 0.28,
 | 
			
		||||
                    a: 0.4,
 | 
			
		||||
                }),
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::Xz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d { x: 1.0, y: 0.0, z: 0.0 },
 | 
			
		||||
                    Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                    Some(Color {
 | 
			
		||||
                        r: 0.28,
 | 
			
		||||
                        g: 0.28,
 | 
			
		||||
                        b: 0.7,
 | 
			
		||||
                        a: 0.4,
 | 
			
		||||
                    }),
 | 
			
		||||
                ),
 | 
			
		||||
                id_generator.next_uuid(),
 | 
			
		||||
                Point3d { x: 1.0, y: 0.0, z: 0.0 },
 | 
			
		||||
                Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                Some(Color {
 | 
			
		||||
                    r: 0.28,
 | 
			
		||||
                    g: 0.28,
 | 
			
		||||
                    b: 0.7,
 | 
			
		||||
                    a: 0.4,
 | 
			
		||||
                }),
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::NegXy,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d {
 | 
			
		||||
                        x: -1.0,
 | 
			
		||||
                        y: 0.0,
 | 
			
		||||
                        z: 0.0,
 | 
			
		||||
                    },
 | 
			
		||||
                    Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                    None,
 | 
			
		||||
                ),
 | 
			
		||||
                id_generator.next_uuid(),
 | 
			
		||||
                Point3d {
 | 
			
		||||
                    x: -1.0,
 | 
			
		||||
                    y: 0.0,
 | 
			
		||||
                    z: 0.0,
 | 
			
		||||
                },
 | 
			
		||||
                Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                None,
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::NegYz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d {
 | 
			
		||||
                        x: 0.0,
 | 
			
		||||
                        y: -1.0,
 | 
			
		||||
                        z: 0.0,
 | 
			
		||||
                    },
 | 
			
		||||
                    Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                    None,
 | 
			
		||||
                ),
 | 
			
		||||
                id_generator.next_uuid(),
 | 
			
		||||
                Point3d {
 | 
			
		||||
                    x: 0.0,
 | 
			
		||||
                    y: -1.0,
 | 
			
		||||
                    z: 0.0,
 | 
			
		||||
                },
 | 
			
		||||
                Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                None,
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::NegXz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d {
 | 
			
		||||
                        x: -1.0,
 | 
			
		||||
                        y: 0.0,
 | 
			
		||||
                        z: 0.0,
 | 
			
		||||
                    },
 | 
			
		||||
                    Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                    None,
 | 
			
		||||
                ),
 | 
			
		||||
                id_generator.next_uuid(),
 | 
			
		||||
                Point3d {
 | 
			
		||||
                    x: -1.0,
 | 
			
		||||
                    y: 0.0,
 | 
			
		||||
                    z: 0.0,
 | 
			
		||||
                },
 | 
			
		||||
                Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                None,
 | 
			
		||||
            ),
 | 
			
		||||
        ]);
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let mut planes = HashMap::new();
 | 
			
		||||
        for (name, (plane_id, x_axis, y_axis, color)) in plane_settings {
 | 
			
		||||
        for (name, plane_id, x_axis, y_axis, color) in plane_settings {
 | 
			
		||||
            planes.insert(
 | 
			
		||||
                name,
 | 
			
		||||
                self.make_default_plane(plane_id, x_axis, y_axis, color, source_range)
 | 
			
		||||
@ -475,6 +463,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        responses: HashMap<uuid::Uuid, BatchResponse>,
 | 
			
		||||
    ) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
 | 
			
		||||
        // Iterate over the responses and check for errors.
 | 
			
		||||
        #[expect(
 | 
			
		||||
            clippy::iter_over_hash_type,
 | 
			
		||||
            reason = "modeling command uses a HashMap and keys are random, so we don't really have a choice"
 | 
			
		||||
        )]
 | 
			
		||||
        for (cmd_id, resp) in responses.iter() {
 | 
			
		||||
            match resp {
 | 
			
		||||
                BatchResponse::Success { response } => {
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
//! The executor for the AST.
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{HashMap, HashSet},
 | 
			
		||||
    sync::Arc,
 | 
			
		||||
};
 | 
			
		||||
use std::{collections::HashSet, sync::Arc};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use async_recursion::async_recursion;
 | 
			
		||||
@ -25,10 +22,12 @@ type Point3D = kcmc::shared::Point3d<f64>;
 | 
			
		||||
 | 
			
		||||
pub use crate::kcl_value::KclValue;
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, Node, NodeRef, TagDeclarator, TagNode},
 | 
			
		||||
    engine::{EngineManager, ExecutionKind},
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    fs::{FileManager, FileSystem},
 | 
			
		||||
    parsing::ast::types::{
 | 
			
		||||
        BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, Node, NodeRef, TagDeclarator, TagNode,
 | 
			
		||||
    },
 | 
			
		||||
    settings::types::UnitLength,
 | 
			
		||||
    source_range::{ModuleId, SourceRange},
 | 
			
		||||
    std::{args::Arg, StdLib},
 | 
			
		||||
@ -191,7 +190,7 @@ impl EnvironmentRef {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
pub struct Environment {
 | 
			
		||||
    bindings: HashMap<String, KclValue>,
 | 
			
		||||
    bindings: IndexMap<String, KclValue>,
 | 
			
		||||
    parent: Option<EnvironmentRef>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -201,7 +200,7 @@ impl Environment {
 | 
			
		||||
    pub fn root() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            // Prelude
 | 
			
		||||
            bindings: HashMap::from([
 | 
			
		||||
            bindings: IndexMap::from([
 | 
			
		||||
                ("ZERO".to_string(), KclValue::from_number(0.0, NO_META)),
 | 
			
		||||
                ("QUARTER_TURN".to_string(), KclValue::from_number(90.0, NO_META)),
 | 
			
		||||
                ("HALF_TURN".to_string(), KclValue::from_number(180.0, NO_META)),
 | 
			
		||||
@ -213,7 +212,7 @@ impl Environment {
 | 
			
		||||
 | 
			
		||||
    pub fn new(parent: EnvironmentRef) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            bindings: HashMap::new(),
 | 
			
		||||
            bindings: IndexMap::new(),
 | 
			
		||||
            parent: Some(parent),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -733,7 +732,7 @@ pub type MemoryFunction =
 | 
			
		||||
    fn(
 | 
			
		||||
        s: Vec<Arg>,
 | 
			
		||||
        memory: ProgramMemory,
 | 
			
		||||
        expression: crate::ast::types::BoxNode<FunctionExpression>,
 | 
			
		||||
        expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
 | 
			
		||||
        metadata: Vec<Metadata>,
 | 
			
		||||
        exec_state: &ExecState,
 | 
			
		||||
        ctx: ExecutorContext,
 | 
			
		||||
@ -768,8 +767,8 @@ pub struct Sketch {
 | 
			
		||||
    /// The starting path.
 | 
			
		||||
    pub start: BasePath,
 | 
			
		||||
    /// Tag identifiers that have been declared in this sketch.
 | 
			
		||||
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
 | 
			
		||||
    pub tags: HashMap<String, TagIdentifier>,
 | 
			
		||||
    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
 | 
			
		||||
    pub tags: IndexMap<String, TagIdentifier>,
 | 
			
		||||
    /// The original id of the sketch. This stays the same even if the sketch is
 | 
			
		||||
    /// is sketched on face etc.
 | 
			
		||||
    #[serde(skip)]
 | 
			
		||||
@ -1842,7 +1841,7 @@ impl ExecutorContext {
 | 
			
		||||
    #[async_recursion]
 | 
			
		||||
    pub(crate) async fn inner_execute<'a>(
 | 
			
		||||
        &'a self,
 | 
			
		||||
        program: NodeRef<'a, crate::ast::types::Program>,
 | 
			
		||||
        program: NodeRef<'a, crate::parsing::ast::types::Program>,
 | 
			
		||||
        exec_state: &mut ExecState,
 | 
			
		||||
        body_type: BodyType,
 | 
			
		||||
    ) -> Result<Option<KclValue>, KclError> {
 | 
			
		||||
@ -1883,7 +1882,7 @@ impl ExecutorContext {
 | 
			
		||||
                    let module_id = exec_state.add_module(resolved_path.clone());
 | 
			
		||||
                    let source = self.fs.read_to_string(&resolved_path, source_range).await?;
 | 
			
		||||
                    // TODO handle parsing errors properly
 | 
			
		||||
                    let program = crate::parser::parse_str(&source, module_id).parse_errs_as_err()?;
 | 
			
		||||
                    let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
 | 
			
		||||
                    let (module_memory, module_exports) = {
 | 
			
		||||
                        exec_state.import_stack.push(resolved_path.clone());
 | 
			
		||||
                        let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
 | 
			
		||||
@ -2239,7 +2238,7 @@ mod tests {
 | 
			
		||||
    use pretty_assertions::assert_eq;
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::ast::types::{Identifier, Node, Parameter};
 | 
			
		||||
    use crate::parsing::ast::types::{Identifier, Node, Parameter};
 | 
			
		||||
 | 
			
		||||
    pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
 | 
			
		||||
        let program = Program::parse(code)?;
 | 
			
		||||
@ -3088,7 +3087,7 @@ let w = f() + f()
 | 
			
		||||
            let func_expr = &Node::no_src(FunctionExpression {
 | 
			
		||||
                params,
 | 
			
		||||
                body: Node {
 | 
			
		||||
                    inner: crate::ast::types::Program {
 | 
			
		||||
                    inner: crate::parsing::ast::types::Program {
 | 
			
		||||
                        body: Vec::new(),
 | 
			
		||||
                        non_code_meta: Default::default(),
 | 
			
		||||
                        shebang: None,
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
use schemars::JsonSchema;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::FunctionExpression,
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    executor::{
 | 
			
		||||
        call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
 | 
			
		||||
    },
 | 
			
		||||
    parsing::ast::types::FunctionExpression,
 | 
			
		||||
    std::args::Arg,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ use crate::{
 | 
			
		||||
pub struct FunctionParam<'a> {
 | 
			
		||||
    pub inner: Option<&'a MemoryFunction>,
 | 
			
		||||
    pub memory: ProgramMemory,
 | 
			
		||||
    pub fn_expr: crate::ast::types::BoxNode<FunctionExpression>,
 | 
			
		||||
    pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
 | 
			
		||||
    pub meta: Vec<Metadata>,
 | 
			
		||||
    pub ctx: ExecutorContext,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,10 +5,10 @@ use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{FunctionExpression, KclNone, TagDeclarator, TagNode},
 | 
			
		||||
    errors::KclErrorDetails,
 | 
			
		||||
    exec::{ProgramMemory, Sketch},
 | 
			
		||||
    executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
 | 
			
		||||
    parsing::ast::types::{FunctionExpression, KclNone, TagDeclarator, TagNode},
 | 
			
		||||
    std::{args::Arg, FnAsArg},
 | 
			
		||||
    ExecState, ExecutorContext, KclError, SourceRange,
 | 
			
		||||
};
 | 
			
		||||
@ -56,7 +56,7 @@ pub enum KclValue {
 | 
			
		||||
        meta: Vec<Metadata>,
 | 
			
		||||
    },
 | 
			
		||||
    TagIdentifier(Box<TagIdentifier>),
 | 
			
		||||
    TagDeclarator(crate::ast::types::BoxNode<TagDeclarator>),
 | 
			
		||||
    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
 | 
			
		||||
    Plane(Box<Plane>),
 | 
			
		||||
    Face(Box<Face>),
 | 
			
		||||
    Sketch {
 | 
			
		||||
@ -75,7 +75,7 @@ pub enum KclValue {
 | 
			
		||||
        #[serde(skip)]
 | 
			
		||||
        func: Option<MemoryFunction>,
 | 
			
		||||
        #[schemars(skip)]
 | 
			
		||||
        expression: crate::ast::types::BoxNode<FunctionExpression>,
 | 
			
		||||
        expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
 | 
			
		||||
        memory: Box<ProgramMemory>,
 | 
			
		||||
        #[serde(rename = "__meta")]
 | 
			
		||||
        meta: Vec<Metadata>,
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,6 @@ macro_rules! eprint {
 | 
			
		||||
#[global_allocator]
 | 
			
		||||
static ALLOC: dhat::Alloc = dhat::Alloc;
 | 
			
		||||
 | 
			
		||||
mod ast;
 | 
			
		||||
mod coredump;
 | 
			
		||||
mod docs;
 | 
			
		||||
mod engine;
 | 
			
		||||
@ -68,7 +67,7 @@ mod kcl_value;
 | 
			
		||||
pub mod lint;
 | 
			
		||||
mod log;
 | 
			
		||||
mod lsp;
 | 
			
		||||
mod parser;
 | 
			
		||||
mod parsing;
 | 
			
		||||
mod settings;
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod simulation_tests;
 | 
			
		||||
@ -77,13 +76,11 @@ mod std;
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
pub mod test_server;
 | 
			
		||||
mod thread;
 | 
			
		||||
mod token;
 | 
			
		||||
mod unparser;
 | 
			
		||||
mod walk;
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
mod wasm;
 | 
			
		||||
 | 
			
		||||
pub use ast::{modify::modify_ast_for_sketch, types::FormatOptions};
 | 
			
		||||
pub use coredump::CoreDump;
 | 
			
		||||
pub use engine::{EngineManager, ExecutionKind};
 | 
			
		||||
pub use errors::{ConnectionError, ExecError, KclError};
 | 
			
		||||
@ -92,6 +89,7 @@ pub use lsp::{
 | 
			
		||||
    copilot::Backend as CopilotLspBackend,
 | 
			
		||||
    kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
 | 
			
		||||
};
 | 
			
		||||
pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
 | 
			
		||||
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
 | 
			
		||||
pub use source_range::{ModuleId, SourceRange};
 | 
			
		||||
 | 
			
		||||
@ -127,7 +125,7 @@ use crate::log::{log, logln};
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
pub struct Program {
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    ast: ast::types::Node<ast::types::Program>,
 | 
			
		||||
    ast: parsing::ast::types::Node<parsing::ast::types::Program>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(any(test, feature = "lsp-test-util"))]
 | 
			
		||||
@ -138,14 +136,14 @@ pub use lsp::test_util::kcl_lsp_server;
 | 
			
		||||
impl Program {
 | 
			
		||||
    pub fn parse(input: &str) -> Result<Program, KclError> {
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = token::lexer(input, module_id)?;
 | 
			
		||||
        let tokens = parsing::token::lexer(input, module_id)?;
 | 
			
		||||
        // TODO handle parsing errors properly
 | 
			
		||||
        let ast = parser::parse_tokens(tokens).parse_errs_as_err()?;
 | 
			
		||||
        let ast = parsing::parse_tokens(tokens).parse_errs_as_err()?;
 | 
			
		||||
 | 
			
		||||
        Ok(Program { ast })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn compute_digest(&mut self) -> ast::types::digest::Digest {
 | 
			
		||||
    pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
 | 
			
		||||
        self.ast.compute_digest()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -167,8 +165,8 @@ impl Program {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ast::types::Node<ast::types::Program>> for Program {
 | 
			
		||||
    fn from(ast: ast::types::Node<ast::types::Program>) -> Program {
 | 
			
		||||
impl From<parsing::ast::types::Node<parsing::ast::types::Program>> for Program {
 | 
			
		||||
    fn from(ast: parsing::ast::types::Node<parsing::ast::types::Program>) -> Program {
 | 
			
		||||
        Self { ast }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@ use anyhow::Result;
 | 
			
		||||
use convert_case::Casing;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{ObjectProperty, VariableDeclarator},
 | 
			
		||||
    lint::rule::{def_finding, Discovered, Finding},
 | 
			
		||||
    parsing::ast::types::{ObjectProperty, VariableDeclarator},
 | 
			
		||||
    walk::Node,
 | 
			
		||||
    SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,8 @@ use std::collections::HashMap;
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{BinaryPart, Expr, LiteralValue, ObjectExpression, UnaryOperator},
 | 
			
		||||
    lint::rule::{def_finding, Discovered, Finding},
 | 
			
		||||
    parsing::ast::types::{BinaryPart, Expr, LiteralValue, ObjectExpression, UnaryOperator},
 | 
			
		||||
    walk::Node,
 | 
			
		||||
    SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,9 +3,9 @@ use std::sync::Arc;
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{CallExpression, NodeRef},
 | 
			
		||||
    docs::StdLibFn,
 | 
			
		||||
    lint::rule::{def_finding, Discovered, Finding},
 | 
			
		||||
    parsing::ast::types::{CallExpression, NodeRef},
 | 
			
		||||
    std::{FunctionKind, StdLib},
 | 
			
		||||
    walk::Node,
 | 
			
		||||
    SourceRange,
 | 
			
		||||
 | 
			
		||||
@ -30,16 +30,16 @@ where
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Discovered {
 | 
			
		||||
    /// Zoo Lint Finding information.
 | 
			
		||||
    pub(super) finding: Finding,
 | 
			
		||||
    pub finding: Finding,
 | 
			
		||||
 | 
			
		||||
    /// Further information about the specific finding.
 | 
			
		||||
    pub(super) description: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
 | 
			
		||||
    /// Source code location.
 | 
			
		||||
    pub(super) pos: SourceRange,
 | 
			
		||||
    pub pos: SourceRange,
 | 
			
		||||
 | 
			
		||||
    /// Is this discovered issue overridden by the programmer?
 | 
			
		||||
    pub(super) overridden: bool,
 | 
			
		||||
    pub overridden: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "pyo3")]
 | 
			
		||||
@ -182,7 +182,7 @@ mod test {
 | 
			
		||||
 | 
			
		||||
    macro_rules! assert_no_finding {
 | 
			
		||||
        ( $check:expr, $finding:expr, $kcl:expr ) => {
 | 
			
		||||
            let prog = $crate::parser::top_level_parse($kcl).unwrap();
 | 
			
		||||
            let prog = $crate::parsing::top_level_parse($kcl).unwrap();
 | 
			
		||||
            for discovered_finding in prog.lint($check).unwrap() {
 | 
			
		||||
                if discovered_finding.finding == $finding {
 | 
			
		||||
                    assert!(false, "Finding {:?} was emitted", $finding.code);
 | 
			
		||||
@ -193,7 +193,7 @@ mod test {
 | 
			
		||||
 | 
			
		||||
    macro_rules! assert_finding {
 | 
			
		||||
        ( $check:expr, $finding:expr, $kcl:expr ) => {
 | 
			
		||||
            let prog = $crate::parser::top_level_parse($kcl).unwrap();
 | 
			
		||||
            let prog = $crate::parsing::top_level_parse($kcl).unwrap();
 | 
			
		||||
 | 
			
		||||
            for discovered_finding in prog.lint($check).unwrap() {
 | 
			
		||||
                if discovered_finding.finding == $finding {
 | 
			
		||||
 | 
			
		||||
@ -3,14 +3,14 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use tower_lsp::lsp_types::notification::Notification;
 | 
			
		||||
 | 
			
		||||
use crate::{ast::types::Node, settings::types::UnitLength};
 | 
			
		||||
use crate::{parsing::ast::types::Node, settings::types::UnitLength};
 | 
			
		||||
 | 
			
		||||
/// A notification that the AST has changed.
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum AstUpdated {}
 | 
			
		||||
 | 
			
		||||
impl Notification for AstUpdated {
 | 
			
		||||
    type Params = Node<crate::ast::types::Program>;
 | 
			
		||||
    type Params = Node<crate::parsing::ast::types::Program>;
 | 
			
		||||
    const METHOD: &'static str = "kcl/astUpdated";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -41,10 +41,12 @@ use tower_lsp::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{Expr, Node, VariableKind},
 | 
			
		||||
    lsp::{backend::Backend as _, util::IntoDiagnostic},
 | 
			
		||||
    parser::PIPE_OPERATOR,
 | 
			
		||||
    token::TokenType,
 | 
			
		||||
    parsing::{
 | 
			
		||||
        ast::types::{Expr, Node, VariableKind},
 | 
			
		||||
        token::TokenType,
 | 
			
		||||
        PIPE_OPERATOR,
 | 
			
		||||
    },
 | 
			
		||||
    ExecState, ModuleId, Program, SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -98,9 +100,9 @@ pub struct Backend {
 | 
			
		||||
    /// The stdlib signatures for the language.
 | 
			
		||||
    pub stdlib_signatures: HashMap<String, SignatureHelp>,
 | 
			
		||||
    /// Token maps.
 | 
			
		||||
    pub token_map: DashMap<String, Vec<crate::token::Token>>,
 | 
			
		||||
    pub token_map: DashMap<String, Vec<crate::parsing::token::Token>>,
 | 
			
		||||
    /// AST maps.
 | 
			
		||||
    pub ast_map: DashMap<String, Node<crate::ast::types::Program>>,
 | 
			
		||||
    pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>,
 | 
			
		||||
    /// Memory maps.
 | 
			
		||||
    pub memory_map: DashMap<String, crate::executor::ProgramMemory>,
 | 
			
		||||
    /// Current code.
 | 
			
		||||
@ -257,7 +259,7 @@ impl crate::lsp::backend::Backend for Backend {
 | 
			
		||||
 | 
			
		||||
        // Lets update the tokens.
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = match crate::token::lexer(¶ms.text, module_id) {
 | 
			
		||||
        let tokens = match crate::parsing::token::lexer(¶ms.text, module_id) {
 | 
			
		||||
            Ok(tokens) => tokens,
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                self.add_to_diagnostics(¶ms, &[err], true).await;
 | 
			
		||||
@ -298,7 +300,7 @@ impl crate::lsp::backend::Backend for Backend {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Lets update the ast.
 | 
			
		||||
        let result = crate::parser::parse_tokens(tokens.clone());
 | 
			
		||||
        let result = crate::parsing::parse_tokens(tokens.clone());
 | 
			
		||||
        // TODO handle parse errors properly
 | 
			
		||||
        let mut ast = match result.parse_errs_as_err() {
 | 
			
		||||
            Ok(ast) => ast,
 | 
			
		||||
@ -376,7 +378,7 @@ impl Backend {
 | 
			
		||||
        self.executor_ctx.read().await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn update_semantic_tokens(&self, tokens: &[crate::token::Token], params: &TextDocumentItem) {
 | 
			
		||||
    async fn update_semantic_tokens(&self, tokens: &[crate::parsing::token::Token], params: &TextDocumentItem) {
 | 
			
		||||
        // Update the semantic tokens map.
 | 
			
		||||
        let mut semantic_tokens = vec![];
 | 
			
		||||
        let mut last_position = Position::new(0, 0);
 | 
			
		||||
@ -1036,7 +1038,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match hover {
 | 
			
		||||
            crate::ast::types::Hover::Function { name, range } => {
 | 
			
		||||
            crate::parsing::ast::types::Hover::Function { name, range } => {
 | 
			
		||||
                // Get the docs for this function.
 | 
			
		||||
                let Some(completion) = self.stdlib_completions.get(&name) else {
 | 
			
		||||
                    return Ok(None);
 | 
			
		||||
@ -1071,8 +1073,8 @@ impl LanguageServer for Backend {
 | 
			
		||||
                    range: Some(range),
 | 
			
		||||
                }))
 | 
			
		||||
            }
 | 
			
		||||
            crate::ast::types::Hover::Signature { .. } => Ok(None),
 | 
			
		||||
            crate::ast::types::Hover::Comment { value, range } => Ok(Some(Hover {
 | 
			
		||||
            crate::parsing::ast::types::Hover::Signature { .. } => Ok(None),
 | 
			
		||||
            crate::parsing::ast::types::Hover::Comment { value, range } => Ok(Some(Hover {
 | 
			
		||||
                contents: HoverContents::Markup(MarkupContent {
 | 
			
		||||
                    kind: MarkupKind::Markdown,
 | 
			
		||||
                    value,
 | 
			
		||||
@ -1230,7 +1232,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match hover {
 | 
			
		||||
            crate::ast::types::Hover::Function { name, range: _ } => {
 | 
			
		||||
            crate::parsing::ast::types::Hover::Function { name, range: _ } => {
 | 
			
		||||
                // Get the docs for this function.
 | 
			
		||||
                let Some(signature) = self.stdlib_signatures.get(&name) else {
 | 
			
		||||
                    return Ok(None);
 | 
			
		||||
@ -1238,7 +1240,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
 | 
			
		||||
                Ok(Some(signature.clone()))
 | 
			
		||||
            }
 | 
			
		||||
            crate::ast::types::Hover::Signature {
 | 
			
		||||
            crate::parsing::ast::types::Hover::Signature {
 | 
			
		||||
                name,
 | 
			
		||||
                parameter_index,
 | 
			
		||||
                range: _,
 | 
			
		||||
@ -1253,7 +1255,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
 | 
			
		||||
                Ok(Some(signature))
 | 
			
		||||
            }
 | 
			
		||||
            crate::ast::types::Hover::Comment { value: _, range: _ } => {
 | 
			
		||||
            crate::parsing::ast::types::Hover::Comment { value: _, range: _ } => {
 | 
			
		||||
                return Ok(None);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -1302,12 +1304,12 @@ impl LanguageServer for Backend {
 | 
			
		||||
        // I don't know if we need to do this again since it should be updated in the context.
 | 
			
		||||
        // But I figure better safe than sorry since this will write back out to the file.
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let Ok(ast) = crate::parser::parse_str(current_code, module_id).parse_errs_as_err() else {
 | 
			
		||||
        let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
        // Now recast it.
 | 
			
		||||
        let recast = ast.recast(
 | 
			
		||||
            &crate::ast::types::FormatOptions {
 | 
			
		||||
            &crate::parsing::ast::types::FormatOptions {
 | 
			
		||||
                tab_size: params.options.tab_size as usize,
 | 
			
		||||
                insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
 | 
			
		||||
                use_tabs: !params.options.insert_spaces,
 | 
			
		||||
@ -1336,7 +1338,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
        // I don't know if we need to do this again since it should be updated in the context.
 | 
			
		||||
        // But I figure better safe than sorry since this will write back out to the file.
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let Ok(mut ast) = crate::parser::parse_str(current_code, module_id).parse_errs_as_err() else {
 | 
			
		||||
        let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,9 +7,9 @@ use tower_lsp::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{Node, Program},
 | 
			
		||||
    executor::ProgramMemory,
 | 
			
		||||
    lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
 | 
			
		||||
    parsing::ast::types::{Node, Program},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[tokio::test(flavor = "multi_thread", worker_threads = 12)]
 | 
			
		||||
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    macro_rules! parse_and_lex {
 | 
			
		||||
        ($func_name:ident, $test_kcl_program:expr) => {
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn $func_name() {
 | 
			
		||||
                let _ = crate::parser::top_level_parse($test_kcl_program);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parse_and_lex!(crash_eof_1, "{\"ގގ\0\0\0\"\".");
 | 
			
		||||
    parse_and_lex!(crash_eof_2, "(/=e\"\u{616}ݝ\"\"");
 | 
			
		||||
}
 | 
			
		||||
@ -1,9 +1,10 @@
 | 
			
		||||
use sha2::{Digest as DigestTrait, Sha256};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
use super::types::{ItemVisibility, VariableKind};
 | 
			
		||||
use crate::parsing::ast::types::{
 | 
			
		||||
    ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
 | 
			
		||||
    ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
 | 
			
		||||
    ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode,
 | 
			
		||||
    CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression,
 | 
			
		||||
    ImportItem, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode,
 | 
			
		||||
    NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program,
 | 
			
		||||
    ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
 | 
			
		||||
};
 | 
			
		||||
@ -209,6 +210,15 @@ impl ReturnStatement {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CommentStyle {
 | 
			
		||||
    fn digestable_id(&self) -> [u8; 2] {
 | 
			
		||||
        match &self {
 | 
			
		||||
            CommentStyle::Line => *b"//",
 | 
			
		||||
            CommentStyle::Block => *b"/*",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NonCodeNode {
 | 
			
		||||
    compute_digest!(|slf, hasher| {
 | 
			
		||||
        match &slf.value {
 | 
			
		||||
@ -264,6 +274,24 @@ impl VariableDeclaration {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VariableKind {
 | 
			
		||||
    fn digestable_id(&self) -> [u8; 1] {
 | 
			
		||||
        match self {
 | 
			
		||||
            VariableKind::Const => [2],
 | 
			
		||||
            VariableKind::Fn => [3],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ItemVisibility {
 | 
			
		||||
    fn digestable_id(&self) -> [u8; 1] {
 | 
			
		||||
        match self {
 | 
			
		||||
            ItemVisibility::Default => [0],
 | 
			
		||||
            ItemVisibility::Export => [1],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VariableDeclarator {
 | 
			
		||||
    compute_digest!(|slf, hasher| {
 | 
			
		||||
        hasher.update(slf.id.compute_digest());
 | 
			
		||||
@ -406,3 +434,31 @@ impl ElseIf {
 | 
			
		||||
        hasher.update(slf.then_val.compute_digest());
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_digest() {
 | 
			
		||||
        let prog1_string = r#"startSketchOn('XY')
 | 
			
		||||
    |> startProfileAt([0, 0], %)
 | 
			
		||||
    |> line([5, 5], %)
 | 
			
		||||
"#;
 | 
			
		||||
        let prog1_digest = crate::parsing::top_level_parse(prog1_string).unwrap().compute_digest();
 | 
			
		||||
 | 
			
		||||
        let prog2_string = r#"startSketchOn('XY')
 | 
			
		||||
    |> startProfileAt([0, 2], %)
 | 
			
		||||
    |> line([5, 5], %)
 | 
			
		||||
"#;
 | 
			
		||||
        let prog2_digest = crate::parsing::top_level_parse(prog2_string).unwrap().compute_digest();
 | 
			
		||||
 | 
			
		||||
        assert!(prog1_digest != prog2_digest);
 | 
			
		||||
 | 
			
		||||
        let prog3_string = r#"startSketchOn('XY')
 | 
			
		||||
    |> startProfileAt([0, 0], %)
 | 
			
		||||
    |> line([5, 5], %)
 | 
			
		||||
"#;
 | 
			
		||||
        let prog3_digest = crate::parsing::top_level_parse(prog3_string).unwrap().compute_digest();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(prog1_digest, prog3_digest);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,14 +2,14 @@ use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use async_recursion::async_recursion;
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
 | 
			
		||||
    CallExpressionKw, Expr, IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
 | 
			
		||||
    Node, ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
 | 
			
		||||
};
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier},
 | 
			
		||||
    parsing::ast::types::{
 | 
			
		||||
        ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
 | 
			
		||||
        CallExpressionKw, Expr, IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
 | 
			
		||||
        Node, ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
 | 
			
		||||
    },
 | 
			
		||||
    source_range::SourceRange,
 | 
			
		||||
    std::{args::Arg, FunctionKind},
 | 
			
		||||
};
 | 
			
		||||
@ -358,8 +358,47 @@ async fn inner_execute_pipe_body(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Node<CallExpressionKw> {
 | 
			
		||||
    pub async fn execute(&self, _exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
 | 
			
		||||
        todo!()
 | 
			
		||||
    #[async_recursion]
 | 
			
		||||
    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
 | 
			
		||||
        let fn_name = &self.callee.name;
 | 
			
		||||
 | 
			
		||||
        // Build a hashmap from argument labels to the final evaluated values.
 | 
			
		||||
        let mut fn_args = HashMap::with_capacity(self.arguments.len());
 | 
			
		||||
        for arg_expr in &self.arguments {
 | 
			
		||||
            let source_range = SourceRange::from(arg_expr.arg.clone());
 | 
			
		||||
            let metadata = Metadata { source_range };
 | 
			
		||||
            let value = ctx
 | 
			
		||||
                .execute_expr(&arg_expr.arg, exec_state, &metadata, StatementKind::Expression)
 | 
			
		||||
                .await?;
 | 
			
		||||
            fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
 | 
			
		||||
        }
 | 
			
		||||
        let fn_args = fn_args; // remove mutability
 | 
			
		||||
 | 
			
		||||
        // Evaluate the unlabeled first param, if any exists.
 | 
			
		||||
        let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
 | 
			
		||||
            let source_range = SourceRange::from(arg_expr.clone());
 | 
			
		||||
            let metadata = Metadata { source_range };
 | 
			
		||||
            let value = ctx
 | 
			
		||||
                .execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
 | 
			
		||||
                .await?;
 | 
			
		||||
            Some(Arg::new(value, source_range))
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let args = crate::std::Args::new_kw(fn_args, unlabeled, self.into(), ctx.clone());
 | 
			
		||||
        match ctx.stdlib.get_either(fn_name) {
 | 
			
		||||
            FunctionKind::Core(func) => {
 | 
			
		||||
                // Attempt to call the function.
 | 
			
		||||
                let mut result = func.std_lib_fn()(exec_state, args).await?;
 | 
			
		||||
                update_memory_for_tags_of_geometry(&mut result, exec_state)?;
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            FunctionKind::UserDefined => {
 | 
			
		||||
                todo!("Part of modeling-app#4600: Support keyword arguments for user-defined functions")
 | 
			
		||||
            }
 | 
			
		||||
            FunctionKind::Std(_) => todo!("There is no KCL std anymore, it's all core."),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -381,76 +420,12 @@ impl Node<CallExpression> {
 | 
			
		||||
            fn_args.push(arg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        match ctx.stdlib.get_either(&self.callee.name) {
 | 
			
		||||
        match ctx.stdlib.get_either(fn_name) {
 | 
			
		||||
            FunctionKind::Core(func) => {
 | 
			
		||||
                // Attempt to call the function.
 | 
			
		||||
                let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
 | 
			
		||||
                let mut result = func.std_lib_fn()(exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
                // If the return result is a sketch or solid, we want to update the
 | 
			
		||||
                // 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
 | 
			
		||||
                // and it works.
 | 
			
		||||
                match result {
 | 
			
		||||
                    KclValue::Sketch { value: ref mut sketch } => {
 | 
			
		||||
                        for (_, tag) in sketch.tags.iter() {
 | 
			
		||||
                            exec_state.memory.update_tag(&tag.value, tag.clone())?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    KclValue::Solid(ref mut solid) => {
 | 
			
		||||
                        for value in &solid.value {
 | 
			
		||||
                            if let Some(tag) = value.get_tag() {
 | 
			
		||||
                                // Get the past tag and update it.
 | 
			
		||||
                                let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) {
 | 
			
		||||
                                    t.clone()
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    // It's probably a fillet or a chamfer.
 | 
			
		||||
                                    // Initialize it.
 | 
			
		||||
                                    TagIdentifier {
 | 
			
		||||
                                        value: tag.name.clone(),
 | 
			
		||||
                                        info: Some(TagEngineInfo {
 | 
			
		||||
                                            id: value.get_id(),
 | 
			
		||||
                                            surface: Some(value.clone()),
 | 
			
		||||
                                            path: None,
 | 
			
		||||
                                            sketch: solid.id,
 | 
			
		||||
                                        }),
 | 
			
		||||
                                        meta: vec![Metadata {
 | 
			
		||||
                                            source_range: tag.clone().into(),
 | 
			
		||||
                                        }],
 | 
			
		||||
                                    }
 | 
			
		||||
                                };
 | 
			
		||||
 | 
			
		||||
                                let Some(ref info) = t.info else {
 | 
			
		||||
                                    return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                                        message: format!("Tag {} does not have path info", tag.name),
 | 
			
		||||
                                        source_ranges: vec![tag.into()],
 | 
			
		||||
                                    }));
 | 
			
		||||
                                };
 | 
			
		||||
 | 
			
		||||
                                let mut info = info.clone();
 | 
			
		||||
                                info.surface = Some(value.clone());
 | 
			
		||||
                                info.sketch = solid.id;
 | 
			
		||||
                                t.info = Some(info);
 | 
			
		||||
 | 
			
		||||
                                exec_state.memory.update_tag(&tag.name, t.clone())?;
 | 
			
		||||
 | 
			
		||||
                                // update the sketch tags.
 | 
			
		||||
                                solid.sketch.tags.insert(tag.name.clone(), t);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Find the stale sketch in memory and update it.
 | 
			
		||||
                        if let Some(current_env) = exec_state
 | 
			
		||||
                            .memory
 | 
			
		||||
                            .environments
 | 
			
		||||
                            .get_mut(exec_state.memory.current_env.index())
 | 
			
		||||
                        {
 | 
			
		||||
                            current_env.update_sketch_tags(&solid.sketch);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    _ => {}
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                update_memory_for_tags_of_geometry(&mut result, exec_state)?;
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            FunctionKind::Std(func) => {
 | 
			
		||||
@ -570,6 +545,73 @@ impl Node<CallExpression> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
 | 
			
		||||
    // If the return result is a sketch or solid, we want to update the
 | 
			
		||||
    // 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
 | 
			
		||||
    // and it works.
 | 
			
		||||
    match result {
 | 
			
		||||
        KclValue::Sketch { value: ref mut sketch } => {
 | 
			
		||||
            for (_, tag) in sketch.tags.iter() {
 | 
			
		||||
                exec_state.memory.update_tag(&tag.value, tag.clone())?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        KclValue::Solid(ref mut solid) => {
 | 
			
		||||
            for value in &solid.value {
 | 
			
		||||
                if let Some(tag) = value.get_tag() {
 | 
			
		||||
                    // Get the past tag and update it.
 | 
			
		||||
                    let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) {
 | 
			
		||||
                        t.clone()
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // It's probably a fillet or a chamfer.
 | 
			
		||||
                        // Initialize it.
 | 
			
		||||
                        TagIdentifier {
 | 
			
		||||
                            value: tag.name.clone(),
 | 
			
		||||
                            info: Some(TagEngineInfo {
 | 
			
		||||
                                id: value.get_id(),
 | 
			
		||||
                                surface: Some(value.clone()),
 | 
			
		||||
                                path: None,
 | 
			
		||||
                                sketch: solid.id,
 | 
			
		||||
                            }),
 | 
			
		||||
                            meta: vec![Metadata {
 | 
			
		||||
                                source_range: tag.clone().into(),
 | 
			
		||||
                            }],
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let Some(ref info) = t.info else {
 | 
			
		||||
                        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                            message: format!("Tag {} does not have path info", tag.name),
 | 
			
		||||
                            source_ranges: vec![tag.into()],
 | 
			
		||||
                        }));
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let mut info = info.clone();
 | 
			
		||||
                    info.surface = Some(value.clone());
 | 
			
		||||
                    info.sketch = solid.id;
 | 
			
		||||
                    t.info = Some(info);
 | 
			
		||||
 | 
			
		||||
                    exec_state.memory.update_tag(&tag.name, t.clone())?;
 | 
			
		||||
 | 
			
		||||
                    // update the sketch tags.
 | 
			
		||||
                    solid.sketch.tags.insert(tag.name.clone(), t);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Find the stale sketch in memory and update it.
 | 
			
		||||
            if let Some(current_env) = exec_state
 | 
			
		||||
                .memory
 | 
			
		||||
                .environments
 | 
			
		||||
                .get_mut(exec_state.memory.current_env.index())
 | 
			
		||||
            {
 | 
			
		||||
                current_env.update_sketch_tags(&solid.sketch);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        _ => {}
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Node<TagDeclarator> {
 | 
			
		||||
    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
 | 
			
		||||
        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
 | 
			
		||||
							
								
								
									
										5
									
								
								src/wasm-lib/kcl/src/parsing/ast/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,5 @@
 | 
			
		||||
pub(crate) mod digest;
 | 
			
		||||
pub(crate) mod execute;
 | 
			
		||||
pub mod modify;
 | 
			
		||||
pub(crate) mod source_range;
 | 
			
		||||
pub mod types;
 | 
			
		||||
@ -7,13 +7,13 @@ use kcmc::{
 | 
			
		||||
use kittycad_modeling_cmds as kcmc;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{
 | 
			
		||||
        ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
 | 
			
		||||
        PipeSubstitution, VariableDeclarator,
 | 
			
		||||
    },
 | 
			
		||||
    engine::EngineManager,
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::Point2d,
 | 
			
		||||
    parsing::ast::types::{
 | 
			
		||||
        ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
 | 
			
		||||
        PipeSubstitution, VariableDeclarator,
 | 
			
		||||
    },
 | 
			
		||||
    source_range::{ModuleId, SourceRange},
 | 
			
		||||
    Program,
 | 
			
		||||
};
 | 
			
		||||
@ -184,7 +184,7 @@ pub async fn modify_ast_for_sketch(
 | 
			
		||||
    let recasted = program.ast.recast(&FormatOptions::default(), 0);
 | 
			
		||||
 | 
			
		||||
    // Re-parse the ast so we get the correct source ranges.
 | 
			
		||||
    *program = crate::parser::parse_str(&recasted, module_id)
 | 
			
		||||
    *program = crate::parsing::parse_str(&recasted, module_id)
 | 
			
		||||
        .parse_errs_as_err()?
 | 
			
		||||
        .into();
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
use super::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject};
 | 
			
		||||
use crate::parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject};
 | 
			
		||||
use crate::source_range::ModuleId;
 | 
			
		||||
 | 
			
		||||
impl BodyItem {
 | 
			
		||||
@ -8,7 +8,7 @@ use crate::SourceRange;
 | 
			
		||||
// TODO: This should be its own type, similar to Program,
 | 
			
		||||
// but guaranteed to have an Expression as its final item.
 | 
			
		||||
// https://github.com/KittyCAD/modeling-app/issues/4015
 | 
			
		||||
type IfBlock = crate::ast::types::Program;
 | 
			
		||||
type IfBlock = crate::parsing::ast::types::Program;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
 | 
			
		||||
#[databake(path = kcl_lib::ast::types)]
 | 
			
		||||
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::Value as JValue;
 | 
			
		||||
 | 
			
		||||
use super::Node;
 | 
			
		||||
use crate::ast::types::{Expr, Literal};
 | 
			
		||||
use crate::parsing::ast::types::{Expr, Literal};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
 | 
			
		||||
#[databake(path = kcl_lib::ast::types)]
 | 
			
		||||
@ -18,8 +18,8 @@ use tower_lsp::lsp_types::{
 | 
			
		||||
    CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use self::execute::execute_pipe_body;
 | 
			
		||||
pub use crate::ast::types::{
 | 
			
		||||
use super::{digest::Digest, execute::execute_pipe_body};
 | 
			
		||||
pub use crate::parsing::ast::types::{
 | 
			
		||||
    condition::{ElseIf, IfExpression},
 | 
			
		||||
    literal_value::LiteralValue,
 | 
			
		||||
    none::KclNone,
 | 
			
		||||
@ -28,19 +28,14 @@ use crate::{
 | 
			
		||||
    docs::StdLibFn,
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    executor::{ExecState, ExecutorContext, KclValue, Metadata, TagIdentifier},
 | 
			
		||||
    parser::PIPE_OPERATOR,
 | 
			
		||||
    parsing::PIPE_OPERATOR,
 | 
			
		||||
    source_range::{ModuleId, SourceRange},
 | 
			
		||||
    std::kcl_stdlib::KclStdLibFn,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
mod condition;
 | 
			
		||||
pub(crate) mod digest;
 | 
			
		||||
pub(crate) mod execute;
 | 
			
		||||
mod literal_value;
 | 
			
		||||
mod none;
 | 
			
		||||
pub(crate) mod source_range;
 | 
			
		||||
 | 
			
		||||
use digest::Digest;
 | 
			
		||||
 | 
			
		||||
pub enum Definition<'a> {
 | 
			
		||||
    Variable(&'a VariableDeclarator),
 | 
			
		||||
@ -1029,15 +1024,6 @@ pub enum CommentStyle {
 | 
			
		||||
    Block,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CommentStyle {
 | 
			
		||||
    fn digestable_id(&self) -> [u8; 2] {
 | 
			
		||||
        match &self {
 | 
			
		||||
            CommentStyle::Line => *b"//",
 | 
			
		||||
            CommentStyle::Block => *b"/*",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
 | 
			
		||||
#[databake(path = kcl_lib::ast::types)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
@ -1502,13 +1488,6 @@ pub enum ItemVisibility {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ItemVisibility {
 | 
			
		||||
    fn digestable_id(&self) -> [u8; 1] {
 | 
			
		||||
        match self {
 | 
			
		||||
            ItemVisibility::Default => [0],
 | 
			
		||||
            ItemVisibility::Export => [1],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_default(&self) -> bool {
 | 
			
		||||
        matches!(self, Self::Default)
 | 
			
		||||
    }
 | 
			
		||||
@ -1732,13 +1711,6 @@ pub enum VariableKind {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VariableKind {
 | 
			
		||||
    fn digestable_id(&self) -> [u8; 1] {
 | 
			
		||||
        match self {
 | 
			
		||||
            VariableKind::Const => [2],
 | 
			
		||||
            VariableKind::Fn => [3],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_completion_items() -> Result<Vec<CompletionItem>> {
 | 
			
		||||
        let mut settings = schemars::gen::SchemaSettings::openapi3();
 | 
			
		||||
        settings.inline_subschemas = true;
 | 
			
		||||
@ -3158,7 +3130,7 @@ fn ghi = (x) => {
 | 
			
		||||
 | 
			
		||||
ghi("things")
 | 
			
		||||
"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
        let folding_ranges = program.get_lsp_folding_ranges();
 | 
			
		||||
        assert_eq!(folding_ranges.len(), 3);
 | 
			
		||||
        assert_eq!(folding_ranges[0].start_line, 29);
 | 
			
		||||
@ -3194,7 +3166,7 @@ fn ghi = (x) => {
 | 
			
		||||
  return x
 | 
			
		||||
}
 | 
			
		||||
"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
        let symbols = program.get_lsp_symbols(code).unwrap();
 | 
			
		||||
        assert_eq!(symbols.len(), 7);
 | 
			
		||||
    }
 | 
			
		||||
@ -3214,7 +3186,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
     }, %)
 | 
			
		||||
  |> extrude(h, %)
 | 
			
		||||
"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        let value = program.get_non_code_meta_for_position(50);
 | 
			
		||||
 | 
			
		||||
@ -3237,7 +3209,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
     }, %)
 | 
			
		||||
  |> extrude(h, %)
 | 
			
		||||
"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        let value = program.get_non_code_meta_for_position(124);
 | 
			
		||||
 | 
			
		||||
@ -3250,7 +3222,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
  |> startProfileAt([0,0], %)
 | 
			
		||||
  |> xLine(5, %) // lin
 | 
			
		||||
"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        let value = program.get_non_code_meta_for_position(86);
 | 
			
		||||
 | 
			
		||||
@ -3262,7 +3234,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
        let some_program_string = r#"fn thing = (arg0: number, arg1: string, tag?: string) => {
 | 
			
		||||
    return arg0
 | 
			
		||||
}"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        // Check the program output for the types of the parameters.
 | 
			
		||||
        let function = program.body.first().unwrap();
 | 
			
		||||
@ -3284,7 +3256,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
        let some_program_string = r#"fn thing = (arg0: number[], arg1: string[], tag?: string) => {
 | 
			
		||||
    return arg0
 | 
			
		||||
}"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        // Check the program output for the types of the parameters.
 | 
			
		||||
        let function = program.body.first().unwrap();
 | 
			
		||||
@ -3307,7 +3279,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
    return arg0
 | 
			
		||||
}"#;
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let program = crate::parser::parse_str(some_program_string, module_id).unwrap();
 | 
			
		||||
        let program = crate::parsing::parse_str(some_program_string, module_id).unwrap();
 | 
			
		||||
 | 
			
		||||
        // Check the program output for the types of the parameters.
 | 
			
		||||
        let function = program.body.first().unwrap();
 | 
			
		||||
@ -3378,7 +3350,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
    return 1
 | 
			
		||||
}"#;
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let program = crate::parser::parse_str(some_program_string, module_id).unwrap();
 | 
			
		||||
        let program = crate::parsing::parse_str(some_program_string, module_id).unwrap();
 | 
			
		||||
 | 
			
		||||
        // Check the program output for the types of the parameters.
 | 
			
		||||
        let function = program.body.first().unwrap();
 | 
			
		||||
@ -3566,7 +3538,7 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_object_bool() {
 | 
			
		||||
        let some_program_string = r#"some_func({thing: true, other_thing: false})"#;
 | 
			
		||||
        let program = crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
 | 
			
		||||
        // We want to get the bool and verify it is a bool.
 | 
			
		||||
 | 
			
		||||
@ -3607,29 +3579,4 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
 | 
			
		||||
        assert_eq!(l.raw, "false");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_digest() {
 | 
			
		||||
        let prog1_string = r#"startSketchOn('XY')
 | 
			
		||||
    |> startProfileAt([0, 0], %)
 | 
			
		||||
    |> line([5, 5], %)
 | 
			
		||||
"#;
 | 
			
		||||
        let prog1_digest = crate::parser::top_level_parse(prog1_string).unwrap().compute_digest();
 | 
			
		||||
 | 
			
		||||
        let prog2_string = r#"startSketchOn('XY')
 | 
			
		||||
    |> startProfileAt([0, 2], %)
 | 
			
		||||
    |> line([5, 5], %)
 | 
			
		||||
"#;
 | 
			
		||||
        let prog2_digest = crate::parser::top_level_parse(prog2_string).unwrap().compute_digest();
 | 
			
		||||
 | 
			
		||||
        assert!(prog1_digest != prog2_digest);
 | 
			
		||||
 | 
			
		||||
        let prog3_string = r#"startSketchOn('XY')
 | 
			
		||||
    |> startProfileAt([0, 0], %)
 | 
			
		||||
    |> line([5, 5], %)
 | 
			
		||||
"#;
 | 
			
		||||
        let prog3_digest = crate::parser::top_level_parse(prog3_string).unwrap().compute_digest();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(prog1_digest, prog3_digest);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -5,7 +5,7 @@ use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use super::Node;
 | 
			
		||||
use crate::{ast::types::ConstraintLevel, executor::KclValue};
 | 
			
		||||
use crate::{executor::KclValue, parsing::ast::types::ConstraintLevel};
 | 
			
		||||
 | 
			
		||||
const KCL_NONE_ID: &str = "KCL_NONE_ID";
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ use winnow::{error::StrContext, stream::Stream};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    token::Token,
 | 
			
		||||
    parsing::token::Token,
 | 
			
		||||
    SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{BinaryExpression, BinaryOperator, BinaryPart, Node},
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    parsing::ast::types::{BinaryExpression, BinaryOperator, BinaryPart, Node},
 | 
			
		||||
    SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -126,7 +126,8 @@ impl From<BinaryOperator> for BinaryExpressionToken {
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{ast::types::Literal, source_range::ModuleId};
 | 
			
		||||
    use crate::parsing::ast::types::Literal;
 | 
			
		||||
    use crate::source_range::ModuleId;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parse_and_evaluate() {
 | 
			
		||||
@ -1,15 +1,19 @@
 | 
			
		||||
use parser_impl::ParseContext;
 | 
			
		||||
use parser::ParseContext;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{Node, Program},
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    parsing::{
 | 
			
		||||
        ast::types::{Node, Program},
 | 
			
		||||
        token::{Token, TokenType},
 | 
			
		||||
    },
 | 
			
		||||
    source_range::{ModuleId, SourceRange},
 | 
			
		||||
    token::{Token, TokenType},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
mod bad_inputs;
 | 
			
		||||
pub(crate) mod ast;
 | 
			
		||||
mod error;
 | 
			
		||||
mod math;
 | 
			
		||||
pub(crate) mod parser_impl;
 | 
			
		||||
pub(crate) mod parser;
 | 
			
		||||
pub(crate) mod token;
 | 
			
		||||
 | 
			
		||||
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
 | 
			
		||||
pub const PIPE_OPERATOR: &str = "|>";
 | 
			
		||||
@ -33,7 +37,7 @@ pub fn top_level_parse(code: &str) -> ParseResult {
 | 
			
		||||
 | 
			
		||||
/// Parse the given KCL code into an AST.
 | 
			
		||||
pub fn parse_str(code: &str, module_id: ModuleId) -> ParseResult {
 | 
			
		||||
    let tokens = pr_try!(crate::token::lexer(code, module_id));
 | 
			
		||||
    let tokens = pr_try!(crate::parsing::token::lexer(code, module_id));
 | 
			
		||||
    parse_tokens(tokens)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -68,7 +72,7 @@ pub fn parse_tokens(tokens: Vec<Token>) -> ParseResult {
 | 
			
		||||
        return Node::<Program>::default().into();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parser_impl::run_parser(&mut tokens.as_slice())
 | 
			
		||||
    parser::run_parser(&mut tokens.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Result of parsing.
 | 
			
		||||
@ -104,7 +108,7 @@ impl ParseResult {
 | 
			
		||||
 | 
			
		||||
    #[cfg(test)]
 | 
			
		||||
    #[track_caller]
 | 
			
		||||
    pub fn unwrap_errs(&self) -> &[parser_impl::error::ParseError] {
 | 
			
		||||
    pub fn unwrap_errs(&self) -> &[error::ParseError] {
 | 
			
		||||
        &self.0.as_ref().unwrap().1.errors
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -145,3 +149,18 @@ impl From<KclError> for ParseResult {
 | 
			
		||||
        ParseResult(Err(e))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    macro_rules! parse_and_lex {
 | 
			
		||||
        ($func_name:ident, $test_kcl_program:expr) => {
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn $func_name() {
 | 
			
		||||
                let _ = crate::parsing::top_level_parse($test_kcl_program);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parse_and_lex!(crash_eof_1, "{\"ގގ\0\0\0\"\".");
 | 
			
		||||
    parse_and_lex!(crash_eof_2, "(/=e\"\u{616}ݝ\"\"");
 | 
			
		||||
}
 | 
			
		||||
@ -8,27 +8,29 @@ use winnow::{
 | 
			
		||||
    token::{any, one_of, take_till},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use self::error::ParseError;
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{
 | 
			
		||||
        ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, BoxNode,
 | 
			
		||||
        CallExpression, CallExpressionKw, CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType,
 | 
			
		||||
        FunctionExpression, Identifier, IfExpression, ImportItem, ImportStatement, ItemVisibility, LabeledArg, Literal,
 | 
			
		||||
        LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NonCodeMeta, NonCodeNode, NonCodeValue,
 | 
			
		||||
        ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
 | 
			
		||||
        Shebang, TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
 | 
			
		||||
    },
 | 
			
		||||
    docs::StdLibFn,
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    parser::{
 | 
			
		||||
        math::BinaryExpressionToken, parser_impl::error::ContextError, PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
 | 
			
		||||
    parsing::{
 | 
			
		||||
        ast::types::{
 | 
			
		||||
            ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, BoxNode,
 | 
			
		||||
            CallExpression, CallExpressionKw, CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive,
 | 
			
		||||
            FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ImportStatement, ItemVisibility,
 | 
			
		||||
            Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NonCodeMeta, NonCodeNode,
 | 
			
		||||
            NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program,
 | 
			
		||||
            ReturnStatement, Shebang, TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration,
 | 
			
		||||
            VariableDeclarator, VariableKind,
 | 
			
		||||
        },
 | 
			
		||||
        error::{self, ContextError, ParseError},
 | 
			
		||||
        math::BinaryExpressionToken,
 | 
			
		||||
        token::{Token, TokenType},
 | 
			
		||||
        PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
 | 
			
		||||
    },
 | 
			
		||||
    token::{Token, TokenType},
 | 
			
		||||
    unparser::ExprContext,
 | 
			
		||||
    SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub(crate) mod error;
 | 
			
		||||
use super::ast::types::LabeledArg;
 | 
			
		||||
 | 
			
		||||
thread_local! {
 | 
			
		||||
    /// The current `ParseContext`. `None` if parsing is not currently happening on this thread.
 | 
			
		||||
@ -2288,14 +2290,14 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{
 | 
			
		||||
        ast::types::{BodyItem, Expr, VariableKind},
 | 
			
		||||
        parsing::ast::types::{BodyItem, Expr, VariableKind},
 | 
			
		||||
        ModuleId,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fn assert_reserved(word: &str) {
 | 
			
		||||
        // Try to use it as a variable name.
 | 
			
		||||
        let code = format!(r#"{} = 0"#, word);
 | 
			
		||||
        let result = crate::parser::top_level_parse(code.as_str());
 | 
			
		||||
        let result = crate::parsing::top_level_parse(code.as_str());
 | 
			
		||||
        let err = &result.unwrap_errs()[0];
 | 
			
		||||
        // Which token causes the error may change.  In "return = 0", for
 | 
			
		||||
        // example, "return" is the problem.
 | 
			
		||||
@ -2313,7 +2315,7 @@ mod tests {
 | 
			
		||||
    fn reserved_words() {
 | 
			
		||||
        // Since these are stored in a set, we sort to make the tests
 | 
			
		||||
        // deterministic.
 | 
			
		||||
        for word in crate::token::RESERVED_WORDS.keys().sorted() {
 | 
			
		||||
        for word in crate::parsing::token::RESERVED_WORDS.keys().sorted() {
 | 
			
		||||
            assert_reserved(word);
 | 
			
		||||
        }
 | 
			
		||||
        assert_reserved("import");
 | 
			
		||||
@ -2322,7 +2324,7 @@ mod tests {
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parse_args() {
 | 
			
		||||
        for (i, (test, expected_len)) in [("someVar", 1), ("5, 3", 2), (r#""a""#, 1)].into_iter().enumerate() {
 | 
			
		||||
            let tokens = crate::token::lexer(test, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test, ModuleId::default()).unwrap();
 | 
			
		||||
            let actual = match arguments.parse(&tokens) {
 | 
			
		||||
                Ok(x) => x,
 | 
			
		||||
                Err(e) => panic!("Failed test {i}, could not parse function arguments from \"{test}\": {e:?}"),
 | 
			
		||||
@ -2333,7 +2335,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn weird_program_unclosed_paren() {
 | 
			
		||||
        let tokens = crate::token::lexer("fn firstPrime(", ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer("fn firstPrime(", ModuleId::default()).unwrap();
 | 
			
		||||
        let last = tokens.last().unwrap();
 | 
			
		||||
        let err: super::error::ErrorKind = program.parse(&tokens).unwrap_err().into();
 | 
			
		||||
        let err = err.unwrap_parse_error();
 | 
			
		||||
@ -2345,7 +2347,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn weird_program_just_a_pipe() {
 | 
			
		||||
        let tokens = crate::token::lexer("|", ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer("|", ModuleId::default()).unwrap();
 | 
			
		||||
        let err: super::error::ErrorKind = program.parse(&tokens).unwrap_err().into();
 | 
			
		||||
        let err = err.unwrap_parse_error();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
@ -2358,7 +2360,7 @@ mod tests {
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parse_binary_expressions() {
 | 
			
		||||
        for (i, test_program) in ["1 + 2 + 3"].into_iter().enumerate() {
 | 
			
		||||
            let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
            let mut slice = tokens.as_slice();
 | 
			
		||||
            let _actual = match binary_expression.parse_next(&mut slice) {
 | 
			
		||||
                Ok(x) => x,
 | 
			
		||||
@ -2369,7 +2371,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_vardec_no_keyword() {
 | 
			
		||||
        let tokens = crate::token::lexer("x = 4", ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer("x = 4", ModuleId::default()).unwrap();
 | 
			
		||||
        let vardec = declaration(&mut tokens.as_slice()).unwrap();
 | 
			
		||||
        assert_eq!(vardec.inner.kind, VariableKind::Const);
 | 
			
		||||
        let vardec = vardec.declarations.first().unwrap();
 | 
			
		||||
@ -2382,7 +2384,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_negative_operands() {
 | 
			
		||||
        let tokens = crate::token::lexer("-leg2", ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer("-leg2", ModuleId::default()).unwrap();
 | 
			
		||||
        let _s = operand.parse_next(&mut tokens.as_slice()).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -2396,7 +2398,7 @@ mod tests {
 | 
			
		||||
            // comment 2
 | 
			
		||||
            return 1
 | 
			
		||||
        }"#;
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let mut slice = tokens.as_slice();
 | 
			
		||||
        let expr = function_decl.map(|t| t.0).parse_next(&mut slice).unwrap();
 | 
			
		||||
        assert_eq!(expr.params, vec![]);
 | 
			
		||||
@ -2414,7 +2416,7 @@ mod tests {
 | 
			
		||||
  const yo = { a = { b = { c = '123' } } } /* block
 | 
			
		||||
comment */
 | 
			
		||||
}"#;
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let mut slice = tokens.as_slice();
 | 
			
		||||
        let expr = function_decl.map(|t| t.0).parse_next(&mut slice).unwrap();
 | 
			
		||||
        let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
 | 
			
		||||
@ -2427,7 +2429,7 @@ comment */
 | 
			
		||||
/* comment at start */
 | 
			
		||||
 | 
			
		||||
const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let program = program.parse(&tokens).unwrap();
 | 
			
		||||
        let mut starting_comments = program.inner.non_code_meta.start_nodes;
 | 
			
		||||
        assert_eq!(starting_comments.len(), 2);
 | 
			
		||||
@ -2445,7 +2447,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_comment_in_pipe() {
 | 
			
		||||
        let tokens = crate::token::lexer(r#"const x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(r#"const x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
 | 
			
		||||
        let mut body = program.parse(&tokens).unwrap().inner.body;
 | 
			
		||||
        let BodyItem::VariableDeclaration(mut item) = body.remove(0) else {
 | 
			
		||||
            panic!("expected vardec");
 | 
			
		||||
@ -2472,7 +2474,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
            return sg
 | 
			
		||||
            return sg
 | 
			
		||||
          }"#;
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let mut slice = tokens.as_slice();
 | 
			
		||||
        let _expr = function_decl.parse_next(&mut slice).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -2484,7 +2486,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
                return 2
 | 
			
		||||
            }";
 | 
			
		||||
        let module_id = ModuleId::from_usize(1);
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, module_id).unwrap();
 | 
			
		||||
        let mut slice = tokens.as_slice();
 | 
			
		||||
        let expr = function_decl.map(|t| t.0).parse_next(&mut slice).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
@ -2549,7 +2551,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        |> c(%) // inline-comment
 | 
			
		||||
        |> d(%)"#;
 | 
			
		||||
 | 
			
		||||
        let tokens = crate::token::lexer(test_input, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_input, ModuleId::default()).unwrap();
 | 
			
		||||
        let mut slice = tokens.as_slice();
 | 
			
		||||
        let Node {
 | 
			
		||||
            inner: PipeExpression {
 | 
			
		||||
@ -2580,7 +2582,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
"#;
 | 
			
		||||
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, module_id).unwrap();
 | 
			
		||||
        let Program { non_code_meta, .. } = function_body.parse(&tokens).unwrap().inner;
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            vec![Node::new(
 | 
			
		||||
@ -2648,7 +2650,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
  comment */
 | 
			
		||||
  return 1"#;
 | 
			
		||||
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let actual = program.parse(&tokens).unwrap();
 | 
			
		||||
        assert_eq!(actual.non_code_meta.non_code_nodes.len(), 1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
@ -2663,7 +2665,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_bracketed_binary_expression() {
 | 
			
		||||
        let input = "(2 - 3)";
 | 
			
		||||
        let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        let actual = match binary_expr_in_parens.parse(&tokens) {
 | 
			
		||||
            Ok(x) => x,
 | 
			
		||||
            Err(e) => panic!("{e:?}"),
 | 
			
		||||
@ -2678,7 +2680,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
            "6 / ( sigmaAllow * width )",
 | 
			
		||||
            "sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))",
 | 
			
		||||
        ] {
 | 
			
		||||
            let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
            let _actual = match expression.parse(&tokens) {
 | 
			
		||||
                Ok(x) => x,
 | 
			
		||||
                Err(e) => panic!("{e:?}"),
 | 
			
		||||
@ -2689,7 +2691,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_arithmetic() {
 | 
			
		||||
        let input = "1 * (2 - 3)";
 | 
			
		||||
        let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        // The RHS should be a binary expression.
 | 
			
		||||
        let actual = binary_expression.parse(&tokens).unwrap();
 | 
			
		||||
        assert_eq!(actual.operator, BinaryOperator::Mul);
 | 
			
		||||
@ -2717,7 +2719,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .enumerate()
 | 
			
		||||
        {
 | 
			
		||||
            let tokens = crate::token::lexer(test_input, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test_input, ModuleId::default()).unwrap();
 | 
			
		||||
            let mut actual = match declaration.parse(&tokens) {
 | 
			
		||||
                Err(e) => panic!("Could not parse test {i}: {e:#?}"),
 | 
			
		||||
                Ok(a) => a,
 | 
			
		||||
@ -2735,7 +2737,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_function_call() {
 | 
			
		||||
        for (i, test_input) in ["const x = f(1)", "const x = f( 1 )"].into_iter().enumerate() {
 | 
			
		||||
            let tokens = crate::token::lexer(test_input, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test_input, ModuleId::default()).unwrap();
 | 
			
		||||
            let _actual = match declaration.parse(&tokens) {
 | 
			
		||||
                Err(e) => panic!("Could not parse test {i}: {e:#?}"),
 | 
			
		||||
                Ok(a) => a,
 | 
			
		||||
@ -2746,7 +2748,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_nested_arithmetic() {
 | 
			
		||||
        let input = "1 * ((2 - 3) / 4)";
 | 
			
		||||
        let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        // The RHS should be a binary expression.
 | 
			
		||||
        let outer = binary_expression.parse(&tokens).unwrap();
 | 
			
		||||
        assert_eq!(outer.operator, BinaryOperator::Mul);
 | 
			
		||||
@ -2765,7 +2767,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    fn binary_expression_ignores_whitespace() {
 | 
			
		||||
        let tests = ["1 - 2", "1- 2", "1 -2", "1-2"];
 | 
			
		||||
        for test in tests {
 | 
			
		||||
            let tokens = crate::token::lexer(test, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test, ModuleId::default()).unwrap();
 | 
			
		||||
            let actual = binary_expression.parse(&tokens).unwrap();
 | 
			
		||||
            assert_eq!(actual.operator, BinaryOperator::Sub);
 | 
			
		||||
            let BinaryPart::Literal(left) = actual.inner.left else {
 | 
			
		||||
@ -2786,7 +2788,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        a comment
 | 
			
		||||
        spanning a few lines */
 | 
			
		||||
        |> z(%)"#;
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let actual = pipe_expression.parse(&tokens).unwrap();
 | 
			
		||||
        let n = actual.non_code_meta.non_code_nodes.len();
 | 
			
		||||
        assert_eq!(n, 1, "expected one comment in pipe expression but found {n}");
 | 
			
		||||
@ -2814,7 +2816,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .enumerate()
 | 
			
		||||
        {
 | 
			
		||||
            let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
            let actual = pipe_expression.parse(&tokens);
 | 
			
		||||
            assert!(actual.is_ok(), "could not parse test {i}, '{test_program}'");
 | 
			
		||||
            let actual = actual.unwrap();
 | 
			
		||||
@ -2959,7 +2961,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .enumerate()
 | 
			
		||||
        {
 | 
			
		||||
            let tokens = crate::token::lexer(test_program, module_id).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test_program, module_id).unwrap();
 | 
			
		||||
            let actual = non_code_node.parse(&tokens);
 | 
			
		||||
            assert!(actual.is_ok(), "could not parse test {i}: {actual:#?}");
 | 
			
		||||
            let actual = actual.unwrap();
 | 
			
		||||
@ -2971,7 +2973,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    fn recognize_invalid_params() {
 | 
			
		||||
        let test_fn = "(let) => { return 1 }";
 | 
			
		||||
        let module_id = ModuleId::from_usize(2);
 | 
			
		||||
        let tokens = crate::token::lexer(test_fn, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_fn, module_id).unwrap();
 | 
			
		||||
        let err = function_decl.parse(&tokens).unwrap_err().into_inner();
 | 
			
		||||
        let cause = err.cause.unwrap();
 | 
			
		||||
        // This is the token `let`
 | 
			
		||||
@ -2987,7 +2989,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        let string_literal = r#""
 | 
			
		||||
           // a comment
 | 
			
		||||
             ""#;
 | 
			
		||||
        let tokens = crate::token::lexer(string_literal, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(string_literal, ModuleId::default()).unwrap();
 | 
			
		||||
        let parsed_literal = literal.parse(&tokens).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parsed_literal.value,
 | 
			
		||||
@ -3004,7 +3006,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        |> lineTo([0, -0], %) // MoveRelative
 | 
			
		||||
 | 
			
		||||
        "#;
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let mut slice = &tokens[..];
 | 
			
		||||
        let _actual = pipe_expression.parse_next(&mut slice).unwrap();
 | 
			
		||||
        assert_eq!(slice[0].token_type, TokenType::Whitespace);
 | 
			
		||||
@ -3013,14 +3015,14 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_pipes_on_pipes() {
 | 
			
		||||
        let test_program = include_str!("../../../tests/executor/inputs/pipes_on_pipes.kcl");
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let _ = run_parser(&mut &*tokens).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_cube() {
 | 
			
		||||
        let test_program = include_str!("../../../tests/executor/inputs/cube.kcl");
 | 
			
		||||
        let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(test_program, ModuleId::default()).unwrap();
 | 
			
		||||
        match program.parse(&tokens) {
 | 
			
		||||
            Ok(_) => {}
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
@ -3038,7 +3040,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
            ("a,b", vec!["a", "b"]),
 | 
			
		||||
        ];
 | 
			
		||||
        for (i, (input, expected)) in tests.into_iter().enumerate() {
 | 
			
		||||
            let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
            let actual = parameters.parse(&tokens);
 | 
			
		||||
            assert!(actual.is_ok(), "could not parse test {i}");
 | 
			
		||||
            let actual_ids: Vec<_> = actual.unwrap().into_iter().map(|p| p.identifier.inner.name).collect();
 | 
			
		||||
@ -3052,7 +3054,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
            return 2
 | 
			
		||||
        }";
 | 
			
		||||
 | 
			
		||||
        let tokens = crate::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(input, ModuleId::default()).unwrap();
 | 
			
		||||
        let actual = function_decl.parse(&tokens);
 | 
			
		||||
        assert!(actual.is_ok(), "could not parse test function");
 | 
			
		||||
    }
 | 
			
		||||
@ -3062,8 +3064,8 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
        let tests = ["const myVar = 5", "const myVar=5", "const myVar =5", "const myVar= 5"];
 | 
			
		||||
        for test in tests {
 | 
			
		||||
            // Run the original parser
 | 
			
		||||
            let tokens = crate::token::lexer(test, ModuleId::default()).unwrap();
 | 
			
		||||
            let mut expected_body = crate::parser::parse_tokens(tokens.clone()).unwrap().inner.body;
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(test, ModuleId::default()).unwrap();
 | 
			
		||||
            let mut expected_body = crate::parsing::parse_tokens(tokens.clone()).unwrap().inner.body;
 | 
			
		||||
            assert_eq!(expected_body.len(), 1);
 | 
			
		||||
            let BodyItem::VariableDeclaration(expected) = expected_body.pop().unwrap() else {
 | 
			
		||||
                panic!("Expected variable declaration");
 | 
			
		||||
@ -3090,7 +3092,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_math_parse() {
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let actual = crate::parser::parse_str(r#"5 + "a""#, module_id).unwrap().inner.body;
 | 
			
		||||
        let actual = crate::parsing::parse_str(r#"5 + "a""#, module_id).unwrap().inner.body;
 | 
			
		||||
        let expr = Node::boxed(
 | 
			
		||||
            BinaryExpression {
 | 
			
		||||
                operator: BinaryOperator::Add,
 | 
			
		||||
@ -3226,7 +3228,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    fn test_abstract_syntax_tree() {
 | 
			
		||||
        let code = "5 +6";
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let result = crate::parser::parse_str(code, module_id).unwrap();
 | 
			
		||||
        let result = crate::parsing::parse_str(code, module_id).unwrap();
 | 
			
		||||
        let expected_result = Node::new(
 | 
			
		||||
            Program {
 | 
			
		||||
                body: vec![BodyItem::ExpressionStatement(Node::new(
 | 
			
		||||
@ -3281,13 +3283,13 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_empty_file() {
 | 
			
		||||
        let some_program_string = r#""#;
 | 
			
		||||
        let result = crate::parser::top_level_parse(some_program_string);
 | 
			
		||||
        let result = crate::parsing::top_level_parse(some_program_string);
 | 
			
		||||
        assert!(result.is_ok());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[track_caller]
 | 
			
		||||
    fn assert_no_err(p: &str) -> (Node<Program>, ParseContext) {
 | 
			
		||||
        let result = crate::parser::top_level_parse(p);
 | 
			
		||||
        let result = crate::parsing::top_level_parse(p);
 | 
			
		||||
        let result = result.0.unwrap();
 | 
			
		||||
        assert!(result.1.errors.is_empty(), "found: {:#?}", result.1.errors);
 | 
			
		||||
        (result.0.unwrap(), result.1)
 | 
			
		||||
@ -3295,7 +3297,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
 | 
			
		||||
    #[track_caller]
 | 
			
		||||
    fn assert_err(p: &str, msg: &str, src: [usize; 2]) {
 | 
			
		||||
        let result = crate::parser::top_level_parse(p);
 | 
			
		||||
        let result = crate::parsing::top_level_parse(p);
 | 
			
		||||
        let err = &result.unwrap_errs()[0];
 | 
			
		||||
        assert_eq!(err.message, msg);
 | 
			
		||||
        assert_eq!(err.source_range.start(), src[0]);
 | 
			
		||||
@ -3304,7 +3306,7 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
 | 
			
		||||
    #[track_caller]
 | 
			
		||||
    fn assert_err_contains(p: &str, expected: &str) {
 | 
			
		||||
        let result = crate::parser::top_level_parse(p);
 | 
			
		||||
        let result = crate::parsing::top_level_parse(p);
 | 
			
		||||
        let err = &result.unwrap_errs()[0].message;
 | 
			
		||||
        assert!(err.contains(expected), "actual='{err}'");
 | 
			
		||||
    }
 | 
			
		||||
@ -3322,14 +3324,14 @@ const mySk1 = startSketchAt([0, 0])"#;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_double_nested_braces() {
 | 
			
		||||
        let code = r#"const prop = yo["one"][two]"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_binary_expression_period_number_first() {
 | 
			
		||||
        let code = r#"const obj = { a: 1, b: 2 }
 | 
			
		||||
const height = 1 - obj.a"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@ -3337,47 +3339,47 @@ const height = 1 - obj.a"#;
 | 
			
		||||
        let code = r#"const obj = { thing: 1 }
 | 
			
		||||
startSketchOn(obj.sketch)"#;
 | 
			
		||||
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_binary_expression_brace_number_first() {
 | 
			
		||||
        let code = r#"const obj = { a: 1, b: 2 }
 | 
			
		||||
const height = 1 - obj["a"]"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_binary_expression_brace_number_second() {
 | 
			
		||||
        let code = r#"const obj = { a: 1, b: 2 }
 | 
			
		||||
const height = obj["a"] - 1"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_binary_expression_in_array_number_first() {
 | 
			
		||||
        let code = r#"const obj = { a: 1, b: 2 }
 | 
			
		||||
const height = [1 - obj["a"], 0]"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_binary_expression_in_array_number_second() {
 | 
			
		||||
        let code = r#"const obj = { a: 1, b: 2 }
 | 
			
		||||
const height = [obj["a"] - 1, 0]"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
 | 
			
		||||
        let code = r#"const obj = { a: 1, b: 2 }
 | 
			
		||||
const height = [obj["a"] -1, 0]"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_anon_fn() {
 | 
			
		||||
        crate::parser::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@ -3415,7 +3417,7 @@ const secondExtrude = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_parens_unicode() {
 | 
			
		||||
        let result = crate::parser::top_level_parse("(ޜ");
 | 
			
		||||
        let result = crate::parsing::top_level_parse("(ޜ");
 | 
			
		||||
        let KclError::Lexical(details) = result.0.unwrap_err() else {
 | 
			
		||||
            panic!();
 | 
			
		||||
        };
 | 
			
		||||
@ -3433,12 +3435,12 @@ const thickness = 0.56
 | 
			
		||||
 | 
			
		||||
const bracket = [-leg2 + thickness, 0]
 | 
			
		||||
"#;
 | 
			
		||||
        crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_nested_open_brackets() {
 | 
			
		||||
        crate::parser::top_level_parse(
 | 
			
		||||
        crate::parsing::top_level_parse(
 | 
			
		||||
            r#"
 | 
			
		||||
z(-[["#,
 | 
			
		||||
        )
 | 
			
		||||
@ -3625,14 +3627,14 @@ e
 | 
			
		||||
        }
 | 
			
		||||
        firstPrimeNumber()
 | 
			
		||||
        "#;
 | 
			
		||||
        let _ast = crate::parser::top_level_parse(code).unwrap();
 | 
			
		||||
        let _ast = crate::parsing::top_level_parse(code).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn array() {
 | 
			
		||||
        let program = r#"[1, 2, 3]"#;
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(program, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(program, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        let _arr = array_elem_by_elem(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3645,7 +3647,7 @@ e
 | 
			
		||||
            3,
 | 
			
		||||
        ]"#;
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(program, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(program, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        let _arr = array_elem_by_elem(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3659,7 +3661,7 @@ e
 | 
			
		||||
            3
 | 
			
		||||
        ]"#;
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(program, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(program, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        let _arr = array_elem_by_elem(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3672,7 +3674,7 @@ e
 | 
			
		||||
            4
 | 
			
		||||
        }";
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(some_program_string, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(some_program_string, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        let _res = if_expr(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3683,7 +3685,7 @@ e
 | 
			
		||||
            4
 | 
			
		||||
        }";
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(some_program_string, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(some_program_string, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        let _res = else_if(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3698,7 +3700,7 @@ e
 | 
			
		||||
            5
 | 
			
		||||
        }";
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(some_program_string, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(some_program_string, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        let _res = if_expr(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3711,7 +3713,7 @@ e
 | 
			
		||||
 | 
			
		||||
thing(false)
 | 
			
		||||
"#;
 | 
			
		||||
        crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@ -3752,7 +3754,7 @@ thing(false)
 | 
			
		||||
    |> line([-5.09, 12.33], %)
 | 
			
		||||
    asdasd
 | 
			
		||||
"#;
 | 
			
		||||
        crate::parser::top_level_parse(test_program).unwrap_errs();
 | 
			
		||||
        crate::parsing::top_level_parse(test_program).unwrap_errs();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@ -3773,14 +3775,14 @@ const b2 = cube([3,3], 4)
 | 
			
		||||
const pt1 = b1[0]
 | 
			
		||||
const pt2 = b2[0]
 | 
			
		||||
"#;
 | 
			
		||||
        crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_math_with_stdlib() {
 | 
			
		||||
        let some_program_string = r#"const d2r = pi() / 2
 | 
			
		||||
let other_thing = 2 * cos(3)"#;
 | 
			
		||||
        crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@ -3798,7 +3800,7 @@ let other_thing = 2 * cos(3)"#;
 | 
			
		||||
}
 | 
			
		||||
let myBox = box([0,0], -3, -16, -10)
 | 
			
		||||
"#;
 | 
			
		||||
        crate::parser::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@ -3818,7 +3820,7 @@ let myBox = box([0,0], -3, -16, -10)
 | 
			
		||||
    fn arg_labels() {
 | 
			
		||||
        let input = r#"length: 3"#;
 | 
			
		||||
        let module_id = ModuleId::default();
 | 
			
		||||
        let tokens = crate::token::lexer(input, module_id).unwrap();
 | 
			
		||||
        let tokens = crate::parsing::token::lexer(input, module_id).unwrap();
 | 
			
		||||
        let mut sl: &[Token] = &tokens;
 | 
			
		||||
        super::labeled_arguments(&mut sl).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
@ -3827,7 +3829,7 @@ let myBox = box([0,0], -3, -16, -10)
 | 
			
		||||
    fn kw_fn() {
 | 
			
		||||
        for input in ["val = foo(x, y: z)", "val = foo(y: z)"] {
 | 
			
		||||
            let module_id = ModuleId::default();
 | 
			
		||||
            let tokens = crate::token::lexer(input, module_id).unwrap();
 | 
			
		||||
            let tokens = crate::parsing::token::lexer(input, module_id).unwrap();
 | 
			
		||||
            let sl = &tokens;
 | 
			
		||||
            super::program.parse(sl).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
@ -3900,7 +3902,6 @@ int(42.3)"#;
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod snapshot_math_tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::ModuleId;
 | 
			
		||||
 | 
			
		||||
    // This macro generates a test function with the given function name.
 | 
			
		||||
    // The macro takes a KCL program, ensures it tokenizes and parses, then compares
 | 
			
		||||
@ -3909,8 +3910,8 @@ mod snapshot_math_tests {
 | 
			
		||||
        ($func_name:ident, $test_kcl_program:expr) => {
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn $func_name() {
 | 
			
		||||
                let module_id = ModuleId::default();
 | 
			
		||||
                let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
 | 
			
		||||
                let module_id = crate::ModuleId::default();
 | 
			
		||||
                let tokens = crate::parsing::token::lexer($test_kcl_program, module_id).unwrap();
 | 
			
		||||
                ParseContext::init();
 | 
			
		||||
 | 
			
		||||
                let actual = match binary_expression.parse(&tokens) {
 | 
			
		||||
@ -3939,7 +3940,6 @@ mod snapshot_math_tests {
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod snapshot_tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::ModuleId;
 | 
			
		||||
 | 
			
		||||
    // This macro generates a test function with the given function name.
 | 
			
		||||
    // The macro takes a KCL program, ensures it tokenizes and parses, then compares
 | 
			
		||||
@ -3948,8 +3948,8 @@ mod snapshot_tests {
 | 
			
		||||
        ($func_name:ident, $test_kcl_program:expr) => {
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn $func_name() {
 | 
			
		||||
                let module_id = ModuleId::default();
 | 
			
		||||
                let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
 | 
			
		||||
                let module_id = crate::ModuleId::default();
 | 
			
		||||
                let tokens = crate::parsing::token::lexer($test_kcl_program, module_id).unwrap();
 | 
			
		||||
                print_tokens(&tokens);
 | 
			
		||||
                ParseContext::init();
 | 
			
		||||
                let actual = match program.parse(&tokens) {
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3609
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3851
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3610
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3852
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3611
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3853
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3612
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3854
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3613
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3855
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3614
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3856
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3615
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3857
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3616
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3858
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3617
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3859
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3618
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3860
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3619
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3861
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3893
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3963
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3964
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
assertion_line: 3721
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3965
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3966
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3974
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3981
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3989
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3993
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/parser/parser_impl.rs
 | 
			
		||||
source: kcl/src/parsing/parser.rs
 | 
			
		||||
assertion_line: 3994
 | 
			
		||||
expression: actual
 | 
			
		||||
snapshot_kind: text
 | 
			
		||||
---
 | 
			
		||||