diff --git a/.env.development b/.env.development
index 9bf3d60b3..b69c3d78e 100644
--- a/.env.development
+++ b/.env.development
@@ -1,6 +1,6 @@
-VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
-VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
-VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
+VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
+VITE_KC_API_BASE_URL=https://api.kittycad.io
+VITE_KC_SITE_BASE_URL=https://kittycad.io
VITE_KC_SKIP_AUTH=false
-VITE_KC_CONNECTION_TIMEOUT_MS=5000
+VITE_KC_CONNECTION_TIMEOUT_MS=15000
VITE_KC_SENTRY_DSN=
diff --git a/.github/workflows/cargo-clippy.yml b/.github/workflows/cargo-clippy.yml
index a0a78a3dd..943f97de3 100644
--- a/.github/workflows/cargo-clippy.yml
+++ b/.github/workflows/cargo-clippy.yml
@@ -40,6 +40,17 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
+ - name: Install ffmpeg
+ run: |
+ sudo apt update
+ sudo apt install \
+ ffmpeg \
+ libavformat-dev \
+ libavutil-dev \
+ libclang-dev \
+ libswscale-dev \
+ --no-install-recommends
+
- name: Run clippy
run: |
cd "${{ matrix.dir }}"
diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml
index 316723804..c727dba0d 100644
--- a/.github/workflows/cargo-test.yml
+++ b/.github/workflows/cargo-test.yml
@@ -41,6 +41,16 @@ jobs:
- uses: taiki-e/install-action@nextest
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
+ - name: Install ffmpeg
+ run: |
+ sudo apt update
+ sudo apt install \
+ ffmpeg \
+ libavformat-dev \
+ libavutil-dev \
+ libclang-dev \
+ libswscale-dev \
+ --no-install-recommends
- name: cargo test
shell: bash
run: |-
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 096072242..d5dabe55e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -153,6 +153,8 @@ jobs:
needs: [build-test-web, build-apps]
env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
+ PUB_DATE: ${{ github.event.release.created_at }}
+ NOTES: ${{ github.event.release.body }}
steps:
- uses: actions/download-artifact@v3
@@ -166,6 +168,8 @@ jobs:
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
jq --null-input \
--arg version "v${VERSION_NO_V}" \
+ --arg pub_date "${PUB_DATE}" \
+ --arg notes "${NOTES}" \
--arg darwin_sig "$DARWIN_SIG" \
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
--arg linux_sig "$LINUX_SIG" \
@@ -174,6 +178,8 @@ jobs:
--arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \
'{
"version": $version,
+ "pub_date": $pub_date,
+ "notes": $notes,
"platforms": {
"darwin-x86_64": {
"signature": $darwin_sig,
@@ -195,6 +201,34 @@ jobs:
}' > last_update.json
cat last_update.json
+ - name: Generate the download static endpoint
+ run: |
+ RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
+ jq --null-input \
+ --arg version "v${VERSION_NO_V}" \
+ --arg pub_date "${PUB_DATE}" \
+ --arg notes "${NOTES}" \
+ --arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
+ --arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
+ --arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
+ '{
+ "version": $version,
+ "pub_date": $pub_date,
+ "notes": $notes,
+ "platforms": {
+ "dmg-universal": {
+ "url": $darwin_url
+ },
+ "appimage-x86_64": {
+ "url": $linux_url
+ },
+ "msi-x86_64": {
+ "url": $windows_url
+ }
+ }
+ }' > last_download.json
+ cat last_download.json
+
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v1.1.1'
with:
@@ -219,6 +253,12 @@ jobs:
path: last_update.json
destination: dl.kittycad.io/releases/modeling-app
+ - name: Upload download endpoint to public bucket
+ uses: google-github-actions/upload-cloud-storage@v1.0.3
+ with:
+ path: last_download.json
+ destination: dl.kittycad.io/releases/modeling-app
+
- name: Upload release files to Github
uses: softprops/action-gh-release@v1
with:
diff --git a/docs/kcl.json b/docs/kcl.json
index 4c2d2d5ce..82f6e7179 100644
--- a/docs/kcl.json
+++ b/docs/kcl.json
@@ -11173,22 +11173,13 @@
},
"to": {
"description": "The to point.",
- "anyOf": [
- {
- "description": "A point.",
- "type": "array",
- "items": {
- "type": "number",
- "format": "double"
- },
- "maxItems": 2,
- "minItems": 2
- },
- {
- "description": "A string like `default`.",
- "type": "string"
- }
- ]
+ "type": "array",
+ "items": {
+ "type": "number",
+ "format": "double"
+ },
+ "maxItems": 2,
+ "minItems": 2
}
}
},
@@ -11201,10 +11192,6 @@
},
"maxItems": 2,
"minItems": 2
- },
- {
- "description": "A string like `default`.",
- "type": "string"
}
]
},
@@ -15341,22 +15328,13 @@
},
"to": {
"description": "The to point.",
- "anyOf": [
- {
- "description": "A point.",
- "type": "array",
- "items": {
- "type": "number",
- "format": "double"
- },
- "maxItems": 2,
- "minItems": 2
- },
- {
- "description": "A string like `default`.",
- "type": "string"
- }
- ]
+ "type": "array",
+ "items": {
+ "type": "number",
+ "format": "double"
+ },
+ "maxItems": 2,
+ "minItems": 2
}
}
},
@@ -15369,10 +15347,6 @@
},
"maxItems": 2,
"minItems": 2
- },
- {
- "description": "A string like `default`.",
- "type": "string"
}
]
},
diff --git a/docs/kcl.md b/docs/kcl.md
index 386c808fc..4e32cabd3 100644
--- a/docs/kcl.md
+++ b/docs/kcl.md
@@ -2044,11 +2044,9 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
// The tag.
tag: string,
// The to point.
- to: [number] |
-string,
+ to: [number],
} |
-[number] |
-string
+[number]
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
```
@@ -2784,11 +2782,9 @@ startSketchAt(data: LineData) -> SketchGroup
// The tag.
tag: string,
// The to point.
- to: [number] |
-string,
+ to: [number],
} |
-[number] |
-string
+[number]
```
#### Returns
diff --git a/package.json b/package.json
index ce563c9cf..a5c1a85c7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "untitled-app",
- "version": "0.5.0",
+ "version": "0.6.1",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.9.0",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 55d3a8ce3..ec86c0970 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -19,7 +19,7 @@ anyhow = "1"
oauth2 = "4.4.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-tauri = { version = "1.3.0", features = [ "updater", "path-all", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
+tauri = { version = "1.3.0", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater"] }
tokio = { version = "1.29.1", features = ["time"] }
toml = "0.6.0"
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index e09d5a792..35911e403 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -8,7 +8,7 @@
},
"package": {
"productName": "kittycad-modeling",
- "version": "0.5.0"
+ "version": "0.6.1"
},
"tauri": {
"allowlist": {
diff --git a/src/App.tsx b/src/App.tsx
index 5fa1fb7d0..cedf6f6b3 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -42,7 +42,9 @@ export function App() {
setOpenPanes,
didDragInStream,
streamDimensions,
+ guiMode,
} = useStore((s) => ({
+ guiMode: s.guiMode,
setCode: s.setCode,
engineCommandManager: s.engineCommandManager,
buttonDownInStream: s.buttonDownInStream,
@@ -109,8 +111,41 @@ export function App() {
})
const newCmdId = uuidv4()
-
- if (buttonDownInStream !== undefined) {
+ if (buttonDownInStream === undefined) {
+ if (
+ guiMode.mode === 'sketch' &&
+ guiMode.sketchMode === ('sketch_line' as any)
+ ) {
+ debounceSocketSend({
+ type: 'modeling_cmd_req',
+ cmd_id: newCmdId,
+ cmd: {
+ type: 'mouse_move',
+ window: { x, y },
+ },
+ })
+ } else {
+ debounceSocketSend({
+ type: 'modeling_cmd_req',
+ cmd: {
+ type: 'highlight_set_entity',
+ selected_at_window: { x, y },
+ },
+ cmd_id: newCmdId,
+ })
+ }
+ } else {
+ if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
+ debounceSocketSend({
+ type: 'modeling_cmd_req',
+ cmd_id: newCmdId,
+ cmd: {
+ type: 'handle_mouse_drag_move',
+ window: { x, y },
+ },
+ })
+ return
+ }
const interactionGuards = cameraMouseDragGuards[cameraControls]
let interaction: CameraDragInteractionType_type
@@ -123,6 +158,7 @@ export function App() {
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
interaction = 'zoom'
} else {
+ console.log('none')
return
}
@@ -135,15 +171,6 @@ export function App() {
},
cmd_id: newCmdId,
})
- } else {
- debounceSocketSend({
- type: 'modeling_cmd_req',
- cmd: {
- type: 'highlight_set_entity',
- selected_at_window: { x, y },
- },
- cmd_id: newCmdId,
- })
}
}
@@ -171,11 +198,11 @@ export function App() {
paneOpacity
}
defaultSize={{
- width: '400px',
+ width: '550px',
height: 'auto',
}}
minWidth={200}
- maxWidth={600}
+ maxWidth={800}
minHeight={'auto'}
maxHeight={'auto'}
handleClasses={{
diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx
index 85b4c7512..5442c199a 100644
--- a/src/Toolbar.tsx
+++ b/src/Toolbar.tsx
@@ -1,4 +1,4 @@
-import { useStore, toolTips } from './useStore'
+import { useStore, toolTips, Selections } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert'
@@ -15,6 +15,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css'
+import { v4 as uuidv4 } from 'uuid'
+import { useAppMode } from 'hooks/useAppMode'
export const Toolbar = () => {
const {
@@ -24,6 +26,7 @@ export const Toolbar = () => {
ast,
updateAst,
programMemory,
+ engineCommandManager,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
@@ -31,7 +34,9 @@ export const Toolbar = () => {
ast: s.ast,
updateAst: s.updateAst,
programMemory: s.programMemory,
+ engineCommandManager: s.engineCommandManager,
}))
+ useAppMode()
useEffect(() => {
console.log('guiMode', guiMode)
@@ -39,7 +44,7 @@ export const Toolbar = () => {
function ToolbarButtons() {
return (
- <>
+
{guiMode.mode === 'default' && (
)}
- {(guiMode.mode === 'canEditSketch' || false) && (
+ {guiMode.mode === 'canEditSketch' && (
)
}
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 727e2e17c..da34b920b 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -4,6 +4,7 @@ import { ProjectWithEntryPointMetadata } from '../Router'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import styles from './AppHeader.module.css'
+import { NetworkHealthIndicator } from './NetworkHealthIndicator'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
@@ -43,7 +44,8 @@ export const AppHeader = ({
)}
{/* If there are children, show them, otherwise show User menu */}
{children || (
-
+
+
)}
diff --git a/src/components/AvailableVarsHelpers.tsx b/src/components/AvailableVarsHelpers.tsx
index 4fbe5c54e..498afd825 100644
--- a/src/components/AvailableVarsHelpers.tsx
+++ b/src/components/AvailableVarsHelpers.tsx
@@ -144,7 +144,7 @@ export function useCalc({
try {
const code = `const __result__ = ${value}\nshow(__result__)`
const ast = parser_wasm(code)
- const _programMem: any = { root: {} }
+ const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
diff --git a/src/components/MemoryPanel.test.tsx b/src/components/MemoryPanel.test.tsx
index 4cf08258f..1cf24ccb0 100644
--- a/src/components/MemoryPanel.test.tsx
+++ b/src/components/MemoryPanel.test.tsx
@@ -10,7 +10,7 @@ describe('processMemory', () => {
// Enable rotations #152
const code = `
const myVar = 5
- const myFn = (a) => {
+ fn myFn = (a) => {
return a - 2
}
const otherVar = myFn(5)
@@ -29,6 +29,7 @@ describe('processMemory', () => {
const ast = parser_wasm(code)
const programMemory = await enginelessExecutor(ast, {
root: {},
+ return: null,
})
const output = processMemory(programMemory)
expect(output.myVar).toEqual(5)
diff --git a/src/components/MemoryPanel.tsx b/src/components/MemoryPanel.tsx
index f0deef3c9..cbb5b4d23 100644
--- a/src/components/MemoryPanel.tsx
+++ b/src/components/MemoryPanel.tsx
@@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { useMemo } from 'react'
-import { ProgramMemory } from '../lang/executor'
+import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
import { Themes } from '../lib/theme'
interface MemoryPanelProps extends CollapsiblePanelProps {
@@ -49,8 +49,12 @@ export const processMemory = (programMemory: ProgramMemory) => {
Object.keys(programMemory.root).forEach((key) => {
const val = programMemory.root[key]
if (typeof val.value !== 'function') {
- if (val.type === 'sketchGroup' || val.type === 'extrudeGroup') {
- processedMemory[key] = val.value.map(({ __geoMeta, ...rest }) => {
+ if (val.type === 'SketchGroup') {
+ processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
+ return rest
+ })
+ } else if (val.type === 'ExtrudeGroup') {
+ processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest
})
} else {
diff --git a/src/components/NetworkHealthIndicator.test.tsx b/src/components/NetworkHealthIndicator.test.tsx
new file mode 100644
index 000000000..ebe135fb3
--- /dev/null
+++ b/src/components/NetworkHealthIndicator.test.tsx
@@ -0,0 +1,51 @@
+import { fireEvent, render, screen } from '@testing-library/react'
+import UserSidebarMenu from './UserSidebarMenu'
+import { BrowserRouter } from 'react-router-dom'
+import { GlobalStateProvider } from './GlobalStateProvider'
+import CommandBarProvider from './CommandBar'
+import {
+ NETWORK_CONTENT,
+ NetworkHealthIndicator,
+} from './NetworkHealthIndicator'
+
+function TestWrap({ children }: { children: React.ReactNode }) {
+ // wrap in router and xState context
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+describe('NetworkHealthIndicator tests', () => {
+ test('Renders the network indicator', () => {
+ render(
+
+
+
+ )
+
+ fireEvent.click(screen.getByTestId('network-toggle'))
+
+ expect(screen.getByTestId('network-good')).toHaveTextContent(
+ NETWORK_CONTENT.good
+ )
+ })
+
+ test('Responds to network changes', () => {
+ render(
+
+
+
+ )
+
+ fireEvent.offline(window)
+ fireEvent.click(screen.getByTestId('network-toggle'))
+
+ expect(screen.getByTestId('network-bad')).toHaveTextContent(
+ NETWORK_CONTENT.bad
+ )
+ })
+})
diff --git a/src/components/NetworkHealthIndicator.tsx b/src/components/NetworkHealthIndicator.tsx
new file mode 100644
index 000000000..2ce1e0e00
--- /dev/null
+++ b/src/components/NetworkHealthIndicator.tsx
@@ -0,0 +1,112 @@
+import {
+ faCheck,
+ faExclamation,
+ faWifi,
+} from '@fortawesome/free-solid-svg-icons'
+import { Popover } from '@headlessui/react'
+import { useEffect, useState } from 'react'
+import { ActionIcon } from './ActionIcon'
+
+export const NETWORK_CONTENT = {
+ good: 'Network health is good',
+ bad: 'Network issue',
+}
+
+const NETWORK_MESSAGES = {
+ offline: 'You are offline',
+}
+
+export const NetworkHealthIndicator = () => {
+ const [networkIssues, setNetworkIssues] = useState
([])
+ const hasIssues = [...networkIssues.values()].length > 0
+
+ useEffect(() => {
+ const offlineListener = () =>
+ setNetworkIssues((issues) => {
+ return [
+ ...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline),
+ NETWORK_MESSAGES.offline,
+ ]
+ })
+ window.addEventListener('offline', offlineListener)
+
+ const onlineListener = () =>
+ setNetworkIssues((issues) => {
+ return [...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline)]
+ })
+ window.addEventListener('online', onlineListener)
+
+ return () => {
+ window.removeEventListener('offline', offlineListener)
+ window.removeEventListener('online', onlineListener)
+ }
+ }, [])
+
+ return (
+
+
+ Network Health
+
+
+
+ {!hasIssues ? (
+
+
+ {NETWORK_CONTENT.good}
+
+ ) : (
+
+
+ {NETWORK_CONTENT.bad}
+ {networkIssues.length > 1 ? 's' : ''}
+
+ {networkIssues.map((issue) => (
+ -
+
+
{issue}
+
+ ))}
+
+ )}
+
+
+ )
+}
diff --git a/src/components/Stream.tsx b/src/components/Stream.tsx
index 80db48a20..8f0e41d70 100644
--- a/src/components/Stream.tsx
+++ b/src/components/Stream.tsx
@@ -7,11 +7,14 @@ import {
} from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
-import { getNormalisedCoordinates } from '../lib/utils'
+import { getNormalisedCoordinates, roundOff } from '../lib/utils'
import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
+import { Models } from '@kittycad/lib'
+import { addStartSketch } from 'lang/modifyAst'
+import { addNewSketchLn } from 'lang/std/sketch'
export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true)
@@ -25,6 +28,11 @@ export const Stream = ({ className = '' }) => {
setDidDragInStream,
streamDimensions,
isExecuting,
+ guiMode,
+ ast,
+ updateAst,
+ setGuiMode,
+ programMemory,
} = useStore((s) => ({
mediaStream: s.mediaStream,
engineCommandManager: s.engineCommandManager,
@@ -34,6 +42,11 @@ export const Stream = ({ className = '' }) => {
setDidDragInStream: s.setDidDragInStream,
streamDimensions: s.streamDimensions,
isExecuting: s.isExecuting,
+ guiMode: s.guiMode,
+ ast: s.ast,
+ updateAst: s.updateAst,
+ setGuiMode: s.setGuiMode,
+ programMemory: s.programMemory,
}))
const {
settings: {
@@ -64,7 +77,7 @@ export const Stream = ({ className = '' }) => {
const newId = uuidv4()
const interactionGuards = cameraMouseDragGuards[cameraControls]
- let interaction: CameraDragInteractionType_type
+ let interaction: CameraDragInteractionType_type = 'rotate'
if (
interactionGuards.pan.callback(e) ||
@@ -81,19 +94,33 @@ export const Stream = ({ className = '' }) => {
interactionGuards.zoom.lenientDragStartButton === e.button
) {
interaction = 'zoom'
- } else {
- return
}
- engineCommandManager?.sendSceneCommand({
- type: 'modeling_cmd_req',
- cmd: {
- type: 'camera_drag_start',
- interaction,
- window: { x, y },
- },
- cmd_id: newId,
- })
+ if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
+ engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd: {
+ type: 'handle_mouse_drag_start',
+ window: { x, y },
+ },
+ cmd_id: newId,
+ })
+ } else if (
+ !(
+ guiMode.mode === 'sketch' &&
+ guiMode.sketchMode === ('sketch_line' as any)
+ )
+ ) {
+ engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd: {
+ type: 'camera_drag_start',
+ interaction,
+ window: { x, y },
+ },
+ cmd_id: newId,
+ })
+ }
setButtonDownInStream(e.button)
setClickCoords({ x, y })
@@ -118,6 +145,7 @@ export const Stream = ({ className = '' }) => {
ctrlKey,
}) => {
if (!videoRef.current) return
+ setButtonDownInStream(undefined)
const { x, y } = getNormalisedCoordinates({
clientX,
clientY,
@@ -128,7 +156,7 @@ export const Stream = ({ className = '' }) => {
const newCmdId = uuidv4()
const interaction = ctrlKey ? 'pan' : 'rotate'
- engineCommandManager?.sendSceneCommand({
+ const command: Models['WebSocketRequest_type'] = {
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_end',
@@ -136,9 +164,8 @@ export const Stream = ({ className = '' }) => {
window: { x, y },
},
cmd_id: newCmdId,
- })
+ }
- setButtonDownInStream(undefined)
if (!didDragInStream) {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
@@ -150,6 +177,95 @@ export const Stream = ({ className = '' }) => {
cmd_id: uuidv4(),
})
}
+
+ if (!didDragInStream && guiMode.mode === 'default') {
+ command.cmd = {
+ type: 'select_with_point',
+ selection_type: 'add',
+ selected_at_window: { x, y },
+ }
+ } else if (
+ (!didDragInStream &&
+ guiMode.mode === 'sketch' &&
+ ['move', 'select'].includes(guiMode.sketchMode)) ||
+ (guiMode.mode === 'sketch' &&
+ guiMode.sketchMode === ('sketch_line' as any))
+ ) {
+ command.cmd = {
+ type: 'mouse_click',
+ window: { x, y },
+ }
+ } else if (
+ guiMode.mode === 'sketch' &&
+ guiMode.sketchMode === ('move' as any)
+ ) {
+ command.cmd = {
+ type: 'handle_mouse_drag_end',
+ window: { x, y },
+ }
+ }
+ engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
+ if (command.cmd.type !== 'mouse_click' || !ast) return
+ if (
+ !(
+ guiMode.mode === 'sketch' &&
+ guiMode.sketchMode === ('sketch_line' as any as 'line')
+ )
+ )
+ return
+
+ if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
+ const curve = await engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd_id: uuidv4(),
+ cmd: {
+ type: 'curve_get_control_points',
+ curve_id: data?.data?.entities_modified[0],
+ },
+ })
+ const coords: { x: number; y: number }[] =
+ curve.data.data.control_points
+ const _addStartSketch = addStartSketch(
+ ast,
+ [roundOff(coords[0].x), roundOff(coords[0].y)],
+ [
+ roundOff(coords[1].x - coords[0].x),
+ roundOff(coords[1].y - coords[0].y),
+ ]
+ )
+ const _modifiedAst = _addStartSketch.modifiedAst
+ const _pathToNode = _addStartSketch.pathToNode
+
+ setGuiMode({
+ ...guiMode,
+ pathToNode: _pathToNode,
+ waitingFirstClick: false,
+ })
+ updateAst(_modifiedAst)
+ } else if (
+ data?.data?.entities_modified?.length &&
+ !guiMode.waitingFirstClick
+ ) {
+ const curve = await engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd_id: uuidv4(),
+ cmd: {
+ type: 'curve_get_control_points',
+ curve_id: data?.data?.entities_modified[0],
+ },
+ })
+ const coords: { x: number; y: number }[] =
+ curve.data.data.control_points
+ const _modifiedAst = addNewSketchLn({
+ node: ast,
+ programMemory,
+ to: [coords[1].x, coords[1].y],
+ fnName: 'line',
+ pathToNode: guiMode.pathToNode,
+ }).modifiedAst
+ updateAst(_modifiedAst)
+ }
+ })
setDidDragInStream(false)
setClickCoords(undefined)
}
diff --git a/src/components/TextEditor.tsx b/src/components/TextEditor.tsx
index fc34b1b20..d0f747370 100644
--- a/src/components/TextEditor.tsx
+++ b/src/components/TextEditor.tsx
@@ -63,7 +63,6 @@ export const TextEditor = ({
sourceRangeMap,
} = useStore((s) => ({
code: s.code,
- defferedCode: s.defferedCode,
defferedSetCode: s.defferedSetCode,
editorView: s.editorView,
engineCommandManager: s.engineCommandManager,
@@ -71,7 +70,6 @@ export const TextEditor = ({
isLSPServerReady: s.isLSPServerReady,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
- setCode: s.setCode,
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges,
diff --git a/src/hooks/useAppMode.ts b/src/hooks/useAppMode.ts
new file mode 100644
index 000000000..a52577245
--- /dev/null
+++ b/src/hooks/useAppMode.ts
@@ -0,0 +1,243 @@
+// needed somewhere to dump this logic,
+// Once we have xState this should be removed
+
+import { useStore, Selections } from 'useStore'
+import { useEffect, useState } from 'react'
+import { v4 as uuidv4 } from 'uuid'
+import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
+import { Models } from '@kittycad/lib/dist/types/src'
+import { isReducedMotion } from 'lang/util'
+import { isOverlap } from 'lib/utils'
+
+interface DefaultPlanes {
+ xy: string
+ yz: string
+ xz: string
+}
+
+export function useAppMode() {
+ const {
+ guiMode,
+ setGuiMode,
+ selectionRanges,
+ engineCommandManager,
+ selectionRangeTypeMap,
+ } = useStore((s) => ({
+ guiMode: s.guiMode,
+ setGuiMode: s.setGuiMode,
+ selectionRanges: s.selectionRanges,
+ engineCommandManager: s.engineCommandManager,
+ selectionRangeTypeMap: s.selectionRangeTypeMap,
+ }))
+ const [defaultPlanes, setDefaultPlanes] = useState(null)
+ useEffect(() => {
+ if (
+ guiMode.mode === 'sketch' &&
+ guiMode.sketchMode === 'selectFace' &&
+ engineCommandManager
+ ) {
+ if (!defaultPlanes) {
+ const xy = createPlane(engineCommandManager, {
+ x_axis: { x: 1, y: 0, z: 0 },
+ y_axis: { x: 0, y: 1, z: 0 },
+ color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
+ })
+ const yz = createPlane(engineCommandManager, {
+ x_axis: { x: 0, y: 1, z: 0 },
+ y_axis: { x: 0, y: 0, z: 1 },
+ color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
+ })
+ const xz = createPlane(engineCommandManager, {
+ x_axis: { x: 1, y: 0, z: 0 },
+ y_axis: { x: 0, y: 0, z: 1 },
+ color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
+ })
+ setDefaultPlanes({ xy, yz, xz })
+ } else {
+ hideDefaultPlanes(engineCommandManager, defaultPlanes)
+ }
+ }
+ if (guiMode.mode !== 'sketch' && defaultPlanes) {
+ Object.values(defaultPlanes).forEach((planeId) => {
+ engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd_id: uuidv4(),
+ cmd: {
+ type: 'object_visible',
+ object_id: planeId,
+ hidden: true,
+ },
+ })
+ })
+ } else if (guiMode.mode === 'default') {
+ const pathId =
+ engineCommandManager &&
+ isCursorInSketchCommandRange(
+ engineCommandManager.artifactMap,
+ selectionRanges
+ )
+ if (pathId) {
+ setGuiMode({
+ mode: 'canEditSketch',
+ rotation: [0, 0, 0, 1],
+ position: [0, 0, 0],
+ pathToNode: [],
+ pathId,
+ })
+ }
+ } else if (guiMode.mode === 'canEditSketch') {
+ if (
+ !engineCommandManager ||
+ !isCursorInSketchCommandRange(
+ engineCommandManager.artifactMap,
+ selectionRanges
+ )
+ ) {
+ setGuiMode({
+ mode: 'default',
+ })
+ }
+ }
+ }, [
+ guiMode,
+ guiMode.mode,
+ engineCommandManager,
+ selectionRanges,
+ selectionRangeTypeMap,
+ ])
+
+ useEffect(() => {
+ const unSub = engineCommandManager?.subscribeTo({
+ event: 'select_with_point',
+ callback: async ({ data }) => {
+ if (!data.entity_id) return
+ if (!defaultPlanes) return
+ if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
+ // user clicked something else in the scene
+ return
+ }
+ const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
+ {
+ type: 'modeling_cmd_req',
+ cmd_id: uuidv4(),
+ cmd: {
+ type: 'sketch_mode_enable',
+ plane_id: data.entity_id,
+ ortho: true,
+ animated: !isReducedMotion(),
+ },
+ }
+ )
+ hideDefaultPlanes(engineCommandManager, defaultPlanes)
+ const sketchUuid = uuidv4()
+ const proms: any[] = []
+ proms.push(
+ engineCommandManager.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd_id: sketchUuid,
+ cmd: {
+ type: 'start_path',
+ },
+ })
+ )
+ proms.push(
+ engineCommandManager.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd_id: uuidv4(),
+ cmd: {
+ type: 'edit_mode_enter',
+ target: sketchUuid,
+ },
+ })
+ )
+ const res = await Promise.all(proms)
+ console.log('res', res)
+ setGuiMode({
+ mode: 'sketch',
+ sketchMode: 'sketchEdit',
+ rotation: [0, 0, 0, 1],
+ position: [0, 0, 0],
+ pathToNode: [],
+ })
+
+ console.log('sketchModeResponse', sketchModeResponse)
+ },
+ })
+ return unSub
+ }, [engineCommandManager, defaultPlanes])
+}
+
+function createPlane(
+ engineCommandManager: EngineCommandManager,
+ {
+ x_axis,
+ y_axis,
+ color,
+ }: {
+ x_axis: Models['Point3d_type']
+ y_axis: Models['Point3d_type']
+ color: Models['Color_type']
+ }
+) {
+ const planeId = uuidv4()
+ engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd: {
+ type: 'make_plane',
+ size: 60,
+ origin: { x: 0, y: 0, z: 0 },
+ x_axis,
+ y_axis,
+ clobber: false,
+ },
+ cmd_id: planeId,
+ })
+ engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd: {
+ type: 'plane_set_color',
+ plane_id: planeId,
+ color,
+ },
+ cmd_id: uuidv4(),
+ })
+ return planeId
+}
+
+function hideDefaultPlanes(
+ engineCommandManager: EngineCommandManager,
+ defaultPlanes: DefaultPlanes
+) {
+ Object.values(defaultPlanes).forEach((planeId) => {
+ engineCommandManager?.sendSceneCommand({
+ type: 'modeling_cmd_req',
+ cmd_id: uuidv4(),
+ cmd: {
+ type: 'object_visible',
+ object_id: planeId,
+ hidden: true,
+ },
+ })
+ })
+}
+
+function isCursorInSketchCommandRange(
+ artifactMap: ArtifactMap,
+ selectionRanges: Selections
+): string | false {
+ const overlapingEntries = Object.entries(artifactMap || {}).filter(
+ ([id, artifact]) =>
+ selectionRanges.codeBasedSelections.some(
+ (selection) =>
+ Array.isArray(selection.range) &&
+ Array.isArray(artifact.range) &&
+ isOverlap(selection.range, artifact.range) &&
+ (artifact.commandType === 'start_path' ||
+ artifact.commandType === 'extend_path' ||
+ 'close_path')
+ )
+ )
+ return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
+ ? overlapingEntries[0][1].parentId
+ : false
+}
diff --git a/src/lang/abstractSyntaxTree.test.ts b/src/lang/abstractSyntaxTree.test.ts
index fea946411..27f0a1397 100644
--- a/src/lang/abstractSyntaxTree.test.ts
+++ b/src/lang/abstractSyntaxTree.test.ts
@@ -1,5 +1,5 @@
import { parser_wasm } from './abstractSyntaxTree'
-import { KCLUnexpectedError } from './errors'
+import { KCLError } from './errors'
import { initPromise } from './rust'
beforeAll(() => initPromise)
@@ -1744,6 +1744,12 @@ describe('parsing errors', () => {
_theError = e
}
const theError = _theError as any
- expect(theError).toEqual(new KCLUnexpectedError('Brace', [[29, 30]]))
+ expect(theError).toEqual(
+ new KCLError(
+ 'unexpected',
+ 'Unexpected token Token { token_type: Brace, start: 29, end: 30, value: "}" }',
+ [[29, 30]]
+ )
+ )
})
})
diff --git a/src/lang/artifact.test.ts b/src/lang/artifact.test.ts
index 614687f90..3e196b45b 100644
--- a/src/lang/artifact.test.ts
+++ b/src/lang/artifact.test.ts
@@ -21,7 +21,7 @@ show(mySketch001)`
)
expect(shown).toEqual([
{
- type: 'sketchGroup',
+ type: 'SketchGroup',
start: {
to: [0, 0],
from: [0, 0],
@@ -77,7 +77,7 @@ show(mySketch001)`
)
expect(shown).toEqual([
{
- type: 'extrudeGroup',
+ type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
@@ -117,7 +117,7 @@ show(theExtrude, sk2)`
)
expect(geos).toEqual([
{
- type: 'extrudeGroup',
+ type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
@@ -126,7 +126,7 @@ show(theExtrude, sk2)`
__meta: [{ sourceRange: [13, 34] }],
},
{
- type: 'extrudeGroup',
+ type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
diff --git a/src/lang/executor.test.ts b/src/lang/executor.test.ts
index cf4c054b0..f4147584e 100644
--- a/src/lang/executor.test.ts
+++ b/src/lang/executor.test.ts
@@ -1,7 +1,7 @@
import fs from 'node:fs'
import { parser_wasm } from './abstractSyntaxTree'
-import { ProgramMemory } from './executor'
+import { ProgramMemory, SketchGroup } from './executor'
import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers'
import { vi } from 'vitest'
@@ -117,10 +117,10 @@ show(mySketch)
// ].join('\n')
// const { root } = await exe(code)
// expect(root.mySk1.value).toHaveLength(3)
- // expect(root?.rotated?.type).toBe('sketchGroup')
+ // expect(root?.rotated?.type).toBe('SketchGroup')
// if (
- // root?.mySk1?.type !== 'sketchGroup' ||
- // root?.rotated?.type !== 'sketchGroup'
+ // root?.mySk1?.type !== 'SketchGroup' ||
+ // root?.rotated?.type !== 'SketchGroup'
// )
// throw new Error('not a sketch group')
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
@@ -143,7 +143,7 @@ show(mySketch)
].join('\n')
const { root } = await exe(code)
expect(root.mySk1).toEqual({
- type: 'sketchGroup',
+ type: 'SketchGroup',
start: {
to: [0, 0],
from: [0, 0],
@@ -199,7 +199,7 @@ show(mySketch)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(root).toEqual({
three: {
- type: 'userVal',
+ type: 'UserVal',
value: 3,
__meta: [
{
@@ -208,7 +208,7 @@ show(mySketch)
],
},
yo: {
- type: 'userVal',
+ type: 'UserVal',
value: [1, '2', 3, 9],
__meta: [
{
@@ -225,7 +225,7 @@ show(mySketch)
].join('\n')
const { root } = await exe(code)
expect(root.yo).toEqual({
- type: 'userVal',
+ type: 'UserVal',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
__meta: [
{
@@ -240,7 +240,7 @@ show(mySketch)
)
const { root } = await exe(code)
expect(root.myVar).toEqual({
- type: 'userVal',
+ type: 'UserVal',
value: '123',
__meta: [
{
@@ -338,7 +338,7 @@ describe('testing math operators', () => {
const { root } = await exe(code)
const sketch = root.part001
// result of `-legLen(5, min(3, 999))` should be -4
- const yVal = sketch.value?.[0]?.to?.[1]
+ const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
expect(yVal).toBe(-4)
})
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
@@ -356,8 +356,8 @@ describe('testing math operators', () => {
const { root } = await exe(code)
const sketch = root.part001
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
- expect(sketch.value?.[1]?.from).toEqual([3, 4])
- expect(sketch.value?.[1]?.to).toEqual([6, 0])
+ expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
+ expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
const removedUnaryExp = code.replace(
`-legLen(segLen('seg01', %), myVar)`,
`legLen(segLen('seg01', %), myVar)`
@@ -366,7 +366,9 @@ describe('testing math operators', () => {
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
// without the minus sign, the y value should be 8
- expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8])
+ expect((removedUnaryExpRootSketch as SketchGroup).value?.[1]?.to).toEqual([
+ 6, 8,
+ ])
})
it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
@@ -397,7 +399,10 @@ show(theExtrude)`
// helpers
-async function exe(code: string, programMemory: ProgramMemory = { root: {} }) {
+async function exe(
+ code: string,
+ programMemory: ProgramMemory = { root: {}, return: null }
+) {
const ast = parser_wasm(code)
const result = await enginelessExecutor(ast, programMemory)
diff --git a/src/lang/executor.ts b/src/lang/executor.ts
index 6c6aba816..bc13426d7 100644
--- a/src/lang/executor.ts
+++ b/src/lang/executor.ts
@@ -5,96 +5,21 @@ import {
SourceRangeMap,
} from './std/engineConnection'
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
+import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from './abstractSyntaxTree'
-export type SourceRange = [number, number]
-export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
-export type Metadata = {
- sourceRange: SourceRange
-}
-export type Position = [number, number, number]
-export type Rotation = [number, number, number, number]
+export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
+export type { Position } from '../wasm-lib/kcl/bindings/Position'
+export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
+export type { Path } from '../wasm-lib/kcl/bindings/Path'
+export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
+export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
+export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
-interface BasePath {
- from: [number, number]
- to: [number, number]
- name?: string
- __geoMeta: {
- id: string
- sourceRange: SourceRange
- }
-}
-
-export interface ToPoint extends BasePath {
- type: 'toPoint'
-}
-
-export interface Base extends BasePath {
- type: 'base'
-}
-
-export interface HorizontalLineTo extends BasePath {
- type: 'horizontalLineTo'
- x: number
-}
-
-export interface AngledLineTo extends BasePath {
- type: 'angledLineTo'
- angle: number
- x?: number
- y?: number
-}
-
-interface GeoMeta {
- __geoMeta: {
- id: string
- sourceRange: SourceRange
- }
-}
-
-export type Path = ToPoint | HorizontalLineTo | AngledLineTo | Base
-
-export interface SketchGroup {
- type: 'sketchGroup'
- id: string
- value: Path[]
- start?: Base
- position: Position
- rotation: Rotation
- __meta: Metadata[]
-}
-
-interface ExtrudePlane {
- type: 'extrudePlane'
- position: Position
- rotation: Rotation
- name?: string
-}
-
-export type ExtrudeSurface = GeoMeta &
- ExtrudePlane /* | ExtrudeRadius | ExtrudeSpline */
-
-export interface ExtrudeGroup {
- type: 'extrudeGroup'
- id: string
- value: ExtrudeSurface[]
- height: number
- position: Position
- rotation: Rotation
- __meta: Metadata[]
-}
-
-/** UserVal not produced by one of our internal functions */
-export interface UserVal {
- type: 'userVal'
- value: any
- __meta: Metadata[]
-}
-
-type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
+export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: MemoryItem
@@ -102,12 +27,12 @@ interface Memory {
export interface ProgramMemory {
root: Memory
- return?: ProgramReturn
+ return: ProgramReturn | null
}
export const executor = async (
node: Program,
- programMemory: ProgramMemory = { root: {} },
+ programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
@@ -132,7 +57,7 @@ export const executor = async (
export const _executor = async (
node: Program,
- programMemory: ProgramMemory = { root: {} },
+ programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager
): Promise => {
try {
diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts
index 33479f3c3..8e6d8994f 100644
--- a/src/lang/modifyAst.test.ts
+++ b/src/lang/modifyAst.test.ts
@@ -176,7 +176,7 @@ show(part001)`
})
describe('Testing moveValueIntoNewVariable', () => {
- const fn = (fnName: string) => `const ${fnName} = (x) => {
+ const fn = (fnName: string) => `fn ${fnName} = (x) => {
return x
}
`
diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts
index 512518e67..8cb3f7462 100644
--- a/src/lang/modifyAst.ts
+++ b/src/lang/modifyAst.ts
@@ -27,6 +27,48 @@ import {
getFirstArg,
createFirstArg,
} from './std/sketch'
+import { isLiteralArrayOrStatic } from './std/sketchcombos'
+
+export function addStartSketch(
+ node: Program,
+ start: [number, number],
+ end: [number, number]
+): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
+ const _node = { ...node }
+ const _name = findUniqueName(node, 'part')
+
+ const startSketchAt = createCallExpression('startSketchAt', [
+ createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
+ ])
+ const initialLineTo = createCallExpression('line', [
+ createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
+ createPipeSubstitution(),
+ ])
+
+ const pipeBody = [startSketchAt, initialLineTo]
+
+ const variableDeclaration = createVariableDeclaration(
+ _name,
+ createPipeExpression(pipeBody)
+ )
+
+ const newIndex = node.body.length
+ _node.body = [...node.body, variableDeclaration]
+
+ let pathToNode: PathToNode = [
+ ['body', ''],
+ [newIndex.toString(10), 'index'],
+ ['declarations', 'VariableDeclaration'],
+ ['0', 'index'],
+ ['init', 'VariableDeclarator'],
+ ]
+
+ return {
+ modifiedAst: _node,
+ id: _name,
+ pathToNode,
+ }
+}
export function addSketchTo(
node: Program,
@@ -151,7 +193,7 @@ export function mutateArrExp(
): boolean {
if (node.type === 'ArrayExpression') {
node.elements.forEach((element, i) => {
- if (element.type === 'Literal') {
+ if (isLiteralArrayOrStatic(element)) {
node.elements[i] = updateWith.elements[i]
}
})
@@ -169,8 +211,8 @@ export function mutateObjExpProp(
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
if (keyIndex !== -1) {
if (
- updateWith.type === 'Literal' &&
- node.properties[keyIndex].value.type === 'Literal'
+ isLiteralArrayOrStatic(updateWith) &&
+ isLiteralArrayOrStatic(node.properties[keyIndex].value)
) {
node.properties[keyIndex].value = updateWith
return true
@@ -180,7 +222,7 @@ export function mutateObjExpProp(
) {
const arrExp = node.properties[keyIndex].value as ArrayExpression
arrExp.elements.forEach((element, i) => {
- if (element.type === 'Literal') {
+ if (isLiteralArrayOrStatic(element)) {
arrExp.elements[i] = updateWith.elements[i]
}
})
diff --git a/src/lang/recast.test.ts b/src/lang/recast.test.ts
index c90dee500..d0e3653ff 100644
--- a/src/lang/recast.test.ts
+++ b/src/lang/recast.test.ts
@@ -224,7 +224,7 @@ const key = 'c'
expect(recasted).toBe(code)
})
it('comments in a fn block', () => {
- const code = `const myFn = () => {
+ const code = `fn myFn = () => {
// this is a comment
const yo = { a: { b: { c: '123' } } }
diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts
index 8d7afed81..b51953872 100644
--- a/src/lang/std/engineConnection.ts
+++ b/src/lang/std/engineConnection.ts
@@ -6,6 +6,8 @@ import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid'
import * as Sentry from '@sentry/react'
+let lastMessage = ''
+
interface CommandInfo {
commandType: CommandTypes
range: SourceRange
@@ -754,6 +756,13 @@ export class EngineCommandManager {
})
}
sendSceneCommand(command: EngineCommand): Promise {
+ if (
+ command.type === 'modeling_cmd_req' &&
+ command.cmd.type !== lastMessage
+ ) {
+ console.log('sending command', command.cmd.type)
+ lastMessage = command.cmd.type
+ }
if (!this.engineConnection?.isReady()) {
console.log('socket not ready')
return Promise.resolve()
@@ -761,7 +770,8 @@ export class EngineCommandManager {
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
const cmd = command.cmd
if (
- cmd.type === 'camera_drag_move' &&
+ (cmd.type === 'camera_drag_move' ||
+ cmd.type === 'handle_mouse_drag_move') &&
this.engineConnection?.unreliableDataChannel
) {
cmd.sequence = this.outSequence
diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts
index 1e9cc8554..9022affc0 100644
--- a/src/lang/std/sketch.ts
+++ b/src/lang/std/sketch.ts
@@ -4,6 +4,7 @@ import {
SketchGroup,
SourceRange,
PathToNode,
+ MemoryItem,
} from '../executor'
import {
Program,
@@ -19,8 +20,9 @@ import {
getNodeFromPathCurry,
getNodePathFromSourceRange,
} from '../queryAst'
+import { isLiteralArrayOrStatic } from './sketchcombos'
import { GuiModes, toolTips, TooTip } from '../../useStore'
-import { splitPathAtPipeExpression } from '../modifyAst'
+import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid'
import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes'
@@ -185,7 +187,7 @@ export const line: SketchLineHelper = {
createCallback,
}) => {
const _node = { ...node }
- const { node: pipe } = getNodeFromPath(
+ const { node: pipe } = getNodeFromPath(
_node,
pathToNode,
'PipeExpression'
@@ -197,12 +199,12 @@ export const line: SketchLineHelper = {
)
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
- if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
+ if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
- if (replaceExisting && createCallback) {
+ if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback(
[newXVal, newYVal],
@@ -220,7 +222,11 @@ export const line: SketchLineHelper = {
createArrayExpression([newXVal, newYVal]),
createPipeSubstitution(),
])
- pipe.body = [...pipe.body, callExp]
+ if (pipe.type === 'PipeExpression') {
+ pipe.body = [...pipe.body, callExp]
+ } else {
+ varDec.init = createPipeExpression([varDec.init, callExp])
+ }
return {
modifiedAst: _node,
pathToNode,
@@ -238,22 +244,10 @@ export const line: SketchLineHelper = {
createLiteral(roundOff(to[1] - from[1], 2)),
])
- if (
- callExpression.arguments?.[0].type === 'Literal' &&
- callExpression.arguments?.[0].value === 'default'
- ) {
- callExpression.arguments[0] = toArrExp
- } else if (callExpression.arguments?.[0].type === 'ObjectExpression') {
+ if (callExpression.arguments?.[0].type === 'ObjectExpression') {
const toProp = callExpression.arguments?.[0].properties?.find(
({ key }) => key.name === 'to'
)
- if (
- toProp &&
- toProp.value.type === 'Literal' &&
- toProp.value.value === 'default'
- ) {
- toProp.value = toArrExp
- }
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
} else {
mutateArrExp(callExpression.arguments?.[0], toArrExp)
@@ -301,7 +295,7 @@ export const xLineTo: SketchLineHelper = {
pathToNode
)
const newX = createLiteral(roundOff(to[0], 2))
- if (callExpression.arguments?.[0]?.type === 'Literal') {
+ if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newX
} else {
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
@@ -349,7 +343,7 @@ export const yLineTo: SketchLineHelper = {
pathToNode
)
const newY = createLiteral(roundOff(to[1], 2))
- if (callExpression.arguments?.[0]?.type === 'Literal') {
+ if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newY
} else {
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
@@ -399,7 +393,7 @@ export const xLine: SketchLineHelper = {
pathToNode
)
const newX = createLiteral(roundOff(to[0] - from[0], 2))
- if (callExpression.arguments?.[0]?.type === 'Literal') {
+ if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newX
} else {
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
@@ -443,7 +437,7 @@ export const yLine: SketchLineHelper = {
pathToNode
)
const newY = createLiteral(roundOff(to[1] - from[1], 2))
- if (callExpression.arguments?.[0]?.type === 'Literal') {
+ if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newY
} else {
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
@@ -546,7 +540,7 @@ export const angledLineOfXLength: SketchLineHelper = {
)
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
- if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
+ if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
const newLine = createCallback
@@ -619,7 +613,7 @@ export const angledLineOfYLength: SketchLineHelper = {
)
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
- if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
+ if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
@@ -876,7 +870,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
const varName = varDec.declarations[0].id.name
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
const intersectPath = sketchGroup.value.find(
- ({ name }) => name === intersectTagName
+ ({ name }: Path) => name === intersectTagName
)
let offset = 0
if (intersectPath) {
@@ -968,60 +962,14 @@ export function addNewSketchLn({
pathToNode,
'VariableDeclarator'
)
- const { node: pipeExp, shallowPath: pipePath } =
- getNodeFromPath(node, pathToNode, 'PipeExpression')
- const maybeStartSketchAt = pipeExp.body.find(
- (exp) =>
- exp.type === 'CallExpression' &&
- exp.callee.name === 'startSketchAt' &&
- exp.arguments[0].type === 'Literal' &&
- exp.arguments[0].value === 'default'
- )
- const maybeDefaultLine = pipeExp.body.findIndex(
- (exp) =>
- exp.type === 'CallExpression' &&
- exp.callee.name === 'line' &&
- exp.arguments[0].type === 'Literal' &&
- exp.arguments[0].value === 'default'
- )
- const defaultLinePath: PathToNode = [
- ...pipePath,
- ['body', ''],
- [maybeDefaultLine, ''],
- ]
+ const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath<
+ PipeExpression | CallExpression
+ >(node, pathToNode, 'PipeExpression')
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
- if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
+ if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
- if (maybeStartSketchAt) {
- const startSketchAt = maybeStartSketchAt as any
- startSketchAt.arguments[0] = createArrayExpression([
- createLiteral(to[0]),
- createLiteral(to[1]),
- ])
- return {
- modifiedAst: node,
- }
- }
- if (maybeDefaultLine !== -1) {
- const defaultLine = getNodeFromPath(
- node,
- defaultLinePath
- ).node
- const { from } = getSketchSegmentFromSourceRange(sketch, [
- defaultLine.start,
- defaultLine.end,
- ]).segment
- return updateArgs({
- node,
- previousProgramMemory,
- pathToNode: defaultLinePath,
- to,
- from,
- })
- }
-
- const last = sketch.value[sketch.value.length - 1]
+ const last = sketch.value[sketch.value.length - 1] || sketch.start
const from = last.to
return add({
@@ -1089,10 +1037,11 @@ export function addTagForSketchOnFace(
function isAngleLiteral(lineArugement: Value): boolean {
return lineArugement?.type === 'ArrayExpression'
- ? lineArugement.elements[0].type === 'Literal'
+ ? isLiteralArrayOrStatic(lineArugement.elements[0])
: lineArugement?.type === 'ObjectExpression'
- ? lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
- .type === 'Literal'
+ ? isLiteralArrayOrStatic(
+ lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
+ )
: false
}
@@ -1198,14 +1147,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
} {
// used for lineTo, line
const firstArg = callExpression.arguments[0]
- if (firstArg.type === 'Literal' && firstArg.value === 'default') {
- return {
- val:
- callExpression.callee.name === 'startSketchAt'
- ? [createLiteral(0), createLiteral(0)]
- : [createLiteral(1), createLiteral(1)],
- }
- }
if (firstArg.type === 'ArrayExpression') {
return { val: [firstArg.elements[0], firstArg.elements[1]] }
}
@@ -1215,8 +1156,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
if (to?.type === 'ArrayExpression') {
const [x, y] = to.elements
return { val: [x, y], tag }
- } else if (to?.type === 'Literal' && to.value === 'default') {
- return { val: [createLiteral(0), createLiteral(0)], tag }
}
}
throw new Error('expected ArrayExpression or ObjectExpression')
diff --git a/src/lang/std/sketchConstraints.test.ts b/src/lang/std/sketchConstraints.test.ts
index 3596cb8f1..819329d8c 100644
--- a/src/lang/std/sketchConstraints.test.ts
+++ b/src/lang/std/sketchConstraints.test.ts
@@ -401,6 +401,11 @@ show(part001)`
programMemory.root['part001'] as SketchGroup,
[index, index]
).segment
- expect(segment).toEqual({ to: [0, 0.04], from: [0, 0.04], name: '' })
+ expect(segment).toEqual({
+ to: [0, 0.04],
+ from: [0, 0.04],
+ name: '',
+ type: 'base',
+ })
})
})
diff --git a/src/lang/std/sketchConstraints.ts b/src/lang/std/sketchConstraints.ts
index e71512333..59fab546e 100644
--- a/src/lang/std/sketchConstraints.ts
+++ b/src/lang/std/sketchConstraints.ts
@@ -4,7 +4,7 @@ import {
VariableDeclarator,
CallExpression,
} from '../abstractSyntaxTreeTypes'
-import { SketchGroup, SourceRange } from '../executor'
+import { SketchGroup, SourceRange, Path } from '../executor'
export function getSketchSegmentFromSourceRange(
sketchGroup: SketchGroup,
@@ -20,10 +20,10 @@ export function getSketchSegmentFromSourceRange(
startSourceRange[1] >= rangeEnd &&
sketchGroup.start
)
- return { segment: sketchGroup.start, index: -1 }
+ return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
const lineIndex = sketchGroup.value.findIndex(
- ({ __geoMeta: { sourceRange } }) =>
+ ({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketchGroup.value[lineIndex]
diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts
index 00e892e0e..7b0dc3314 100644
--- a/src/lang/std/sketchcombos.ts
+++ b/src/lang/std/sketchcombos.ts
@@ -28,6 +28,7 @@ import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { PathToNode, ProgramMemory } from '../executor'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
+import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
type LineInputsType =
| 'xAbsolute'
@@ -1136,27 +1137,18 @@ export function getRemoveConstraintsTransform(
// check if the function is locked down and so can't be transformed
const firstArg = getFirstArg(sketchFnExp)
- if (Array.isArray(firstArg.val)) {
- const [a, b] = firstArg.val
- if (a?.type !== 'Literal' || b?.type !== 'Literal') {
- return transformInfo
- }
- } else {
- if (firstArg.val?.type !== 'Literal') {
- return transformInfo
- }
+ if (isNotLiteralArrayOrStatic(firstArg.val)) {
+ return transformInfo
}
// check if the function has no constraints
const isTwoValFree =
- Array.isArray(firstArg.val) &&
- firstArg.val?.[0]?.type === 'Literal' &&
- firstArg.val?.[1]?.type === 'Literal'
+ Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
if (isTwoValFree) {
return false
}
const isOneValFree =
- !Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
+ !Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
if (isOneValFree) {
return transformInfo
}
@@ -1187,25 +1179,12 @@ function getTransformMapPath(
// check if the function is locked down and so can't be transformed
const firstArg = getFirstArg(sketchFnExp)
- if (Array.isArray(firstArg.val)) {
- const [a, b] = firstArg.val
- if (a?.type !== 'Literal' && b?.type !== 'Literal') {
- return false
- }
- } else {
- if (firstArg.val?.type !== 'Literal') {
- return false
- }
+ if (isNotLiteralArrayOrStatic(firstArg.val)) {
+ return false
}
// check if the function has no constraints
- const isTwoValFree =
- Array.isArray(firstArg.val) &&
- firstArg.val?.[0]?.type === 'Literal' &&
- firstArg.val?.[1]?.type === 'Literal'
- const isOneValFree =
- !Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
- if (isTwoValFree || isOneValFree) {
+ if (isLiteralArrayOrStatic(firstArg.val)) {
const info = transformMap?.[name]?.free?.[constraintType]
if (info)
return {
@@ -1259,7 +1238,7 @@ export function getConstraintType(
if (fnName === 'xLineTo') return 'yAbsolute'
if (fnName === 'yLineTo') return 'xAbsolute'
} else {
- const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
+ const isFirstArgLockedDown = isNotLiteralArrayOrStatic(val[0])
if (fnName === 'line')
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
if (fnName === 'lineTo')
@@ -1452,7 +1431,7 @@ export function transformAstSketchLines({
const varName = varDec.id.name
const sketchGroup = programMemory.root?.[varName]
- if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
+ if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
throw new Error('not a sketch group')
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
const referencedSegment = referencedSegmentRange
@@ -1538,23 +1517,46 @@ export function getConstraintLevelFromSourceRange(
const firstArg = getFirstArg(sketchFnExp)
// check if the function is fully constrained
- if (Array.isArray(firstArg.val)) {
- const [a, b] = firstArg.val
- if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full'
- } else {
- if (firstArg.val?.type !== 'Literal') return 'full'
+ if (isNotLiteralArrayOrStatic(firstArg.val)) {
+ return 'full'
}
// check if the function has no constraints
const isTwoValFree =
- Array.isArray(firstArg.val) &&
- firstArg.val?.[0]?.type === 'Literal' &&
- firstArg.val?.[1]?.type === 'Literal'
+ Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
const isOneValFree =
- !Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
+ !Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
if (isTwoValFree) return 'free'
if (isOneValFree) return 'partial'
return 'partial'
}
+
+export function isLiteralArrayOrStatic(
+ val: Value | [Value, Value] | [Value, Value, Value] | undefined
+): boolean {
+ if (!val) return false
+
+ if (Array.isArray(val)) {
+ const [a, b] = val
+ return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
+ }
+ return (
+ val.type === 'Literal' ||
+ (val.type === 'UnaryExpression' && val.argument.type === 'Literal')
+ )
+}
+
+export function isNotLiteralArrayOrStatic(
+ val: Value | [Value, Value] | [Value, Value, Value]
+): boolean {
+ if (Array.isArray(val)) {
+ const [a, b] = val
+ return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
+ }
+ return (
+ (val.type !== 'Literal' && val.type !== 'UnaryExpression') ||
+ (val.type === 'UnaryExpression' && val.argument.type !== 'Literal')
+ )
+}
diff --git a/src/lang/tokeniser.test.ts b/src/lang/tokeniser.test.ts
index 2a33a3a34..a21fa03f7 100644
--- a/src/lang/tokeniser.test.ts
+++ b/src/lang/tokeniser.test.ts
@@ -131,10 +131,12 @@ const yi=45`
})
it('test negative and decimal numbers', () => {
expect(stringSummaryLexer('-1')).toEqual([
- "number '-1' from 0 to 2",
+ "operator '-' from 0 to 1",
+ "number '1' from 1 to 2",
])
expect(stringSummaryLexer('-1.5')).toEqual([
- "number '-1.5' from 0 to 4",
+ "operator '-' from 0 to 1",
+ "number '1.5' from 1 to 4",
])
expect(stringSummaryLexer('1.5')).toEqual([
"number '1.5' from 0 to 3",
@@ -158,10 +160,12 @@ const yi=45`
"whitespace ' ' from 3 to 4",
"operator '+' from 4 to 5",
"whitespace ' ' from 5 to 6",
- "number '-2.5' from 6 to 10",
+ "operator '-' from 6 to 7",
+ "number '2.5' from 7 to 10",
])
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
- "number '-1.5' from 0 to 4",
+ "operator '-' from 0 to 1",
+ "number '1.5' from 1 to 4",
"whitespace ' ' from 4 to 5",
"operator '+' from 5 to 6",
"whitespace ' ' from 6 to 7",
diff --git a/src/lib/tauriFS.ts b/src/lib/tauriFS.ts
index 9c7782c6a..a93e30142 100644
--- a/src/lib/tauriFS.ts
+++ b/src/lib/tauriFS.ts
@@ -5,7 +5,7 @@ import {
readDir,
writeTextFile,
} from '@tauri-apps/api/fs'
-import { documentDir } from '@tauri-apps/api/path'
+import { documentDir, homeDir } from '@tauri-apps/api/path'
import { isTauri } from './isTauri'
import { ProjectWithEntryPointMetadata } from '../Router'
import { metadata } from 'tauri-plugin-fs-extra-api'
@@ -32,7 +32,13 @@ export async function initializeProjectDirectory(directory: string) {
return directory
}
- const docDirectory = await documentDir()
+ let docDirectory: string
+ try {
+ docDirectory = await documentDir()
+ } catch (e) {
+ console.log(e)
+ docDirectory = await homeDir() // seems to work better on Linux
+ }
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER
diff --git a/src/lib/testHelpers.ts b/src/lib/testHelpers.ts
index 82d2e1b12..626502de8 100644
--- a/src/lib/testHelpers.ts
+++ b/src/lib/testHelpers.ts
@@ -49,7 +49,7 @@ class MockEngineCommandManager {
export async function enginelessExecutor(
ast: Program,
- pm: ProgramMemory = { root: {} }
+ pm: ProgramMemory = { root: {}, return: null }
): Promise {
const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {},
@@ -64,7 +64,7 @@ export async function enginelessExecutor(
export async function executor(
ast: Program,
- pm: ProgramMemory = { root: {} }
+ pm: ProgramMemory = { root: {}, return: null }
): Promise {
const engineCommandManager = new EngineCommandManager({
setIsStreamReady: () => {},
diff --git a/src/useStore.ts b/src/useStore.ts
index c609918db..9113e4b74 100644
--- a/src/useStore.ts
+++ b/src/useStore.ts
@@ -54,9 +54,12 @@ export type TooTip =
| 'yLineTo'
| 'angledLineThatIntersects'
-export const toolTips: TooTip[] = [
- 'lineTo',
+export const toolTips = [
+ 'sketch_line',
+ 'move',
+ // original tooltips
'line',
+ 'lineTo',
'angledLine',
'angledLineOfXLength',
'angledLineOfYLength',
@@ -67,7 +70,7 @@ export const toolTips: TooTip[] = [
'xLineTo',
'yLineTo',
'angledLineThatIntersects',
-]
+] as any as TooTip[]
export type GuiModes =
| {
@@ -77,6 +80,7 @@ export type GuiModes =
mode: 'sketch'
sketchMode: TooTip
isTooltip: true
+ waitingFirstClick: boolean
rotation: Rotation
position: Position
id?: string
@@ -95,6 +99,7 @@ export type GuiModes =
}
| {
mode: 'canEditSketch'
+ pathId: string
pathToNode: PathToNode
rotation: Rotation
position: Position
@@ -133,8 +138,8 @@ export interface StoreState {
kclErrors: KCLError[]
addKCLError: (err: KCLError) => void
resetKCLErrors: () => void
- ast: Program | null
- setAst: (ast: Program | null) => void
+ ast: Program
+ setAst: (ast: Program) => void
updateAst: (
ast: Program,
optionalParams?: {
@@ -233,12 +238,13 @@ export const useStore = create()(
}
})
setTimeout(() => {
- editorView.dispatch({
- selection: EditorSelection.create(
- ranges,
- selections.codeBasedSelections.length - 1
- ),
- })
+ ranges.length &&
+ editorView.dispatch({
+ selection: EditorSelection.create(
+ ranges,
+ selections.codeBasedSelections.length - 1
+ ),
+ })
})
},
setCursor2: (codeSelections) => {
@@ -292,7 +298,15 @@ export const useStore = create()(
resetKCLErrors: () => {
set({ kclErrors: [] })
},
- ast: null,
+ ast: {
+ start: 0,
+ end: 0,
+ body: [],
+ nonCodeMeta: {
+ noneCodeNodes: {},
+ start: null,
+ },
+ },
setAst: (ast) => {
set({ ast })
},
@@ -301,7 +315,11 @@ export const useStore = create()(
const astWithUpdatedSource = parser_wasm(newCode)
callBack(astWithUpdatedSource)
- set({ ast: astWithUpdatedSource, code: newCode })
+ set({
+ ast: astWithUpdatedSource,
+ code: newCode,
+ defferedCode: newCode,
+ })
if (focusPath) {
const { node } = getNodeFromPath(
astWithUpdatedSource,
@@ -353,7 +371,7 @@ export const useStore = create()(
setError: (error = '') => {
set({ errorState: { isError: !!error, error } })
},
- programMemory: { root: {}, pendingMemory: {} },
+ programMemory: { root: {}, return: null },
setProgramMemory: (programMemory) => set({ programMemory }),
isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock
index 70e633be9..fbc019723 100644
--- a/src/wasm-lib/Cargo.lock
+++ b/src/wasm-lib/Cargo.lock
@@ -150,7 +150,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -229,6 +229,32 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bindgen"
+version = "0.64.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
+dependencies = [
+ "bitflags 1.3.2",
+ "cexpr",
+ "clang-sys",
+ "lazy_static",
+ "lazycell",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "bit_field"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -290,6 +316,12 @@ version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -314,6 +346,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -336,6 +377,17 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "clang-sys"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
[[package]]
name = "clap"
version = "3.2.25"
@@ -400,7 +452,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -418,6 +470,12 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
[[package]]
name = "colorchoice"
version = "1.0.0"
@@ -482,6 +540,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
@@ -492,6 +559,30 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
@@ -501,6 +592,12 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -521,7 +618,7 @@ dependencies = [
"hashbrown 0.14.0",
"lock_api",
"once_cell",
- "parking_lot_core",
+ "parking_lot_core 0.9.8",
]
[[package]]
@@ -548,7 +645,7 @@ dependencies = [
"quote",
"serde",
"serde_tokenstream",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -562,7 +659,7 @@ dependencies = [
"quote",
"serde",
"serde_tokenstream",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -667,12 +764,85 @@ dependencies = [
"similar",
]
+[[package]]
+name = "exr"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18"
+dependencies = [
+ "bit_field",
+ "flume",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
[[package]]
name = "fastrand"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+[[package]]
+name = "fdeflate"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "ffmpeg-next"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8af03c47ad26832ab3aabc4cdbf210af3d3b878783edd5a7ba044ba33aab7a60"
+dependencies = [
+ "bitflags 1.3.2",
+ "ffmpeg-sys-next",
+ "libc",
+]
+
+[[package]]
+name = "ffmpeg-sys-next"
+version = "6.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf650f461ccf130f4eef4927affed703cc387b183bfc4a7dfee86a076c131127"
+dependencies = [
+ "bindgen",
+ "cc",
+ "libc",
+ "num_cpus",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "flume"
+version = "0.10.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "nanorand",
+ "pin-project",
+ "spin 0.9.8",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -763,7 +933,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -819,12 +989,28 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "gif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
[[package]]
name = "gimli"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
[[package]]
name = "gloo-utils"
version = "0.2.0"
@@ -857,6 +1043,15 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "half"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
+dependencies = [
+ "crunchy",
+]
+
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1010,6 +1205,37 @@ dependencies = [
"unicode-normalization",
]
+[[package]]
+name = "image"
+version = "0.24.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "exr",
+ "gif",
+ "jpeg-decoder",
+ "num-rational",
+ "num-traits",
+ "png",
+ "qoi",
+ "tiff",
+]
+
+[[package]]
+name = "image-compare"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "419d59423b7202f6a2a95d3b9cf11a8cc9bf83a29cf7dda4d617a90e8c5ccfcf"
+dependencies = [
+ "image",
+ "itertools 0.10.5",
+ "rayon",
+ "thiserror",
+]
+
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -1031,6 +1257,18 @@ dependencies = [
"hashbrown 0.14.0",
]
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "ipnet"
version = "2.8.0"
@@ -1072,6 +1310,15 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
+dependencies = [
+ "rayon",
+]
+
[[package]]
name = "js-sys"
version = "0.3.64"
@@ -1131,18 +1378,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
dependencies = [
"anyhow",
+ "async-trait",
"base64 0.21.2",
"bytes",
"chrono",
"data-encoding",
+ "format_serde_error",
+ "futures",
+ "http",
"itertools 0.10.5",
+ "log",
"parse-display",
"phonenumber",
+ "rand",
+ "reqwest",
+ "reqwest-conditional-middleware",
+ "reqwest-middleware 0.2.3",
+ "reqwest-retry",
+ "reqwest-tracing",
"schemars",
"serde",
"serde_bytes",
"serde_json",
+ "serde_urlencoded",
"thiserror",
+ "tracing",
"url",
"uuid",
]
@@ -1153,12 +1413,34 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "lebe"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -1212,12 +1494,27 @@ dependencies = [
"url",
]
+[[package]]
+name = "matchit"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"
+
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "mime"
version = "0.3.17"
@@ -1247,6 +1544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
+ "simd-adler32",
]
[[package]]
@@ -1260,6 +1558,15 @@ dependencies = [
"windows-sys 0.48.0",
]
+[[package]]
+name = "nanorand"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
+dependencies = [
+ "getrandom",
+]
+
[[package]]
name = "newline-converter"
version = "0.3.0"
@@ -1300,6 +1607,17 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-traits"
version = "0.2.16"
@@ -1380,7 +1698,7 @@ dependencies = [
"rand",
"regex",
"reqwest",
- "reqwest-middleware",
+ "reqwest-middleware 0.1.6",
"rustfmt-wrapper",
"schemars",
"serde",
@@ -1415,12 +1733,42 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+[[package]]
+name = "opentelemetry"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8"
+dependencies = [
+ "async-trait",
+ "crossbeam-channel",
+ "futures-channel",
+ "futures-executor",
+ "futures-util",
+ "js-sys",
+ "lazy_static",
+ "percent-encoding",
+ "pin-project",
+ "rand",
+ "thiserror",
+]
+
[[package]]
name = "os_str_bytes"
version = "6.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -1428,7 +1776,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core",
+ "parking_lot_core 0.9.8",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
]
[[package]]
@@ -1467,9 +1829,15 @@ dependencies = [
"regex",
"regex-syntax 0.7.4",
"structmeta",
- "syn 2.0.29",
+ "syn 2.0.32",
]
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
[[package]]
name = "percent-encoding"
version = "2.3.0"
@@ -1523,7 +1891,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -1538,6 +1906,25 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "png"
+version = "0.17.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1587,6 +1974,15 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
[[package]]
name = "quick-xml"
version = "0.28.2"
@@ -1641,6 +2037,28 @@ dependencies = [
"getrandom",
]
+[[package]]
+name = "rayon"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -1757,6 +2175,18 @@ dependencies = [
"winreg",
]
+[[package]]
+name = "reqwest-conditional-middleware"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277"
+dependencies = [
+ "async-trait",
+ "reqwest",
+ "reqwest-middleware 0.2.3",
+ "task-local-extensions",
+]
+
[[package]]
name = "reqwest-middleware"
version = "0.1.6"
@@ -1773,6 +2203,73 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "reqwest-middleware"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff44108c7925d082f2861e683a88618b68235ad9cdc60d64d9d1188efc951cdb"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "http",
+ "reqwest",
+ "serde",
+ "task-local-extensions",
+ "thiserror",
+]
+
+[[package]]
+name = "reqwest-retry"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c6a11c05102e5bec712c0619b8c7b7eda8b21a558a0bd981ceee15c38df8be4"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "chrono",
+ "futures",
+ "getrandom",
+ "http",
+ "hyper",
+ "parking_lot 0.11.2",
+ "reqwest",
+ "reqwest-middleware 0.2.3",
+ "retry-policies",
+ "task-local-extensions",
+ "tokio",
+ "tracing",
+ "wasm-timer",
+]
+
+[[package]]
+name = "reqwest-tracing"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14b1e66540e0cac90acadaf7109bf99c90d95abcc94b4c096bfa16a2d7aa7a71"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "getrandom",
+ "matchit",
+ "opentelemetry",
+ "reqwest",
+ "reqwest-middleware 0.2.3",
+ "task-local-extensions",
+ "tracing",
+ "tracing-opentelemetry",
+]
+
+[[package]]
+name = "retry-policies"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "rand",
+]
+
[[package]]
name = "ring"
version = "0.16.20"
@@ -1782,7 +2279,7 @@ dependencies = [
"cc",
"libc",
"once_cell",
- "spin",
+ "spin 0.5.2",
"untrusted",
"web-sys",
"winapi",
@@ -1794,6 +2291,12 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
[[package]]
name = "rustfmt-wrapper"
version = "0.2.0"
@@ -2005,7 +2508,7 @@ checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2021,9 +2524,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.105"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
+checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
dependencies = [
"indexmap 2.0.0",
"itoa",
@@ -2039,7 +2542,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2051,7 +2554,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2102,6 +2605,21 @@ dependencies = [
"digest",
]
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@@ -2111,6 +2629,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
[[package]]
name = "similar"
version = "2.2.1"
@@ -2223,6 +2747,15 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
[[package]]
name = "strsim"
version = "0.10.0"
@@ -2238,7 +2771,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2249,7 +2782,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2265,9 +2798,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.29"
+version = "2.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
dependencies = [
"proc-macro2",
"quote",
@@ -2354,7 +2887,7 @@ checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2367,6 +2900,17 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "tiff"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
[[package]]
name = "time"
version = "0.1.45"
@@ -2434,7 +2978,7 @@ dependencies = [
"libc",
"mio",
"num_cpus",
- "parking_lot",
+ "parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.5.3",
@@ -2450,7 +2994,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2566,7 +3110,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2595,7 +3139,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
]
[[package]]
@@ -2605,6 +3149,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-opentelemetry"
+version = "0.17.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f"
+dependencies = [
+ "once_cell",
+ "opentelemetry",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+dependencies = [
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
]
[[package]]
@@ -2644,7 +3225,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
"termcolor",
]
@@ -2668,6 +3249,19 @@ dependencies = [
"utf-8",
]
+[[package]]
+name = "twenty-twenty"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77f4c2039b998d194da9717843bb8c634609b5ccacac513be56985569f6e732"
+dependencies = [
+ "anyhow",
+ "ffmpeg-next",
+ "image",
+ "image-compare",
+ "uuid",
+]
+
[[package]]
name = "typenum"
version = "1.16.0"
@@ -2769,6 +3363,18 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
[[package]]
name = "version_check"
version = "0.9.4"
@@ -2827,7 +3433,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
"wasm-bindgen-shared",
]
@@ -2862,7 +3468,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn 2.0.32",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2877,14 +3483,20 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
name = "wasm-lib"
version = "0.1.0"
dependencies = [
+ "anyhow",
"bson",
"futures",
"gloo-utils",
+ "image",
"js-sys",
"kcl-lib",
"kittycad",
+ "reqwest",
"serde_json",
+ "tokio",
"tower-lsp",
+ "twenty-twenty",
+ "uuid",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
@@ -2904,6 +3516,21 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "wasm-timer"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
+dependencies = [
+ "futures",
+ "js-sys",
+ "parking_lot 0.11.2",
+ "pin-utils",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "web-sys"
version = "0.3.64"
@@ -2920,6 +3547,12 @@ version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -3125,3 +3758,12 @@ name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
diff --git a/src/wasm-lib/Cargo.toml b/src/wasm-lib/Cargo.toml
index 72481a712..ebd0ee0d9 100644
--- a/src/wasm-lib/Cargo.toml
+++ b/src/wasm-lib/Cargo.toml
@@ -12,10 +12,19 @@ bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" }
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
-serde_json = "1.0.93"
+serde_json = "1.0.106"
wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37"
+[dev-dependencies]
+anyhow = "1"
+image = "0.24.7"
+kittycad = "0.2.25"
+reqwest = { version = "0.11.20", default-features = false }
+tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
+twenty-twenty = "0.6.1"
+uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
+
[target.'cfg(target_arch = "wasm32")'.dependencies]
futures = "0.3.28"
js-sys = "0.3.64"
diff --git a/src/wasm-lib/derive-docs/Cargo.toml b/src/wasm-lib/derive-docs/Cargo.toml
index 7037177c0..a1e0b87e1 100644
--- a/src/wasm-lib/derive-docs/Cargo.toml
+++ b/src/wasm-lib/derive-docs/Cargo.toml
@@ -16,7 +16,7 @@ proc-macro2 = "1"
quote = "1"
serde = { version = "1.0.186", features = ["derive"] }
serde_tokenstream = "0.2"
-syn = { version = "2.0.29", features = ["full"] }
+syn = { version = "2.0.32", features = ["full"] }
[dev-dependencies]
expectorate = "1.0.7"
diff --git a/src/wasm-lib/kcl/Cargo.toml b/src/wasm-lib/kcl/Cargo.toml
index cbd3cfebe..a676ac9b5 100644
--- a/src/wasm-lib/kcl/Cargo.toml
+++ b/src/wasm-lib/kcl/Cargo.toml
@@ -19,7 +19,7 @@ parse-display = "0.8.2"
regex = "1.7.1"
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
serde = {version = "1.0.152", features = ["derive"] }
-serde_json = "1.0.93"
+serde_json = "1.0.106"
thiserror = "1.0.47"
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
diff --git a/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs b/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs
index 55d1a0e1e..aa4086c60 100644
--- a/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs
+++ b/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs
@@ -12,7 +12,7 @@ use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, R
use crate::{
engine::EngineConnection,
errors::{KclError, KclErrorDetails},
- executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange},
+ executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
parser::PIPE_OPERATOR,
};
@@ -449,6 +449,7 @@ pub enum BinaryPart {
BinaryExpression(Box),
CallExpression(Box),
UnaryExpression(Box),
+ MemberExpression(Box),
}
impl From for crate::executor::SourceRange {
@@ -471,6 +472,7 @@ impl BinaryPart {
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
+ BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
}
}
@@ -481,6 +483,7 @@ impl BinaryPart {
BinaryPart::BinaryExpression(binary_expression) => binary_expression.start(),
BinaryPart::CallExpression(call_expression) => call_expression.start(),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
+ BinaryPart::MemberExpression(member_expression) => member_expression.start(),
}
}
@@ -491,6 +494,7 @@ impl BinaryPart {
BinaryPart::BinaryExpression(binary_expression) => binary_expression.end(),
BinaryPart::CallExpression(call_expression) => call_expression.end(),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
+ BinaryPart::MemberExpression(member_expression) => member_expression.end(),
}
}
@@ -523,6 +527,7 @@ impl BinaryPart {
source_ranges: vec![unary_expression.into()],
}))
}
+ BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
}
}
@@ -536,6 +541,9 @@ impl BinaryPart {
}
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
+ BinaryPart::MemberExpression(member_expression) => {
+ member_expression.get_hover_value_for_position(pos, code)
+ }
}
}
@@ -553,6 +561,9 @@ impl BinaryPart {
BinaryPart::UnaryExpression(ref mut unary_expression) => {
unary_expression.rename_identifiers(old_name, new_name)
}
+ BinaryPart::MemberExpression(ref mut member_expression) => {
+ member_expression.rename_identifiers(old_name, new_name)
+ }
}
}
}
@@ -751,12 +762,7 @@ impl CallExpression {
})
})?
.clone(),
- Value::MemberExpression(member_expression) => {
- return Err(KclError::Semantic(KclErrorDetails {
- message: format!("MemberExpression not implemented here: {:?}", member_expression),
- source_ranges: vec![member_expression.into()],
- }));
- }
+ Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
Value::FunctionExpression(function_expression) => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
@@ -1082,23 +1088,23 @@ impl Literal {
impl From for MemoryItem {
fn from(literal: Literal) -> Self {
- MemoryItem::UserVal {
+ MemoryItem::UserVal(UserVal {
value: literal.value.clone(),
meta: vec![Metadata {
source_range: literal.into(),
}],
- }
+ })
}
}
impl From<&Box> for MemoryItem {
fn from(literal: &Box) -> Self {
- MemoryItem::UserVal {
+ MemoryItem::UserVal(UserVal {
value: literal.value.clone(),
meta: vec![Metadata {
source_range: literal.into(),
}],
- }
+ })
}
}
@@ -1227,12 +1233,7 @@ impl ArrayExpression {
source_ranges: vec![pipe_substitution.into()],
}));
}
- Value::MemberExpression(member_expression) => {
- return Err(KclError::Semantic(KclErrorDetails {
- message: format!("MemberExpression not implemented here: {:?}", member_expression),
- source_ranges: vec![member_expression.into()],
- }));
- }
+ Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
Value::FunctionExpression(function_expression) => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
@@ -1245,12 +1246,12 @@ impl ArrayExpression {
results.push(result);
}
- Ok(MemoryItem::UserVal {
+ Ok(MemoryItem::UserVal(UserVal {
value: results.into(),
meta: vec![Metadata {
source_range: self.into(),
}],
- })
+ }))
}
/// Rename all identifiers that have the old name to the new given name.
@@ -1370,12 +1371,12 @@ impl ObjectExpression {
object.insert(property.key.name.clone(), result.get_json_value()?);
}
- Ok(MemoryItem::UserVal {
+ Ok(MemoryItem::UserVal(UserVal {
value: object.into(),
meta: vec![Metadata {
source_range: self.into(),
}],
- })
+ }))
}
/// Rename all identifiers that have the old name to the new given name.
@@ -1554,6 +1555,38 @@ impl MemberExpression {
None
}
+ pub fn get_result_array(&self, memory: &mut ProgramMemory, index: usize) -> Result {
+ let array = match &self.object {
+ MemberObject::MemberExpression(member_expr) => member_expr.get_result(memory)?,
+ MemberObject::Identifier(identifier) => {
+ let value = memory.get(&identifier.name, identifier.into())?;
+ value.clone()
+ }
+ }
+ .get_json_value()?;
+
+ if let serde_json::Value::Array(array) = array {
+ if let Some(value) = array.get(index) {
+ Ok(MemoryItem::UserVal(UserVal {
+ value: value.clone(),
+ meta: vec![Metadata {
+ source_range: self.into(),
+ }],
+ }))
+ } else {
+ Err(KclError::UndefinedValue(KclErrorDetails {
+ message: format!("index {} not found in array", index),
+ source_ranges: vec![self.clone().into()],
+ }))
+ }
+ } else {
+ Err(KclError::Semantic(KclErrorDetails {
+ message: format!("MemberExpression array is not an array: {:?}", array),
+ source_ranges: vec![self.clone().into()],
+ }))
+ }
+ }
+
pub fn get_result(&self, memory: &mut ProgramMemory) -> Result {
let property_name = match &self.property {
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
@@ -1562,9 +1595,12 @@ impl MemberExpression {
// Parse this as a string.
if let serde_json::Value::String(string) = value {
string
+ } else if let serde_json::Value::Number(_) = &value {
+ // It can also be a number if we are getting a member of an array.
+ return self.get_result_array(memory, parse_json_number_as_usize(&value, self.into())?);
} else {
return Err(KclError::Semantic(KclErrorDetails {
- message: format!("Expected string literal for property name, found {:?}", value),
+ message: format!("Expected string literal or number for property name, found {:?}", value),
source_ranges: vec![literal.into()],
}));
}
@@ -1582,12 +1618,12 @@ impl MemberExpression {
if let serde_json::Value::Object(map) = object {
if let Some(value) = map.get(&property_name) {
- Ok(MemoryItem::UserVal {
+ Ok(MemoryItem::UserVal(UserVal {
value: value.clone(),
meta: vec![Metadata {
source_range: self.into(),
}],
- })
+ }))
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("Property {} not found in object", property_name),
@@ -1715,12 +1751,12 @@ impl BinaryExpression {
parse_json_value_as_string(&right_json_value),
) {
let value = serde_json::Value::String(format!("{}{}", left, right));
- return Ok(MemoryItem::UserVal {
+ return Ok(MemoryItem::UserVal(UserVal {
value,
meta: vec![Metadata {
source_range: self.into(),
}],
- });
+ }));
}
}
@@ -1735,12 +1771,12 @@ impl BinaryExpression {
BinaryOperator::Mod => (left % right).into(),
};
- Ok(MemoryItem::UserVal {
+ Ok(MemoryItem::UserVal(UserVal {
value,
meta: vec![Metadata {
source_range: self.into(),
}],
- })
+ }))
}
/// Rename all identifiers that have the old name to the new given name.
@@ -1766,6 +1802,22 @@ pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange
}
}
+pub fn parse_json_number_as_usize(j: &serde_json::Value, source_range: SourceRange) -> Result {
+ if let serde_json::Value::Number(n) = &j {
+ Ok(n.as_i64().ok_or_else(|| {
+ KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![source_range],
+ message: format!("Invalid index: {}", j),
+ })
+ })? as usize)
+ } else {
+ Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![source_range],
+ message: format!("Invalid index: {}", j),
+ }))
+ }
+}
+
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option {
if let serde_json::Value::String(n) = &j {
Some(n.clone())
@@ -1845,12 +1897,12 @@ impl UnaryExpression {
.get_json_value()?,
self.into(),
)?;
- Ok(MemoryItem::UserVal {
+ Ok(MemoryItem::UserVal(UserVal {
value: (-(num)).into(),
meta: vec![Metadata {
source_range: self.into(),
}],
- })
+ }))
}
/// Returns a hover value that includes the given character position.
@@ -2231,7 +2283,7 @@ show(part001)
#[test]
fn test_recast_comment_in_a_fn_block() {
- let some_program_string = r#"const myFn = () => {
+ let some_program_string = r#"fn myFn = () => {
// this is a comment
const yo = { a: { b: { c: '123' } } } /* block
comment */
@@ -2247,7 +2299,7 @@ show(part001)
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
- r#"const myFn = () => {
+ r#"fn myFn = () => {
// this is a comment
const yo = { a: { b: { c: '123' } } }
/* block
@@ -2542,4 +2594,15 @@ show(firstExtrude)
"#
);
}
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_recast_math_start_negative() {
+ let some_program_string = r#"const myVar = -5 + 6"#;
+ let tokens = crate::tokeniser::lexer(some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ let program = parser.ast().unwrap();
+
+ let recasted = program.recast(&Default::default(), 0);
+ assert_eq!(recasted.trim(), some_program_string);
+ }
}
diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs
index f2c66ea4a..7e19e3043 100644
--- a/src/wasm-lib/kcl/src/executor.rs
+++ b/src/wasm-lib/kcl/src/executor.rs
@@ -98,16 +98,14 @@ impl ProgramReturn {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
-#[serde(tag = "type", rename_all = "camelCase")]
+#[serde(tag = "type")]
pub enum MemoryItem {
- UserVal {
- value: serde_json::Value,
- #[serde(rename = "__meta")]
- meta: Vec,
- },
+ UserVal(UserVal),
SketchGroup(SketchGroup),
ExtrudeGroup(ExtrudeGroup),
+ #[ts(skip)]
ExtrudeTransform(ExtrudeTransform),
+ #[ts(skip)]
Function {
#[serde(skip)]
func: Option,
@@ -119,7 +117,16 @@ pub enum MemoryItem {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
-#[serde(rename_all = "camelCase")]
+#[serde(tag = "type", rename_all = "camelCase")]
+pub struct UserVal {
+ pub value: serde_json::Value,
+ #[serde(rename = "__meta")]
+ pub meta: Vec,
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
+#[ts(export)]
+#[serde(tag = "type", rename_all = "camelCase")]
pub struct ExtrudeTransform {
pub position: Position,
pub rotation: Rotation,
@@ -138,7 +145,7 @@ pub type MemoryFunction = fn(
impl From for Vec {
fn from(item: MemoryItem) -> Self {
match item {
- MemoryItem::UserVal { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
+ MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
@@ -149,8 +156,8 @@ impl From for Vec {
impl MemoryItem {
pub fn get_json_value(&self) -> Result {
- if let MemoryItem::UserVal { value, .. } = self {
- Ok(value.clone())
+ if let MemoryItem::UserVal(user_val) = self {
+ Ok(user_val.value.clone())
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a user value: {:?}", self),
@@ -186,7 +193,7 @@ impl MemoryItem {
/// A sketch group is a collection of paths.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
-#[serde(rename_all = "camelCase")]
+#[serde(tag = "type", rename_all = "camelCase")]
pub struct SketchGroup {
/// The id of the sketch group.
pub id: uuid::Uuid,
@@ -238,7 +245,7 @@ impl SketchGroup {
/// An extrude group is a collection of extrude surfaces.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
-#[serde(rename_all = "camelCase")]
+#[serde(tag = "type", rename_all = "camelCase")]
pub struct ExtrudeGroup {
/// The id of the extrude group.
pub id: uuid::Uuid,
@@ -276,15 +283,15 @@ pub enum BodyType {
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
-pub struct Position(pub [f64; 3]);
+pub struct Position(#[ts(type = "[number, number, number]")] pub [f64; 3]);
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)]
-pub struct Rotation(pub [f64; 4]);
+pub struct Rotation(#[ts(type = "[number, number, number, number]")] pub [f64; 4]);
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[ts(export)]
-pub struct SourceRange(pub [usize; 2]);
+pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
impl SourceRange {
/// Create a new source range.
@@ -401,8 +408,10 @@ impl From for Metadata {
#[serde(rename_all = "camelCase")]
pub struct BasePath {
/// The from point.
+ #[ts(type = "[number, number]")]
pub from: [f64; 2],
/// The to point.
+ #[ts(type = "[number, number]")]
pub to: [f64; 2],
/// The name of the path.
pub name: String,
@@ -804,16 +813,16 @@ show(part001)"#,
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_fn_definitions() {
- let ast = r#"const def = (x) => {
+ let ast = r#"fn def = (x) => {
return x
}
-const ghi = (x) => {
+fn ghi = (x) => {
return x
}
-const jkl = (x) => {
+fn jkl = (x) => {
return x
}
-const hmm = (x) => {
+fn hmm = (x) => {
return x
}
@@ -981,7 +990,7 @@ show(firstExtrude)"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_sketch() {
- let ast = r#"const box = (h, l, w) => {
+ let ast = r#"fn box = (h, l, w) => {
const myBox = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
@@ -998,4 +1007,159 @@ show(fnBox)"#;
parse_execute(ast).await.unwrap();
}
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_get_member_of_object_with_function_period() {
+ let ast = r#"fn box = (obj) => {
+ let myBox = startSketchAt(obj.start)
+ |> line([0, obj.l], %)
+ |> line([obj.w, 0], %)
+ |> line([0, -obj.l], %)
+ |> close(%)
+ |> extrude(obj.h, %)
+
+ return myBox
+}
+
+const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
+
+show(thisBox)
+"#;
+ parse_execute(ast).await.unwrap();
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_get_member_of_object_with_function_brace() {
+ let ast = r#"fn box = (obj) => {
+ let myBox = startSketchAt(obj["start"])
+ |> line([0, obj["l"]], %)
+ |> line([obj["w"], 0], %)
+ |> line([0, -obj["l"]], %)
+ |> close(%)
+ |> extrude(obj["h"], %)
+
+ return myBox
+}
+
+const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
+
+show(thisBox)
+"#;
+ parse_execute(ast).await.unwrap();
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_get_member_of_object_with_function_mix_period_brace() {
+ let ast = r#"fn box = (obj) => {
+ let myBox = startSketchAt(obj["start"])
+ |> line([0, obj["l"]], %)
+ |> line([obj["w"], 0], %)
+ |> line([10 - obj["w"], -obj.l], %)
+ |> close(%)
+ |> extrude(obj["h"], %)
+
+ return myBox
+}
+
+const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
+
+show(thisBox)
+"#;
+ parse_execute(ast).await.unwrap();
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ #[ignore] // ignore til we get loops
+ async fn test_execute_with_function_sketch_loop_objects() {
+ let ast = r#"fn box = (obj) => {
+ let myBox = startSketchAt(obj.start)
+ |> line([0, obj.l], %)
+ |> line([obj.w, 0], %)
+ |> line([0, -obj.l], %)
+ |> close(%)
+ |> extrude(obj.h, %)
+
+ return myBox
+}
+
+for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
+ const thisBox = box(var)
+ show(thisBox)
+}"#;
+
+ parse_execute(ast).await.unwrap();
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ #[ignore] // ignore til we get loops
+ async fn test_execute_with_function_sketch_loop_array() {
+ let ast = r#"fn box = (h, l, w, start) => {
+ const myBox = startSketchAt([0,0])
+ |> line([0, l], %)
+ |> line([w, 0], %)
+ |> line([0, -l], %)
+ |> close(%)
+ |> extrude(h, %)
+
+ return myBox
+}
+
+
+for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
+ const thisBox = box(var[0], var[1], var[2], var[3])
+ show(thisBox)
+}"#;
+
+ parse_execute(ast).await.unwrap();
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_get_member_of_array_with_function() {
+ let ast = r#"fn box = (array) => {
+ let myBox = startSketchAt(array[0])
+ |> line([0, array[1]], %)
+ |> line([array[2], 0], %)
+ |> line([0, -array[1]], %)
+ |> close(%)
+ |> extrude(array[3], %)
+
+ return myBox
+}
+
+const thisBox = box([[0,0], 6, 10, 3])
+
+show(thisBox)
+"#;
+ parse_execute(ast).await.unwrap();
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_math_execute_with_functions() {
+ let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
+ let memory = parse_execute(ast).await.unwrap();
+ assert_eq!(
+ serde_json::json!(5.0),
+ memory.root.get("myVar").unwrap().get_json_value().unwrap()
+ );
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_math_execute() {
+ let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
+ let memory = parse_execute(ast).await.unwrap();
+ assert_eq!(
+ serde_json::json!(7.4),
+ memory.root.get("myVar").unwrap().get_json_value().unwrap()
+ );
+ }
+
+ #[tokio::test(flavor = "multi_thread")]
+ async fn test_math_execute_start_negative() {
+ let ast = r#"const myVar = -5 + 6"#;
+ let memory = parse_execute(ast).await.unwrap();
+ assert_eq!(
+ serde_json::json!(1.0),
+ memory.root.get("myVar").unwrap().get_json_value().unwrap()
+ );
+ }
}
diff --git a/src/wasm-lib/kcl/src/math_parser.rs b/src/wasm-lib/kcl/src/math_parser.rs
index 95053e162..daca1d98b 100644
--- a/src/wasm-lib/kcl/src/math_parser.rs
+++ b/src/wasm-lib/kcl/src/math_parser.rs
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::{
abstract_syntax_tree_types::{
- BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, ValueMeta,
+ BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression, ValueMeta,
},
errors::{KclError, KclErrorDetails},
executor::SourceRange,
@@ -81,6 +81,7 @@ pub enum MathExpression {
BinaryExpression(Box),
ExtendedBinaryExpression(Box),
ParenthesisToken(Box),
+ MemberExpression(Box),
}
impl MathExpression {
@@ -92,6 +93,7 @@ impl MathExpression {
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(),
+ MathExpression::MemberExpression(member_expression) => member_expression.start(),
}
}
@@ -103,6 +105,7 @@ impl MathExpression {
MathExpression::BinaryExpression(binary_expression) => binary_expression.end(),
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(),
+ MathExpression::MemberExpression(member_expression) => member_expression.end(),
}
}
}
@@ -133,7 +136,7 @@ impl ReversePolishNotation {
}
let current_token = self.parser.get_token(0)?;
- if current_token.token_type == TokenType::Word || current_token.token_type == TokenType::Keyword {
+ if current_token.token_type == TokenType::Word {
if let Ok(next) = self.parser.get_token(1) {
if next.token_type == TokenType::Brace && next.value == "(" {
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
@@ -149,6 +152,24 @@ impl ReversePolishNotation {
);
return rpn.parse();
}
+ if (current_token.token_type == TokenType::Word)
+ && (next.token_type == TokenType::Period
+ || (next.token_type == TokenType::Brace && next.value == "["))
+ {
+ // Find the end of the binary expression, ie the member expression.
+ let end = self.parser.make_member_expression(0)?.last_index;
+ let rpn = ReversePolishNotation::new(
+ &self.parser.tokens[end + 1..],
+ &self
+ .previous_postfix
+ .iter()
+ .cloned()
+ .chain(self.parser.tokens[0..end + 1].iter().cloned())
+ .collect::>(),
+ &self.operators,
+ );
+ return rpn.parse();
+ }
}
let rpn = ReversePolishNotation::new(
@@ -164,7 +185,6 @@ impl ReversePolishNotation {
return rpn.parse();
} else if current_token.token_type == TokenType::Number
|| current_token.token_type == TokenType::Word
- || current_token.token_type == TokenType::Keyword
|| current_token.token_type == TokenType::String
{
let rpn = ReversePolishNotation::new(
@@ -180,6 +200,35 @@ impl ReversePolishNotation {
return rpn.parse();
} else if let Ok(binop) = BinaryOperator::from_str(current_token.value.as_str()) {
if !self.operators.is_empty() {
+ if binop == BinaryOperator::Sub {
+ // We need to check if we have a "sub" and if the previous token is a word or
+ // number or string, then we need to treat it as a negative number.
+ // This oddity only applies to the "-" operator.
+ if let Some(prevtoken) = self.previous_postfix.last() {
+ if prevtoken.token_type == TokenType::Operator {
+ // Get the next token and see if it is a number.
+ if let Ok(nexttoken) = self.parser.get_token(1) {
+ if nexttoken.token_type == TokenType::Number {
+ // We have a negative number/ word or string.
+ // Change the value of the token to be the negative number/ word or string.
+ let mut new_token = nexttoken.clone();
+ new_token.value = format!("-{}", nexttoken.value);
+ let rpn = ReversePolishNotation::new(
+ &self.parser.tokens[2..],
+ &self
+ .previous_postfix
+ .iter()
+ .cloned()
+ .chain(vec![new_token.clone()])
+ .collect::>(),
+ &self.operators,
+ );
+ return rpn.parse();
+ }
+ }
+ }
+ }
+ }
if let Ok(prevbinop) = BinaryOperator::from_str(self.operators[self.operators.len() - 1].value.as_str())
{
if prevbinop.precedence() >= binop.precedence() {
@@ -196,6 +245,29 @@ impl ReversePolishNotation {
return rpn.parse();
}
}
+ } else if self.previous_postfix.is_empty()
+ && current_token.token_type == TokenType::Operator
+ && current_token.value == "-"
+ {
+ if let Ok(nexttoken) = self.parser.get_token(1) {
+ if nexttoken.token_type == TokenType::Number {
+ // We have a negative number/ word or string.
+ // Change the value of the token to be the negative number/ word or string.
+ let mut new_token = nexttoken.clone();
+ new_token.value = format!("-{}", nexttoken.value);
+ let rpn = ReversePolishNotation::new(
+ &self.parser.tokens[2..],
+ &self
+ .previous_postfix
+ .iter()
+ .cloned()
+ .chain(vec![new_token.clone()])
+ .collect::>(),
+ &self.operators,
+ );
+ return rpn.parse();
+ }
+ }
}
let rpn = ReversePolishNotation::new(
@@ -299,7 +371,7 @@ impl ReversePolishNotation {
return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![SourceRange([a.start(), a.end()])],
message: format!("{:?}", a),
- }))
+ }));
}
};
}
@@ -338,7 +410,7 @@ impl ReversePolishNotation {
start_extended: None,
})));
return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack);
- } else if current_token.token_type == TokenType::Word || current_token.token_type == TokenType::Keyword {
+ } else if current_token.token_type == TokenType::Word {
if reverse_polish_notation_tokens.len() > 1 {
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
&& reverse_polish_notation_tokens[1].value == "("
@@ -350,6 +422,18 @@ impl ReversePolishNotation {
)));
return self.build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
}
+ if reverse_polish_notation_tokens[1].token_type == TokenType::Period
+ || (reverse_polish_notation_tokens[1].token_type == TokenType::Brace
+ && reverse_polish_notation_tokens[1].value == "[")
+ {
+ let mut new_stack = stack;
+ let member_expression = self.parser.make_member_expression(0)?;
+ new_stack.push(MathExpression::MemberExpression(Box::new(member_expression.expression)));
+ return self.build_tree(
+ &reverse_polish_notation_tokens[member_expression.last_index + 1..],
+ new_stack,
+ );
+ }
let mut new_stack = stack;
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
name: current_token.value.clone(),
@@ -396,7 +480,7 @@ impl ReversePolishNotation {
return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![current_token.into()],
message: format!("{:?}", a),
- }))
+ }));
}
};
let paran = match &stack[stack.len() - 2] {
@@ -445,7 +529,7 @@ impl ReversePolishNotation {
return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![current_token.into()],
message: format!("{:?}", a),
- }))
+ }));
}
};
let mut new_stack = stack[0..stack.len() - 2].to_vec();
@@ -483,6 +567,10 @@ impl ReversePolishNotation {
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.start),
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.start),
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.start),
+ MathExpression::MemberExpression(member_expression) => (
+ BinaryPart::MemberExpression(member_expression.clone()),
+ member_expression.start,
+ ),
a => {
return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![current_token.into()],
@@ -513,6 +601,10 @@ impl ReversePolishNotation {
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.end),
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.end),
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.end),
+ MathExpression::MemberExpression(member_expression) => (
+ BinaryPart::MemberExpression(member_expression.clone()),
+ member_expression.end,
+ ),
a => {
return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![current_token.into()],
@@ -521,13 +613,7 @@ impl ReversePolishNotation {
}
};
- let right_end = match right.0.clone() {
- BinaryPart::BinaryExpression(_bin_exp) => right.1,
- BinaryPart::Literal(lit) => lit.end,
- BinaryPart::Identifier(ident) => ident.end,
- BinaryPart::CallExpression(call) => call.end,
- BinaryPart::UnaryExpression(unary_exp) => unary_exp.end,
- };
+ let right_end = right.0.clone().end();
let tree = BinaryExpression {
operator: BinaryOperator::from_str(¤t_token.value.clone()).map_err(|err| {
@@ -562,25 +648,13 @@ impl MathParser {
pub fn parse(&mut self) -> Result {
let rpn = self.rpn.parse()?;
let tree_with_maybe_bad_top_level_start_end = self.rpn.build_tree(&rpn, vec![])?;
- let left_start = match tree_with_maybe_bad_top_level_start_end.clone().left {
- BinaryPart::BinaryExpression(bin_exp) => bin_exp.start,
- BinaryPart::Literal(lit) => lit.start,
- BinaryPart::Identifier(ident) => ident.start,
- BinaryPart::CallExpression(call) => call.start,
- BinaryPart::UnaryExpression(unary_exp) => unary_exp.start,
- };
+ let left_start = tree_with_maybe_bad_top_level_start_end.clone().left.start();
let min_start = if left_start < tree_with_maybe_bad_top_level_start_end.start {
left_start
} else {
tree_with_maybe_bad_top_level_start_end.start
};
- let right_end = match tree_with_maybe_bad_top_level_start_end.clone().right {
- BinaryPart::BinaryExpression(bin_exp) => bin_exp.end,
- BinaryPart::Literal(lit) => lit.end,
- BinaryPart::Identifier(ident) => ident.end,
- BinaryPart::CallExpression(call) => call.end,
- BinaryPart::UnaryExpression(unary_exp) => unary_exp.end,
- };
+ let right_end = tree_with_maybe_bad_top_level_start_end.clone().right.end();
let max_end = if right_end > tree_with_maybe_bad_top_level_start_end.end {
right_end
} else {
@@ -629,6 +703,60 @@ mod test {
);
}
+ #[test]
+ fn test_parse_expression_add_no_spaces() {
+ let tokens = crate::tokeniser::lexer("1+2");
+ let mut parser = MathParser::new(&tokens);
+ let result = parser.parse().unwrap();
+ assert_eq!(
+ result,
+ BinaryExpression {
+ operator: BinaryOperator::Add,
+ start: 0,
+ end: 3,
+ left: BinaryPart::Literal(Box::new(Literal {
+ value: serde_json::Value::Number(serde_json::Number::from(1)),
+ raw: "1".to_string(),
+ start: 0,
+ end: 1,
+ })),
+ right: BinaryPart::Literal(Box::new(Literal {
+ value: serde_json::Value::Number(serde_json::Number::from(2)),
+ raw: "2".to_string(),
+ start: 2,
+ end: 3,
+ })),
+ }
+ );
+ }
+
+ #[test]
+ fn test_parse_expression_sub_no_spaces() {
+ let tokens = crate::tokeniser::lexer("1 -2");
+ let mut parser = MathParser::new(&tokens);
+ let result = parser.parse().unwrap();
+ assert_eq!(
+ result,
+ BinaryExpression {
+ operator: BinaryOperator::Sub,
+ start: 0,
+ end: 4,
+ left: BinaryPart::Literal(Box::new(Literal {
+ value: serde_json::Value::Number(serde_json::Number::from(1)),
+ raw: "1".to_string(),
+ start: 0,
+ end: 1,
+ })),
+ right: BinaryPart::Literal(Box::new(Literal {
+ value: serde_json::Value::Number(serde_json::Number::from(2)),
+ raw: "2".to_string(),
+ start: 3,
+ end: 4,
+ })),
+ }
+ );
+ }
+
#[test]
fn test_parse_expression_plus_followed_by_star() {
let tokens = crate::tokeniser::lexer("1 + 2 * 3");
diff --git a/src/wasm-lib/kcl/src/parser.rs b/src/wasm-lib/kcl/src/parser.rs
index 0c0769bfd..885850471 100644
--- a/src/wasm-lib/kcl/src/parser.rs
+++ b/src/wasm-lib/kcl/src/parser.rs
@@ -427,7 +427,7 @@ impl Parser {
}
let current_token = self.get_token(index)?;
let very_next_token = self.get_token(index + 1)?;
- if (current_token.token_type == TokenType::Word || current_token.token_type == TokenType::Keyword)
+ if (current_token.token_type == TokenType::Word)
&& very_next_token.token_type == TokenType::Brace
&& very_next_token.value == "("
{
@@ -550,22 +550,41 @@ impl Parser {
&self,
index: usize,
_previous_keys: Option>,
+ has_opening_brace: bool,
) -> Result, KclError> {
let previous_keys = _previous_keys.unwrap_or(vec![]);
let next_token = self.next_meaningful_token(index, None)?;
- let _next_token = next_token.clone();
- if _next_token.index == self.tokens.len() - 1 {
+ if next_token.index == self.tokens.len() - 1 {
return Ok(previous_keys);
}
- let period_or_opening_bracket = match next_token.token {
+ let mut has_opening_brace = match &next_token.token {
Some(next_token_val) => {
- if next_token_val.token_type == TokenType::Brace && next_token_val.value == "]" {
- self.next_meaningful_token(next_token.index, None)?
+ if next_token_val.token_type == TokenType::Brace && next_token_val.value == "[" {
+ true
} else {
- _next_token
+ has_opening_brace
}
}
- None => _next_token,
+ None => has_opening_brace,
+ };
+ let period_or_opening_bracket = match &next_token.token {
+ Some(next_token_val) => {
+ if has_opening_brace && next_token_val.token_type == TokenType::Brace && next_token_val.value == "]" {
+ // We need to reset our has_opening_brace flag, since we've closed it.
+ has_opening_brace = false;
+ let next_next_token = self.next_meaningful_token(next_token.index, None)?;
+ if let Some(next_next_token_val) = &next_next_token.token {
+ if next_next_token_val.token_type == TokenType::Brace && next_next_token_val.value == "[" {
+ // Set the opening brace flag again, since we've opened it again.
+ has_opening_brace = true;
+ }
+ }
+ next_next_token.clone()
+ } else {
+ next_token.clone()
+ }
+ }
+ None => next_token.clone(),
};
if let Some(period_or_opening_bracket_token) = period_or_opening_bracket.token {
if period_or_opening_bracket_token.token_type != TokenType::Period
@@ -573,11 +592,26 @@ impl Parser {
{
return Ok(previous_keys);
}
+
+ // We don't care if we never opened the brace.
+ if !has_opening_brace && period_or_opening_bracket_token.token_type == TokenType::Brace {
+ return Ok(previous_keys);
+ }
+
+ // Make sure its the right kind of brace, we don't care about ().
+ if period_or_opening_bracket_token.token_type == TokenType::Brace
+ && period_or_opening_bracket_token.value != "["
+ && period_or_opening_bracket_token.value != "]"
+ {
+ return Ok(previous_keys);
+ }
+
let key_token = self.next_meaningful_token(period_or_opening_bracket.index, None)?;
let next_period_or_opening_bracket = self.next_meaningful_token(key_token.index, None)?;
let is_braced = match next_period_or_opening_bracket.token {
Some(next_period_or_opening_bracket_val) => {
- next_period_or_opening_bracket_val.token_type == TokenType::Brace
+ has_opening_brace
+ && next_period_or_opening_bracket_val.token_type == TokenType::Brace
&& next_period_or_opening_bracket_val.value == "]"
}
None => false,
@@ -588,23 +622,19 @@ impl Parser {
key_token.index
};
if let Some(key_token_token) = key_token.token {
- let key = if key_token_token.token_type == TokenType::Word
- || key_token_token.token_type == TokenType::Keyword
- {
+ let key = if key_token_token.token_type == TokenType::Word {
LiteralIdentifier::Identifier(Box::new(self.make_identifier(key_token.index)?))
} else {
LiteralIdentifier::Literal(Box::new(self.make_literal(key_token.index)?))
};
- let computed = is_braced
- && (key_token_token.token_type == TokenType::Word
- || key_token_token.token_type == TokenType::Keyword);
+ let computed = is_braced && key_token_token.token_type == TokenType::Word;
let mut new_previous_keys = previous_keys;
new_previous_keys.push(ObjectKeyInfo {
key,
index: end_index,
computed,
});
- self.collect_object_keys(key_token.index, Some(new_previous_keys))
+ self.collect_object_keys(key_token.index, Some(new_previous_keys), has_opening_brace)
} else {
Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![period_or_opening_bracket_token.clone().into()],
@@ -616,9 +646,9 @@ impl Parser {
}
}
- fn make_member_expression(&self, index: usize) -> Result {
+ pub fn make_member_expression(&self, index: usize) -> Result {
let current_token = self.get_token(index)?;
- let mut keys_info = self.collect_object_keys(index, None)?;
+ let mut keys_info = self.collect_object_keys(index, None, false)?;
if keys_info.is_empty() {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![current_token.into()],
@@ -653,6 +683,7 @@ impl Parser {
fn find_end_of_binary_expression(&self, index: usize) -> Result {
let current_token = self.get_token(index)?;
+
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
let closing_parenthesis = self.find_closing_brace(index, 0, "")?;
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
@@ -669,28 +700,42 @@ impl Parser {
Ok(closing_parenthesis)
};
}
- if (current_token.token_type == TokenType::Keyword || current_token.token_type == TokenType::Word)
- && self.get_token(index + 1)?.token_type == TokenType::Brace
- && self.get_token(index + 1)?.value == "("
- {
- let closing_parenthesis = self.find_closing_brace(index + 1, 0, "")?;
- let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
- return if let Some(maybe_another_operator_token) = maybe_another_operator.token {
- if maybe_another_operator_token.token_type != TokenType::Operator
- || maybe_another_operator_token.value == PIPE_OPERATOR
+
+ if current_token.token_type == TokenType::Word {
+ if let Ok(next_token) = self.get_token(index + 1) {
+ if next_token.token_type == TokenType::Period
+ || (next_token.token_type == TokenType::Brace && next_token.value == "[")
{
- Ok(closing_parenthesis)
- } else {
- let next_right = self.next_meaningful_token(maybe_another_operator.index, None)?;
- self.find_end_of_binary_expression(next_right.index)
+ let member_expression = self.make_member_expression(index)?;
+ return self.find_end_of_binary_expression(member_expression.last_index);
}
- } else {
- Ok(closing_parenthesis)
- };
+
+ if next_token.token_type == TokenType::Brace && next_token.value == "(" {
+ let closing_parenthesis = self.find_closing_brace(index + 1, 0, "")?;
+ let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
+ return if let Some(maybe_another_operator_token) = maybe_another_operator.token {
+ if maybe_another_operator_token.token_type != TokenType::Operator
+ || maybe_another_operator_token.value == PIPE_OPERATOR
+ {
+ Ok(closing_parenthesis)
+ } else {
+ let next_right = self.next_meaningful_token(maybe_another_operator.index, None)?;
+ self.find_end_of_binary_expression(next_right.index)
+ }
+ } else {
+ Ok(closing_parenthesis)
+ };
+ }
+ }
}
+
let maybe_operator = self.next_meaningful_token(index, None)?;
if let Some(maybe_operator_token) = maybe_operator.token {
- if maybe_operator_token.token_type != TokenType::Operator || maybe_operator_token.value == PIPE_OPERATOR {
+ if maybe_operator_token.token_type == TokenType::Number {
+ return self.find_end_of_binary_expression(maybe_operator.index);
+ } else if maybe_operator_token.token_type != TokenType::Operator
+ || maybe_operator_token.value == PIPE_OPERATOR
+ {
return Ok(index);
}
let next_right = self.next_meaningful_token(maybe_operator.index, None)?;
@@ -731,7 +776,6 @@ impl Parser {
}
}
if current_token.token_type == TokenType::Word
- || current_token.token_type == TokenType::Keyword
|| current_token.token_type == TokenType::Number
|| current_token.token_type == TokenType::String
{
@@ -745,6 +789,29 @@ impl Parser {
}
}
}
+
+ // Account for negative numbers.
+ if current_token.token_type == TokenType::Operator || current_token.value == "-" {
+ if let Some(next_token) = &next.token {
+ if next_token.token_type == TokenType::Word
+ || next_token.token_type == TokenType::Number
+ || next_token.token_type == TokenType::String
+ {
+ // See if the next token is an operator.
+ let next_right = self.next_meaningful_token(next.index, None)?;
+ if let Some(next_right_token) = next_right.token {
+ if next_right_token.token_type == TokenType::Operator {
+ let binary_expression = self.make_binary_expression(index)?;
+ return Ok(ValueReturn {
+ value: Value::BinaryExpression(Box::new(binary_expression.expression)),
+ last_index: binary_expression.last_index,
+ });
+ }
+ }
+ }
+ }
+ }
+
if current_token.token_type == TokenType::Brace && current_token.value == "{" {
let object_expression = self.make_object_expression(index)?;
return Ok(ValueReturn {
@@ -761,11 +828,25 @@ impl Parser {
}
if let Some(next_token) = next.token {
- if (current_token.token_type == TokenType::Keyword || current_token.token_type == TokenType::Word)
+ if (current_token.token_type == TokenType::Word)
&& (next_token.token_type == TokenType::Period
|| (next_token.token_type == TokenType::Brace && next_token.value == "["))
{
let member_expression = self.make_member_expression(index)?;
+ // If the next token is an operator, we need to make a binary expression.
+ let next_right = self.next_meaningful_token(member_expression.last_index, None)?;
+ if let Some(next_right_token) = next_right.token {
+ if next_right_token.token_type == TokenType::Operator
+ || next_right_token.token_type == TokenType::Number
+ {
+ let binary_expression = self.make_binary_expression(index)?;
+ return Ok(ValueReturn {
+ value: Value::BinaryExpression(Box::new(binary_expression.expression)),
+ last_index: binary_expression.last_index,
+ });
+ }
+ }
+
return Ok(ValueReturn {
value: Value::MemberExpression(Box::new(member_expression.expression)),
last_index: member_expression.last_index,
@@ -820,7 +901,7 @@ impl Parser {
Err(KclError::Unexpected(KclErrorDetails {
source_ranges: vec![current_token.into()],
- message: format!("{:?}", current_token.token_type),
+ message: format!("Unexpected token {:?}", current_token),
}))
}
@@ -841,10 +922,12 @@ impl Parser {
if let Some(next_token_token) = next_token.token {
let is_closing_brace = next_token_token.token_type == TokenType::Brace && next_token_token.value == "]";
let is_comma = next_token_token.token_type == TokenType::Comma;
- if !is_closing_brace && !is_comma {
+ // Check if we have a double period, which would act as an expansion operator.
+ let is_double_period = next_token_token.token_type == TokenType::DoublePeriod;
+ if !is_closing_brace && !is_comma && !is_double_period {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![next_token_token.clone().into()],
- message: format!("Expected a comma or closing brace, found {:?}", next_token_token.value),
+ message: format!("Expected a `,`, `]`, or `..`, found {:?}", next_token_token.value),
}));
}
let next_call_index = if is_closing_brace {
@@ -852,9 +935,60 @@ impl Parser {
} else {
self.next_meaningful_token(next_token.index, None)?.index
};
- let mut _previous_elements = previous_elements;
- _previous_elements.push(current_element.value);
- self.make_array_elements(next_call_index, _previous_elements)
+
+ if is_double_period {
+ // We want to expand the array.
+ // Make sure the previous element is a number literal.
+ if first_element_token.token_type != TokenType::Number {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![first_element_token.into()],
+ message: "`..` expansion operator requires a number literal on both sides".to_string(),
+ }));
+ }
+
+ // Make sure the next element is a number literal.
+ let last_element_token = self.get_token(next_call_index)?;
+ if last_element_token.token_type != TokenType::Number {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![last_element_token.into()],
+ message: "`..` expansion operator requires a number literal on both sides".to_string(),
+ }));
+ }
+
+ // Expand the array.
+ let mut previous_elements = previous_elements.clone();
+ let first_element = first_element_token.value.parse::().map_err(|_| {
+ KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![first_element_token.into()],
+ message: "expected a number literal".to_string(),
+ })
+ })?;
+ let last_element = last_element_token.value.parse::().map_err(|_| {
+ KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![last_element_token.into()],
+ message: "expected a number literal".to_string(),
+ })
+ })?;
+ if first_element > last_element {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![first_element_token.into(), last_element_token.into()],
+ message: "first element must be less than or equal to the last element".to_string(),
+ }));
+ }
+ for i in first_element..=last_element {
+ previous_elements.push(Value::Literal(Box::new(Literal {
+ start: first_element_token.start,
+ end: first_element_token.end,
+ value: i.into(),
+ raw: i.to_string(),
+ })));
+ }
+ return self.make_array_elements(next_call_index + 1, previous_elements);
+ }
+
+ let mut previous_elements = previous_elements.clone();
+ previous_elements.push(current_element.value);
+ self.make_array_elements(next_call_index, previous_elements)
} else {
Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![first_element_token.into()],
@@ -867,12 +1001,18 @@ impl Parser {
let opening_brace_token = self.get_token(index)?;
let first_element_token = self.next_meaningful_token(index, None)?;
// Make sure there is a closing brace.
- let _closing_brace = self.find_closing_brace(index, 0, "")?;
+ let closing_brace_index = self.find_closing_brace(index, 0, "").map_err(|_| {
+ KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![opening_brace_token.into()],
+ message: "missing a closing brace for the array".to_string(),
+ })
+ })?;
+ let closing_brace_token = self.get_token(closing_brace_index)?;
let array_elements = self.make_array_elements(first_element_token.index, Vec::new())?;
Ok(ArrayReturn {
expression: ArrayExpression {
start: opening_brace_token.start,
- end: self.get_token(array_elements.last_index)?.end,
+ end: closing_brace_token.end,
elements: array_elements.elements,
},
last_index: array_elements.last_index,
@@ -949,9 +1089,23 @@ impl Parser {
});
}
let argument_token = self.next_meaningful_token(index, None)?;
+
if let Some(argument_token_token) = argument_token.token {
let next_brace_or_comma = self.next_meaningful_token(argument_token.index, None)?;
if let Some(next_brace_or_comma_token) = next_brace_or_comma.token {
+ if (argument_token_token.token_type == TokenType::Word)
+ && (next_brace_or_comma_token.token_type == TokenType::Period
+ || (next_brace_or_comma_token.token_type == TokenType::Brace
+ && next_brace_or_comma_token.value == "["))
+ {
+ let member_expression = self.make_member_expression(argument_token.index)?;
+ let mut _previous_args = previous_args;
+ _previous_args.push(Value::MemberExpression(Box::new(member_expression.expression)));
+ let next_comma_or_brace_token_index =
+ self.next_meaningful_token(member_expression.last_index, None)?.index;
+ return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
+ }
+
let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma
|| next_brace_or_comma_token.token_type == TokenType::Brace;
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" {
@@ -962,12 +1116,12 @@ impl Parser {
_previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression)));
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
}
+
if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" {
- let unary_expression = self.make_unary_expression(argument_token.index)?;
- let next_comma_or_brace_token_index =
- self.next_meaningful_token(unary_expression.last_index, None)?.index;
+ let value = self.make_value(argument_token.index)?;
+ let next_comma_or_brace_token_index = self.next_meaningful_token(value.last_index, None)?.index;
let mut _previous_args = previous_args;
- _previous_args.push(Value::UnaryExpression(Box::new(unary_expression.expression)));
+ _previous_args.push(value.value);
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
}
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "{" {
@@ -978,8 +1132,7 @@ impl Parser {
_previous_args.push(Value::ObjectExpression(Box::new(object_expression.expression)));
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
}
- if (argument_token_token.token_type == TokenType::Keyword
- || argument_token_token.token_type == TokenType::Word
+ if (argument_token_token.token_type == TokenType::Word
|| argument_token_token.token_type == TokenType::Number
|| argument_token_token.token_type == TokenType::String)
&& next_brace_or_comma_token.token_type == TokenType::Operator
@@ -998,6 +1151,7 @@ impl Parser {
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
return self.make_arguments(binary_expression.last_index, _previous_args);
}
+
if argument_token_token.token_type == TokenType::Operator
&& argument_token_token.value == PIPE_SUBSTITUTION_OPERATOR
{
@@ -1010,8 +1164,7 @@ impl Parser {
_previous_args.push(value);
return self.make_arguments(next_brace_or_comma.index, _previous_args);
}
- if (argument_token_token.token_type == TokenType::Keyword
- || argument_token_token.token_type == TokenType::Word)
+ if argument_token_token.token_type == TokenType::Word
&& next_brace_or_comma_token.token_type == TokenType::Brace
&& next_brace_or_comma_token.value == "("
{
@@ -1085,9 +1238,14 @@ impl Parser {
let brace_token = self.next_meaningful_token(index, None)?;
let callee = self.make_identifier(index)?;
// Make sure there is a closing brace.
- let _closing_brace_token = self.find_closing_brace(brace_token.index, 0, "")?;
+ let closing_brace_index = self.find_closing_brace(brace_token.index, 0, "").map_err(|_| {
+ KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![current_token.into()],
+ message: "missing a closing brace for the function call".to_string(),
+ })
+ })?;
+ let closing_brace_token = self.get_token(closing_brace_index)?;
let args = self.make_arguments(brace_token.index, vec![])?;
- let closing_brace_token = self.get_token(args.last_index)?;
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
} else {
@@ -1127,6 +1285,18 @@ impl Parser {
previous_declarators: Vec,
) -> Result {
let current_token = self.get_token(index)?;
+
+ // Make sure they are not assigning a variable to a reserved keyword.
+ if current_token.token_type == TokenType::Keyword {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![current_token.into()],
+ message: format!(
+ "Cannot assign a variable to a reserved keyword: {}",
+ current_token.value
+ ),
+ }));
+ }
+
let assignment = self.next_meaningful_token(index, None)?;
let Some(assignment_token) = assignment.token else {
return Err(KclError::Unimplemented(KclErrorDetails {
@@ -1169,17 +1339,40 @@ impl Parser {
fn make_variable_declaration(&self, index: usize) -> Result {
let current_token = self.get_token(index)?;
let declaration_start_token = self.next_meaningful_token(index, None)?;
+ let kind = VariableKind::from_str(¤t_token.value).map_err(|_| {
+ KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![current_token.into()],
+ message: format!("Unexpected token: {}", current_token.value),
+ })
+ })?;
let variable_declarators_result = self.make_variable_declarators(declaration_start_token.index, vec![])?;
+
+ // Check if we have a fn variable kind but are not assigning a function.
+ if !variable_declarators_result.declarations.is_empty() {
+ if let Some(declarator) = variable_declarators_result.declarations.get(0) {
+ if let Value::FunctionExpression(_) = declarator.init {
+ if kind != VariableKind::Fn {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![current_token.into()],
+ message: format!("Expected a `fn` variable kind, found: `{}`", current_token.value),
+ }));
+ }
+ } else {
+ // If we have anything other than a function, make sure we are not using the `fn` variable kind.
+ if kind == VariableKind::Fn {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![current_token.into()],
+ message: format!("Expected a `let` variable kind, found: `{}`", current_token.value),
+ }));
+ }
+ }
+ }
+ }
Ok(VariableDeclarationResult {
declaration: VariableDeclaration {
start: current_token.start,
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
- kind: VariableKind::from_str(¤t_token.value).map_err(|_| {
- KclError::Syntax(KclErrorDetails {
- source_ranges: vec![current_token.into()],
- message: "Unexpected token".to_string(),
- })
- })?,
+ kind,
declarations: variable_declarators_result.declarations,
},
last_index: variable_declarators_result.last_index,
@@ -1189,27 +1382,38 @@ impl Parser {
fn make_params(&self, index: usize, previous_params: Vec) -> Result {
let brace_or_comma_token = self.get_token(index)?;
let argument = self.next_meaningful_token(index, None)?;
- if let Some(argument_token) = argument.token {
- let should_finish_recursion = (argument_token.token_type == TokenType::Brace
- && argument_token.value == ")")
- || (brace_or_comma_token.token_type == TokenType::Brace && brace_or_comma_token.value == ")");
- if should_finish_recursion {
- return Ok(ParamsResult {
- params: previous_params,
- last_index: index,
- });
- }
- let next_brace_or_comma_token = self.next_meaningful_token(argument.index, None)?;
- let identifier = self.make_identifier(argument.index)?;
- let mut _previous_params = previous_params;
- _previous_params.push(identifier);
- self.make_params(next_brace_or_comma_token.index, _previous_params)
- } else {
- Err(KclError::Unimplemented(KclErrorDetails {
+ let Some(argument_token) = argument.token else {
+ return Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![brace_or_comma_token.into()],
- message: format!("Unexpected token {}", brace_or_comma_token.value),
- }))
+ message: format!("expected a function parameter, found: {}", brace_or_comma_token.value),
+ }));
+ };
+
+ let should_finish_recursion = (argument_token.token_type == TokenType::Brace && argument_token.value == ")")
+ || (brace_or_comma_token.token_type == TokenType::Brace && brace_or_comma_token.value == ")");
+ if should_finish_recursion {
+ return Ok(ParamsResult {
+ params: previous_params,
+ last_index: index,
+ });
}
+
+ // Make sure they are not assigning a variable to a reserved keyword.
+ if argument_token.token_type == TokenType::Keyword {
+ return Err(KclError::Syntax(KclErrorDetails {
+ source_ranges: vec![argument_token.clone().into()],
+ message: format!(
+ "Cannot assign a variable to a reserved keyword: {}",
+ argument_token.value
+ ),
+ }));
+ }
+
+ let next_brace_or_comma_token = self.next_meaningful_token(argument.index, None)?;
+ let identifier = self.make_identifier(argument.index)?;
+ let mut _previous_params = previous_params;
+ _previous_params.push(identifier);
+ self.make_params(next_brace_or_comma_token.index, _previous_params)
}
fn make_unary_expression(&self, index: usize) -> Result {
@@ -1239,10 +1443,11 @@ impl Parser {
Value::Literal(literal) => BinaryPart::Literal(literal),
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
+ Value::MemberExpression(member_expression) => BinaryPart::MemberExpression(member_expression),
_ => {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![current_token.into()],
- message: "Invalid argument for unary expression".to_string(),
+ message: format!("Invalid argument for unary expression: {:?}", argument.value),
}));
}
},
@@ -1462,7 +1667,7 @@ impl Parser {
}
if let Some(next_token) = next.token {
- if (token.token_type == TokenType::Keyword || token.token_type == TokenType::Word)
+ if token.token_type == TokenType::Word
&& next_token.token_type == TokenType::Brace
&& next_token.value == "("
{
@@ -1488,9 +1693,7 @@ impl Parser {
let next_thing = self.next_meaningful_token(token_index, None)?;
if let Some(next_thing_token) = next_thing.token {
- if (token.token_type == TokenType::Number
- || token.token_type == TokenType::Word
- || token.token_type == TokenType::Keyword)
+ if (token.token_type == TokenType::Number || token.token_type == TokenType::Word)
&& next_thing_token.token_type == TokenType::Operator
{
if let Some(node) = &next_thing.non_code_node {
@@ -1513,7 +1716,7 @@ impl Parser {
Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![token.into()],
- message: "unexpected token".to_string(),
+ message: format!("unexpected token {}", token.value),
}))
}
@@ -1730,7 +1933,7 @@ const key = 'c'"#,
fn test_collect_object_keys() {
let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]");
let parser = Parser::new(tokens);
- let keys_info = parser.collect_object_keys(6, None).unwrap();
+ let keys_info = parser.collect_object_keys(6, None, false).unwrap();
assert_eq!(keys_info.len(), 2);
let first_key = match keys_info[0].key.clone() {
LiteralIdentifier::Identifier(identifier) => format!("identifier-{}", identifier.name),
@@ -2759,6 +2962,73 @@ show(mySk1)"#;
assert!(result.err().unwrap().to_string().contains("Unexpected token"));
}
+ #[test]
+ fn test_parse_member_expression_double_nested_braces() {
+ let tokens = crate::tokeniser::lexer(r#"const prop = yo["one"][two]"#);
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_parse_member_expression_binary_expression_period_number_first() {
+ let tokens = crate::tokeniser::lexer(
+ r#"const obj = { a: 1, b: 2 }
+const height = 1 - obj.a"#,
+ );
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_parse_member_expression_binary_expression_brace_number_first() {
+ let tokens = crate::tokeniser::lexer(
+ r#"const obj = { a: 1, b: 2 }
+const height = 1 - obj["a"]"#,
+ );
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_parse_member_expression_binary_expression_brace_number_second() {
+ let tokens = crate::tokeniser::lexer(
+ r#"const obj = { a: 1, b: 2 }
+const height = obj["a"] - 1"#,
+ );
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_parse_member_expression_binary_expression_in_array_number_first() {
+ let tokens = crate::tokeniser::lexer(
+ r#"const obj = { a: 1, b: 2 }
+const height = [1 - obj["a"], 0]"#,
+ );
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_parse_member_expression_binary_expression_in_array_number_second() {
+ let tokens = crate::tokeniser::lexer(
+ r#"const obj = { a: 1, b: 2 }
+const height = [obj["a"] - 1, 0]"#,
+ );
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
+ let tokens = crate::tokeniser::lexer(
+ r#"const obj = { a: 1, b: 2 }
+const height = [obj["a"] -1, 0]"#,
+ );
+ let parser = Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
#[test]
fn test_parse_half_pipe() {
let tokens = crate::tokeniser::lexer(
@@ -2816,7 +3086,10 @@ z(-[["#,
let parser = Parser::new(tokens);
let result = parser.ast();
assert!(result.is_err());
- assert!(result.err().unwrap().to_string().contains("unexpected end"));
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ r#"syntax: KclErrorDetails { source_ranges: [SourceRange([1, 2])], message: "missing a closing brace for the function call" }"#
+ );
}
#[test]
@@ -2828,7 +3101,10 @@ z(-[["#,
let parser = Parser::new(tokens);
let result = parser.ast();
assert!(result.is_err());
- assert!(result.err().unwrap().to_string().contains("unexpected end"));
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 1])], message: "missing a closing brace for the function call" }"#
+ );
}
#[test]
@@ -2881,4 +3157,201 @@ e
.to_string()
.contains("unexpected end of expression"));
}
+
+ #[test]
+ fn test_parse_expand_array() {
+ let code = "const myArray = [0..10]";
+ let parser = Parser::new(crate::tokeniser::lexer(code));
+ let result = parser.ast().unwrap();
+ let expected_result = Program {
+ start: 0,
+ end: 23,
+ body: vec![BodyItem::VariableDeclaration(VariableDeclaration {
+ start: 0,
+ end: 23,
+ declarations: vec![VariableDeclarator {
+ start: 6,
+ end: 23,
+ id: Identifier {
+ start: 6,
+ end: 13,
+ name: "myArray".to_string(),
+ },
+ init: Value::ArrayExpression(Box::new(ArrayExpression {
+ start: 16,
+ end: 23,
+ elements: vec![
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 0.into(),
+ raw: "0".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 1.into(),
+ raw: "1".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 2.into(),
+ raw: "2".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 3.into(),
+ raw: "3".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 4.into(),
+ raw: "4".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 5.into(),
+ raw: "5".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 6.into(),
+ raw: "6".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 7.into(),
+ raw: "7".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 8.into(),
+ raw: "8".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 9.into(),
+ raw: "9".to_string(),
+ })),
+ Value::Literal(Box::new(Literal {
+ start: 17,
+ end: 18,
+ value: 10.into(),
+ raw: "10".to_string(),
+ })),
+ ],
+ })),
+ }],
+ kind: VariableKind::Const,
+ })],
+ non_code_meta: NoneCodeMeta {
+ none_code_nodes: Default::default(),
+ start: None,
+ },
+ };
+
+ assert_eq!(result, expected_result);
+ }
+
+ #[test]
+ fn test_error_keyword_in_variable() {
+ let some_program_string = r#"const let = "thing""#;
+ let tokens = crate::tokeniser::lexer(some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ let result = parser.ast();
+ assert!(result.is_err());
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ r#"syntax: KclErrorDetails { source_ranges: [SourceRange([6, 9])], message: "Cannot assign a variable to a reserved keyword: let" }"#
+ );
+ }
+
+ #[test]
+ fn test_error_keyword_in_fn_name() {
+ let some_program_string = r#"fn let = () {}"#;
+ let tokens = crate::tokeniser::lexer(some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ let result = parser.ast();
+ assert!(result.is_err());
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ r#"syntax: KclErrorDetails { source_ranges: [SourceRange([3, 6])], message: "Cannot assign a variable to a reserved keyword: let" }"#
+ );
+ }
+
+ #[test]
+ fn test_error_keyword_in_fn_args() {
+ let some_program_string = r#"fn thing = (let) => {
+ return 1
+}"#;
+ let tokens = crate::tokeniser::lexer(some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ let result = parser.ast();
+ assert!(result.is_err());
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ r#"syntax: KclErrorDetails { source_ranges: [SourceRange([12, 15])], message: "Cannot assign a variable to a reserved keyword: let" }"#
+ );
+ }
+
+ #[test]
+ fn test_keyword_ok_in_fn_args_return() {
+ let some_program_string = r#"fn thing = (param) => {
+ return true
+}
+
+thing(false)
+"#;
+ let tokens = crate::tokeniser::lexer(some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ parser.ast().unwrap();
+ }
+
+ #[test]
+ fn test_error_define_function_as_var() {
+ for name in ["var", "let", "const"] {
+ let some_program_string = format!(
+ r#"{} thing = (param) => {{
+ return true
+}}
+
+thing(false)
+"#,
+ name
+ );
+ let tokens = crate::tokeniser::lexer(&some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ let result = parser.ast();
+ assert!(result.is_err());
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ format!(
+ r#"syntax: KclErrorDetails {{ source_ranges: [SourceRange([0, {}])], message: "Expected a `fn` variable kind, found: `{}`" }}"#,
+ name.len(),
+ name
+ )
+ );
+ }
+ }
+
+ #[test]
+ fn test_error_define_var_as_function() {
+ let some_program_string = r#"fn thing = "thing""#;
+ let tokens = crate::tokeniser::lexer(some_program_string);
+ let parser = crate::parser::Parser::new(tokens);
+ let result = parser.ast();
+ assert!(result.is_err());
+ assert_eq!(
+ result.err().unwrap().to_string(),
+ r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 2])], message: "Expected a `let` variable kind, found: `fn`" }"#
+ );
+ }
}
diff --git a/src/wasm-lib/kcl/src/std/mod.rs b/src/wasm-lib/kcl/src/std/mod.rs
index 5f46572ef..d00b746c0 100644
--- a/src/wasm-lib/kcl/src/std/mod.rs
+++ b/src/wasm-lib/kcl/src/std/mod.rs
@@ -103,12 +103,12 @@ impl<'a> Args<'a> {
}
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result {
- Ok(MemoryItem::UserVal {
+ Ok(MemoryItem::UserVal(crate::executor::UserVal {
value: j,
meta: vec![Metadata {
source_range: self.source_range,
}],
- })
+ }))
}
fn make_user_val_from_f64(&self, f: f64) -> Result {
diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs
index 81c7b0a98..6ffe56e5f 100644
--- a/src/wasm-lib/kcl/src/std/sketch.rs
+++ b/src/wasm-lib/kcl/src/std/sketch.rs
@@ -161,34 +161,12 @@ pub enum LineData {
/// A point with a tag.
PointWithTag {
/// The to point.
- to: PointOrDefault,
+ to: [f64; 2],
/// The tag.
tag: String,
},
/// A point.
Point([f64; 2]),
- /// A string like `default`.
- Default(String),
-}
-
-/// A point or a default value.
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
-#[ts(export)]
-#[serde(rename_all = "camelCase", untagged)]
-pub enum PointOrDefault {
- /// A point.
- Point([f64; 2]),
- /// A string like `default`.
- Default(String),
-}
-
-impl PointOrDefault {
- fn get_point_with_default(&self, default: [f64; 2]) -> [f64; 2] {
- match self {
- PointOrDefault::Point(point) => *point,
- PointOrDefault::Default(_) => default,
- }
- }
}
/// Draw a line.
@@ -205,12 +183,9 @@ pub fn line(args: &mut Args) -> Result {
}]
fn inner_line(data: LineData, sketch_group: SketchGroup, args: &mut Args) -> Result {
let from = sketch_group.get_coords_from_paths()?;
-
- let default = [0.2, 1.0];
let inner_args = match &data {
- LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
+ LineData::PointWithTag { to, .. } => *to,
LineData::Point(to) => *to,
- LineData::Default(_) => default,
};
let to = [from.x + inner_args[0], from.y + inner_args[1]];
@@ -283,10 +258,7 @@ pub fn x_line(args: &mut Args) -> Result {
}]
fn inner_x_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result {
let line_data = match data {
- AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
- to: PointOrDefault::Point([length, 0.0]),
- tag,
- },
+ AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [length, 0.0], tag },
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
};
@@ -308,10 +280,7 @@ pub fn y_line(args: &mut Args) -> Result {
}]
fn inner_y_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result {
let line_data = match data {
- AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
- to: PointOrDefault::Point([0.0, length]),
- tag,
- },
+ AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [0.0, length], tag },
AxisLineData::Length(length) => LineData::Point([0.0, length]),
};
@@ -427,10 +396,7 @@ fn inner_angled_line_of_x_length(
let new_sketch_group = inner_line(
if let AngledLineData::AngleWithTag { tag, .. } = data {
- LineData::PointWithTag {
- to: PointOrDefault::Point(to),
- tag,
- }
+ LineData::PointWithTag { to, tag }
} else {
LineData::Point(to)
},
@@ -525,10 +491,7 @@ fn inner_angled_line_of_y_length(
let new_sketch_group = inner_line(
if let AngledLineData::AngleWithTag { tag, .. } = data {
- LineData::PointWithTag {
- to: PointOrDefault::Point(to),
- tag,
- }
+ LineData::PointWithTag { to, tag }
} else {
LineData::Point(to)
},
@@ -654,11 +617,9 @@ pub fn start_sketch_at(args: &mut Args) -> Result {
name = "startSketchAt",
}]
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result {
- let default = [0.0, 0.0];
let to = match &data {
- LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
+ LineData::PointWithTag { to, .. } => *to,
LineData::Point(to) => *to,
- LineData::Default(_) => default,
};
let id = uuid::Uuid::new_v4();
@@ -992,16 +953,12 @@ mod tests {
use pretty_assertions::assert_eq;
- use crate::std::sketch::{LineData, PointOrDefault};
+ use crate::std::sketch::LineData;
#[test]
fn test_deserialize_line_data() {
- let mut str_json = "\"default\"".to_string();
- let data: LineData = serde_json::from_str(&str_json).unwrap();
- assert_eq!(data, LineData::Default("default".to_string()));
-
let data = LineData::Point([0.0, 1.0]);
- str_json = serde_json::to_string(&data).unwrap();
+ let mut str_json = serde_json::to_string(&data).unwrap();
assert_eq!(str_json, "[0.0,1.0]");
str_json = "[0, 1]".to_string();
@@ -1013,7 +970,7 @@ mod tests {
assert_eq!(
data,
LineData::PointWithTag {
- to: PointOrDefault::Point([0.0, 1.0]),
+ to: [0.0, 1.0],
tag: "thing".to_string()
}
);
diff --git a/src/wasm-lib/kcl/src/tokeniser.rs b/src/wasm-lib/kcl/src/tokeniser.rs
index a1ebf2e3c..9bdbd8147 100644
--- a/src/wasm-lib/kcl/src/tokeniser.rs
+++ b/src/wasm-lib/kcl/src/tokeniser.rs
@@ -34,6 +34,8 @@ pub enum TokenType {
Colon,
/// A period.
Period,
+ /// A double period: `..`.
+ DoublePeriod,
/// A line comment.
LineComment,
/// A block comment.
@@ -54,7 +56,12 @@ impl TryFrom for SemanticTokenType {
TokenType::LineComment => Self::COMMENT,
TokenType::BlockComment => Self::COMMENT,
TokenType::Function => Self::FUNCTION,
- TokenType::Whitespace | TokenType::Brace | TokenType::Comma | TokenType::Colon | TokenType::Period => {
+ TokenType::Whitespace
+ | TokenType::Brace
+ | TokenType::Comma
+ | TokenType::Colon
+ | TokenType::Period
+ | TokenType::DoublePeriod => {
anyhow::bail!("unsupported token type: {:?}", token_type)
}
})
@@ -135,7 +142,7 @@ lazy_static! {
static ref WORD: Regex = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*").unwrap();
// TODO: these should be generated using our struct types for these.
static ref KEYWORD: Regex =
- Regex::new(r"^(if|else|for|while|return|break|continue|fn|let|true|false|nil|and|or|not|var|const)\b").unwrap();
+ Regex::new(r"^(if|else|for|while|return|break|continue|fn|let|mut|loop|true|false|nil|and|or|not|var|const)\b").unwrap();
static ref OPERATOR: Regex = Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
static ref BLOCK_START: Regex = Regex::new(r"^\{").unwrap();
@@ -147,6 +154,7 @@ lazy_static! {
static ref COMMA: Regex = Regex::new(r"^,").unwrap();
static ref COLON: Regex = Regex::new(r"^:").unwrap();
static ref PERIOD: Regex = Regex::new(r"^\.").unwrap();
+ static ref DOUBLE_PERIOD: Regex = Regex::new(r"^\.\.").unwrap();
static ref LINECOMMENT: Regex = Regex::new(r"^//.*").unwrap();
static ref BLOCKCOMMENT: Regex = Regex::new(r"^/\*[\s\S]*?\*/").unwrap();
}
@@ -196,6 +204,9 @@ fn is_comma(character: &str) -> bool {
fn is_colon(character: &str) -> bool {
COLON.is_match(character)
}
+fn is_double_period(character: &str) -> bool {
+ DOUBLE_PERIOD.is_match(character)
+}
fn is_period(character: &str) -> bool {
PERIOD.is_match(character)
}
@@ -296,13 +307,6 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option {
start_index,
));
}
- if is_number(str_from_index) {
- return Some(make_token(
- TokenType::Number,
- &match_first(str_from_index, &NUMBER)?,
- start_index,
- ));
- }
if is_operator(str_from_index) {
return Some(make_token(
TokenType::Operator,
@@ -310,6 +314,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option {
start_index,
));
}
+ if is_number(str_from_index) {
+ return Some(make_token(
+ TokenType::Number,
+ &match_first(str_from_index, &NUMBER)?,
+ start_index,
+ ));
+ }
if is_keyword(str_from_index) {
return Some(make_token(
TokenType::Keyword,
@@ -331,6 +342,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option {
start_index,
));
}
+ if is_double_period(str_from_index) {
+ return Some(make_token(
+ TokenType::DoublePeriod,
+ &match_first(str_from_index, &DOUBLE_PERIOD)?,
+ start_index,
+ ));
+ }
if is_period(str_from_index) {
return Some(make_token(
TokenType::Period,
diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs
new file mode 100644
index 000000000..2f78589e4
--- /dev/null
+++ b/src/wasm-lib/tests/executor/main.rs
@@ -0,0 +1,98 @@
+use anyhow::Result;
+
+/// Executes a kcl program and takes a snapshot of the result.
+/// This returns the bytes of the snapshot.
+async fn execute_and_snapshot(code: &str) -> Result {
+ let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
+ let http_client = reqwest::Client::builder()
+ .user_agent(user_agent)
+ // For file conversions we need this to be long.
+ .timeout(std::time::Duration::from_secs(600))
+ .connect_timeout(std::time::Duration::from_secs(60));
+ let ws_client = reqwest::Client::builder()
+ .user_agent(user_agent)
+ // For file conversions we need this to be long.
+ .timeout(std::time::Duration::from_secs(600))
+ .connect_timeout(std::time::Duration::from_secs(60))
+ .tcp_keepalive(std::time::Duration::from_secs(600))
+ .http1_only();
+
+ let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
+
+ // Create the client.
+ let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
+
+ let ws = client
+ .modeling()
+ .commands_ws(None, None, None, None, Some(false))
+ .await?;
+
+ // Create a temporary file to write the output to.
+ let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
+
+ let tokens = kcl_lib::tokeniser::lexer(code);
+ let parser = kcl_lib::parser::Parser::new(tokens);
+ let program = parser.ast()?;
+ let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
+ let mut engine = kcl_lib::engine::EngineConnection::new(
+ ws,
+ std::env::temp_dir().display().to_string().as_str(),
+ output_file.display().to_string().as_str(),
+ )
+ .await?;
+ let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)?;
+
+ // Send a snapshot request to the engine.
+ engine.send_modeling_cmd(
+ uuid::Uuid::new_v4(),
+ kcl_lib::executor::SourceRange::default(),
+ kittycad::types::ModelingCmd::TakeSnapshot {
+ format: kittycad::types::ImageFormat::Png,
+ },
+ )?;
+
+ // Wait for the snapshot to be taken.
+ engine.wait_for_snapshot().await;
+
+ // Read the output file.
+ let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
+ Ok(actual)
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn test_execute_with_function_sketch() {
+ let code = r#"fn box = (h, l, w) => {
+ const myBox = startSketchAt([0,0])
+ |> line([0, l], %)
+ |> line([w, 0], %)
+ |> line([0, -l], %)
+ |> close(%)
+ |> extrude(h, %)
+
+ return myBox
+}
+
+const fnBox = box(3, 6, 10)
+
+show(fnBox)"#;
+
+ let result = execute_and_snapshot(code).await.unwrap();
+ twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 1.0);
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn test_execute_with_angled_line() {
+ let code = r#"const part001 = startSketchAt([4.83, 12.56])
+ |> line([15.1, 2.48], %)
+ |> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
+ |> line([-15.17, -4.1], %)
+ |> angledLine([segAng('seg01', %), 12.35], %)
+ |> line([-13.02, 10.03], %)
+ |> close(%)
+ |> extrude(4, %)
+
+show(part001)"#;
+
+ let result = execute_and_snapshot(code).await.unwrap();
+ twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 1.0);
+}
diff --git a/src/wasm-lib/tests/executor/outputs/angled_line.png b/src/wasm-lib/tests/executor/outputs/angled_line.png
new file mode 100644
index 000000000..5c8944913
Binary files /dev/null and b/src/wasm-lib/tests/executor/outputs/angled_line.png differ
diff --git a/src/wasm-lib/tests/executor/outputs/function_sketch.png b/src/wasm-lib/tests/executor/outputs/function_sketch.png
new file mode 100644
index 000000000..b540f4069
Binary files /dev/null and b/src/wasm-lib/tests/executor/outputs/function_sketch.png differ