Merge branch 'main' into franknoirot/xstate-toolbar
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
||||||
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||||
VITE_KC_SENTRY_DSN=
|
VITE_KC_SENTRY_DSN=
|
||||||
|
11
.github/workflows/cargo-clippy.yml
vendored
11
.github/workflows/cargo-clippy.yml
vendored
@ -40,6 +40,17 @@ jobs:
|
|||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
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
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
|
10
.github/workflows/cargo-test.yml
vendored
10
.github/workflows/cargo-test.yml
vendored
@ -41,6 +41,16 @@ jobs:
|
|||||||
- uses: taiki-e/install-action@nextest
|
- uses: taiki-e/install-action@nextest
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
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
|
- name: cargo test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
|
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -153,6 +153,8 @@ jobs:
|
|||||||
needs: [build-test-web, build-apps]
|
needs: [build-test-web, build-apps]
|
||||||
env:
|
env:
|
||||||
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
||||||
|
PUB_DATE: ${{ github.event.release.created_at }}
|
||||||
|
NOTES: ${{ github.event.release.body }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
@ -166,6 +168,8 @@ jobs:
|
|||||||
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
||||||
jq --null-input \
|
jq --null-input \
|
||||||
--arg version "v${VERSION_NO_V}" \
|
--arg version "v${VERSION_NO_V}" \
|
||||||
|
--arg pub_date "${PUB_DATE}" \
|
||||||
|
--arg notes "${NOTES}" \
|
||||||
--arg darwin_sig "$DARWIN_SIG" \
|
--arg darwin_sig "$DARWIN_SIG" \
|
||||||
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
||||||
--arg linux_sig "$LINUX_SIG" \
|
--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" \
|
--arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
|
"pub_date": $pub_date,
|
||||||
|
"notes": $notes,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"darwin-x86_64": {
|
"darwin-x86_64": {
|
||||||
"signature": $darwin_sig,
|
"signature": $darwin_sig,
|
||||||
@ -195,6 +201,34 @@ jobs:
|
|||||||
}' > last_update.json
|
}' > last_update.json
|
||||||
cat 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
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v1.1.1'
|
uses: 'google-github-actions/auth@v1.1.1'
|
||||||
with:
|
with:
|
||||||
@ -219,6 +253,12 @@ jobs:
|
|||||||
path: last_update.json
|
path: last_update.json
|
||||||
destination: dl.kittycad.io/releases/modeling-app
|
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
|
- name: Upload release files to Github
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
@ -11173,22 +11173,13 @@
|
|||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"description": "The to point.",
|
"description": "The to point.",
|
||||||
"anyOf": [
|
"type": "array",
|
||||||
{
|
"items": {
|
||||||
"description": "A point.",
|
"type": "number",
|
||||||
"type": "array",
|
"format": "double"
|
||||||
"items": {
|
},
|
||||||
"type": "number",
|
"maxItems": 2,
|
||||||
"format": "double"
|
"minItems": 2
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -11201,10 +11192,6 @@
|
|||||||
},
|
},
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -15341,22 +15328,13 @@
|
|||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"description": "The to point.",
|
"description": "The to point.",
|
||||||
"anyOf": [
|
"type": "array",
|
||||||
{
|
"items": {
|
||||||
"description": "A point.",
|
"type": "number",
|
||||||
"type": "array",
|
"format": "double"
|
||||||
"items": {
|
},
|
||||||
"type": "number",
|
"maxItems": 2,
|
||||||
"format": "double"
|
"minItems": 2
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -15369,10 +15347,6 @@
|
|||||||
},
|
},
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
12
docs/kcl.md
12
docs/kcl.md
@ -2044,11 +2044,9 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
// The tag.
|
// The tag.
|
||||||
tag: string,
|
tag: string,
|
||||||
// The to point.
|
// The to point.
|
||||||
to: [number] |
|
to: [number],
|
||||||
string,
|
|
||||||
} |
|
} |
|
||||||
[number] |
|
[number]
|
||||||
string
|
|
||||||
```
|
```
|
||||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
||||||
```
|
```
|
||||||
@ -2784,11 +2782,9 @@ startSketchAt(data: LineData) -> SketchGroup
|
|||||||
// The tag.
|
// The tag.
|
||||||
tag: string,
|
tag: string,
|
||||||
// The to point.
|
// The to point.
|
||||||
to: [number] |
|
to: [number],
|
||||||
string,
|
|
||||||
} |
|
} |
|
||||||
[number] |
|
[number]
|
||||||
string
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.5.0",
|
"version": "0.6.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.9.0",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
|
@ -19,7 +19,7 @@ anyhow = "1"
|
|||||||
oauth2 = "4.4.1"
|
oauth2 = "4.4.1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
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"] }
|
tokio = { version = "1.29.1", features = ["time"] }
|
||||||
toml = "0.6.0"
|
toml = "0.6.0"
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "kittycad-modeling",
|
"productName": "kittycad-modeling",
|
||||||
"version": "0.5.0"
|
"version": "0.6.1"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
53
src/App.tsx
53
src/App.tsx
@ -42,7 +42,9 @@ export function App() {
|
|||||||
setOpenPanes,
|
setOpenPanes,
|
||||||
didDragInStream,
|
didDragInStream,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
|
guiMode,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
|
guiMode: s.guiMode,
|
||||||
setCode: s.setCode,
|
setCode: s.setCode,
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
@ -109,8 +111,41 @@ export function App() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const newCmdId = uuidv4()
|
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]
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
||||||
let interaction: CameraDragInteractionType_type
|
let interaction: CameraDragInteractionType_type
|
||||||
|
|
||||||
@ -123,6 +158,7 @@ export function App() {
|
|||||||
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
} else {
|
} else {
|
||||||
|
console.log('none')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,15 +171,6 @@ export function App() {
|
|||||||
},
|
},
|
||||||
cmd_id: newCmdId,
|
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
|
paneOpacity
|
||||||
}
|
}
|
||||||
defaultSize={{
|
defaultSize={{
|
||||||
width: '400px',
|
width: '550px',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
}}
|
}}
|
||||||
minWidth={200}
|
minWidth={200}
|
||||||
maxWidth={600}
|
maxWidth={800}
|
||||||
minHeight={'auto'}
|
minHeight={'auto'}
|
||||||
maxHeight={'auto'}
|
maxHeight={'auto'}
|
||||||
handleClasses={{
|
handleClasses={{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useStore, toolTips } from './useStore'
|
import { useStore, toolTips, Selections } from './useStore'
|
||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/queryAst'
|
import { getNodePathFromSourceRange } from './lang/queryAst'
|
||||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
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 { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import styles from './Toolbar.module.css'
|
import styles from './Toolbar.module.css'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { useAppMode } from 'hooks/useAppMode'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -24,6 +26,7 @@ export const Toolbar = () => {
|
|||||||
ast,
|
ast,
|
||||||
updateAst,
|
updateAst,
|
||||||
programMemory,
|
programMemory,
|
||||||
|
engineCommandManager,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
@ -31,7 +34,9 @@ export const Toolbar = () => {
|
|||||||
ast: s.ast,
|
ast: s.ast,
|
||||||
updateAst: s.updateAst,
|
updateAst: s.updateAst,
|
||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
|
engineCommandManager: s.engineCommandManager,
|
||||||
}))
|
}))
|
||||||
|
useAppMode()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('guiMode', guiMode)
|
console.log('guiMode', guiMode)
|
||||||
@ -39,7 +44,7 @@ export const Toolbar = () => {
|
|||||||
|
|
||||||
function ToolbarButtons() {
|
function ToolbarButtons() {
|
||||||
return (
|
return (
|
||||||
<>
|
<span className="overflow-x-auto">
|
||||||
{guiMode.mode === 'default' && (
|
{guiMode.mode === 'default' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -71,9 +76,18 @@ export const Toolbar = () => {
|
|||||||
SketchOnFace
|
SketchOnFace
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{(guiMode.mode === 'canEditSketch' || false) && (
|
{guiMode.mode === 'canEditSketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
console.log('guiMode.pathId', guiMode.pathId)
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: guiMode.pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
@ -125,14 +139,23 @@ export const Toolbar = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{guiMode.mode === 'sketch' && (
|
{guiMode.mode === 'sketch' && (
|
||||||
<button onClick={() => setGuiMode({ mode: 'default' })}>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
})
|
||||||
|
setGuiMode({ mode: 'default' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
Exit sketch
|
Exit sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{toolTips
|
{toolTips
|
||||||
.filter(
|
.filter(
|
||||||
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
||||||
(sketchFnName) => ['line'].includes(sketchFnName)
|
(sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
|
||||||
)
|
)
|
||||||
.map((sketchFnName) => {
|
.map((sketchFnName) => {
|
||||||
if (
|
if (
|
||||||
@ -143,7 +166,18 @@ export const Toolbar = () => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={sketchFnName}
|
key={sketchFnName}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'set_tool',
|
||||||
|
tool:
|
||||||
|
guiMode.sketchMode === sketchFnName
|
||||||
|
? 'select'
|
||||||
|
: (sketchFnName as any),
|
||||||
|
},
|
||||||
|
})
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
...guiMode,
|
...guiMode,
|
||||||
...(guiMode.sketchMode === sketchFnName
|
...(guiMode.sketchMode === sketchFnName
|
||||||
@ -153,10 +187,11 @@ export const Toolbar = () => {
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
sketchMode: sketchFnName,
|
sketchMode: sketchFnName,
|
||||||
|
waitingFirstClick: true,
|
||||||
isTooltip: true,
|
isTooltip: true,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
{sketchFnName}
|
{sketchFnName}
|
||||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
{guiMode.sketchMode === sketchFnName && '✅'}
|
||||||
@ -180,7 +215,7 @@ export const Toolbar = () => {
|
|||||||
<Intersect />
|
<Intersect />
|
||||||
<RemoveConstrainingValues />
|
<RemoveConstrainingValues />
|
||||||
<SetAngleBetween />
|
<SetAngleBetween />
|
||||||
</>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { ProjectWithEntryPointMetadata } from '../Router'
|
|||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
|
import { NetworkHealthIndicator } from './NetworkHealthIndicator'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -43,7 +44,8 @@ export const AppHeader = ({
|
|||||||
)}
|
)}
|
||||||
{/* If there are children, show them, otherwise show User menu */}
|
{/* If there are children, show them, otherwise show User menu */}
|
||||||
{children || (
|
{children || (
|
||||||
<div className="ml-auto">
|
<div className="ml-auto flex items-center gap-1">
|
||||||
|
<NetworkHealthIndicator />
|
||||||
<UserSidebarMenu user={user} />
|
<UserSidebarMenu user={user} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -144,7 +144,7 @@ export function useCalc({
|
|||||||
try {
|
try {
|
||||||
const code = `const __result__ = ${value}\nshow(__result__)`
|
const code = `const __result__ = ${value}\nshow(__result__)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
const _programMem: any = { root: {} }
|
const _programMem: any = { root: {}, return: null }
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ describe('processMemory', () => {
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const myVar = 5
|
const myVar = 5
|
||||||
const myFn = (a) => {
|
fn myFn = (a) => {
|
||||||
return a - 2
|
return a - 2
|
||||||
}
|
}
|
||||||
const otherVar = myFn(5)
|
const otherVar = myFn(5)
|
||||||
@ -29,6 +29,7 @@ describe('processMemory', () => {
|
|||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
const programMemory = await enginelessExecutor(ast, {
|
const programMemory = await enginelessExecutor(ast, {
|
||||||
root: {},
|
root: {},
|
||||||
|
return: null,
|
||||||
})
|
})
|
||||||
const output = processMemory(programMemory)
|
const output = processMemory(programMemory)
|
||||||
expect(output.myVar).toEqual(5)
|
expect(output.myVar).toEqual(5)
|
||||||
|
@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
|
|||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ProgramMemory } from '../lang/executor'
|
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
|
|
||||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||||
@ -49,8 +49,12 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
|||||||
Object.keys(programMemory.root).forEach((key) => {
|
Object.keys(programMemory.root).forEach((key) => {
|
||||||
const val = programMemory.root[key]
|
const val = programMemory.root[key]
|
||||||
if (typeof val.value !== 'function') {
|
if (typeof val.value !== 'function') {
|
||||||
if (val.type === 'sketchGroup' || val.type === 'extrudeGroup') {
|
if (val.type === 'SketchGroup') {
|
||||||
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }) => {
|
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||||
|
return rest
|
||||||
|
})
|
||||||
|
} else if (val.type === 'ExtrudeGroup') {
|
||||||
|
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||||
return rest
|
return rest
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
51
src/components/NetworkHealthIndicator.test.tsx
Normal file
51
src/components/NetworkHealthIndicator.test.tsx
Normal file
@ -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 (
|
||||||
|
<BrowserRouter>
|
||||||
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('NetworkHealthIndicator tests', () => {
|
||||||
|
test('Renders the network indicator', () => {
|
||||||
|
render(
|
||||||
|
<TestWrap>
|
||||||
|
<NetworkHealthIndicator />
|
||||||
|
</TestWrap>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('network-toggle'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('network-good')).toHaveTextContent(
|
||||||
|
NETWORK_CONTENT.good
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Responds to network changes', () => {
|
||||||
|
render(
|
||||||
|
<TestWrap>
|
||||||
|
<NetworkHealthIndicator />
|
||||||
|
</TestWrap>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.offline(window)
|
||||||
|
fireEvent.click(screen.getByTestId('network-toggle'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('network-bad')).toHaveTextContent(
|
||||||
|
NETWORK_CONTENT.bad
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
112
src/components/NetworkHealthIndicator.tsx
Normal file
112
src/components/NetworkHealthIndicator.tsx
Normal file
@ -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<string[]>([])
|
||||||
|
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 (
|
||||||
|
<Popover className="relative">
|
||||||
|
<Popover.Button
|
||||||
|
className={
|
||||||
|
'p-0 border-none relative ' +
|
||||||
|
(hasIssues
|
||||||
|
? 'focus-visible:outline-destroy-80'
|
||||||
|
: 'focus-visible:outline-succeed-80')
|
||||||
|
}
|
||||||
|
data-testid="network-toggle"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Network Health</span>
|
||||||
|
<ActionIcon
|
||||||
|
icon={faWifi}
|
||||||
|
iconClassName={
|
||||||
|
hasIssues
|
||||||
|
? 'text-destroy-80 dark:text-destroy-30'
|
||||||
|
: 'text-succeed-80 dark:text-succeed-30'
|
||||||
|
}
|
||||||
|
bgClassName={
|
||||||
|
hasIssues
|
||||||
|
? 'hover:bg-destroy-10/50 hover:dark:bg-destroy-80/50 rounded'
|
||||||
|
: 'hover:bg-succeed-10/50 hover:dark:bg-succeed-80/50 rounded'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Popover.Button>
|
||||||
|
<Popover.Panel className="absolute right-0 left-auto top-full mt-1 w-56 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch py-2 bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm">
|
||||||
|
{!hasIssues ? (
|
||||||
|
<span
|
||||||
|
className="flex items-center justify-center gap-1 px-4"
|
||||||
|
data-testid="network-good"
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon={faCheck}
|
||||||
|
bgClassName={'bg-succeed-10/50 dark:bg-succeed-80/50 rounded'}
|
||||||
|
iconClassName={'text-succeed-80 dark:text-succeed-30'}
|
||||||
|
/>
|
||||||
|
{NETWORK_CONTENT.good}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||||
|
<span
|
||||||
|
className="font-bold text-xs uppercase text-destroy-60 dark:text-destroy-50 px-4"
|
||||||
|
data-testid="network-bad"
|
||||||
|
>
|
||||||
|
{NETWORK_CONTENT.bad}
|
||||||
|
{networkIssues.length > 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
{networkIssues.map((issue) => (
|
||||||
|
<li
|
||||||
|
key={issue}
|
||||||
|
className="flex items-center gap-1 py-2 my-2 last:mb-0"
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon={faExclamation}
|
||||||
|
bgClassName={'bg-destroy-10/50 dark:bg-destroy-80/50 rounded'}
|
||||||
|
iconClassName={'text-destroy-80 dark:text-destroy-30'}
|
||||||
|
className="ml-4"
|
||||||
|
/>
|
||||||
|
<p className="flex-1 mr-4">{issue}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
@ -7,11 +7,14 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { getNormalisedCoordinates } from '../lib/utils'
|
import { getNormalisedCoordinates, roundOff } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
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 = '' }) => {
|
export const Stream = ({ className = '' }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -25,6 +28,11 @@ export const Stream = ({ className = '' }) => {
|
|||||||
setDidDragInStream,
|
setDidDragInStream,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
|
guiMode,
|
||||||
|
ast,
|
||||||
|
updateAst,
|
||||||
|
setGuiMode,
|
||||||
|
programMemory,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
mediaStream: s.mediaStream,
|
mediaStream: s.mediaStream,
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
@ -34,6 +42,11 @@ export const Stream = ({ className = '' }) => {
|
|||||||
setDidDragInStream: s.setDidDragInStream,
|
setDidDragInStream: s.setDidDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
isExecuting: s.isExecuting,
|
isExecuting: s.isExecuting,
|
||||||
|
guiMode: s.guiMode,
|
||||||
|
ast: s.ast,
|
||||||
|
updateAst: s.updateAst,
|
||||||
|
setGuiMode: s.setGuiMode,
|
||||||
|
programMemory: s.programMemory,
|
||||||
}))
|
}))
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
@ -64,7 +77,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const newId = uuidv4()
|
const newId = uuidv4()
|
||||||
|
|
||||||
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
||||||
let interaction: CameraDragInteractionType_type
|
let interaction: CameraDragInteractionType_type = 'rotate'
|
||||||
|
|
||||||
if (
|
if (
|
||||||
interactionGuards.pan.callback(e) ||
|
interactionGuards.pan.callback(e) ||
|
||||||
@ -81,19 +94,33 @@ export const Stream = ({ className = '' }) => {
|
|||||||
interactionGuards.zoom.lenientDragStartButton === e.button
|
interactionGuards.zoom.lenientDragStartButton === e.button
|
||||||
) {
|
) {
|
||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
||||||
type: 'modeling_cmd_req',
|
engineCommandManager?.sendSceneCommand({
|
||||||
cmd: {
|
type: 'modeling_cmd_req',
|
||||||
type: 'camera_drag_start',
|
cmd: {
|
||||||
interaction,
|
type: 'handle_mouse_drag_start',
|
||||||
window: { x, y },
|
window: { x, y },
|
||||||
},
|
},
|
||||||
cmd_id: newId,
|
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)
|
setButtonDownInStream(e.button)
|
||||||
setClickCoords({ x, y })
|
setClickCoords({ x, y })
|
||||||
@ -118,6 +145,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
ctrlKey,
|
ctrlKey,
|
||||||
}) => {
|
}) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
|
setButtonDownInStream(undefined)
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
@ -128,7 +156,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
const interaction = ctrlKey ? 'pan' : 'rotate'
|
const interaction = ctrlKey ? 'pan' : 'rotate'
|
||||||
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
const command: Models['WebSocketRequest_type'] = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'camera_drag_end',
|
type: 'camera_drag_end',
|
||||||
@ -136,9 +164,8 @@ export const Stream = ({ className = '' }) => {
|
|||||||
window: { x, y },
|
window: { x, y },
|
||||||
},
|
},
|
||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
})
|
}
|
||||||
|
|
||||||
setButtonDownInStream(undefined)
|
|
||||||
if (!didDragInStream) {
|
if (!didDragInStream) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
@ -150,6 +177,95 @@ export const Stream = ({ className = '' }) => {
|
|||||||
cmd_id: uuidv4(),
|
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)
|
setDidDragInStream(false)
|
||||||
setClickCoords(undefined)
|
setClickCoords(undefined)
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,6 @@ export const TextEditor = ({
|
|||||||
sourceRangeMap,
|
sourceRangeMap,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
code: s.code,
|
code: s.code,
|
||||||
defferedCode: s.defferedCode,
|
|
||||||
defferedSetCode: s.defferedSetCode,
|
defferedSetCode: s.defferedSetCode,
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
@ -71,7 +70,6 @@ export const TextEditor = ({
|
|||||||
isLSPServerReady: s.isLSPServerReady,
|
isLSPServerReady: s.isLSPServerReady,
|
||||||
selectionRanges: s.selectionRanges,
|
selectionRanges: s.selectionRanges,
|
||||||
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
||||||
setCode: s.setCode,
|
|
||||||
setEditorView: s.setEditorView,
|
setEditorView: s.setEditorView,
|
||||||
setIsLSPServerReady: s.setIsLSPServerReady,
|
setIsLSPServerReady: s.setIsLSPServerReady,
|
||||||
setSelectionRanges: s.setSelectionRanges,
|
setSelectionRanges: s.setSelectionRanges,
|
||||||
|
243
src/hooks/useAppMode.ts
Normal file
243
src/hooks/useAppMode.ts
Normal file
@ -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<DefaultPlanes | null>(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
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parser_wasm } from './abstractSyntaxTree'
|
||||||
import { KCLUnexpectedError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { initPromise } from './rust'
|
import { initPromise } from './rust'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
@ -1744,6 +1744,12 @@ describe('parsing errors', () => {
|
|||||||
_theError = e
|
_theError = e
|
||||||
}
|
}
|
||||||
const theError = _theError as any
|
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]]
|
||||||
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ show(mySketch001)`
|
|||||||
)
|
)
|
||||||
expect(shown).toEqual([
|
expect(shown).toEqual([
|
||||||
{
|
{
|
||||||
type: 'sketchGroup',
|
type: 'SketchGroup',
|
||||||
start: {
|
start: {
|
||||||
to: [0, 0],
|
to: [0, 0],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -77,7 +77,7 @@ show(mySketch001)`
|
|||||||
)
|
)
|
||||||
expect(shown).toEqual([
|
expect(shown).toEqual([
|
||||||
{
|
{
|
||||||
type: 'extrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
height: 2,
|
height: 2,
|
||||||
@ -117,7 +117,7 @@ show(theExtrude, sk2)`
|
|||||||
)
|
)
|
||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
{
|
{
|
||||||
type: 'extrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
height: 2,
|
height: 2,
|
||||||
@ -126,7 +126,7 @@ show(theExtrude, sk2)`
|
|||||||
__meta: [{ sourceRange: [13, 34] }],
|
__meta: [{ sourceRange: [13, 34] }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'extrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
height: 2,
|
height: 2,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
|
|
||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parser_wasm } from './abstractSyntaxTree'
|
||||||
import { ProgramMemory } from './executor'
|
import { ProgramMemory, SketchGroup } from './executor'
|
||||||
import { initPromise } from './rust'
|
import { initPromise } from './rust'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { vi } from 'vitest'
|
import { vi } from 'vitest'
|
||||||
@ -117,10 +117,10 @@ show(mySketch)
|
|||||||
// ].join('\n')
|
// ].join('\n')
|
||||||
// const { root } = await exe(code)
|
// const { root } = await exe(code)
|
||||||
// expect(root.mySk1.value).toHaveLength(3)
|
// expect(root.mySk1.value).toHaveLength(3)
|
||||||
// expect(root?.rotated?.type).toBe('sketchGroup')
|
// expect(root?.rotated?.type).toBe('SketchGroup')
|
||||||
// if (
|
// if (
|
||||||
// root?.mySk1?.type !== 'sketchGroup' ||
|
// root?.mySk1?.type !== 'SketchGroup' ||
|
||||||
// root?.rotated?.type !== 'sketchGroup'
|
// root?.rotated?.type !== 'SketchGroup'
|
||||||
// )
|
// )
|
||||||
// throw new Error('not a sketch group')
|
// throw new Error('not a sketch group')
|
||||||
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
|
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
|
||||||
@ -143,7 +143,7 @@ show(mySketch)
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
expect(root.mySk1).toEqual({
|
expect(root.mySk1).toEqual({
|
||||||
type: 'sketchGroup',
|
type: 'SketchGroup',
|
||||||
start: {
|
start: {
|
||||||
to: [0, 0],
|
to: [0, 0],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -199,7 +199,7 @@ show(mySketch)
|
|||||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||||
expect(root).toEqual({
|
expect(root).toEqual({
|
||||||
three: {
|
three: {
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: 3,
|
value: 3,
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -208,7 +208,7 @@ show(mySketch)
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
yo: {
|
yo: {
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: [1, '2', 3, 9],
|
value: [1, '2', 3, 9],
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -225,7 +225,7 @@ show(mySketch)
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
expect(root.yo).toEqual({
|
expect(root.yo).toEqual({
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -240,7 +240,7 @@ show(mySketch)
|
|||||||
)
|
)
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
expect(root.myVar).toEqual({
|
expect(root.myVar).toEqual({
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: '123',
|
value: '123',
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -338,7 +338,7 @@ describe('testing math operators', () => {
|
|||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = root.part001
|
||||||
// result of `-legLen(5, min(3, 999))` should be -4
|
// 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)
|
expect(yVal).toBe(-4)
|
||||||
})
|
})
|
||||||
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
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 { root } = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = root.part001
|
||||||
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
||||||
expect(sketch.value?.[1]?.from).toEqual([3, 4])
|
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
||||||
expect(sketch.value?.[1]?.to).toEqual([6, 0])
|
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
||||||
const removedUnaryExp = code.replace(
|
const removedUnaryExp = code.replace(
|
||||||
`-legLen(segLen('seg01', %), myVar)`,
|
`-legLen(segLen('seg01', %), myVar)`,
|
||||||
`legLen(segLen('seg01', %), myVar)`
|
`legLen(segLen('seg01', %), myVar)`
|
||||||
@ -366,7 +366,9 @@ describe('testing math operators', () => {
|
|||||||
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
||||||
|
|
||||||
// without the minus sign, the y value should be 8
|
// 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 () => {
|
it('with nested callExpression and binaryExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||||
@ -397,7 +399,10 @@ show(theExtrude)`
|
|||||||
|
|
||||||
// helpers
|
// 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 ast = parser_wasm(code)
|
||||||
|
|
||||||
const result = await enginelessExecutor(ast, programMemory)
|
const result = await enginelessExecutor(ast, programMemory)
|
||||||
|
@ -5,96 +5,21 @@ import {
|
|||||||
SourceRangeMap,
|
SourceRangeMap,
|
||||||
} from './std/engineConnection'
|
} from './std/engineConnection'
|
||||||
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
|
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 { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
import { rangeTypeFix } from './abstractSyntaxTree'
|
import { rangeTypeFix } from './abstractSyntaxTree'
|
||||||
|
|
||||||
export type SourceRange = [number, number]
|
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
|
||||||
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
|
export type { Position } from '../wasm-lib/kcl/bindings/Position'
|
||||||
export type Metadata = {
|
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
||||||
sourceRange: SourceRange
|
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
||||||
}
|
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
||||||
export type Position = [number, number, number]
|
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||||
export type Rotation = [number, number, number, number]
|
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||||
|
|
||||||
interface BasePath {
|
export type PathToNode = [string | number, string][]
|
||||||
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
|
|
||||||
|
|
||||||
interface Memory {
|
interface Memory {
|
||||||
[key: string]: MemoryItem
|
[key: string]: MemoryItem
|
||||||
@ -102,12 +27,12 @@ interface Memory {
|
|||||||
|
|
||||||
export interface ProgramMemory {
|
export interface ProgramMemory {
|
||||||
root: Memory
|
root: Memory
|
||||||
return?: ProgramReturn
|
return: ProgramReturn | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executor = async (
|
export const executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {} },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
// work around while the gemotry is still be stored on the frontend
|
// work around while the gemotry is still be stored on the frontend
|
||||||
// will be removed when the stream UI is added.
|
// will be removed when the stream UI is added.
|
||||||
@ -132,7 +57,7 @@ export const executor = async (
|
|||||||
|
|
||||||
export const _executor = async (
|
export const _executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {} },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
try {
|
try {
|
||||||
|
@ -176,7 +176,7 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Testing moveValueIntoNewVariable', () => {
|
describe('Testing moveValueIntoNewVariable', () => {
|
||||||
const fn = (fnName: string) => `const ${fnName} = (x) => {
|
const fn = (fnName: string) => `fn ${fnName} = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -27,6 +27,48 @@ import {
|
|||||||
getFirstArg,
|
getFirstArg,
|
||||||
createFirstArg,
|
createFirstArg,
|
||||||
} from './std/sketch'
|
} 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(
|
export function addSketchTo(
|
||||||
node: Program,
|
node: Program,
|
||||||
@ -151,7 +193,7 @@ export function mutateArrExp(
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (node.type === 'ArrayExpression') {
|
if (node.type === 'ArrayExpression') {
|
||||||
node.elements.forEach((element, i) => {
|
node.elements.forEach((element, i) => {
|
||||||
if (element.type === 'Literal') {
|
if (isLiteralArrayOrStatic(element)) {
|
||||||
node.elements[i] = updateWith.elements[i]
|
node.elements[i] = updateWith.elements[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -169,8 +211,8 @@ export function mutateObjExpProp(
|
|||||||
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
||||||
if (keyIndex !== -1) {
|
if (keyIndex !== -1) {
|
||||||
if (
|
if (
|
||||||
updateWith.type === 'Literal' &&
|
isLiteralArrayOrStatic(updateWith) &&
|
||||||
node.properties[keyIndex].value.type === 'Literal'
|
isLiteralArrayOrStatic(node.properties[keyIndex].value)
|
||||||
) {
|
) {
|
||||||
node.properties[keyIndex].value = updateWith
|
node.properties[keyIndex].value = updateWith
|
||||||
return true
|
return true
|
||||||
@ -180,7 +222,7 @@ export function mutateObjExpProp(
|
|||||||
) {
|
) {
|
||||||
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
||||||
arrExp.elements.forEach((element, i) => {
|
arrExp.elements.forEach((element, i) => {
|
||||||
if (element.type === 'Literal') {
|
if (isLiteralArrayOrStatic(element)) {
|
||||||
arrExp.elements[i] = updateWith.elements[i]
|
arrExp.elements[i] = updateWith.elements[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -224,7 +224,7 @@ const key = 'c'
|
|||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments in a fn block', () => {
|
it('comments in a fn block', () => {
|
||||||
const code = `const myFn = () => {
|
const code = `fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import { exportSave } from 'lib/exportSave'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
|
|
||||||
|
let lastMessage = ''
|
||||||
|
|
||||||
interface CommandInfo {
|
interface CommandInfo {
|
||||||
commandType: CommandTypes
|
commandType: CommandTypes
|
||||||
range: SourceRange
|
range: SourceRange
|
||||||
@ -754,6 +756,13 @@ export class EngineCommandManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
sendSceneCommand(command: EngineCommand): Promise<any> {
|
||||||
|
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()) {
|
if (!this.engineConnection?.isReady()) {
|
||||||
console.log('socket not ready')
|
console.log('socket not ready')
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@ -761,7 +770,8 @@ export class EngineCommandManager {
|
|||||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||||
const cmd = command.cmd
|
const cmd = command.cmd
|
||||||
if (
|
if (
|
||||||
cmd.type === 'camera_drag_move' &&
|
(cmd.type === 'camera_drag_move' ||
|
||||||
|
cmd.type === 'handle_mouse_drag_move') &&
|
||||||
this.engineConnection?.unreliableDataChannel
|
this.engineConnection?.unreliableDataChannel
|
||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
SketchGroup,
|
SketchGroup,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
|
MemoryItem,
|
||||||
} from '../executor'
|
} from '../executor'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
@ -19,8 +20,9 @@ import {
|
|||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
} from '../queryAst'
|
} from '../queryAst'
|
||||||
|
import { isLiteralArrayOrStatic } from './sketchcombos'
|
||||||
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
||||||
import { splitPathAtPipeExpression } from '../modifyAst'
|
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||||
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
||||||
|
|
||||||
import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes'
|
import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes'
|
||||||
@ -185,7 +187,7 @@ export const line: SketchLineHelper = {
|
|||||||
createCallback,
|
createCallback,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
@ -197,12 +199,12 @@ export const line: SketchLineHelper = {
|
|||||||
)
|
)
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
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 newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
const newYVal = createLiteral(roundOff(to[1] - from[1], 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 { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
const { callExp, valueUsedInTransform } = createCallback(
|
const { callExp, valueUsedInTransform } = createCallback(
|
||||||
[newXVal, newYVal],
|
[newXVal, newYVal],
|
||||||
@ -220,7 +222,11 @@ export const line: SketchLineHelper = {
|
|||||||
createArrayExpression([newXVal, newYVal]),
|
createArrayExpression([newXVal, newYVal]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
pipe.body = [...pipe.body, callExp]
|
if (pipe.type === 'PipeExpression') {
|
||||||
|
pipe.body = [...pipe.body, callExp]
|
||||||
|
} else {
|
||||||
|
varDec.init = createPipeExpression([varDec.init, callExp])
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -238,22 +244,10 @@ export const line: SketchLineHelper = {
|
|||||||
createLiteral(roundOff(to[1] - from[1], 2)),
|
createLiteral(roundOff(to[1] - from[1], 2)),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (
|
if (callExpression.arguments?.[0].type === 'ObjectExpression') {
|
||||||
callExpression.arguments?.[0].type === 'Literal' &&
|
|
||||||
callExpression.arguments?.[0].value === 'default'
|
|
||||||
) {
|
|
||||||
callExpression.arguments[0] = toArrExp
|
|
||||||
} else if (callExpression.arguments?.[0].type === 'ObjectExpression') {
|
|
||||||
const toProp = callExpression.arguments?.[0].properties?.find(
|
const toProp = callExpression.arguments?.[0].properties?.find(
|
||||||
({ key }) => key.name === 'to'
|
({ key }) => key.name === 'to'
|
||||||
)
|
)
|
||||||
if (
|
|
||||||
toProp &&
|
|
||||||
toProp.value.type === 'Literal' &&
|
|
||||||
toProp.value.value === 'default'
|
|
||||||
) {
|
|
||||||
toProp.value = toArrExp
|
|
||||||
}
|
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
|
||||||
} else {
|
} else {
|
||||||
mutateArrExp(callExpression.arguments?.[0], toArrExp)
|
mutateArrExp(callExpression.arguments?.[0], toArrExp)
|
||||||
@ -301,7 +295,7 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newX = createLiteral(roundOff(to[0], 2))
|
const newX = createLiteral(roundOff(to[0], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newX
|
callExpression.arguments[0] = newX
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
||||||
@ -349,7 +343,7 @@ export const yLineTo: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newY = createLiteral(roundOff(to[1], 2))
|
const newY = createLiteral(roundOff(to[1], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newY
|
callExpression.arguments[0] = newY
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
||||||
@ -399,7 +393,7 @@ export const xLine: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newX
|
callExpression.arguments[0] = newX
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
||||||
@ -443,7 +437,7 @@ export const yLine: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newY
|
callExpression.arguments[0] = newY
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
||||||
@ -546,7 +540,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
)
|
)
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
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 angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
||||||
const newLine = createCallback
|
const newLine = createCallback
|
||||||
@ -619,7 +613,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
)
|
)
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
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 angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
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 varName = varDec.declarations[0].id.name
|
||||||
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
||||||
const intersectPath = sketchGroup.value.find(
|
const intersectPath = sketchGroup.value.find(
|
||||||
({ name }) => name === intersectTagName
|
({ name }: Path) => name === intersectTagName
|
||||||
)
|
)
|
||||||
let offset = 0
|
let offset = 0
|
||||||
if (intersectPath) {
|
if (intersectPath) {
|
||||||
@ -968,60 +962,14 @@ export function addNewSketchLn({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
const { node: pipeExp, shallowPath: pipePath } =
|
const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath<
|
||||||
getNodeFromPath<PipeExpression>(node, pathToNode, 'PipeExpression')
|
PipeExpression | CallExpression
|
||||||
const maybeStartSketchAt = pipeExp.body.find(
|
>(node, pathToNode, 'PipeExpression')
|
||||||
(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 variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
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 last = sketch.value[sketch.value.length - 1] || sketch.start
|
||||||
const startSketchAt = maybeStartSketchAt as any
|
|
||||||
startSketchAt.arguments[0] = createArrayExpression([
|
|
||||||
createLiteral(to[0]),
|
|
||||||
createLiteral(to[1]),
|
|
||||||
])
|
|
||||||
return {
|
|
||||||
modifiedAst: node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maybeDefaultLine !== -1) {
|
|
||||||
const defaultLine = getNodeFromPath<CallExpression>(
|
|
||||||
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 from = last.to
|
const from = last.to
|
||||||
|
|
||||||
return add({
|
return add({
|
||||||
@ -1089,10 +1037,11 @@ export function addTagForSketchOnFace(
|
|||||||
|
|
||||||
function isAngleLiteral(lineArugement: Value): boolean {
|
function isAngleLiteral(lineArugement: Value): boolean {
|
||||||
return lineArugement?.type === 'ArrayExpression'
|
return lineArugement?.type === 'ArrayExpression'
|
||||||
? lineArugement.elements[0].type === 'Literal'
|
? isLiteralArrayOrStatic(lineArugement.elements[0])
|
||||||
: lineArugement?.type === 'ObjectExpression'
|
: lineArugement?.type === 'ObjectExpression'
|
||||||
? lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
? isLiteralArrayOrStatic(
|
||||||
.type === 'Literal'
|
lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
||||||
|
)
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1198,14 +1147,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
|||||||
} {
|
} {
|
||||||
// used for lineTo, line
|
// used for lineTo, line
|
||||||
const firstArg = callExpression.arguments[0]
|
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') {
|
if (firstArg.type === 'ArrayExpression') {
|
||||||
return { val: [firstArg.elements[0], firstArg.elements[1]] }
|
return { val: [firstArg.elements[0], firstArg.elements[1]] }
|
||||||
}
|
}
|
||||||
@ -1215,8 +1156,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
|||||||
if (to?.type === 'ArrayExpression') {
|
if (to?.type === 'ArrayExpression') {
|
||||||
const [x, y] = to.elements
|
const [x, y] = to.elements
|
||||||
return { val: [x, y], tag }
|
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')
|
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||||
|
@ -401,6 +401,11 @@ show(part001)`
|
|||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.root['part001'] as SketchGroup,
|
||||||
[index, index]
|
[index, index]
|
||||||
).segment
|
).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',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
} from '../abstractSyntaxTreeTypes'
|
} from '../abstractSyntaxTreeTypes'
|
||||||
import { SketchGroup, SourceRange } from '../executor'
|
import { SketchGroup, SourceRange, Path } from '../executor'
|
||||||
|
|
||||||
export function getSketchSegmentFromSourceRange(
|
export function getSketchSegmentFromSourceRange(
|
||||||
sketchGroup: SketchGroup,
|
sketchGroup: SketchGroup,
|
||||||
@ -20,10 +20,10 @@ export function getSketchSegmentFromSourceRange(
|
|||||||
startSourceRange[1] >= rangeEnd &&
|
startSourceRange[1] >= rangeEnd &&
|
||||||
sketchGroup.start
|
sketchGroup.start
|
||||||
)
|
)
|
||||||
return { segment: sketchGroup.start, index: -1 }
|
return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
|
||||||
|
|
||||||
const lineIndex = sketchGroup.value.findIndex(
|
const lineIndex = sketchGroup.value.findIndex(
|
||||||
({ __geoMeta: { sourceRange } }) =>
|
({ __geoMeta: { sourceRange } }: Path) =>
|
||||||
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
||||||
)
|
)
|
||||||
const line = sketchGroup.value[lineIndex]
|
const line = sketchGroup.value[lineIndex]
|
||||||
|
@ -28,6 +28,7 @@ import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
|
|||||||
import { PathToNode, ProgramMemory } from '../executor'
|
import { PathToNode, ProgramMemory } from '../executor'
|
||||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||||
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
||||||
|
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
|
||||||
|
|
||||||
type LineInputsType =
|
type LineInputsType =
|
||||||
| 'xAbsolute'
|
| 'xAbsolute'
|
||||||
@ -1136,27 +1137,18 @@ export function getRemoveConstraintsTransform(
|
|||||||
|
|
||||||
// check if the function is locked down and so can't be transformed
|
// check if the function is locked down and so can't be transformed
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return transformInfo
|
||||||
if (a?.type !== 'Literal' || b?.type !== 'Literal') {
|
|
||||||
return transformInfo
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') {
|
|
||||||
return transformInfo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
Array.isArray(firstArg.val) &&
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
if (isTwoValFree) {
|
if (isTwoValFree) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
if (isOneValFree) {
|
if (isOneValFree) {
|
||||||
return transformInfo
|
return transformInfo
|
||||||
}
|
}
|
||||||
@ -1187,25 +1179,12 @@ function getTransformMapPath(
|
|||||||
|
|
||||||
// check if the function is locked down and so can't be transformed
|
// check if the function is locked down and so can't be transformed
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return false
|
||||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
if (isLiteralArrayOrStatic(firstArg.val)) {
|
||||||
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) {
|
|
||||||
const info = transformMap?.[name]?.free?.[constraintType]
|
const info = transformMap?.[name]?.free?.[constraintType]
|
||||||
if (info)
|
if (info)
|
||||||
return {
|
return {
|
||||||
@ -1259,7 +1238,7 @@ export function getConstraintType(
|
|||||||
if (fnName === 'xLineTo') return 'yAbsolute'
|
if (fnName === 'xLineTo') return 'yAbsolute'
|
||||||
if (fnName === 'yLineTo') return 'xAbsolute'
|
if (fnName === 'yLineTo') return 'xAbsolute'
|
||||||
} else {
|
} else {
|
||||||
const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
|
const isFirstArgLockedDown = isNotLiteralArrayOrStatic(val[0])
|
||||||
if (fnName === 'line')
|
if (fnName === 'line')
|
||||||
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
||||||
if (fnName === 'lineTo')
|
if (fnName === 'lineTo')
|
||||||
@ -1452,7 +1431,7 @@ export function transformAstSketchLines({
|
|||||||
|
|
||||||
const varName = varDec.id.name
|
const varName = varDec.id.name
|
||||||
const sketchGroup = programMemory.root?.[varName]
|
const sketchGroup = programMemory.root?.[varName]
|
||||||
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
|
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
||||||
throw new Error('not a sketch group')
|
throw new Error('not a sketch group')
|
||||||
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
|
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
|
||||||
const referencedSegment = referencedSegmentRange
|
const referencedSegment = referencedSegmentRange
|
||||||
@ -1538,23 +1517,46 @@ export function getConstraintLevelFromSourceRange(
|
|||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
|
|
||||||
// check if the function is fully constrained
|
// check if the function is fully constrained
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return 'full'
|
||||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full'
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') return 'full'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
Array.isArray(firstArg.val) &&
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
|
|
||||||
if (isTwoValFree) return 'free'
|
if (isTwoValFree) return 'free'
|
||||||
if (isOneValFree) return 'partial'
|
if (isOneValFree) return 'partial'
|
||||||
|
|
||||||
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')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -131,10 +131,12 @@ const yi=45`
|
|||||||
})
|
})
|
||||||
it('test negative and decimal numbers', () => {
|
it('test negative and decimal numbers', () => {
|
||||||
expect(stringSummaryLexer('-1')).toEqual([
|
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([
|
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([
|
expect(stringSummaryLexer('1.5')).toEqual([
|
||||||
"number '1.5' from 0 to 3",
|
"number '1.5' from 0 to 3",
|
||||||
@ -158,10 +160,12 @@ const yi=45`
|
|||||||
"whitespace ' ' from 3 to 4",
|
"whitespace ' ' from 3 to 4",
|
||||||
"operator '+' from 4 to 5",
|
"operator '+' from 4 to 5",
|
||||||
"whitespace ' ' from 5 to 6",
|
"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([
|
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",
|
"whitespace ' ' from 4 to 5",
|
||||||
"operator '+' from 5 to 6",
|
"operator '+' from 5 to 6",
|
||||||
"whitespace ' ' from 6 to 7",
|
"whitespace ' ' from 6 to 7",
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
readDir,
|
readDir,
|
||||||
writeTextFile,
|
writeTextFile,
|
||||||
} from '@tauri-apps/api/fs'
|
} 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 { isTauri } from './isTauri'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { ProjectWithEntryPointMetadata } from '../Router'
|
||||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
@ -32,7 +32,13 @@ export async function initializeProjectDirectory(directory: string) {
|
|||||||
return directory
|
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
|
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class MockEngineCommandManager {
|
|||||||
|
|
||||||
export async function enginelessExecutor(
|
export async function enginelessExecutor(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
pm: ProgramMemory = { root: {} }
|
pm: ProgramMemory = { root: {}, return: null }
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
@ -64,7 +64,7 @@ export async function enginelessExecutor(
|
|||||||
|
|
||||||
export async function executor(
|
export async function executor(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
pm: ProgramMemory = { root: {} }
|
pm: ProgramMemory = { root: {}, return: null }
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
const engineCommandManager = new EngineCommandManager({
|
const engineCommandManager = new EngineCommandManager({
|
||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
|
@ -54,9 +54,12 @@ export type TooTip =
|
|||||||
| 'yLineTo'
|
| 'yLineTo'
|
||||||
| 'angledLineThatIntersects'
|
| 'angledLineThatIntersects'
|
||||||
|
|
||||||
export const toolTips: TooTip[] = [
|
export const toolTips = [
|
||||||
'lineTo',
|
'sketch_line',
|
||||||
|
'move',
|
||||||
|
// original tooltips
|
||||||
'line',
|
'line',
|
||||||
|
'lineTo',
|
||||||
'angledLine',
|
'angledLine',
|
||||||
'angledLineOfXLength',
|
'angledLineOfXLength',
|
||||||
'angledLineOfYLength',
|
'angledLineOfYLength',
|
||||||
@ -67,7 +70,7 @@ export const toolTips: TooTip[] = [
|
|||||||
'xLineTo',
|
'xLineTo',
|
||||||
'yLineTo',
|
'yLineTo',
|
||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
]
|
] as any as TooTip[]
|
||||||
|
|
||||||
export type GuiModes =
|
export type GuiModes =
|
||||||
| {
|
| {
|
||||||
@ -77,6 +80,7 @@ export type GuiModes =
|
|||||||
mode: 'sketch'
|
mode: 'sketch'
|
||||||
sketchMode: TooTip
|
sketchMode: TooTip
|
||||||
isTooltip: true
|
isTooltip: true
|
||||||
|
waitingFirstClick: boolean
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
id?: string
|
id?: string
|
||||||
@ -95,6 +99,7 @@ export type GuiModes =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
mode: 'canEditSketch'
|
mode: 'canEditSketch'
|
||||||
|
pathId: string
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
@ -133,8 +138,8 @@ export interface StoreState {
|
|||||||
kclErrors: KCLError[]
|
kclErrors: KCLError[]
|
||||||
addKCLError: (err: KCLError) => void
|
addKCLError: (err: KCLError) => void
|
||||||
resetKCLErrors: () => void
|
resetKCLErrors: () => void
|
||||||
ast: Program | null
|
ast: Program
|
||||||
setAst: (ast: Program | null) => void
|
setAst: (ast: Program) => void
|
||||||
updateAst: (
|
updateAst: (
|
||||||
ast: Program,
|
ast: Program,
|
||||||
optionalParams?: {
|
optionalParams?: {
|
||||||
@ -233,12 +238,13 @@ export const useStore = create<StoreState>()(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editorView.dispatch({
|
ranges.length &&
|
||||||
selection: EditorSelection.create(
|
editorView.dispatch({
|
||||||
ranges,
|
selection: EditorSelection.create(
|
||||||
selections.codeBasedSelections.length - 1
|
ranges,
|
||||||
),
|
selections.codeBasedSelections.length - 1
|
||||||
})
|
),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setCursor2: (codeSelections) => {
|
setCursor2: (codeSelections) => {
|
||||||
@ -292,7 +298,15 @@ export const useStore = create<StoreState>()(
|
|||||||
resetKCLErrors: () => {
|
resetKCLErrors: () => {
|
||||||
set({ kclErrors: [] })
|
set({ kclErrors: [] })
|
||||||
},
|
},
|
||||||
ast: null,
|
ast: {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: [],
|
||||||
|
nonCodeMeta: {
|
||||||
|
noneCodeNodes: {},
|
||||||
|
start: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
setAst: (ast) => {
|
setAst: (ast) => {
|
||||||
set({ ast })
|
set({ ast })
|
||||||
},
|
},
|
||||||
@ -301,7 +315,11 @@ export const useStore = create<StoreState>()(
|
|||||||
const astWithUpdatedSource = parser_wasm(newCode)
|
const astWithUpdatedSource = parser_wasm(newCode)
|
||||||
callBack(astWithUpdatedSource)
|
callBack(astWithUpdatedSource)
|
||||||
|
|
||||||
set({ ast: astWithUpdatedSource, code: newCode })
|
set({
|
||||||
|
ast: astWithUpdatedSource,
|
||||||
|
code: newCode,
|
||||||
|
defferedCode: newCode,
|
||||||
|
})
|
||||||
if (focusPath) {
|
if (focusPath) {
|
||||||
const { node } = getNodeFromPath<any>(
|
const { node } = getNodeFromPath<any>(
|
||||||
astWithUpdatedSource,
|
astWithUpdatedSource,
|
||||||
@ -353,7 +371,7 @@ export const useStore = create<StoreState>()(
|
|||||||
setError: (error = '') => {
|
setError: (error = '') => {
|
||||||
set({ errorState: { isError: !!error, error } })
|
set({ errorState: { isError: !!error, error } })
|
||||||
},
|
},
|
||||||
programMemory: { root: {}, pendingMemory: {} },
|
programMemory: { root: {}, return: null },
|
||||||
setProgramMemory: (programMemory) => set({ programMemory }),
|
setProgramMemory: (programMemory) => set({ programMemory }),
|
||||||
isShiftDown: false,
|
isShiftDown: false,
|
||||||
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
||||||
|
698
src/wasm-lib/Cargo.lock
generated
698
src/wasm-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,10 +12,19 @@ bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
|||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
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 = "0.2.87"
|
||||||
wasm-bindgen-futures = "0.4.37"
|
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]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
js-sys = "0.3.64"
|
js-sys = "0.3.64"
|
||||||
|
@ -16,7 +16,7 @@ proc-macro2 = "1"
|
|||||||
quote = "1"
|
quote = "1"
|
||||||
serde = { version = "1.0.186", features = ["derive"] }
|
serde = { version = "1.0.186", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.29", features = ["full"] }
|
syn = { version = "2.0.32", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expectorate = "1.0.7"
|
expectorate = "1.0.7"
|
||||||
|
@ -19,7 +19,7 @@ parse-display = "0.8.2"
|
|||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
||||||
serde = {version = "1.0.152", features = ["derive"] }
|
serde = {version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.93"
|
serde_json = "1.0.106"
|
||||||
thiserror = "1.0.47"
|
thiserror = "1.0.47"
|
||||||
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
|
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"] }
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
|
@ -12,7 +12,7 @@ use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, R
|
|||||||
use crate::{
|
use crate::{
|
||||||
engine::EngineConnection,
|
engine::EngineConnection,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange},
|
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
||||||
parser::PIPE_OPERATOR,
|
parser::PIPE_OPERATOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -449,6 +449,7 @@ pub enum BinaryPart {
|
|||||||
BinaryExpression(Box<BinaryExpression>),
|
BinaryExpression(Box<BinaryExpression>),
|
||||||
CallExpression(Box<CallExpression>),
|
CallExpression(Box<CallExpression>),
|
||||||
UnaryExpression(Box<UnaryExpression>),
|
UnaryExpression(Box<UnaryExpression>),
|
||||||
|
MemberExpression(Box<MemberExpression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BinaryPart> for crate::executor::SourceRange {
|
impl From<BinaryPart> for crate::executor::SourceRange {
|
||||||
@ -471,6 +472,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
|
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
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::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_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::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_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()],
|
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::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::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) => {
|
BinaryPart::UnaryExpression(ref mut unary_expression) => {
|
||||||
unary_expression.rename_identifiers(old_name, new_name)
|
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(),
|
.clone(),
|
||||||
Value::MemberExpression(member_expression) => {
|
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
|
||||||
source_ranges: vec![member_expression.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Value::FunctionExpression(function_expression) => {
|
Value::FunctionExpression(function_expression) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||||
@ -1082,23 +1088,23 @@ impl Literal {
|
|||||||
|
|
||||||
impl From<Literal> for MemoryItem {
|
impl From<Literal> for MemoryItem {
|
||||||
fn from(literal: Literal) -> Self {
|
fn from(literal: Literal) -> Self {
|
||||||
MemoryItem::UserVal {
|
MemoryItem::UserVal(UserVal {
|
||||||
value: literal.value.clone(),
|
value: literal.value.clone(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: literal.into(),
|
source_range: literal.into(),
|
||||||
}],
|
}],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Box<Literal>> for MemoryItem {
|
impl From<&Box<Literal>> for MemoryItem {
|
||||||
fn from(literal: &Box<Literal>) -> Self {
|
fn from(literal: &Box<Literal>) -> Self {
|
||||||
MemoryItem::UserVal {
|
MemoryItem::UserVal(UserVal {
|
||||||
value: literal.value.clone(),
|
value: literal.value.clone(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: literal.into(),
|
source_range: literal.into(),
|
||||||
}],
|
}],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1227,12 +1233,7 @@ impl ArrayExpression {
|
|||||||
source_ranges: vec![pipe_substitution.into()],
|
source_ranges: vec![pipe_substitution.into()],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Value::MemberExpression(member_expression) => {
|
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
|
||||||
source_ranges: vec![member_expression.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Value::FunctionExpression(function_expression) => {
|
Value::FunctionExpression(function_expression) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||||
@ -1245,12 +1246,12 @@ impl ArrayExpression {
|
|||||||
results.push(result);
|
results.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: results.into(),
|
value: results.into(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// 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()?);
|
object.insert(property.key.name.clone(), result.get_json_value()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: object.into(),
|
value: object.into(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
@ -1554,6 +1555,38 @@ impl MemberExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_result_array(&self, memory: &mut ProgramMemory, index: usize) -> Result<MemoryItem, KclError> {
|
||||||
|
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<MemoryItem, KclError> {
|
pub fn get_result(&self, memory: &mut ProgramMemory) -> Result<MemoryItem, KclError> {
|
||||||
let property_name = match &self.property {
|
let property_name = match &self.property {
|
||||||
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
|
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
|
||||||
@ -1562,9 +1595,12 @@ impl MemberExpression {
|
|||||||
// Parse this as a string.
|
// Parse this as a string.
|
||||||
if let serde_json::Value::String(string) = value {
|
if let serde_json::Value::String(string) = value {
|
||||||
string
|
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 {
|
} else {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
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()],
|
source_ranges: vec![literal.into()],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -1582,12 +1618,12 @@ impl MemberExpression {
|
|||||||
|
|
||||||
if let serde_json::Value::Object(map) = object {
|
if let serde_json::Value::Object(map) = object {
|
||||||
if let Some(value) = map.get(&property_name) {
|
if let Some(value) = map.get(&property_name) {
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: value.clone(),
|
value: value.clone(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("Property {} not found in object", property_name),
|
message: format!("Property {} not found in object", property_name),
|
||||||
@ -1715,12 +1751,12 @@ impl BinaryExpression {
|
|||||||
parse_json_value_as_string(&right_json_value),
|
parse_json_value_as_string(&right_json_value),
|
||||||
) {
|
) {
|
||||||
let value = serde_json::Value::String(format!("{}{}", left, right));
|
let value = serde_json::Value::String(format!("{}{}", left, right));
|
||||||
return Ok(MemoryItem::UserVal {
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
value,
|
value,
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1735,12 +1771,12 @@ impl BinaryExpression {
|
|||||||
BinaryOperator::Mod => (left % right).into(),
|
BinaryOperator::Mod => (left % right).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value,
|
value,
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// 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<usize, KclError> {
|
||||||
|
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<String> {
|
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
||||||
if let serde_json::Value::String(n) = &j {
|
if let serde_json::Value::String(n) = &j {
|
||||||
Some(n.clone())
|
Some(n.clone())
|
||||||
@ -1845,12 +1897,12 @@ impl UnaryExpression {
|
|||||||
.get_json_value()?,
|
.get_json_value()?,
|
||||||
self.into(),
|
self.into(),
|
||||||
)?;
|
)?;
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: (-(num)).into(),
|
value: (-(num)).into(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a hover value that includes the given character position.
|
/// Returns a hover value that includes the given character position.
|
||||||
@ -2231,7 +2283,7 @@ show(part001)
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_comment_in_a_fn_block() {
|
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
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } } /* block
|
const yo = { a: { b: { c: '123' } } } /* block
|
||||||
comment */
|
comment */
|
||||||
@ -2247,7 +2299,7 @@ show(part001)
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"const myFn = () => {
|
r#"fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
/* block
|
/* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,16 +98,14 @@ impl ProgramReturn {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type")]
|
||||||
pub enum MemoryItem {
|
pub enum MemoryItem {
|
||||||
UserVal {
|
UserVal(UserVal),
|
||||||
value: serde_json::Value,
|
|
||||||
#[serde(rename = "__meta")]
|
|
||||||
meta: Vec<Metadata>,
|
|
||||||
},
|
|
||||||
SketchGroup(SketchGroup),
|
SketchGroup(SketchGroup),
|
||||||
ExtrudeGroup(ExtrudeGroup),
|
ExtrudeGroup(ExtrudeGroup),
|
||||||
|
#[ts(skip)]
|
||||||
ExtrudeTransform(ExtrudeTransform),
|
ExtrudeTransform(ExtrudeTransform),
|
||||||
|
#[ts(skip)]
|
||||||
Function {
|
Function {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
func: Option<MemoryFunction>,
|
func: Option<MemoryFunction>,
|
||||||
@ -119,7 +117,16 @@ pub enum MemoryItem {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[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<Metadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub struct ExtrudeTransform {
|
pub struct ExtrudeTransform {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub rotation: Rotation,
|
pub rotation: Rotation,
|
||||||
@ -138,7 +145,7 @@ pub type MemoryFunction = fn(
|
|||||||
impl From<MemoryItem> for Vec<SourceRange> {
|
impl From<MemoryItem> for Vec<SourceRange> {
|
||||||
fn from(item: MemoryItem) -> Self {
|
fn from(item: MemoryItem) -> Self {
|
||||||
match item {
|
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::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::ExtrudeGroup(e) => e.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(),
|
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
@ -149,8 +156,8 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
|||||||
|
|
||||||
impl MemoryItem {
|
impl MemoryItem {
|
||||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||||
if let MemoryItem::UserVal { value, .. } = self {
|
if let MemoryItem::UserVal(user_val) = self {
|
||||||
Ok(value.clone())
|
Ok(user_val.value.clone())
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("Not a user value: {:?}", self),
|
message: format!("Not a user value: {:?}", self),
|
||||||
@ -186,7 +193,7 @@ impl MemoryItem {
|
|||||||
/// A sketch group is a collection of paths.
|
/// A sketch group is a collection of paths.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub struct SketchGroup {
|
pub struct SketchGroup {
|
||||||
/// The id of the sketch group.
|
/// The id of the sketch group.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
@ -238,7 +245,7 @@ impl SketchGroup {
|
|||||||
/// An extrude group is a collection of extrude surfaces.
|
/// An extrude group is a collection of extrude surfaces.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub struct ExtrudeGroup {
|
pub struct ExtrudeGroup {
|
||||||
/// The id of the extrude group.
|
/// The id of the extrude group.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
@ -276,15 +283,15 @@ pub enum BodyType {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[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)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[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)]
|
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct SourceRange(pub [usize; 2]);
|
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
|
||||||
|
|
||||||
impl SourceRange {
|
impl SourceRange {
|
||||||
/// Create a new source range.
|
/// Create a new source range.
|
||||||
@ -401,8 +408,10 @@ impl From<SourceRange> for Metadata {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BasePath {
|
pub struct BasePath {
|
||||||
/// The from point.
|
/// The from point.
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
pub from: [f64; 2],
|
pub from: [f64; 2],
|
||||||
/// The to point.
|
/// The to point.
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
pub to: [f64; 2],
|
pub to: [f64; 2],
|
||||||
/// The name of the path.
|
/// The name of the path.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -804,16 +813,16 @@ show(part001)"#,
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_fn_definitions() {
|
async fn test_execute_fn_definitions() {
|
||||||
let ast = r#"const def = (x) => {
|
let ast = r#"fn def = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
const ghi = (x) => {
|
fn ghi = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
const jkl = (x) => {
|
fn jkl = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
const hmm = (x) => {
|
fn hmm = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -981,7 +990,7 @@ show(firstExtrude)"#;
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_with_function_sketch() {
|
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])
|
const myBox = startSketchAt([0,0])
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
@ -998,4 +1007,159 @@ show(fnBox)"#;
|
|||||||
|
|
||||||
parse_execute(ast).await.unwrap();
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_syntax_tree_types::{
|
abstract_syntax_tree_types::{
|
||||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, ValueMeta,
|
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression, ValueMeta,
|
||||||
},
|
},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::SourceRange,
|
executor::SourceRange,
|
||||||
@ -81,6 +81,7 @@ pub enum MathExpression {
|
|||||||
BinaryExpression(Box<BinaryExpression>),
|
BinaryExpression(Box<BinaryExpression>),
|
||||||
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
|
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
|
||||||
ParenthesisToken(Box<ParenthesisToken>),
|
ParenthesisToken(Box<ParenthesisToken>),
|
||||||
|
MemberExpression(Box<MemberExpression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathExpression {
|
impl MathExpression {
|
||||||
@ -92,6 +93,7 @@ impl MathExpression {
|
|||||||
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
|
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
|
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
|
||||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.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::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
|
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
|
||||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.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)?;
|
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 let Ok(next) = self.parser.get_token(1) {
|
||||||
if next.token_type == TokenType::Brace && next.value == "(" {
|
if next.token_type == TokenType::Brace && next.value == "(" {
|
||||||
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
|
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
|
||||||
@ -149,6 +152,24 @@ impl ReversePolishNotation {
|
|||||||
);
|
);
|
||||||
return rpn.parse();
|
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::<Vec<Token>>(),
|
||||||
|
&self.operators,
|
||||||
|
);
|
||||||
|
return rpn.parse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rpn = ReversePolishNotation::new(
|
let rpn = ReversePolishNotation::new(
|
||||||
@ -164,7 +185,6 @@ impl ReversePolishNotation {
|
|||||||
return rpn.parse();
|
return rpn.parse();
|
||||||
} else if current_token.token_type == TokenType::Number
|
} else if current_token.token_type == TokenType::Number
|
||||||
|| current_token.token_type == TokenType::Word
|
|| current_token.token_type == TokenType::Word
|
||||||
|| current_token.token_type == TokenType::Keyword
|
|
||||||
|| current_token.token_type == TokenType::String
|
|| current_token.token_type == TokenType::String
|
||||||
{
|
{
|
||||||
let rpn = ReversePolishNotation::new(
|
let rpn = ReversePolishNotation::new(
|
||||||
@ -180,6 +200,35 @@ impl ReversePolishNotation {
|
|||||||
return rpn.parse();
|
return rpn.parse();
|
||||||
} else if let Ok(binop) = BinaryOperator::from_str(current_token.value.as_str()) {
|
} else if let Ok(binop) = BinaryOperator::from_str(current_token.value.as_str()) {
|
||||||
if !self.operators.is_empty() {
|
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::<Vec<Token>>(),
|
||||||
|
&self.operators,
|
||||||
|
);
|
||||||
|
return rpn.parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Ok(prevbinop) = BinaryOperator::from_str(self.operators[self.operators.len() - 1].value.as_str())
|
if let Ok(prevbinop) = BinaryOperator::from_str(self.operators[self.operators.len() - 1].value.as_str())
|
||||||
{
|
{
|
||||||
if prevbinop.precedence() >= binop.precedence() {
|
if prevbinop.precedence() >= binop.precedence() {
|
||||||
@ -196,6 +245,29 @@ impl ReversePolishNotation {
|
|||||||
return rpn.parse();
|
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::<Vec<Token>>(),
|
||||||
|
&self.operators,
|
||||||
|
);
|
||||||
|
return rpn.parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rpn = ReversePolishNotation::new(
|
let rpn = ReversePolishNotation::new(
|
||||||
@ -299,7 +371,7 @@ impl ReversePolishNotation {
|
|||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![SourceRange([a.start(), a.end()])],
|
source_ranges: vec![SourceRange([a.start(), a.end()])],
|
||||||
message: format!("{:?}", a),
|
message: format!("{:?}", a),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -338,7 +410,7 @@ impl ReversePolishNotation {
|
|||||||
start_extended: None,
|
start_extended: None,
|
||||||
})));
|
})));
|
||||||
return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
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.len() > 1 {
|
||||||
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
||||||
&& reverse_polish_notation_tokens[1].value == "("
|
&& 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);
|
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;
|
let mut new_stack = stack;
|
||||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||||
name: current_token.value.clone(),
|
name: current_token.value.clone(),
|
||||||
@ -396,7 +480,7 @@ impl ReversePolishNotation {
|
|||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
message: format!("{:?}", a),
|
message: format!("{:?}", a),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let paran = match &stack[stack.len() - 2] {
|
let paran = match &stack[stack.len() - 2] {
|
||||||
@ -445,7 +529,7 @@ impl ReversePolishNotation {
|
|||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
message: format!("{:?}", a),
|
message: format!("{:?}", a),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut new_stack = stack[0..stack.len() - 2].to_vec();
|
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::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.start),
|
||||||
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.start),
|
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.start),
|
||||||
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.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 => {
|
a => {
|
||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
@ -513,6 +601,10 @@ impl ReversePolishNotation {
|
|||||||
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.end),
|
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.end),
|
||||||
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.end),
|
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.end),
|
||||||
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.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 => {
|
a => {
|
||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
@ -521,13 +613,7 @@ impl ReversePolishNotation {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let right_end = match right.0.clone() {
|
let right_end = right.0.clone().end();
|
||||||
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 tree = BinaryExpression {
|
let tree = BinaryExpression {
|
||||||
operator: BinaryOperator::from_str(¤t_token.value.clone()).map_err(|err| {
|
operator: BinaryOperator::from_str(¤t_token.value.clone()).map_err(|err| {
|
||||||
@ -562,25 +648,13 @@ impl MathParser {
|
|||||||
pub fn parse(&mut self) -> Result<BinaryExpression, KclError> {
|
pub fn parse(&mut self) -> Result<BinaryExpression, KclError> {
|
||||||
let rpn = self.rpn.parse()?;
|
let rpn = self.rpn.parse()?;
|
||||||
let tree_with_maybe_bad_top_level_start_end = self.rpn.build_tree(&rpn, vec![])?;
|
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 {
|
let left_start = tree_with_maybe_bad_top_level_start_end.clone().left.start();
|
||||||
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 min_start = if left_start < tree_with_maybe_bad_top_level_start_end.start {
|
let min_start = if left_start < tree_with_maybe_bad_top_level_start_end.start {
|
||||||
left_start
|
left_start
|
||||||
} else {
|
} else {
|
||||||
tree_with_maybe_bad_top_level_start_end.start
|
tree_with_maybe_bad_top_level_start_end.start
|
||||||
};
|
};
|
||||||
let right_end = match tree_with_maybe_bad_top_level_start_end.clone().right {
|
let right_end = tree_with_maybe_bad_top_level_start_end.clone().right.end();
|
||||||
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 max_end = if right_end > tree_with_maybe_bad_top_level_start_end.end {
|
let max_end = if right_end > tree_with_maybe_bad_top_level_start_end.end {
|
||||||
right_end
|
right_end
|
||||||
} else {
|
} 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]
|
#[test]
|
||||||
fn test_parse_expression_plus_followed_by_star() {
|
fn test_parse_expression_plus_followed_by_star() {
|
||||||
let tokens = crate::tokeniser::lexer("1 + 2 * 3");
|
let tokens = crate::tokeniser::lexer("1 + 2 * 3");
|
||||||
|
@ -427,7 +427,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
let very_next_token = self.get_token(index + 1)?;
|
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.token_type == TokenType::Brace
|
||||||
&& very_next_token.value == "("
|
&& very_next_token.value == "("
|
||||||
{
|
{
|
||||||
@ -550,22 +550,41 @@ impl Parser {
|
|||||||
&self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
||||||
|
has_opening_brace: bool,
|
||||||
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
||||||
let previous_keys = _previous_keys.unwrap_or(vec![]);
|
let previous_keys = _previous_keys.unwrap_or(vec![]);
|
||||||
let next_token = self.next_meaningful_token(index, None)?;
|
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);
|
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) => {
|
Some(next_token_val) => {
|
||||||
if next_token_val.token_type == TokenType::Brace && next_token_val.value == "]" {
|
if next_token_val.token_type == TokenType::Brace && next_token_val.value == "[" {
|
||||||
self.next_meaningful_token(next_token.index, None)?
|
true
|
||||||
} else {
|
} 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 let Some(period_or_opening_bracket_token) = period_or_opening_bracket.token {
|
||||||
if period_or_opening_bracket_token.token_type != TokenType::Period
|
if period_or_opening_bracket_token.token_type != TokenType::Period
|
||||||
@ -573,11 +592,26 @@ impl Parser {
|
|||||||
{
|
{
|
||||||
return Ok(previous_keys);
|
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 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 next_period_or_opening_bracket = self.next_meaningful_token(key_token.index, None)?;
|
||||||
let is_braced = match next_period_or_opening_bracket.token {
|
let is_braced = match next_period_or_opening_bracket.token {
|
||||||
Some(next_period_or_opening_bracket_val) => {
|
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 == "]"
|
&& next_period_or_opening_bracket_val.value == "]"
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
@ -588,23 +622,19 @@ impl Parser {
|
|||||||
key_token.index
|
key_token.index
|
||||||
};
|
};
|
||||||
if let Some(key_token_token) = key_token.token {
|
if let Some(key_token_token) = key_token.token {
|
||||||
let key = if key_token_token.token_type == TokenType::Word
|
let key = if key_token_token.token_type == TokenType::Word {
|
||||||
|| key_token_token.token_type == TokenType::Keyword
|
|
||||||
{
|
|
||||||
LiteralIdentifier::Identifier(Box::new(self.make_identifier(key_token.index)?))
|
LiteralIdentifier::Identifier(Box::new(self.make_identifier(key_token.index)?))
|
||||||
} else {
|
} else {
|
||||||
LiteralIdentifier::Literal(Box::new(self.make_literal(key_token.index)?))
|
LiteralIdentifier::Literal(Box::new(self.make_literal(key_token.index)?))
|
||||||
};
|
};
|
||||||
let computed = is_braced
|
let computed = is_braced && key_token_token.token_type == TokenType::Word;
|
||||||
&& (key_token_token.token_type == TokenType::Word
|
|
||||||
|| key_token_token.token_type == TokenType::Keyword);
|
|
||||||
let mut new_previous_keys = previous_keys;
|
let mut new_previous_keys = previous_keys;
|
||||||
new_previous_keys.push(ObjectKeyInfo {
|
new_previous_keys.push(ObjectKeyInfo {
|
||||||
key,
|
key,
|
||||||
index: end_index,
|
index: end_index,
|
||||||
computed,
|
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 {
|
} else {
|
||||||
Err(KclError::Unimplemented(KclErrorDetails {
|
Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
source_ranges: vec![period_or_opening_bracket_token.clone().into()],
|
source_ranges: vec![period_or_opening_bracket_token.clone().into()],
|
||||||
@ -616,9 +646,9 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_member_expression(&self, index: usize) -> Result<MemberExpressionReturn, KclError> {
|
pub fn make_member_expression(&self, index: usize) -> Result<MemberExpressionReturn, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
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() {
|
if keys_info.is_empty() {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
@ -653,6 +683,7 @@ impl Parser {
|
|||||||
|
|
||||||
fn find_end_of_binary_expression(&self, index: usize) -> Result<usize, KclError> {
|
fn find_end_of_binary_expression(&self, index: usize) -> Result<usize, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
|
|
||||||
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||||
let closing_parenthesis = self.find_closing_brace(index, 0, "")?;
|
let closing_parenthesis = self.find_closing_brace(index, 0, "")?;
|
||||||
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
||||||
@ -669,28 +700,42 @@ impl Parser {
|
|||||||
Ok(closing_parenthesis)
|
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
|
if current_token.token_type == TokenType::Word {
|
||||||
&& self.get_token(index + 1)?.value == "("
|
if let Ok(next_token) = self.get_token(index + 1) {
|
||||||
{
|
if next_token.token_type == TokenType::Period
|
||||||
let closing_parenthesis = self.find_closing_brace(index + 1, 0, "")?;
|
|| (next_token.token_type == TokenType::Brace && next_token.value == "[")
|
||||||
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)
|
let member_expression = self.make_member_expression(index)?;
|
||||||
} else {
|
return self.find_end_of_binary_expression(member_expression.last_index);
|
||||||
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)
|
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)?;
|
let maybe_operator = self.next_meaningful_token(index, None)?;
|
||||||
if let Some(maybe_operator_token) = maybe_operator.token {
|
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);
|
return Ok(index);
|
||||||
}
|
}
|
||||||
let next_right = self.next_meaningful_token(maybe_operator.index, None)?;
|
let next_right = self.next_meaningful_token(maybe_operator.index, None)?;
|
||||||
@ -731,7 +776,6 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if current_token.token_type == TokenType::Word
|
if current_token.token_type == TokenType::Word
|
||||||
|| current_token.token_type == TokenType::Keyword
|
|
||||||
|| current_token.token_type == TokenType::Number
|
|| current_token.token_type == TokenType::Number
|
||||||
|| current_token.token_type == TokenType::String
|
|| 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 == "{" {
|
if current_token.token_type == TokenType::Brace && current_token.value == "{" {
|
||||||
let object_expression = self.make_object_expression(index)?;
|
let object_expression = self.make_object_expression(index)?;
|
||||||
return Ok(ValueReturn {
|
return Ok(ValueReturn {
|
||||||
@ -761,11 +828,25 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_token) = next.token {
|
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::Period
|
||||||
|| (next_token.token_type == TokenType::Brace && next_token.value == "["))
|
|| (next_token.token_type == TokenType::Brace && next_token.value == "["))
|
||||||
{
|
{
|
||||||
let member_expression = self.make_member_expression(index)?;
|
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 {
|
return Ok(ValueReturn {
|
||||||
value: Value::MemberExpression(Box::new(member_expression.expression)),
|
value: Value::MemberExpression(Box::new(member_expression.expression)),
|
||||||
last_index: member_expression.last_index,
|
last_index: member_expression.last_index,
|
||||||
@ -820,7 +901,7 @@ impl Parser {
|
|||||||
|
|
||||||
Err(KclError::Unexpected(KclErrorDetails {
|
Err(KclError::Unexpected(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
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 {
|
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_closing_brace = next_token_token.token_type == TokenType::Brace && next_token_token.value == "]";
|
||||||
let is_comma = next_token_token.token_type == TokenType::Comma;
|
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 {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![next_token_token.clone().into()],
|
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 {
|
let next_call_index = if is_closing_brace {
|
||||||
@ -852,9 +935,60 @@ impl Parser {
|
|||||||
} else {
|
} else {
|
||||||
self.next_meaningful_token(next_token.index, None)?.index
|
self.next_meaningful_token(next_token.index, None)?.index
|
||||||
};
|
};
|
||||||
let mut _previous_elements = previous_elements;
|
|
||||||
_previous_elements.push(current_element.value);
|
if is_double_period {
|
||||||
self.make_array_elements(next_call_index, _previous_elements)
|
// 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::<i64>().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::<i64>().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 {
|
} else {
|
||||||
Err(KclError::Unimplemented(KclErrorDetails {
|
Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
source_ranges: vec![first_element_token.into()],
|
source_ranges: vec![first_element_token.into()],
|
||||||
@ -867,12 +1001,18 @@ impl Parser {
|
|||||||
let opening_brace_token = self.get_token(index)?;
|
let opening_brace_token = self.get_token(index)?;
|
||||||
let first_element_token = self.next_meaningful_token(index, None)?;
|
let first_element_token = self.next_meaningful_token(index, None)?;
|
||||||
// Make sure there is a closing brace.
|
// 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())?;
|
let array_elements = self.make_array_elements(first_element_token.index, Vec::new())?;
|
||||||
Ok(ArrayReturn {
|
Ok(ArrayReturn {
|
||||||
expression: ArrayExpression {
|
expression: ArrayExpression {
|
||||||
start: opening_brace_token.start,
|
start: opening_brace_token.start,
|
||||||
end: self.get_token(array_elements.last_index)?.end,
|
end: closing_brace_token.end,
|
||||||
elements: array_elements.elements,
|
elements: array_elements.elements,
|
||||||
},
|
},
|
||||||
last_index: array_elements.last_index,
|
last_index: array_elements.last_index,
|
||||||
@ -949,9 +1089,23 @@ impl Parser {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let argument_token = self.next_meaningful_token(index, None)?;
|
let argument_token = self.next_meaningful_token(index, None)?;
|
||||||
|
|
||||||
if let Some(argument_token_token) = argument_token.token {
|
if let Some(argument_token_token) = argument_token.token {
|
||||||
let next_brace_or_comma = self.next_meaningful_token(argument_token.index, None)?;
|
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 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
|
let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma
|
||||||
|| next_brace_or_comma_token.token_type == TokenType::Brace;
|
|| next_brace_or_comma_token.token_type == TokenType::Brace;
|
||||||
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" {
|
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)));
|
_previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression)));
|
||||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" {
|
if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" {
|
||||||
let unary_expression = self.make_unary_expression(argument_token.index)?;
|
let value = self.make_value(argument_token.index)?;
|
||||||
let next_comma_or_brace_token_index =
|
let next_comma_or_brace_token_index = self.next_meaningful_token(value.last_index, None)?.index;
|
||||||
self.next_meaningful_token(unary_expression.last_index, None)?.index;
|
|
||||||
let mut _previous_args = previous_args;
|
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);
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
}
|
}
|
||||||
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "{" {
|
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)));
|
_previous_args.push(Value::ObjectExpression(Box::new(object_expression.expression)));
|
||||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
}
|
}
|
||||||
if (argument_token_token.token_type == TokenType::Keyword
|
if (argument_token_token.token_type == TokenType::Word
|
||||||
|| argument_token_token.token_type == TokenType::Word
|
|
||||||
|| argument_token_token.token_type == TokenType::Number
|
|| argument_token_token.token_type == TokenType::Number
|
||||||
|| argument_token_token.token_type == TokenType::String)
|
|| argument_token_token.token_type == TokenType::String)
|
||||||
&& next_brace_or_comma_token.token_type == TokenType::Operator
|
&& 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)));
|
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
|
||||||
return self.make_arguments(binary_expression.last_index, _previous_args);
|
return self.make_arguments(binary_expression.last_index, _previous_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if argument_token_token.token_type == TokenType::Operator
|
if argument_token_token.token_type == TokenType::Operator
|
||||||
&& argument_token_token.value == PIPE_SUBSTITUTION_OPERATOR
|
&& argument_token_token.value == PIPE_SUBSTITUTION_OPERATOR
|
||||||
{
|
{
|
||||||
@ -1010,8 +1164,7 @@ impl Parser {
|
|||||||
_previous_args.push(value);
|
_previous_args.push(value);
|
||||||
return self.make_arguments(next_brace_or_comma.index, _previous_args);
|
return self.make_arguments(next_brace_or_comma.index, _previous_args);
|
||||||
}
|
}
|
||||||
if (argument_token_token.token_type == TokenType::Keyword
|
if argument_token_token.token_type == TokenType::Word
|
||||||
|| argument_token_token.token_type == TokenType::Word)
|
|
||||||
&& next_brace_or_comma_token.token_type == TokenType::Brace
|
&& next_brace_or_comma_token.token_type == TokenType::Brace
|
||||||
&& next_brace_or_comma_token.value == "("
|
&& next_brace_or_comma_token.value == "("
|
||||||
{
|
{
|
||||||
@ -1085,9 +1238,14 @@ impl Parser {
|
|||||||
let brace_token = self.next_meaningful_token(index, None)?;
|
let brace_token = self.next_meaningful_token(index, None)?;
|
||||||
let callee = self.make_identifier(index)?;
|
let callee = self.make_identifier(index)?;
|
||||||
// Make sure there is a closing brace.
|
// 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 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) {
|
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
||||||
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
||||||
} else {
|
} else {
|
||||||
@ -1127,6 +1285,18 @@ impl Parser {
|
|||||||
previous_declarators: Vec<VariableDeclarator>,
|
previous_declarators: Vec<VariableDeclarator>,
|
||||||
) -> Result<VariableDeclaratorsReturn, KclError> {
|
) -> Result<VariableDeclaratorsReturn, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
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 assignment = self.next_meaningful_token(index, None)?;
|
||||||
let Some(assignment_token) = assignment.token else {
|
let Some(assignment_token) = assignment.token else {
|
||||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
@ -1169,17 +1339,40 @@ impl Parser {
|
|||||||
fn make_variable_declaration(&self, index: usize) -> Result<VariableDeclarationResult, KclError> {
|
fn make_variable_declaration(&self, index: usize) -> Result<VariableDeclarationResult, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
let declaration_start_token = self.next_meaningful_token(index, None)?;
|
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![])?;
|
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 {
|
Ok(VariableDeclarationResult {
|
||||||
declaration: VariableDeclaration {
|
declaration: VariableDeclaration {
|
||||||
start: current_token.start,
|
start: current_token.start,
|
||||||
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
|
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
|
||||||
kind: VariableKind::from_str(¤t_token.value).map_err(|_| {
|
kind,
|
||||||
KclError::Syntax(KclErrorDetails {
|
|
||||||
source_ranges: vec![current_token.into()],
|
|
||||||
message: "Unexpected token".to_string(),
|
|
||||||
})
|
|
||||||
})?,
|
|
||||||
declarations: variable_declarators_result.declarations,
|
declarations: variable_declarators_result.declarations,
|
||||||
},
|
},
|
||||||
last_index: variable_declarators_result.last_index,
|
last_index: variable_declarators_result.last_index,
|
||||||
@ -1189,27 +1382,38 @@ impl Parser {
|
|||||||
fn make_params(&self, index: usize, previous_params: Vec<Identifier>) -> Result<ParamsResult, KclError> {
|
fn make_params(&self, index: usize, previous_params: Vec<Identifier>) -> Result<ParamsResult, KclError> {
|
||||||
let brace_or_comma_token = self.get_token(index)?;
|
let brace_or_comma_token = self.get_token(index)?;
|
||||||
let argument = self.next_meaningful_token(index, None)?;
|
let argument = self.next_meaningful_token(index, None)?;
|
||||||
if let Some(argument_token) = argument.token {
|
let Some(argument_token) = argument.token else {
|
||||||
let should_finish_recursion = (argument_token.token_type == TokenType::Brace
|
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
&& 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 {
|
|
||||||
source_ranges: vec![brace_or_comma_token.into()],
|
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<UnaryExpressionResult, KclError> {
|
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||||
@ -1239,10 +1443,11 @@ impl Parser {
|
|||||||
Value::Literal(literal) => BinaryPart::Literal(literal),
|
Value::Literal(literal) => BinaryPart::Literal(literal),
|
||||||
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
|
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
|
||||||
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
|
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
|
||||||
|
Value::MemberExpression(member_expression) => BinaryPart::MemberExpression(member_expression),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
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 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.token_type == TokenType::Brace
|
||||||
&& next_token.value == "("
|
&& next_token.value == "("
|
||||||
{
|
{
|
||||||
@ -1488,9 +1693,7 @@ impl Parser {
|
|||||||
|
|
||||||
let next_thing = self.next_meaningful_token(token_index, None)?;
|
let next_thing = self.next_meaningful_token(token_index, None)?;
|
||||||
if let Some(next_thing_token) = next_thing.token {
|
if let Some(next_thing_token) = next_thing.token {
|
||||||
if (token.token_type == TokenType::Number
|
if (token.token_type == TokenType::Number || token.token_type == TokenType::Word)
|
||||||
|| token.token_type == TokenType::Word
|
|
||||||
|| token.token_type == TokenType::Keyword)
|
|
||||||
&& next_thing_token.token_type == TokenType::Operator
|
&& next_thing_token.token_type == TokenType::Operator
|
||||||
{
|
{
|
||||||
if let Some(node) = &next_thing.non_code_node {
|
if let Some(node) = &next_thing.non_code_node {
|
||||||
@ -1513,7 +1716,7 @@ impl Parser {
|
|||||||
|
|
||||||
Err(KclError::Syntax(KclErrorDetails {
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![token.into()],
|
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() {
|
fn test_collect_object_keys() {
|
||||||
let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]");
|
let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]");
|
||||||
let parser = Parser::new(tokens);
|
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);
|
assert_eq!(keys_info.len(), 2);
|
||||||
let first_key = match keys_info[0].key.clone() {
|
let first_key = match keys_info[0].key.clone() {
|
||||||
LiteralIdentifier::Identifier(identifier) => format!("identifier-{}", identifier.name),
|
LiteralIdentifier::Identifier(identifier) => format!("identifier-{}", identifier.name),
|
||||||
@ -2759,6 +2962,73 @@ show(mySk1)"#;
|
|||||||
assert!(result.err().unwrap().to_string().contains("Unexpected token"));
|
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]
|
#[test]
|
||||||
fn test_parse_half_pipe() {
|
fn test_parse_half_pipe() {
|
||||||
let tokens = crate::tokeniser::lexer(
|
let tokens = crate::tokeniser::lexer(
|
||||||
@ -2816,7 +3086,10 @@ z(-[["#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
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]
|
#[test]
|
||||||
@ -2828,7 +3101,10 @@ z(-[["#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
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]
|
#[test]
|
||||||
@ -2881,4 +3157,201 @@ e
|
|||||||
.to_string()
|
.to_string()
|
||||||
.contains("unexpected end of expression"));
|
.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`" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,12 +103,12 @@ impl<'a> Args<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(crate::executor::UserVal {
|
||||||
value: j,
|
value: j,
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.source_range,
|
source_range: self.source_range,
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {
|
fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {
|
||||||
|
@ -161,34 +161,12 @@ pub enum LineData {
|
|||||||
/// A point with a tag.
|
/// A point with a tag.
|
||||||
PointWithTag {
|
PointWithTag {
|
||||||
/// The to point.
|
/// The to point.
|
||||||
to: PointOrDefault,
|
to: [f64; 2],
|
||||||
/// The tag.
|
/// The tag.
|
||||||
tag: String,
|
tag: String,
|
||||||
},
|
},
|
||||||
/// A point.
|
/// A point.
|
||||||
Point([f64; 2]),
|
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.
|
/// Draw a line.
|
||||||
@ -205,12 +183,9 @@ pub fn line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
}]
|
}]
|
||||||
fn inner_line(data: LineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_line(data: LineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.get_coords_from_paths()?;
|
||||||
|
|
||||||
let default = [0.2, 1.0];
|
|
||||||
let inner_args = match &data {
|
let inner_args = match &data {
|
||||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
LineData::PointWithTag { to, .. } => *to,
|
||||||
LineData::Point(to) => *to,
|
LineData::Point(to) => *to,
|
||||||
LineData::Default(_) => default,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let to = [from.x + inner_args[0], from.y + inner_args[1]];
|
let to = [from.x + inner_args[0], from.y + inner_args[1]];
|
||||||
@ -283,10 +258,7 @@ pub fn x_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
}]
|
}]
|
||||||
fn inner_x_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_x_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let line_data = match data {
|
let line_data = match data {
|
||||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [length, 0.0], tag },
|
||||||
to: PointOrDefault::Point([length, 0.0]),
|
|
||||||
tag,
|
|
||||||
},
|
|
||||||
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
|
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -308,10 +280,7 @@ pub fn y_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
}]
|
}]
|
||||||
fn inner_y_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_y_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let line_data = match data {
|
let line_data = match data {
|
||||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [0.0, length], tag },
|
||||||
to: PointOrDefault::Point([0.0, length]),
|
|
||||||
tag,
|
|
||||||
},
|
|
||||||
AxisLineData::Length(length) => LineData::Point([0.0, length]),
|
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(
|
let new_sketch_group = inner_line(
|
||||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||||
LineData::PointWithTag {
|
LineData::PointWithTag { to, tag }
|
||||||
to: PointOrDefault::Point(to),
|
|
||||||
tag,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LineData::Point(to)
|
LineData::Point(to)
|
||||||
},
|
},
|
||||||
@ -525,10 +491,7 @@ fn inner_angled_line_of_y_length(
|
|||||||
|
|
||||||
let new_sketch_group = inner_line(
|
let new_sketch_group = inner_line(
|
||||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||||
LineData::PointWithTag {
|
LineData::PointWithTag { to, tag }
|
||||||
to: PointOrDefault::Point(to),
|
|
||||||
tag,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LineData::Point(to)
|
LineData::Point(to)
|
||||||
},
|
},
|
||||||
@ -654,11 +617,9 @@ pub fn start_sketch_at(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
name = "startSketchAt",
|
name = "startSketchAt",
|
||||||
}]
|
}]
|
||||||
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let default = [0.0, 0.0];
|
|
||||||
let to = match &data {
|
let to = match &data {
|
||||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
LineData::PointWithTag { to, .. } => *to,
|
||||||
LineData::Point(to) => *to,
|
LineData::Point(to) => *to,
|
||||||
LineData::Default(_) => default,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
@ -992,16 +953,12 @@ mod tests {
|
|||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::std::sketch::{LineData, PointOrDefault};
|
use crate::std::sketch::LineData;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_line_data() {
|
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]);
|
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]");
|
assert_eq!(str_json, "[0.0,1.0]");
|
||||||
|
|
||||||
str_json = "[0, 1]".to_string();
|
str_json = "[0, 1]".to_string();
|
||||||
@ -1013,7 +970,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
data,
|
data,
|
||||||
LineData::PointWithTag {
|
LineData::PointWithTag {
|
||||||
to: PointOrDefault::Point([0.0, 1.0]),
|
to: [0.0, 1.0],
|
||||||
tag: "thing".to_string()
|
tag: "thing".to_string()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -34,6 +34,8 @@ pub enum TokenType {
|
|||||||
Colon,
|
Colon,
|
||||||
/// A period.
|
/// A period.
|
||||||
Period,
|
Period,
|
||||||
|
/// A double period: `..`.
|
||||||
|
DoublePeriod,
|
||||||
/// A line comment.
|
/// A line comment.
|
||||||
LineComment,
|
LineComment,
|
||||||
/// A block comment.
|
/// A block comment.
|
||||||
@ -54,7 +56,12 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
|||||||
TokenType::LineComment => Self::COMMENT,
|
TokenType::LineComment => Self::COMMENT,
|
||||||
TokenType::BlockComment => Self::COMMENT,
|
TokenType::BlockComment => Self::COMMENT,
|
||||||
TokenType::Function => Self::FUNCTION,
|
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)
|
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();
|
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.
|
// TODO: these should be generated using our struct types for these.
|
||||||
static ref KEYWORD: Regex =
|
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 OPERATOR: Regex = Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
|
||||||
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
|
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
|
||||||
static ref BLOCK_START: 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 COMMA: Regex = Regex::new(r"^,").unwrap();
|
||||||
static ref COLON: Regex = Regex::new(r"^:").unwrap();
|
static ref COLON: Regex = Regex::new(r"^:").unwrap();
|
||||||
static ref PERIOD: 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 LINECOMMENT: Regex = Regex::new(r"^//.*").unwrap();
|
||||||
static ref BLOCKCOMMENT: Regex = Regex::new(r"^/\*[\s\S]*?\*/").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 {
|
fn is_colon(character: &str) -> bool {
|
||||||
COLON.is_match(character)
|
COLON.is_match(character)
|
||||||
}
|
}
|
||||||
|
fn is_double_period(character: &str) -> bool {
|
||||||
|
DOUBLE_PERIOD.is_match(character)
|
||||||
|
}
|
||||||
fn is_period(character: &str) -> bool {
|
fn is_period(character: &str) -> bool {
|
||||||
PERIOD.is_match(character)
|
PERIOD.is_match(character)
|
||||||
}
|
}
|
||||||
@ -296,13 +307,6 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
|||||||
start_index,
|
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) {
|
if is_operator(str_from_index) {
|
||||||
return Some(make_token(
|
return Some(make_token(
|
||||||
TokenType::Operator,
|
TokenType::Operator,
|
||||||
@ -310,6 +314,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
|||||||
start_index,
|
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) {
|
if is_keyword(str_from_index) {
|
||||||
return Some(make_token(
|
return Some(make_token(
|
||||||
TokenType::Keyword,
|
TokenType::Keyword,
|
||||||
@ -331,6 +342,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
|||||||
start_index,
|
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) {
|
if is_period(str_from_index) {
|
||||||
return Some(make_token(
|
return Some(make_token(
|
||||||
TokenType::Period,
|
TokenType::Period,
|
||||||
|
98
src/wasm-lib/tests/executor/main.rs
Normal file
98
src/wasm-lib/tests/executor/main.rs
Normal file
@ -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<image::DynamicImage> {
|
||||||
|
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);
|
||||||
|
}
|
BIN
src/wasm-lib/tests/executor/outputs/angled_line.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/angled_line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
src/wasm-lib/tests/executor/outputs/function_sketch.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/function_sketch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Reference in New Issue
Block a user