Merge branch 'main' into franknoirot/xstate-toolbar
This commit is contained in:
@ -1,4 +1,7 @@
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
||||
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS=0
|
||||
VITE_KC_SENTRY_DSN=
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
||||
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||
VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS=30000
|
||||
VITE_KC_SENTRY_DSN=https://a814f2f66734989a90367f48feee28ca@o1042111.ingest.sentry.io/4505789425844224
|
||||
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -124,6 +124,8 @@ jobs:
|
||||
publish-apps-release:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event_name == 'release'
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [build-test-web, build-apps]
|
||||
env:
|
||||
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
||||
@ -189,3 +191,8 @@ jobs:
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: dl.kittycad.io/releases/modeling-app
|
||||
|
||||
- name: Upload release files to Github
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: artifact/*/kittycad-modeling-app*
|
||||
|
||||
1993
docs/kcl.json
1993
docs/kcl.json
File diff suppressed because it is too large
Load Diff
350
docs/kcl.md
350
docs/kcl.md
@ -33,6 +33,8 @@
|
||||
* [`angledLineThatIntersects`](#angledLineThatIntersects)
|
||||
* [`startSketchAt`](#startSketchAt)
|
||||
* [`close`](#close)
|
||||
* [`arc`](#arc)
|
||||
* [`bezierCurve`](#bezierCurve)
|
||||
|
||||
|
||||
## Functions
|
||||
@ -3046,3 +3048,351 @@ close(sketch_group: SketchGroup) -> SketchGroup
|
||||
|
||||
|
||||
|
||||
### arc
|
||||
|
||||
Draw an arc.
|
||||
|
||||
|
||||
|
||||
```
|
||||
arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
* `data`: `ArcData` - Data to draw an arc.
|
||||
```
|
||||
{
|
||||
// The end angle.
|
||||
"angle_end": number,
|
||||
// The start angle.
|
||||
"angle_start": number,
|
||||
// The radius.
|
||||
"radius": number,
|
||||
// The tag.
|
||||
"tag": string,
|
||||
} |
|
||||
{
|
||||
// The end angle.
|
||||
"angle_end": number,
|
||||
// The start angle.
|
||||
"angle_start": number,
|
||||
// The radius.
|
||||
"radius": number,
|
||||
} |
|
||||
{
|
||||
// The center.
|
||||
"center": [number],
|
||||
// The radius.
|
||||
"radius": number,
|
||||
// The tag.
|
||||
"tag": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
} |
|
||||
{
|
||||
// The center.
|
||||
"center": [number],
|
||||
// The radius.
|
||||
"radius": number,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
}
|
||||
```
|
||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
||||
```
|
||||
{
|
||||
// The id of the sketch group.
|
||||
"id": uuid,
|
||||
// The position of the sketch group.
|
||||
"position": [number],
|
||||
// The rotation of the sketch group.
|
||||
"rotation": [number],
|
||||
// The starting path.
|
||||
"start": {
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
"value": [{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
// The y coordinate.
|
||||
"y": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
}],
|
||||
}
|
||||
```
|
||||
|
||||
#### Returns
|
||||
|
||||
* `SketchGroup` - A sketch group is a collection of paths.
|
||||
```
|
||||
{
|
||||
// The id of the sketch group.
|
||||
"id": uuid,
|
||||
// The position of the sketch group.
|
||||
"position": [number],
|
||||
// The rotation of the sketch group.
|
||||
"rotation": [number],
|
||||
// The starting path.
|
||||
"start": {
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
"value": [{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
// The y coordinate.
|
||||
"y": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
}],
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### bezierCurve
|
||||
|
||||
Draw a bezier curve.
|
||||
|
||||
|
||||
|
||||
```
|
||||
bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
* `data`: `BezierData` - Data to draw a bezier curve.
|
||||
```
|
||||
{
|
||||
// The first control point.
|
||||
"control1": [number],
|
||||
// The second control point.
|
||||
"control2": [number],
|
||||
// The tag.
|
||||
"tag": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
} |
|
||||
{
|
||||
// The first control point.
|
||||
"control1": [number],
|
||||
// The second control point.
|
||||
"control2": [number],
|
||||
// The to point.
|
||||
"to": [number],
|
||||
}
|
||||
```
|
||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
||||
```
|
||||
{
|
||||
// The id of the sketch group.
|
||||
"id": uuid,
|
||||
// The position of the sketch group.
|
||||
"position": [number],
|
||||
// The rotation of the sketch group.
|
||||
"rotation": [number],
|
||||
// The starting path.
|
||||
"start": {
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
"value": [{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
// The y coordinate.
|
||||
"y": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
}],
|
||||
}
|
||||
```
|
||||
|
||||
#### Returns
|
||||
|
||||
* `SketchGroup` - A sketch group is a collection of paths.
|
||||
```
|
||||
{
|
||||
// The id of the sketch group.
|
||||
"id": uuid,
|
||||
// The position of the sketch group.
|
||||
"position": [number],
|
||||
// The rotation of the sketch group.
|
||||
"rotation": [number],
|
||||
// The starting path.
|
||||
"start": {
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
"value": [{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
// The x coordinate.
|
||||
"x": number,
|
||||
// The y coordinate.
|
||||
"y": number,
|
||||
} |
|
||||
{
|
||||
// The from point.
|
||||
"from": [number],
|
||||
// The name of the path.
|
||||
"name": string,
|
||||
// The to point.
|
||||
"to": [number],
|
||||
"type": string,
|
||||
}],
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
@ -9,8 +9,9 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "^0.0.34",
|
||||
"@kittycad/lib": "^0.0.35",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@sentry/react": "^7.65.0",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
@ -56,13 +57,13 @@
|
||||
"build:both:local": "yarn build:wasm && vite build",
|
||||
"test": "vitest --mode development",
|
||||
"test:nowatch": "vitest run --mode development",
|
||||
"test:rust": "(cd src/wasm-lib && cargo test && cargo clippy)",
|
||||
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)",
|
||||
"test:cov": "vitest run --coverage --mode development",
|
||||
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
|
||||
"simpleserver": "http-server ./public --cors -p 3000",
|
||||
"fmt": "prettier --write ./src",
|
||||
"fmt-check": "prettier --check ./src",
|
||||
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test --all) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
|
||||
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
|
||||
"remove-importmeta": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||
"lint": "eslint --fix src",
|
||||
|
||||
56
src-tauri/Cargo.lock
generated
56
src-tauri/Cargo.lock
generated
@ -648,6 +648,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
@ -1150,7 +1156,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@ -1163,6 +1169,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
@ -1378,7 +1390,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -2128,7 +2151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"line-wrap",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
@ -2701,14 +2724,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "2.3.3"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
|
||||
checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64 0.21.2",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
@ -2717,9 +2741,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "2.3.3"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
|
||||
checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@ -3075,9 +3099,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929b3bd1248afc07b63e33a6a53c3f82c32d0b0a5e216e4530e94c467e019389"
|
||||
checksum = "7d2edd6a259b5591c8efdeb9d5702cb53515b82a6affebd55c7fd6d3a27b7d1b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@ -3088,7 +3112,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"tauri-winres",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3186,12 +3209,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6f9c2dafef5cbcf52926af57ce9561bd33bb41d7394f8bb849c0330260d864"
|
||||
checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
"dunce",
|
||||
"glob",
|
||||
"heck 0.4.1",
|
||||
"html5ever",
|
||||
@ -3407,7 +3431,7 @@ version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"nom8",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@ -3420,7 +3444,7 @@ version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.6.2",
|
||||
|
||||
@ -12,7 +12,7 @@ rust-version = "1.60"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.3.0", features = [] }
|
||||
tauri-build = { version = "1.4.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "kittycad-modeling-app",
|
||||
"version": "0.1.0"
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
52
src/App.tsx
52
src/App.tsx
@ -49,6 +49,7 @@ import { PROJECT_ENTRYPOINT } from './lib/tauriFS'
|
||||
import { IndexLoaderData } from './Router'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { onboardingPaths } from 'routes/Onboarding'
|
||||
|
||||
export function App() {
|
||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||
@ -154,7 +155,7 @@ export function App() {
|
||||
useHotkeys('shift + d', () => togglePane('debug'))
|
||||
|
||||
const paneOpacity =
|
||||
onboardingStatus === 'camera'
|
||||
onboardingStatus === onboardingPaths.CAMERA
|
||||
? 'opacity-20'
|
||||
: didDragInStream
|
||||
? 'opacity-40'
|
||||
@ -278,6 +279,8 @@ export function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isStreamReady) return
|
||||
if (!engineCommandManager) return
|
||||
let unsubFn: any[] = []
|
||||
const asyncWrap = async () => {
|
||||
try {
|
||||
if (!code) {
|
||||
@ -288,11 +291,8 @@ export function App() {
|
||||
setAst(_ast)
|
||||
resetLogs()
|
||||
resetKCLErrors()
|
||||
if (engineCommandManager) {
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
}
|
||||
if (!engineCommandManager) return
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
const programMemory = await _executor(
|
||||
_ast,
|
||||
{
|
||||
@ -326,22 +326,29 @@ export function App() {
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
setArtifactMap({ artifactMap, sourceRangeMap })
|
||||
engineCommandManager.onHover((id) => {
|
||||
if (!id) {
|
||||
setHighlightRange([0, 0])
|
||||
} else {
|
||||
const sourceRange = sourceRangeMap[id]
|
||||
setHighlightRange(sourceRange)
|
||||
}
|
||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||
event: 'highlight_set_entity',
|
||||
callback: ({ data }) => {
|
||||
if (!data?.entity_id) {
|
||||
setHighlightRange([0, 0])
|
||||
} else {
|
||||
const sourceRange = sourceRangeMap[data.entity_id]
|
||||
setHighlightRange(sourceRange)
|
||||
}
|
||||
},
|
||||
})
|
||||
engineCommandManager.onClick((selections) => {
|
||||
if (!selections) {
|
||||
setCursor2()
|
||||
return
|
||||
}
|
||||
const { id, type } = selections
|
||||
setCursor2({ range: sourceRangeMap[id], type })
|
||||
const unSubClick = engineCommandManager.subscribeTo({
|
||||
event: 'select_with_point',
|
||||
callback: ({ data }) => {
|
||||
if (!data?.entity_id) {
|
||||
setCursor2()
|
||||
return
|
||||
}
|
||||
const sourceRange = sourceRangeMap[data.entity_id]
|
||||
setCursor2({ range: sourceRange, type: 'default' })
|
||||
},
|
||||
})
|
||||
unsubFn.push(unSubHover, unSubClick)
|
||||
if (programMemory !== undefined) {
|
||||
setProgramMemory(programMemory)
|
||||
}
|
||||
@ -358,7 +365,10 @@ export function App() {
|
||||
}
|
||||
}
|
||||
asyncWrap()
|
||||
}, [code, isStreamReady])
|
||||
return () => {
|
||||
unsubFn.forEach((fn) => fn())
|
||||
}
|
||||
}, [code, isStreamReady, engineCommandManager])
|
||||
|
||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||
engineCommandManager?.sendSceneCommand(message)
|
||||
|
||||
@ -3,8 +3,15 @@ import {
|
||||
createBrowserRouter,
|
||||
Outlet,
|
||||
redirect,
|
||||
useLocation,
|
||||
RouterProvider,
|
||||
} from 'react-router-dom'
|
||||
import {
|
||||
matchRoutes,
|
||||
createRoutesFromChildren,
|
||||
useNavigationType,
|
||||
} from 'react-router'
|
||||
import { useEffect } from 'react'
|
||||
import { ErrorPage } from './components/ErrorPage'
|
||||
import { Settings } from './routes/Settings'
|
||||
import Onboarding, {
|
||||
@ -31,6 +38,40 @@ import {
|
||||
} from './machines/settingsMachine'
|
||||
import { ContextFrom } from 'xstate'
|
||||
import CommandBarProvider from 'components/CommandBar'
|
||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||
import * as Sentry from '@sentry/react'
|
||||
|
||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||
Sentry.init({
|
||||
dsn: VITE_KC_SENTRY_DSN,
|
||||
// TODO(paultag): pass in the right env here.
|
||||
// environment: "production",
|
||||
integrations: [
|
||||
new Sentry.BrowserTracing({
|
||||
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
|
||||
useEffect,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
matchRoutes
|
||||
),
|
||||
}),
|
||||
new Sentry.Replay(),
|
||||
],
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for performance monitoring.
|
||||
tracesSampleRate: 1.0,
|
||||
|
||||
// TODO: Add in kittycad.io endpoints
|
||||
tracePropagationTargets: ['localhost'],
|
||||
|
||||
// Capture Replay for 10% of all sessions,
|
||||
// plus for 100% of sessions with an error
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
})
|
||||
}
|
||||
|
||||
const prependRoutes =
|
||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||
@ -121,7 +162,9 @@ const router = createBrowserRouter(
|
||||
notEnRouteToOnboarding && hasValidOnboardingStatus
|
||||
|
||||
if (shouldRedirectToOnboarding) {
|
||||
return redirect(makeUrlPathRelative(paths.ONBOARDING.INDEX) + status)
|
||||
return redirect(
|
||||
makeUrlPathRelative(paths.ONBOARDING.INDEX) + status.slice(1)
|
||||
)
|
||||
}
|
||||
|
||||
if (params.id && params.id !== 'new') {
|
||||
|
||||
7
src/components/AppHeader.module.css
Normal file
7
src/components/AppHeader.module.css
Normal file
@ -0,0 +1,7 @@
|
||||
/*
|
||||
Some CSS cannot be represented
|
||||
in Tailwind, such as complex grid layouts.
|
||||
*/
|
||||
.header {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
@ -3,6 +3,7 @@ import UserSidebarMenu from './UserSidebarMenu'
|
||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import styles from './AppHeader.module.css'
|
||||
|
||||
interface AppHeaderProps extends React.PropsWithChildren {
|
||||
showToolbar?: boolean
|
||||
@ -27,7 +28,9 @@ export const AppHeader = ({
|
||||
return (
|
||||
<header
|
||||
className={
|
||||
'overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/50 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 flex justify-between items-center ' +
|
||||
(showToolbar ? 'grid ' : 'flex justify-between ') +
|
||||
styles.header +
|
||||
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
||||
className
|
||||
}
|
||||
>
|
||||
@ -39,7 +42,11 @@ export const AppHeader = ({
|
||||
</div>
|
||||
)}
|
||||
{/* If there are children, show them, otherwise show User menu */}
|
||||
{children || <UserSidebarMenu user={user} />}
|
||||
{children || (
|
||||
<div className="ml-auto">
|
||||
<UserSidebarMenu user={user} />
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
.panel {
|
||||
@apply relative overflow-auto z-0;
|
||||
@apply bg-chalkboard-20/40;
|
||||
@apply bg-chalkboard-10/70 backdrop-blur-sm;
|
||||
}
|
||||
|
||||
:global(.dark) .panel {
|
||||
@apply bg-chalkboard-110/50;
|
||||
@apply bg-chalkboard-110/50 backdrop-blur-0;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
settingsMachine,
|
||||
} from 'machines/settingsMachine'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { setThemeClass } from 'lib/theme'
|
||||
import { setThemeClass, Themes } from 'lib/theme'
|
||||
import {
|
||||
AnyStateMachine,
|
||||
ContextFrom,
|
||||
@ -87,10 +87,21 @@ export const GlobalStateProvider = ({
|
||||
commandBarMeta: settingsCommandBarMeta,
|
||||
})
|
||||
|
||||
useEffect(
|
||||
() => setThemeClass(settingsState.context.theme),
|
||||
[settingsState.context.theme]
|
||||
)
|
||||
// Listen for changes to the system theme and update the app theme accordingly
|
||||
// This is only done if the theme setting is set to 'system'.
|
||||
// It can't be done in XState (in an invoked callback, for example)
|
||||
// because there doesn't seem to be a good way to listen to
|
||||
// events outside of the machine that also depend on the machine's context
|
||||
useEffect(() => {
|
||||
const matcher = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const listener = (e: MediaQueryListEvent) => {
|
||||
if (settingsState.context.theme !== 'system') return
|
||||
setThemeClass(e.matches ? Themes.Dark : Themes.Light)
|
||||
}
|
||||
|
||||
matcher.addEventListener('change', listener)
|
||||
return () => matcher.removeEventListener('change', listener)
|
||||
}, [settingsState.context])
|
||||
|
||||
// Auth machine setup
|
||||
const [authState, authSend] = useMachine(authMachine, {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { faHome } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ProjectWithEntryPointMetadata, paths } from '../Router'
|
||||
import { isTauri } from '../lib/isTauri'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ExportButton } from './ExportButton'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
const ProjectSidebarMenu = ({
|
||||
project,
|
||||
@ -34,7 +35,7 @@ const ProjectSidebarMenu = ({
|
||||
) : (
|
||||
<Popover className="relative">
|
||||
<Popover.Button
|
||||
className="border-0 px-1 pr-2 pl-0 flex items-center gap-4 focus:outline-none focus:ring-2 focus:ring-energy-50"
|
||||
className="border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
||||
data-testid="project-sidebar-toggle"
|
||||
>
|
||||
<img
|
||||
@ -46,54 +47,77 @@ const ProjectSidebarMenu = ({
|
||||
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||
</span>
|
||||
</Popover.Button>
|
||||
<Popover.Overlay className="fixed z-20 inset-0 bg-chalkboard-110/50" />
|
||||
<Transition
|
||||
enter="duration-200 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-100 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
as={Fragment}
|
||||
>
|
||||
<Popover.Overlay className="fixed z-20 inset-0 bg-chalkboard-110/50" />
|
||||
</Transition>
|
||||
|
||||
<Popover.Panel className="fixed inset-0 right-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-energy-100 shadow-md rounded-r-lg overflow-hidden">
|
||||
<div className="flex items-center gap-4 px-4 py-3 bg-energy-100">
|
||||
<img
|
||||
src="/kitt-8bit-winking.svg"
|
||||
alt="KittyCAD App"
|
||||
className="h-9 w-auto"
|
||||
/>
|
||||
<Transition
|
||||
enter="duration-100 ease-out"
|
||||
enterFrom="opacity-0 -translate-x-1/4"
|
||||
enterTo="opacity-100 translate-x-0"
|
||||
leave="duration-75 ease-in"
|
||||
leaveFrom="opacity-100 translate-x-0"
|
||||
leaveTo="opacity-0 -translate-x-4"
|
||||
as={Fragment}
|
||||
>
|
||||
<Popover.Panel className="fixed inset-0 right-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-energy-100 dark:border-energy-100/50 shadow-md rounded-r-lg overflow-hidden">
|
||||
<div className="flex items-center gap-4 px-4 py-3 bg-energy-100">
|
||||
<img
|
||||
src="/kitt-8bit-winking.svg"
|
||||
alt="KittyCAD App"
|
||||
className="h-9 w-auto"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<p
|
||||
className="m-0 text-energy-10 text-mono"
|
||||
data-testid="projectName"
|
||||
>
|
||||
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||
</p>
|
||||
{project?.entrypoint_metadata && (
|
||||
<p className="m-0 text-energy-40 text-xs" data-testid="createdAt">
|
||||
Created{' '}
|
||||
{project?.entrypoint_metadata.createdAt.toLocaleDateString()}
|
||||
<div>
|
||||
<p
|
||||
className="m-0 text-energy-10 text-mono"
|
||||
data-testid="projectName"
|
||||
>
|
||||
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||
</p>
|
||||
{project?.entrypoint_metadata && (
|
||||
<p
|
||||
className="m-0 text-energy-40 text-xs"
|
||||
data-testid="createdAt"
|
||||
>
|
||||
Created{' '}
|
||||
{project?.entrypoint_metadata.createdAt.toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
<ExportButton
|
||||
className={{
|
||||
button:
|
||||
'border-transparent dark:border-transparent dark:hover:border-energy-60',
|
||||
}}
|
||||
>
|
||||
Export Model
|
||||
</ExportButton>
|
||||
{isTauri() && (
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to={paths.HOME}
|
||||
icon={{
|
||||
icon: faHome,
|
||||
}}
|
||||
className="border-transparent dark:border-transparent dark:hover:border-energy-60"
|
||||
>
|
||||
Go to Home
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
<ExportButton
|
||||
className={{
|
||||
button:
|
||||
'border-transparent dark:border-transparent dark:hover:border-energy-60',
|
||||
}}
|
||||
>
|
||||
Export Model
|
||||
</ExportButton>
|
||||
{isTauri() && (
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to={paths.HOME}
|
||||
icon={{
|
||||
icon: faHome,
|
||||
}}
|
||||
className="border-transparent dark:border-transparent dark:hover:border-energy-60"
|
||||
>
|
||||
Go to Home
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useState } from 'react'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { paths } from '../Router'
|
||||
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
|
||||
import { Models } from '@kittycad/lib'
|
||||
@ -61,82 +61,102 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
Menu
|
||||
</ActionButton>
|
||||
)}
|
||||
<Popover.Overlay className="fixed z-20 inset-0 bg-chalkboard-110/50" />
|
||||
<Transition
|
||||
enter="duration-200 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-100 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
as={Fragment}
|
||||
>
|
||||
<Popover.Overlay className="fixed z-20 inset-0 bg-chalkboard-110/50" />
|
||||
</Transition>
|
||||
|
||||
<Popover.Panel className="fixed inset-0 left-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-liquid-100 shadow-md rounded-l-lg overflow-hidden">
|
||||
{({ close }) => (
|
||||
<>
|
||||
{user && (
|
||||
<div className="flex items-center gap-4 px-4 py-3 bg-liquid-100">
|
||||
{user.image && !imageLoadFailed && (
|
||||
<div className="rounded-full shadow-inner overflow-hidden">
|
||||
<img
|
||||
src={user.image}
|
||||
alt={user.name || ''}
|
||||
className="h-8 w-8"
|
||||
referrerPolicy="no-referrer"
|
||||
onError={() => setImageLoadFailed(true)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<p
|
||||
className="m-0 text-liquid-10 text-mono"
|
||||
data-testid="username"
|
||||
>
|
||||
{displayedName || ''}
|
||||
</p>
|
||||
{displayedName !== user.email && (
|
||||
<p
|
||||
className="m-0 text-liquid-40 text-xs"
|
||||
data-testid="email"
|
||||
>
|
||||
{user.email}
|
||||
</p>
|
||||
<Transition
|
||||
enter="duration-100 ease-out"
|
||||
enterFrom="opacity-0 translate-x-1/4"
|
||||
enterTo="opacity-100 translate-x-0"
|
||||
leave="duration-75 ease-in"
|
||||
leaveFrom="opacity-100 translate-x-0"
|
||||
leaveTo="opacity-0 translate-x-4"
|
||||
as={Fragment}
|
||||
>
|
||||
<Popover.Panel className="fixed inset-0 left-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-liquid-100 dark:border-liquid-100/50 shadow-md rounded-l-lg overflow-hidden">
|
||||
{({ close }) => (
|
||||
<>
|
||||
{user && (
|
||||
<div className="flex items-center gap-4 px-4 py-3 bg-liquid-100">
|
||||
{user.image && !imageLoadFailed && (
|
||||
<div className="rounded-full shadow-inner overflow-hidden">
|
||||
<img
|
||||
src={user.image}
|
||||
alt={user.name || ''}
|
||||
className="h-8 w-8"
|
||||
referrerPolicy="no-referrer"
|
||||
onError={() => setImageLoadFailed(true)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<p
|
||||
className="m-0 text-liquid-10 text-mono"
|
||||
data-testid="username"
|
||||
>
|
||||
{displayedName || ''}
|
||||
</p>
|
||||
{displayedName !== user.email && (
|
||||
<p
|
||||
className="m-0 text-liquid-40 text-xs"
|
||||
data-testid="email"
|
||||
>
|
||||
{user.email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: faGear }}
|
||||
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||
onClick={() => {
|
||||
// since /settings is a nested route the sidebar doesn't close
|
||||
// automatically when navigating to it
|
||||
close()
|
||||
navigate(makeUrlPathRelative(paths.SETTINGS))
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to="https://github.com/KittyCAD/modeling-app/discussions"
|
||||
icon={{ icon: faGithub }}
|
||||
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||
>
|
||||
Request a feature
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => send('Log out')}
|
||||
icon={{
|
||||
icon: faSignOutAlt,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName:
|
||||
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||
}}
|
||||
className="border-transparent dark:border-transparent hover:border-destroy-40 dark:hover:border-destroy-60"
|
||||
>
|
||||
Sign out
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: faGear }}
|
||||
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||
onClick={() => {
|
||||
// since /settings is a nested route the sidebar doesn't close
|
||||
// automatically when navigating to it
|
||||
close()
|
||||
navigate(makeUrlPathRelative(paths.SETTINGS))
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to="https://github.com/KittyCAD/modeling-app/discussions"
|
||||
icon={{ icon: faGithub }}
|
||||
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||
>
|
||||
Request a feature
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => send('Log out')}
|
||||
icon={{
|
||||
icon: faSignOutAlt,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName:
|
||||
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||
}}
|
||||
className="border-transparent dark:border-transparent hover:border-destroy-40 dark:hover:border-destroy-60"
|
||||
>
|
||||
Sign out
|
||||
</ActionButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
</>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ export const VITE_KC_API_WS_MODELING_URL = import.meta.env
|
||||
.VITE_KC_API_WS_MODELING_URL
|
||||
export const VITE_KC_API_BASE_URL = import.meta.env.VITE_KC_API_BASE_URL
|
||||
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
|
||||
export const VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS = import.meta.env
|
||||
.VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS
|
||||
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
|
||||
.VITE_KC_CONNECTION_TIMEOUT_MS
|
||||
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
|
||||
export const TEST = import.meta.env.TEST
|
||||
|
||||
@ -86,8 +86,18 @@ code {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
#code-mirror-override .cm-activeLine,
|
||||
#code-mirror-override .cm-activeLineGutter {
|
||||
@apply bg-liquid-10/50;
|
||||
}
|
||||
|
||||
.dark #code-mirror-override .cm-activeLine,
|
||||
.dark #code-mirror-override .cm-activeLineGutter {
|
||||
@apply bg-liquid-80/50;
|
||||
}
|
||||
|
||||
#code-mirror-override .cm-gutters {
|
||||
@apply bg-chalkboard-10/50;
|
||||
@apply bg-chalkboard-10/30;
|
||||
}
|
||||
|
||||
.dark #code-mirror-override .cm-gutters {
|
||||
@ -99,14 +109,24 @@ code {
|
||||
}
|
||||
#code-mirror-override .cm-cursor {
|
||||
display: block;
|
||||
width: 200px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgb(0, 55, 94) 0%,
|
||||
#0084e2ff 2%,
|
||||
#0084e255 5%,
|
||||
transparent 100%
|
||||
);
|
||||
width: 1ch;
|
||||
@apply bg-liquid-40 mix-blend-multiply;
|
||||
|
||||
animation: blink 2s ease-out infinite;
|
||||
}
|
||||
|
||||
.dark #code-mirror-override .cm-cursor {
|
||||
@apply bg-liquid-50;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
15% {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.react-json-view {
|
||||
|
||||
@ -16,10 +16,8 @@ export const parser_wasm = (code: string): Program => {
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
const kclError = new KCLError(
|
||||
parsed.kind,
|
||||
parsed.kind === 'invalid_expression' ? parsed.kind : parsed.msg,
|
||||
parsed.kind === 'invalid_expression'
|
||||
? [[parsed.start, parsed.end]]
|
||||
: rangeTypeFix(parsed.sourceRanges)
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
@ -36,10 +34,8 @@ export async function asyncParser(code: string): Promise<Program> {
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
const kclError = new KCLError(
|
||||
parsed.kind,
|
||||
parsed.kind === 'invalid_expression' ? parsed.kind : parsed.msg,
|
||||
parsed.kind === 'invalid_expression'
|
||||
? [[parsed.start, parsed.end]]
|
||||
: rangeTypeFix(parsed.sourceRanges)
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
|
||||
@ -146,10 +146,8 @@ export const _executor = async (
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
const kclError = new KCLError(
|
||||
parsed.kind,
|
||||
parsed.kind === 'invalid_expression' ? parsed.kind : parsed.msg,
|
||||
parsed.kind === 'invalid_expression'
|
||||
? [[parsed.start, parsed.end]]
|
||||
: rangeTypeFix(parsed.sourceRanges)
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import { SourceRange } from 'lang/executor'
|
||||
import { Selections } from 'useStore'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||
import {
|
||||
VITE_KC_API_WS_MODELING_URL,
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS,
|
||||
VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS,
|
||||
} from 'env'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { exportSave } from 'lib/exportSave'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as Sentry from '@sentry/react'
|
||||
|
||||
interface ResultCommand {
|
||||
type: 'result'
|
||||
@ -22,16 +27,6 @@ export interface SourceRangeMap {
|
||||
[key: string]: SourceRange
|
||||
}
|
||||
|
||||
interface SelectionsArgs {
|
||||
id: string
|
||||
type: Selections['codeBasedSelections'][number]['type']
|
||||
}
|
||||
|
||||
interface CursorSelectionsArgs {
|
||||
otherSelections: Selections['otherSelections']
|
||||
idBasedSelections: { type: string; id: string }[]
|
||||
}
|
||||
|
||||
interface NewTrackArgs {
|
||||
conn: EngineConnection
|
||||
mediaStream: MediaStream
|
||||
@ -45,7 +40,7 @@ type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||
export class EngineConnection {
|
||||
websocket?: WebSocket
|
||||
pc?: RTCPeerConnection
|
||||
lossyDataChannel?: RTCDataChannel
|
||||
unreliableDataChannel?: RTCDataChannel
|
||||
|
||||
private ready: boolean
|
||||
|
||||
@ -107,6 +102,11 @@ export class EngineConnection {
|
||||
isReady() {
|
||||
return this.ready
|
||||
}
|
||||
// shouldTrace will return true when Sentry should be used to instrument
|
||||
// the Engine.
|
||||
shouldTrace() {
|
||||
return Sentry.getCurrentHub()?.getClient()?.getOptions()?.sendClientReports
|
||||
}
|
||||
// connect will attempt to connect to the Engine over a WebSocket, and
|
||||
// establish the WebRTC connections.
|
||||
//
|
||||
@ -116,6 +116,44 @@ export class EngineConnection {
|
||||
// TODO(paultag): make this safe to call multiple times, and figure out
|
||||
// when a connection is in progress (state: connecting or something).
|
||||
|
||||
// Information on the connect transaction
|
||||
|
||||
class SpanPromise {
|
||||
span: Sentry.Span
|
||||
promise: Promise<void>
|
||||
resolve?: (v: void) => void
|
||||
|
||||
constructor(span: Sentry.Span) {
|
||||
this.span = span
|
||||
this.promise = new Promise((resolve) => {
|
||||
this.resolve = (v: void) => {
|
||||
// here we're going to invoke finish before resolving the
|
||||
// promise so that a `.then()` will order strictly after
|
||||
// all spans have -- for sure -- been resolved, rather than
|
||||
// doing a `then` on this promise.
|
||||
this.span.finish()
|
||||
resolve(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let webrtcMediaTransaction: Sentry.Transaction
|
||||
let websocketSpan: SpanPromise
|
||||
let mediaTrackSpan: SpanPromise
|
||||
let dataChannelSpan: SpanPromise
|
||||
let handshakeSpan: SpanPromise
|
||||
let iceSpan: SpanPromise
|
||||
|
||||
if (this.shouldTrace()) {
|
||||
webrtcMediaTransaction = Sentry.startTransaction({
|
||||
name: 'webrtc-media',
|
||||
})
|
||||
websocketSpan = new SpanPromise(
|
||||
webrtcMediaTransaction.startChild({ op: 'websocket' })
|
||||
)
|
||||
}
|
||||
|
||||
this.websocket = new WebSocket(this.url, [])
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
|
||||
@ -129,6 +167,37 @@ export class EngineConnection {
|
||||
})
|
||||
|
||||
this.websocket.addEventListener('open', (event) => {
|
||||
if (this.shouldTrace()) {
|
||||
websocketSpan.resolve?.()
|
||||
|
||||
handshakeSpan = new SpanPromise(
|
||||
webrtcMediaTransaction.startChild({ op: 'handshake' })
|
||||
)
|
||||
iceSpan = new SpanPromise(
|
||||
webrtcMediaTransaction.startChild({ op: 'ice' })
|
||||
)
|
||||
dataChannelSpan = new SpanPromise(
|
||||
webrtcMediaTransaction.startChild({
|
||||
op: 'data-channel',
|
||||
})
|
||||
)
|
||||
mediaTrackSpan = new SpanPromise(
|
||||
webrtcMediaTransaction.startChild({
|
||||
op: 'media-track',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
handshakeSpan.promise,
|
||||
iceSpan.promise,
|
||||
dataChannelSpan.promise,
|
||||
mediaTrackSpan.promise,
|
||||
]).then(() => {
|
||||
console.log('All spans finished, reporting')
|
||||
webrtcMediaTransaction?.finish()
|
||||
})
|
||||
|
||||
this.onWebsocketOpen(this)
|
||||
})
|
||||
|
||||
@ -191,6 +260,13 @@ export class EngineConnection {
|
||||
sdp: answer.sdp,
|
||||
})
|
||||
)
|
||||
|
||||
if (this.shouldTrace()) {
|
||||
// When both ends have a local and remote SDP, we've been able to
|
||||
// set up successfully. We'll still need to find the right ICE
|
||||
// servers, but this is hand-shook.
|
||||
handshakeSpan.resolve?.()
|
||||
}
|
||||
}
|
||||
} else if (resp.type === 'trickle_ice') {
|
||||
let candidate = resp.data?.candidate
|
||||
@ -220,9 +296,9 @@ export class EngineConnection {
|
||||
// PeerConnection and waiting for events to fire our callbacks.
|
||||
|
||||
this.pc.addEventListener('connectionstatechange', (event) => {
|
||||
// if (this.pc?.iceConnectionState === 'disconnected') {
|
||||
// this.close()
|
||||
// }
|
||||
if (this.pc?.iceConnectionState === 'connected') {
|
||||
iceSpan.resolve?.()
|
||||
}
|
||||
})
|
||||
|
||||
this.pc.addEventListener('icecandidate', (event) => {
|
||||
@ -272,8 +348,142 @@ export class EngineConnection {
|
||||
})
|
||||
|
||||
this.pc.addEventListener('track', (event) => {
|
||||
console.log('received track', event)
|
||||
const mediaStream = event.streams[0]
|
||||
|
||||
if (this.shouldTrace()) {
|
||||
let mediaStreamTrack = mediaStream.getVideoTracks()[0]
|
||||
mediaStreamTrack.addEventListener('unmute', () => {
|
||||
// let settings = mediaStreamTrack.getSettings()
|
||||
// mediaTrackSpan.span.setTag("fps", settings.frameRate)
|
||||
// mediaTrackSpan.span.setTag("width", settings.width)
|
||||
// mediaTrackSpan.span.setTag("height", settings.height)
|
||||
mediaTrackSpan.resolve?.()
|
||||
})
|
||||
}
|
||||
|
||||
// Set up the background thread to keep an eye on statistical
|
||||
// information about the WebRTC media stream from the server to
|
||||
// us. We'll also eventually want more global statistical information,
|
||||
// but this will give us a baseline.
|
||||
if (parseInt(VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS) !== 0) {
|
||||
setInterval(() => {
|
||||
if (this.pc === undefined) {
|
||||
return
|
||||
}
|
||||
if (!this.shouldTrace()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the WebRTC Statistics API to collect statistical information
|
||||
// about the WebRTC connection we're using to report to Sentry.
|
||||
mediaStream.getVideoTracks().forEach((videoTrack) => {
|
||||
let trackStats = new Map<string, any>()
|
||||
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
||||
// Sentry only allows 10 metrics per transaction. We're going
|
||||
// to have to pick carefully here, eventually send like a prom
|
||||
// file or something to the peer.
|
||||
|
||||
const transaction = Sentry.startTransaction({
|
||||
name: 'webrtc-stats',
|
||||
})
|
||||
videoTrackStats.forEach((videoTrackReport) => {
|
||||
if (videoTrackReport.type === 'inbound-rtp') {
|
||||
// RTC Stream Info
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.framesDecoded',
|
||||
// videoTrackReport.framesDecoded,
|
||||
// 'frame'
|
||||
// )
|
||||
transaction.setMeasurement(
|
||||
'rtcFramesDropped',
|
||||
videoTrackReport.framesDropped,
|
||||
''
|
||||
)
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.framesReceived',
|
||||
// videoTrackReport.framesReceived,
|
||||
// 'frame'
|
||||
// )
|
||||
transaction.setMeasurement(
|
||||
'rtcFramesPerSecond',
|
||||
videoTrackReport.framesPerSecond,
|
||||
'fps'
|
||||
)
|
||||
transaction.setMeasurement(
|
||||
'rtcFreezeCount',
|
||||
videoTrackReport.freezeCount,
|
||||
''
|
||||
)
|
||||
transaction.setMeasurement(
|
||||
'rtcJitter',
|
||||
videoTrackReport.jitter,
|
||||
'second'
|
||||
)
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.jitterBufferDelay',
|
||||
// videoTrackReport.jitterBufferDelay,
|
||||
// ''
|
||||
// )
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.jitterBufferEmittedCount',
|
||||
// videoTrackReport.jitterBufferEmittedCount,
|
||||
// ''
|
||||
// )
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.jitterBufferMinimumDelay',
|
||||
// videoTrackReport.jitterBufferMinimumDelay,
|
||||
// ''
|
||||
// )
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.jitterBufferTargetDelay',
|
||||
// videoTrackReport.jitterBufferTargetDelay,
|
||||
// ''
|
||||
// )
|
||||
transaction.setMeasurement(
|
||||
'rtcKeyFramesDecoded',
|
||||
videoTrackReport.keyFramesDecoded,
|
||||
''
|
||||
)
|
||||
transaction.setMeasurement(
|
||||
'rtcTotalFreezesDuration',
|
||||
videoTrackReport.totalFreezesDuration,
|
||||
'second'
|
||||
)
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.totalInterFrameDelay',
|
||||
// videoTrackReport.totalInterFrameDelay,
|
||||
// ''
|
||||
// )
|
||||
transaction.setMeasurement(
|
||||
'rtcTotalPausesDuration',
|
||||
videoTrackReport.totalPausesDuration,
|
||||
'second'
|
||||
)
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.totalProcessingDelay',
|
||||
// videoTrackReport.totalProcessingDelay,
|
||||
// 'second'
|
||||
// )
|
||||
} else if (videoTrackReport.type === 'transport') {
|
||||
// // Bytes i/o
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.bytesReceived',
|
||||
// videoTrackReport.bytesReceived,
|
||||
// 'byte'
|
||||
// )
|
||||
// transaction.setMeasurement(
|
||||
// 'mediaStreamTrack.bytesSent',
|
||||
// videoTrackReport.bytesSent,
|
||||
// 'byte'
|
||||
// )
|
||||
}
|
||||
})
|
||||
transaction?.finish()
|
||||
})
|
||||
})
|
||||
}, VITE_KC_CONNECTION_WEBRTC_REPORT_STATS_MS)
|
||||
}
|
||||
|
||||
this.onNewTrack({
|
||||
conn: this,
|
||||
mediaStream: mediaStream,
|
||||
@ -285,45 +495,48 @@ export class EngineConnection {
|
||||
let connectionStarted = new Date()
|
||||
|
||||
this.pc.addEventListener('datachannel', (event) => {
|
||||
this.lossyDataChannel = event.channel
|
||||
this.unreliableDataChannel = event.channel
|
||||
|
||||
console.log('accepted lossy data channel', event.channel.label)
|
||||
this.lossyDataChannel.addEventListener('open', (event) => {
|
||||
console.log('lossy data channel opened', event)
|
||||
console.log('accepted unreliable data channel', event.channel.label)
|
||||
this.unreliableDataChannel.addEventListener('open', (event) => {
|
||||
console.log('unreliable data channel opened', event)
|
||||
if (this.shouldTrace()) {
|
||||
dataChannelSpan.resolve?.()
|
||||
}
|
||||
|
||||
this.onDataChannelOpen(this)
|
||||
|
||||
let timeToConnectMs = new Date().getTime() - connectionStarted.getTime()
|
||||
console.log(`engine connection time to connect: ${timeToConnectMs}ms`)
|
||||
this.onEngineConnectionOpen(this)
|
||||
this.ready = true
|
||||
})
|
||||
|
||||
this.lossyDataChannel.addEventListener('close', (event) => {
|
||||
console.log('lossy data channel closed')
|
||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||
console.log('unreliable data channel closed')
|
||||
this.close()
|
||||
})
|
||||
|
||||
this.lossyDataChannel.addEventListener('error', (event) => {
|
||||
console.log('lossy data channel error')
|
||||
this.unreliableDataChannel.addEventListener('error', (event) => {
|
||||
console.log('unreliable data channel error')
|
||||
this.close()
|
||||
})
|
||||
})
|
||||
|
||||
this.onConnectionStarted(this)
|
||||
}
|
||||
send(message: object) {
|
||||
send(message: object | string) {
|
||||
// TODO(paultag): Add in logic to determine the connection state and
|
||||
// take actions if needed?
|
||||
this.websocket?.send(JSON.stringify(message))
|
||||
this.websocket?.send(
|
||||
typeof message === 'string' ? message : JSON.stringify(message)
|
||||
)
|
||||
}
|
||||
close() {
|
||||
this.websocket?.close()
|
||||
this.pc?.close()
|
||||
this.lossyDataChannel?.close()
|
||||
this.unreliableDataChannel?.close()
|
||||
this.websocket = undefined
|
||||
this.pc = undefined
|
||||
this.lossyDataChannel = undefined
|
||||
this.unreliableDataChannel = undefined
|
||||
|
||||
this.onClose(this)
|
||||
this.ready = false
|
||||
@ -331,6 +544,23 @@ export class EngineConnection {
|
||||
}
|
||||
|
||||
export type EngineCommand = Models['WebSocketRequest_type']
|
||||
type ModelTypes = Models['OkModelingCmdResponse_type']['type']
|
||||
|
||||
type UnreliableResponses = Extract<
|
||||
Models['OkModelingCmdResponse_type'],
|
||||
{ type: 'highlight_set_entity' }
|
||||
>
|
||||
interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
||||
event: T
|
||||
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
||||
}
|
||||
|
||||
interface Subscription<T extends ModelTypes> {
|
||||
event: T
|
||||
callback: (
|
||||
data: Extract<Models['OkModelingCmdResponse_type'], { type: T }>
|
||||
) => void
|
||||
}
|
||||
|
||||
export class EngineCommandManager {
|
||||
artifactMap: ArtifactMap = {}
|
||||
@ -340,10 +570,17 @@ export class EngineCommandManager {
|
||||
engineConnection?: EngineConnection
|
||||
waitForReady: Promise<void> = new Promise(() => {})
|
||||
private resolveReady = () => {}
|
||||
onHoverCallback: (id?: string) => void = () => {}
|
||||
onClickCallback: (selection?: SelectionsArgs) => void = () => {}
|
||||
onCursorsSelectedCallback: (selections: CursorSelectionsArgs) => void =
|
||||
() => {}
|
||||
|
||||
subscriptions: {
|
||||
[event: string]: {
|
||||
[localUnsubscribeId: string]: (a: any) => void
|
||||
}
|
||||
} = {} as any
|
||||
unreliableSubscriptions: {
|
||||
[event: string]: {
|
||||
[localUnsubscribeId: string]: (a: any) => void
|
||||
}
|
||||
} = {} as any
|
||||
constructor({
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
@ -373,20 +610,28 @@ export class EngineCommandManager {
|
||||
},
|
||||
onConnectionStarted: (engineConnection) => {
|
||||
engineConnection?.pc?.addEventListener('datachannel', (event) => {
|
||||
let lossyDataChannel = event.channel
|
||||
let unreliableDataChannel = event.channel
|
||||
|
||||
lossyDataChannel.addEventListener('message', (event) => {
|
||||
const result: Models['OkModelingCmdResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
unreliableDataChannel.addEventListener('message', (event) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
if (
|
||||
result?.data?.sequence &&
|
||||
result?.data.sequence > this.inSequence &&
|
||||
result.type === 'highlight_set_entity'
|
||||
) {
|
||||
this.inSequence = result.data.sequence
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
if (
|
||||
result.type === 'highlight_set_entity' &&
|
||||
result?.data?.sequence &&
|
||||
result.data.sequence > this.inSequence
|
||||
) {
|
||||
this.onHoverCallback(result.data.entity_id)
|
||||
this.inSequence = result.data.sequence
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -418,8 +663,8 @@ export class EngineCommandManager {
|
||||
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.log('peer is not sending video to us')
|
||||
this.engineConnection?.close()
|
||||
this.engineConnection?.connect()
|
||||
// this.engineConnection?.close()
|
||||
// this.engineConnection?.connect()
|
||||
})
|
||||
|
||||
setMediaStream(mediaStream)
|
||||
@ -433,18 +678,11 @@ export class EngineCommandManager {
|
||||
return
|
||||
}
|
||||
const modelingResponse = message.data.modeling_response
|
||||
Object.values(this.subscriptions[modelingResponse.type] || {}).forEach(
|
||||
(callback) => callback(modelingResponse)
|
||||
)
|
||||
|
||||
const command = this.artifactMap[id]
|
||||
if (modelingResponse.type === 'select_with_point') {
|
||||
if (modelingResponse?.data?.entity_id) {
|
||||
this.onClickCallback({
|
||||
id: modelingResponse?.data?.entity_id,
|
||||
type: 'default',
|
||||
})
|
||||
} else {
|
||||
this.onClickCallback()
|
||||
}
|
||||
}
|
||||
if (command && command.type === 'pending') {
|
||||
const resolve = command.resolve
|
||||
this.artifactMap[id] = {
|
||||
@ -453,6 +691,7 @@ export class EngineCommandManager {
|
||||
}
|
||||
resolve({
|
||||
id,
|
||||
data: modelingResponse,
|
||||
})
|
||||
} else {
|
||||
this.artifactMap[id] = {
|
||||
@ -468,21 +707,49 @@ export class EngineCommandManager {
|
||||
this.artifactMap = {}
|
||||
this.sourceRangeMap = {}
|
||||
}
|
||||
subscribeTo<T extends ModelTypes>({
|
||||
event,
|
||||
callback,
|
||||
}: Subscription<T>): () => void {
|
||||
const localUnsubscribeId = uuidv4()
|
||||
const otherEventCallbacks = this.subscriptions[event]
|
||||
if (otherEventCallbacks) {
|
||||
otherEventCallbacks[localUnsubscribeId] = callback
|
||||
} else {
|
||||
this.subscriptions[event] = {
|
||||
[localUnsubscribeId]: callback,
|
||||
}
|
||||
}
|
||||
return () => this.unSubscribeTo(event, localUnsubscribeId)
|
||||
}
|
||||
private unSubscribeTo(event: ModelTypes, id: string) {
|
||||
delete this.subscriptions[event][id]
|
||||
}
|
||||
subscribeToUnreliable<T extends UnreliableResponses['type']>({
|
||||
event,
|
||||
callback,
|
||||
}: UnreliableSubscription<T>): () => void {
|
||||
const localUnsubscribeId = uuidv4()
|
||||
const otherEventCallbacks = this.unreliableSubscriptions[event]
|
||||
if (otherEventCallbacks) {
|
||||
otherEventCallbacks[localUnsubscribeId] = callback
|
||||
} else {
|
||||
this.unreliableSubscriptions[event] = {
|
||||
[localUnsubscribeId]: callback,
|
||||
}
|
||||
}
|
||||
return () => this.unSubscribeToUnreliable(event, localUnsubscribeId)
|
||||
}
|
||||
private unSubscribeToUnreliable(
|
||||
event: UnreliableResponses['type'],
|
||||
id: string
|
||||
) {
|
||||
delete this.unreliableSubscriptions[event][id]
|
||||
}
|
||||
endSession() {
|
||||
// this.websocket?.close()
|
||||
// socket.off('command')
|
||||
}
|
||||
onHover(callback: (id?: string) => void) {
|
||||
// It's when the user hovers over a part in the 3d scene, and so the engine should tell the
|
||||
// frontend about that (with it's id) so that the FE can highlight code associated with that id
|
||||
this.onHoverCallback = callback
|
||||
}
|
||||
onClick(callback: (selection?: SelectionsArgs) => void) {
|
||||
// It's when the user clicks on a part in the 3d scene, and so the engine should tell the
|
||||
// frontend about that (with it's id) so that the FE can put the user's cursor on the right
|
||||
// line of code
|
||||
this.onClickCallback = callback
|
||||
}
|
||||
cusorsSelected(selections: {
|
||||
otherSelections: Selections['otherSelections']
|
||||
idBasedSelections: { type: string; id: string }[]
|
||||
@ -507,32 +774,38 @@ export class EngineCommandManager {
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
}
|
||||
sendSceneCommand(command: EngineCommand) {
|
||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
||||
if (!this.engineConnection?.isReady()) {
|
||||
console.log('socket not ready')
|
||||
return
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (command.type !== 'modeling_cmd_req') return
|
||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||
const cmd = command.cmd
|
||||
if (
|
||||
cmd.type === 'camera_drag_move' &&
|
||||
this.engineConnection?.lossyDataChannel
|
||||
this.engineConnection?.unreliableDataChannel
|
||||
) {
|
||||
cmd.sequence = this.outSequence
|
||||
this.outSequence++
|
||||
this.engineConnection?.lossyDataChannel?.send(JSON.stringify(command))
|
||||
return
|
||||
this.engineConnection?.unreliableDataChannel?.send(
|
||||
JSON.stringify(command)
|
||||
)
|
||||
return Promise.resolve()
|
||||
} else if (
|
||||
cmd.type === 'highlight_set_entity' &&
|
||||
this.engineConnection?.lossyDataChannel
|
||||
this.engineConnection?.unreliableDataChannel
|
||||
) {
|
||||
cmd.sequence = this.outSequence
|
||||
this.outSequence++
|
||||
this.engineConnection?.lossyDataChannel?.send(JSON.stringify(command))
|
||||
return
|
||||
this.engineConnection?.unreliableDataChannel?.send(
|
||||
JSON.stringify(command)
|
||||
)
|
||||
return Promise.resolve()
|
||||
}
|
||||
console.log('sending command', command)
|
||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||
this.engineConnection?.send(command)
|
||||
return this.handlePendingCommand(command.cmd_id)
|
||||
}
|
||||
sendModelingCommand({
|
||||
id,
|
||||
@ -541,15 +814,18 @@ export class EngineCommandManager {
|
||||
}: {
|
||||
id: string
|
||||
range: SourceRange
|
||||
command: EngineCommand
|
||||
command: EngineCommand | string
|
||||
}): Promise<any> {
|
||||
this.sourceRangeMap[id] = range
|
||||
|
||||
if (!this.engineConnection?.isReady()) {
|
||||
console.log('socket not ready')
|
||||
return new Promise(() => {})
|
||||
return Promise.resolve()
|
||||
}
|
||||
this.engineConnection?.send(command)
|
||||
return this.handlePendingCommand(id)
|
||||
}
|
||||
handlePendingCommand(id: string) {
|
||||
let resolve: (val: any) => void = () => {}
|
||||
const promise = new Promise((_resolve, reject) => {
|
||||
resolve = _resolve
|
||||
@ -575,10 +851,9 @@ export class EngineCommandManager {
|
||||
if (commandStr === undefined) {
|
||||
throw new Error('commandStr is undefined')
|
||||
}
|
||||
const command: EngineCommand = JSON.parse(commandStr)
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
|
||||
return this.sendModelingCommand({ id, range, command })
|
||||
return this.sendModelingCommand({ id, range, command: commandStr })
|
||||
}
|
||||
commandResult(id: string): Promise<any> {
|
||||
const command = this.artifactMap[id]
|
||||
|
||||
@ -73,8 +73,6 @@ export function createMachineCommand<T extends AnyStateMachine>({
|
||||
arg.defaultValue as keyof typeof state.context
|
||||
] as string | undefined
|
||||
|
||||
console.log(arg.name, { defaultValueFromContext })
|
||||
|
||||
const options =
|
||||
arg.options instanceof Array
|
||||
? arg.options.map((o) => ({
|
||||
|
||||
@ -4,17 +4,18 @@ export enum Themes {
|
||||
System = 'system',
|
||||
}
|
||||
|
||||
// Get the theme from the system settings manually
|
||||
export function getSystemTheme(): Exclude<Themes, 'system'> {
|
||||
return typeof window !== 'undefined' &&
|
||||
'matchMedia' in window &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? Themes.Dark
|
||||
return typeof window !== 'undefined' && 'matchMedia' in window
|
||||
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? Themes.Dark
|
||||
: Themes.Light
|
||||
: Themes.Light
|
||||
}
|
||||
|
||||
// Set the theme class on the body element
|
||||
export function setThemeClass(theme: Themes) {
|
||||
const systemTheme = theme === Themes.System && getSystemTheme()
|
||||
if (theme === Themes.Dark || systemTheme === Themes.Dark) {
|
||||
if (theme === Themes.Dark) {
|
||||
document.body.classList.add('dark')
|
||||
} else {
|
||||
document.body.classList.remove('dark')
|
||||
|
||||
@ -3,6 +3,23 @@ import { Models } from '@kittycad/lib'
|
||||
import withBaseURL from '../lib/withBaseURL'
|
||||
import { CommandBarMeta } from '../lib/commands'
|
||||
|
||||
const SKIP_AUTH =
|
||||
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
|
||||
const LOCAL_USER: Models['User_type'] = {
|
||||
id: '8675309',
|
||||
name: 'Test User',
|
||||
email: 'kittycad.sidebar.test@example.com',
|
||||
image: 'https://placekitten.com/200/200',
|
||||
created_at: 'yesteryear',
|
||||
updated_at: 'today',
|
||||
company: 'Test Company',
|
||||
discord: 'Test User#1234',
|
||||
github: 'testuser',
|
||||
phone: '555-555-5555',
|
||||
first_name: 'Test',
|
||||
last_name: 'User',
|
||||
}
|
||||
|
||||
export interface UserContext {
|
||||
user?: Models['User_type']
|
||||
token?: string
|
||||
@ -81,7 +98,9 @@ export const authMachine = createMachine<UserContext, Events>(
|
||||
schema: { events: {} as { type: 'Log out' } | { type: 'Log in' } },
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
context: { token: persistedToken },
|
||||
context: {
|
||||
token: persistedToken,
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {},
|
||||
@ -98,12 +117,17 @@ async function getUser(context: UserContext) {
|
||||
}
|
||||
if (!context.token && '__TAURI__' in window) throw 'not log in'
|
||||
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
})
|
||||
const user = await response.json()
|
||||
if ('error_code' in user) throw new Error(user.message)
|
||||
return user
|
||||
if (SKIP_AUTH) return LOCAL_USER
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
})
|
||||
const user = await response.json()
|
||||
if ('error_code' in user) throw new Error(user.message)
|
||||
return user
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { assign, createMachine } from 'xstate'
|
||||
import { BaseUnit, baseUnitsUnion } from '../useStore'
|
||||
import { CommandBarMeta } from '../lib/commands'
|
||||
import { Themes } from '../lib/theme'
|
||||
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
|
||||
|
||||
export enum UnitSystem {
|
||||
Imperial = 'imperial',
|
||||
Metric = 'metric',
|
||||
}
|
||||
|
||||
export const SETTINGS_PERSIST_KEY = 'SETTINGS_PERSIST_KEY'
|
||||
|
||||
@ -42,7 +47,7 @@ export const settingsCommandBarMeta: CommandBarMeta = {
|
||||
name: 'unitSystem',
|
||||
type: 'select',
|
||||
defaultValue: 'unitSystem',
|
||||
options: [{ name: 'imperial' }, { name: 'metric' }],
|
||||
options: [{ name: UnitSystem.Imperial }, { name: UnitSystem.Metric }],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -70,7 +75,7 @@ export const settingsMachine = createMachine(
|
||||
context: {
|
||||
theme: Themes.System,
|
||||
defaultProjectName: '',
|
||||
unitSystem: 'imperial' as 'imperial' | 'metric',
|
||||
unitSystem: UnitSystem.Imperial,
|
||||
baseUnit: 'in' as BaseUnit,
|
||||
defaultDirectory: '',
|
||||
showDebugPanel: false,
|
||||
@ -79,6 +84,7 @@ export const settingsMachine = createMachine(
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {
|
||||
entry: ['setThemeClass'],
|
||||
on: {
|
||||
'Set Theme': {
|
||||
actions: [
|
||||
@ -87,6 +93,7 @@ export const settingsMachine = createMachine(
|
||||
}),
|
||||
'persistSettings',
|
||||
'toastSuccess',
|
||||
'setThemeClass',
|
||||
],
|
||||
target: 'idle',
|
||||
internal: true,
|
||||
@ -154,7 +161,6 @@ export const settingsMachine = createMachine(
|
||||
onboardingStatus: (_, event) => event.data.onboardingStatus,
|
||||
}),
|
||||
'persistSettings',
|
||||
'toastSuccess',
|
||||
],
|
||||
target: 'idle',
|
||||
internal: true,
|
||||
@ -173,7 +179,7 @@ export const settingsMachine = createMachine(
|
||||
| { type: 'Set Default Directory'; data: { defaultDirectory: string } }
|
||||
| {
|
||||
type: 'Set Unit System'
|
||||
data: { unitSystem: 'imperial' | 'metric' }
|
||||
data: { unitSystem: UnitSystem }
|
||||
}
|
||||
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
||||
| { type: 'Set Onboarding Status'; data: { onboardingStatus: string } }
|
||||
@ -189,6 +195,13 @@ export const settingsMachine = createMachine(
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
setThemeClass: (context, event) => {
|
||||
const currentTheme =
|
||||
event.type === 'Set Theme' ? event.data.theme : context.theme
|
||||
setThemeClass(
|
||||
currentTheme === Themes.System ? getSystemTheme() : currentTheme
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -21,7 +21,7 @@ export interface Typegen0 {
|
||||
| 'Set Theme'
|
||||
| 'Set Unit System'
|
||||
| 'Toggle Debug Panel'
|
||||
toastSuccess:
|
||||
setThemeClass:
|
||||
| 'Set Base Unit'
|
||||
| 'Set Default Directory'
|
||||
| 'Set Default Project Name'
|
||||
@ -29,6 +29,14 @@ export interface Typegen0 {
|
||||
| 'Set Theme'
|
||||
| 'Set Unit System'
|
||||
| 'Toggle Debug Panel'
|
||||
| 'xstate.init'
|
||||
toastSuccess:
|
||||
| 'Set Base Unit'
|
||||
| 'Set Default Directory'
|
||||
| 'Set Default Project Name'
|
||||
| 'Set Theme'
|
||||
| 'Set Unit System'
|
||||
| 'Toggle Debug Panel'
|
||||
}
|
||||
eventsCausingDelays: {}
|
||||
eventsCausingGuards: {}
|
||||
|
||||
@ -20,9 +20,25 @@ export default function Units() {
|
||||
>
|
||||
<h1 className="text-2xl font-bold">Camera</h1>
|
||||
<p className="mt-6">
|
||||
Moving the camera is easy. Just click and drag anywhere in the scene
|
||||
to rotate the camera, or hold down the <kbd>Ctrl</kbd> key and drag to
|
||||
pan the camera.
|
||||
Moving the camera is easy! The controls are as you might expect:
|
||||
</p>
|
||||
<ul className="list-disc list-outside ms-8 mb-4">
|
||||
<li>Click and drag anywhere in the scene to rotate the camera</li>
|
||||
<li>
|
||||
Hold down the <kbd>Shift</kbd> key while clicking and dragging to
|
||||
pan the camera
|
||||
</li>
|
||||
<li>
|
||||
Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can
|
||||
also use the scroll wheel to zoom in and out.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
What you're seeing here is just a video, and your interactions are
|
||||
being sent to our Geometry Engine API, which sends back video frames
|
||||
in real time. How cool is that? It means that you can use KittyCAD
|
||||
Modeling App (or whatever you want to build) on any device, even a
|
||||
cheap laptop with no graphics card!
|
||||
</p>
|
||||
<div className="flex justify-between mt-6">
|
||||
<ActionButton
|
||||
|
||||
@ -3,9 +3,9 @@ import { BaseUnit, baseUnits } from '../../useStore'
|
||||
import { ActionButton } from '../../components/ActionButton'
|
||||
import { SettingsSection } from '../Settings'
|
||||
import { Toggle } from '../../components/Toggle/Toggle'
|
||||
import { useState } from 'react'
|
||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { UnitSystem } from 'machines/settingsMachine'
|
||||
|
||||
export default function Units() {
|
||||
const dismiss = useDismiss()
|
||||
@ -16,15 +16,6 @@ export default function Units() {
|
||||
context: { unitSystem, baseUnit },
|
||||
},
|
||||
} = useGlobalStateContext()
|
||||
const [tempUnitSystem, setTempUnitSystem] = useState(unitSystem)
|
||||
const [tempBaseUnit, setTempBaseUnit] = useState(baseUnit)
|
||||
|
||||
function handleNextClick() {
|
||||
send({ type: 'Set Unit System', data: { unitSystem: tempUnitSystem } })
|
||||
send({ type: 'Set Base Unit', data: { baseUnit: tempBaseUnit } })
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||
@ -38,10 +29,16 @@ export default function Units() {
|
||||
offLabel="Imperial"
|
||||
onLabel="Metric"
|
||||
name="settings-units"
|
||||
checked={tempUnitSystem === 'metric'}
|
||||
onChange={(e) =>
|
||||
setTempUnitSystem(e.target.checked ? 'metric' : 'imperial')
|
||||
}
|
||||
checked={unitSystem === UnitSystem.Metric}
|
||||
onChange={(e) => {
|
||||
const newUnitSystem = e.target.checked
|
||||
? UnitSystem.Metric
|
||||
: UnitSystem.Imperial
|
||||
send({
|
||||
type: 'Set Unit System',
|
||||
data: { unitSystem: newUnitSystem },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
@ -51,8 +48,13 @@ export default function Units() {
|
||||
<select
|
||||
id="base-unit"
|
||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||
value={tempBaseUnit}
|
||||
onChange={(e) => setTempBaseUnit(e.target.value as BaseUnit)}
|
||||
value={baseUnit}
|
||||
onChange={(e) => {
|
||||
send({
|
||||
type: 'Set Base Unit',
|
||||
data: { baseUnit: e.target.value as BaseUnit },
|
||||
})
|
||||
}}
|
||||
>
|
||||
{baseUnits[unitSystem].map((unit) => (
|
||||
<option key={unit} value={unit}>
|
||||
@ -77,7 +79,7 @@ export default function Units() {
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={handleNextClick}
|
||||
onClick={next}
|
||||
icon={{ icon: faArrowRight }}
|
||||
>
|
||||
Next: Camera
|
||||
|
||||
@ -8,15 +8,17 @@ import { AppHeader } from '../components/AppHeader'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { BaseUnit, baseUnits } from '../useStore'
|
||||
import { Toggle } from '../components/Toggle/Toggle'
|
||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { IndexLoaderData, paths } from '../Router'
|
||||
import { Themes } from '../lib/theme'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { UnitSystem } from 'machines/settingsMachine'
|
||||
|
||||
export const Settings = () => {
|
||||
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
useHotkeys('esc', () => navigate('../'))
|
||||
const {
|
||||
settings: {
|
||||
@ -135,9 +137,11 @@ export const Settings = () => {
|
||||
offLabel="Imperial"
|
||||
onLabel="Metric"
|
||||
name="settings-units"
|
||||
checked={unitSystem === 'metric'}
|
||||
checked={unitSystem === UnitSystem.Metric}
|
||||
onChange={(e) => {
|
||||
const newUnitSystem = e.target.checked ? 'metric' : 'imperial'
|
||||
const newUnitSystem = e.target.checked
|
||||
? UnitSystem.Metric
|
||||
: UnitSystem.Imperial
|
||||
send({
|
||||
type: 'Set Unit System',
|
||||
data: { unitSystem: newUnitSystem },
|
||||
@ -201,24 +205,26 @@ export const Settings = () => {
|
||||
))}
|
||||
</select>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
title="Onboarding"
|
||||
description="Replay the onboarding process"
|
||||
>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => {
|
||||
send({
|
||||
type: 'Set Onboarding Status',
|
||||
data: { onboardingStatus: '' },
|
||||
})
|
||||
navigate('..' + paths.ONBOARDING.INDEX)
|
||||
}}
|
||||
icon={{ icon: faArrowRotateBack }}
|
||||
{location.pathname.includes(paths.FILE) && (
|
||||
<SettingsSection
|
||||
title="Onboarding"
|
||||
description="Replay the onboarding process"
|
||||
>
|
||||
Replay Onboarding
|
||||
</ActionButton>
|
||||
</SettingsSection>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => {
|
||||
send({
|
||||
type: 'Set Onboarding Status',
|
||||
data: { onboardingStatus: '' },
|
||||
})
|
||||
navigate('..' + paths.ONBOARDING.INDEX)
|
||||
}}
|
||||
icon={{ icon: faArrowRotateBack }}
|
||||
>
|
||||
Replay Onboarding
|
||||
</ActionButton>
|
||||
</SettingsSection>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
56
src/wasm-lib/Cargo.lock
generated
56
src/wasm-lib/Cargo.lock
generated
@ -29,10 +29,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
@ -187,9 +188,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bson"
|
||||
version = "2.6.1"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aeb8bae494e49dbc330dd23cf78f6f7accee22f640ce3ab17841badaa4ce232"
|
||||
checksum = "58da0ae1e701ea752cc46c1bb9f39d5ecefc7395c3ecd526261a566d4f16e0c2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64 0.13.1",
|
||||
@ -198,7 +199,7 @@ dependencies = [
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
@ -413,6 +414,20 @@ dependencies = [
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "075291fd1d6d70a886078f7b1c132a160559ceb9a0fe143177872d40ea587906"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
@ -932,12 +947,12 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kcl"
|
||||
version = "0.1.0"
|
||||
name = "kcl-lib"
|
||||
version = "0.1.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bson",
|
||||
"derive-docs",
|
||||
"derive-docs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"expectorate",
|
||||
"futures",
|
||||
"js-sys",
|
||||
@ -953,7 +968,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"ts-rs",
|
||||
"ts-rs-json-value",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@ -961,9 +976,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.2.20"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e71916b50966110cb9f70aa6c310748a153fdcb0183a02615324a6f457fd18e8"
|
||||
checksum = "b8b33e5df8f82b97e5f5af94ff1400ae37449d0f5f1bb79acedf17cf2193680f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.2",
|
||||
@ -975,6 +990,7 @@ dependencies = [
|
||||
"phonenumber",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"url",
|
||||
@ -1696,9 +1712,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.12"
|
||||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
|
||||
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
@ -1713,9 +1729,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.12"
|
||||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
|
||||
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2346,9 +2362,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
name = "ts-rs-json-value"
|
||||
version = "7.0.0"
|
||||
source = "git+https://github.com/kittycad/ts-rs.git?branch=serde_json#94e2a19c41194e47009fafc7b5a2c28ae544a6e8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66d07e64e1e39d693819307757ad16878ff2be1f26d6fc2137c4e23bc0c0545"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@ -2359,7 +2376,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "7.0.0"
|
||||
source = "git+https://github.com/kittycad/ts-rs.git?branch=serde_json#94e2a19c41194e47009fafc7b5a2c28ae544a6e8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
@ -2592,7 +2610,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bson",
|
||||
"gloo-utils",
|
||||
"kcl",
|
||||
"kcl-lib",
|
||||
"kittycad",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
|
||||
@ -8,10 +8,10 @@ edition = "2021"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
bson = { version = "2.6.1", features = ["uuid-1", "chrono"] }
|
||||
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
||||
gloo-utils = "0.2.0"
|
||||
kcl = { path = "kcl" }
|
||||
kittycad = { version = "0.2.15", default-features = false, features = ["js"] }
|
||||
kcl-lib = { path = "kcl" }
|
||||
kittycad = { version = "0.2.23", default-features = false, features = ["js"] }
|
||||
serde_json = "1.0.93"
|
||||
wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
|
||||
@ -3,6 +3,7 @@ name = "derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@ -35,10 +35,7 @@ struct StdlibMetadata {
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn stdlib(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
pub fn stdlib(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
do_output(do_stdlib(attr.into(), item.into()))
|
||||
}
|
||||
|
||||
@ -50,9 +47,7 @@ fn do_stdlib(
|
||||
do_stdlib_inner(metadata, attr, item)
|
||||
}
|
||||
|
||||
fn do_output(
|
||||
res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>,
|
||||
) -> proc_macro::TokenStream {
|
||||
fn do_output(res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>) -> proc_macro::TokenStream {
|
||||
match res {
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
Ok((stdlib_docs, errors)) => {
|
||||
@ -207,11 +202,7 @@ fn do_stdlib_inner(
|
||||
syn::FnArg::Typed(pat) => pat.ty.as_ref().into_token_stream(),
|
||||
};
|
||||
|
||||
let ty_string = ty
|
||||
.to_string()
|
||||
.replace('&', "")
|
||||
.replace("mut", "")
|
||||
.replace(' ', "");
|
||||
let ty_string = ty.to_string().replace('&', "").replace("mut", "").replace(' ', "");
|
||||
let ty_string = ty_string.trim().to_string();
|
||||
let ty_ident = if ty_string.starts_with("Vec<") {
|
||||
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
|
||||
@ -370,8 +361,7 @@ fn extract_doc_from_attrs(attrs: &[syn::Attribute]) -> (Option<String>, Option<S
|
||||
if let syn::Meta::NameValue(nv) = &attr.meta {
|
||||
if nv.path.is_ident(&doc) {
|
||||
if let syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(s),
|
||||
..
|
||||
lit: syn::Lit::Str(s), ..
|
||||
}) = &nv.value
|
||||
{
|
||||
return normalize_comment_string(s.value());
|
||||
@ -508,10 +498,7 @@ mod tests {
|
||||
let _expected = quote! {};
|
||||
|
||||
assert!(errors.is_empty());
|
||||
expectorate::assert_contents(
|
||||
"tests/lineTo.gen",
|
||||
&openapitor::types::get_text_fmt(&item).unwrap(),
|
||||
);
|
||||
expectorate::assert_contents("tests/lineTo.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -540,9 +527,6 @@ mod tests {
|
||||
let _expected = quote! {};
|
||||
|
||||
assert!(errors.is_empty());
|
||||
expectorate::assert_contents(
|
||||
"tests/min.gen",
|
||||
&openapitor::types::get_text_fmt(&item).unwrap(),
|
||||
);
|
||||
expectorate::assert_contents("tests/min.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
[package]
|
||||
name = "kcl"
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language"
|
||||
version = "0.1.0"
|
||||
version = "0.1.10"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
derive-docs = { path = "../derive-docs" }
|
||||
kittycad = { version = "0.2.15", default-features = false, features = ["js"] }
|
||||
derive-docs = { version = "0.1.0" }
|
||||
kittycad = { version = "0.2.23", default-features = false, features = ["js"] }
|
||||
lazy_static = "1.4.0"
|
||||
parse-display = "0.8.2"
|
||||
regex = "1.7.1"
|
||||
@ -17,7 +18,7 @@ schemars = { version = "0.8", features = ["url", "uuid1"] }
|
||||
serde = {version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
thiserror = "1.0.47"
|
||||
ts-rs = { git = "https://github.com/kittycad/ts-rs.git", branch = "serde_json", features = ["serde-json-impl", "uuid-impl"] }
|
||||
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "uuid-impl"] }
|
||||
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||
wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
@ -26,12 +27,16 @@ wasm-bindgen-futures = "0.4.37"
|
||||
js-sys = { version = "0.3.64" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
bson = { version = "2.6.1", features = ["uuid-1", "chrono"] }
|
||||
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
||||
futures = { version = "0.3.28" }
|
||||
reqwest = { version = "0.11.20", default-features = false }
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] }
|
||||
|
||||
[features]
|
||||
default = ["engine"]
|
||||
engine = []
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
debug = true
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Map;
|
||||
@ -56,7 +57,7 @@ pub trait ValueMeta {
|
||||
|
||||
macro_rules! impl_value_meta {
|
||||
{$name:ident} => {
|
||||
impl ValueMeta for $name {
|
||||
impl crate::abstract_syntax_tree_types::ValueMeta for $name {
|
||||
fn start(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
@ -86,6 +87,8 @@ macro_rules! impl_value_meta {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use impl_value_meta;
|
||||
|
||||
impl Value {
|
||||
pub fn start(&self) -> usize {
|
||||
match self {
|
||||
@ -192,16 +195,11 @@ impl BinaryPart {
|
||||
BinaryPart::BinaryExpression(binary_expression) => {
|
||||
binary_expression.get_result(memory, pipe_info, stdlib, engine)
|
||||
}
|
||||
BinaryPart::CallExpression(call_expression) => {
|
||||
call_expression.execute(memory, pipe_info, stdlib, engine)
|
||||
}
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, stdlib, engine),
|
||||
BinaryPart::UnaryExpression(unary_expression) => {
|
||||
// Return an error this should not happen.
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"UnaryExpression should not be a BinaryPart: {:?}",
|
||||
unary_expression
|
||||
),
|
||||
message: format!("UnaryExpression should not be a BinaryPart: {:?}", unary_expression),
|
||||
source_ranges: vec![unary_expression.into()],
|
||||
}))
|
||||
}
|
||||
@ -313,10 +311,7 @@ impl CallExpression {
|
||||
}
|
||||
Value::PipeExpression(pipe_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"PipeExpression not implemented here: {:?}",
|
||||
pipe_expression
|
||||
),
|
||||
message: format!("PipeExpression not implemented here: {:?}", pipe_expression),
|
||||
source_ranges: vec![pipe_expression.into()],
|
||||
}));
|
||||
}
|
||||
@ -325,29 +320,20 @@ impl CallExpression {
|
||||
.get(&pipe_info.index - 1)
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"PipeSubstitution index out of bounds: {:?}",
|
||||
pipe_info
|
||||
),
|
||||
message: format!("PipeSubstitution index out of bounds: {:?}", pipe_info),
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
})
|
||||
})?
|
||||
.clone(),
|
||||
Value::MemberExpression(member_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"MemberExpression not implemented here: {:?}",
|
||||
member_expression
|
||||
),
|
||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"FunctionExpression not implemented here: {:?}",
|
||||
function_expression
|
||||
),
|
||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
@ -363,14 +349,7 @@ impl CallExpression {
|
||||
if pipe_info.is_in_pipe {
|
||||
pipe_info.index += 1;
|
||||
pipe_info.previous_results.push(result);
|
||||
execute_pipe_body(
|
||||
memory,
|
||||
&pipe_info.body.clone(),
|
||||
pipe_info,
|
||||
self.into(),
|
||||
stdlib,
|
||||
engine,
|
||||
)
|
||||
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), stdlib, engine)
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
@ -390,14 +369,7 @@ impl CallExpression {
|
||||
pipe_info.index += 1;
|
||||
pipe_info.previous_results.push(result);
|
||||
|
||||
execute_pipe_body(
|
||||
memory,
|
||||
&pipe_info.body.clone(),
|
||||
pipe_info,
|
||||
self.into(),
|
||||
stdlib,
|
||||
engine,
|
||||
)
|
||||
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), stdlib, engine)
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
@ -412,11 +384,22 @@ pub struct VariableDeclaration {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub declarations: Vec<VariableDeclarator>,
|
||||
pub kind: String, // Change to enum if there are specific values
|
||||
pub kind: VariableKind, // Change to enum if there are specific values
|
||||
}
|
||||
|
||||
impl_value_meta!(VariableDeclaration);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[display(style = "snake_case")]
|
||||
pub enum VariableKind {
|
||||
Let,
|
||||
Const,
|
||||
Fn,
|
||||
Var,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -533,28 +516,19 @@ impl ArrayExpression {
|
||||
}
|
||||
Value::PipeSubstitution(pipe_substitution) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"PipeSubstitution not implemented here: {:?}",
|
||||
pipe_substitution
|
||||
),
|
||||
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
}));
|
||||
}
|
||||
Value::MemberExpression(member_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"MemberExpression not implemented here: {:?}",
|
||||
member_expression
|
||||
),
|
||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"FunctionExpression not implemented here: {:?}",
|
||||
function_expression
|
||||
),
|
||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
@ -619,28 +593,19 @@ impl ObjectExpression {
|
||||
}
|
||||
Value::PipeSubstitution(pipe_substitution) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"PipeSubstitution not implemented here: {:?}",
|
||||
pipe_substitution
|
||||
),
|
||||
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
}));
|
||||
}
|
||||
Value::MemberExpression(member_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"MemberExpression not implemented here: {:?}",
|
||||
member_expression
|
||||
),
|
||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"FunctionExpression not implemented here: {:?}",
|
||||
function_expression
|
||||
),
|
||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
@ -712,10 +677,7 @@ impl MemberExpression {
|
||||
string
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected string literal for property name, found {:?}",
|
||||
value
|
||||
),
|
||||
message: format!("Expected string literal for property name, found {:?}", value),
|
||||
source_ranges: vec![literal.into()],
|
||||
}));
|
||||
}
|
||||
@ -837,10 +799,7 @@ impl BinaryExpression {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_json_number_as_f64(
|
||||
j: &serde_json::Value,
|
||||
source_range: SourceRange,
|
||||
) -> Result<f64, KclError> {
|
||||
pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange) -> Result<f64, KclError> {
|
||||
if let serde_json::Value::Number(n) = &j {
|
||||
n.as_f64().ok_or_else(|| {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
|
||||
@ -44,6 +44,11 @@ impl StdLibFnArg {
|
||||
get_type_string_from_schema(&self.schema)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_autocomplete_string(&self) -> Result<String> {
|
||||
get_autocomplete_string_from_schema(&self.schema)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn description(&self) -> Option<String> {
|
||||
get_description_string_from_schema(&self.schema)
|
||||
@ -93,9 +98,24 @@ pub trait StdLibFn {
|
||||
deprecated: self.deprecated(),
|
||||
})
|
||||
}
|
||||
|
||||
fn fn_signature(&self) -> String {
|
||||
let mut signature = String::new();
|
||||
signature.push_str(&format!("{}(", self.name()));
|
||||
for (i, arg) in self.args().iter().enumerate() {
|
||||
if i > 0 {
|
||||
signature.push_str(", ");
|
||||
}
|
||||
signature.push_str(&format!("{}: {}", arg.name, arg.type_));
|
||||
}
|
||||
signature.push_str(") -> ");
|
||||
signature.push_str(&self.return_value().type_);
|
||||
|
||||
signature
|
||||
}
|
||||
}
|
||||
|
||||
fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Option<String> {
|
||||
pub fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Option<String> {
|
||||
if let schemars::schema::Schema::Object(o) = schema {
|
||||
if let Some(metadata) = &o.metadata {
|
||||
if let Some(description) = &metadata.description {
|
||||
@ -107,7 +127,7 @@ fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Opti
|
||||
None
|
||||
}
|
||||
|
||||
fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(String, bool)> {
|
||||
pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(String, bool)> {
|
||||
match schema {
|
||||
schemars::schema::Schema::Object(o) => {
|
||||
if let Some(format) = &o.format {
|
||||
@ -147,15 +167,9 @@ fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(Str
|
||||
if let Some(array_val) = &o.array {
|
||||
if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items {
|
||||
// Let's print out the object's properties.
|
||||
return Ok((
|
||||
format!("[{}]", get_type_string_from_schema(items)?.0),
|
||||
false,
|
||||
));
|
||||
return Ok((format!("[{}]", get_type_string_from_schema(items)?.0), false));
|
||||
} else if let Some(items) = &array_val.contains {
|
||||
return Ok((
|
||||
format!("[{}]", get_type_string_from_schema(items)?.0),
|
||||
false,
|
||||
));
|
||||
return Ok((format!("[{}]", get_type_string_from_schema(items)?.0), false));
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,3 +207,78 @@ fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(Str
|
||||
schemars::schema::Schema::Bool(_) => Ok((Primitive::Bool.to_string(), false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) -> Result<String> {
|
||||
match schema {
|
||||
schemars::schema::Schema::Object(o) => {
|
||||
if let Some(format) = &o.format {
|
||||
if format == "uuid" {
|
||||
return Ok(Primitive::Uuid.to_string());
|
||||
} else if format == "double" || format == "uint" {
|
||||
return Ok(Primitive::Number.to_string());
|
||||
} else {
|
||||
anyhow::bail!("unknown format: {}", format);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(obj_val) = &o.object {
|
||||
let mut fn_docs = String::new();
|
||||
fn_docs.push_str("{\n");
|
||||
// Let's print out the object's properties.
|
||||
for (prop_name, prop) in obj_val.properties.iter() {
|
||||
if prop_name.starts_with('_') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(description) = get_description_string_from_schema(prop) {
|
||||
fn_docs.push_str(&format!("\t// {}\n", description));
|
||||
}
|
||||
fn_docs.push_str(&format!(
|
||||
"\t\"{}\": {},\n",
|
||||
prop_name,
|
||||
get_autocomplete_string_from_schema(prop)?,
|
||||
));
|
||||
}
|
||||
|
||||
fn_docs.push('}');
|
||||
|
||||
return Ok(fn_docs);
|
||||
}
|
||||
|
||||
if let Some(array_val) = &o.array {
|
||||
if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items {
|
||||
// Let's print out the object's properties.
|
||||
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
|
||||
} else if let Some(items) = &array_val.contains {
|
||||
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(subschemas) = &o.subschemas {
|
||||
let mut fn_docs = String::new();
|
||||
if let Some(items) = &subschemas.one_of {
|
||||
if let Some(item) = items.iter().next() {
|
||||
// Let's print out the object's properties.
|
||||
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
|
||||
}
|
||||
} else if let Some(items) = &subschemas.any_of {
|
||||
if let Some(item) = items.iter().next() {
|
||||
// Let's print out the object's properties.
|
||||
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
|
||||
}
|
||||
|
||||
return Ok(fn_docs);
|
||||
}
|
||||
|
||||
if let Some(schemars::schema::SingleOrVec::Single(_string)) = &o.instance_type {
|
||||
return Ok(Primitive::String.to_string());
|
||||
}
|
||||
|
||||
anyhow::bail!("unknown type: {:#?}", o)
|
||||
}
|
||||
schemars::schema::Schema::Bool(_) => Ok(Primitive::Bool.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,10 @@ use crate::errors::{KclError, KclErrorDetails};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EngineConnection {
|
||||
tcp_write:
|
||||
futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>,
|
||||
tcp_write: futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>,
|
||||
tcp_read_handle: tokio::task::JoinHandle<Result<()>>,
|
||||
export_notifier: Arc<tokio::sync::Notify>,
|
||||
snapshot_notifier: Arc<tokio::sync::Notify>,
|
||||
}
|
||||
|
||||
impl Drop for EngineConnection {
|
||||
@ -42,7 +42,7 @@ impl TcpRead {
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(ws: reqwest::Upgraded, export_dir: &str) -> Result<EngineConnection> {
|
||||
pub async fn new(ws: reqwest::Upgraded, export_dir: &str, snapshot_file: &str) -> Result<EngineConnection> {
|
||||
// Make sure the export directory exists and that it is a directory.
|
||||
let export_dir = std::path::Path::new(export_dir).to_owned();
|
||||
if !export_dir.exists() {
|
||||
@ -50,10 +50,7 @@ impl EngineConnection {
|
||||
}
|
||||
// Make sure it is a directory.
|
||||
if !export_dir.is_dir() {
|
||||
anyhow::bail!(
|
||||
"Export directory is not a directory: {}",
|
||||
export_dir.display()
|
||||
);
|
||||
anyhow::bail!("Export directory is not a directory: {}", export_dir.display());
|
||||
}
|
||||
|
||||
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
@ -70,15 +67,22 @@ impl EngineConnection {
|
||||
let export_notifier = Arc::new(tokio::sync::Notify::new());
|
||||
let export_notifier_clone = export_notifier.clone();
|
||||
|
||||
let snapshot_notifier = Arc::new(tokio::sync::Notify::new());
|
||||
let snapshot_notifier_clone = snapshot_notifier.clone();
|
||||
|
||||
let snapshot_file = snapshot_file.to_owned();
|
||||
|
||||
let tcp_read_handle = tokio::spawn(async move {
|
||||
// Get Websocket messages from API server
|
||||
loop {
|
||||
match tcp_read.read().await {
|
||||
Ok(ws_resp) => {
|
||||
if !ws_resp.success {
|
||||
println!("got ws errors: {:?}", ws_resp.errors);
|
||||
export_notifier.notify_one();
|
||||
continue;
|
||||
if let Some(success) = ws_resp.success {
|
||||
if !success {
|
||||
println!("got ws errors: {:?}", ws_resp.errors);
|
||||
export_notifier.notify_one();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(msg) = ws_resp.resp {
|
||||
@ -92,7 +96,20 @@ impl EngineConnection {
|
||||
OkWebSocketResponseData::TrickleIce { candidate } => {
|
||||
println!("got trickle ice: {:?}", candidate);
|
||||
}
|
||||
OkWebSocketResponseData::Modeling { .. } => {}
|
||||
OkWebSocketResponseData::Modeling { modeling_response } => {
|
||||
if let kittycad::types::OkModelingCmdResponse::TakeSnapshot { data } =
|
||||
modeling_response
|
||||
{
|
||||
if snapshot_file.is_empty() {
|
||||
println!("Got snapshot, but no snapshot file specified.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the snapshot locally.
|
||||
std::fs::write(&snapshot_file, data.contents)?;
|
||||
snapshot_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
OkWebSocketResponseData::Export { files } => {
|
||||
// Save the files to our export directory.
|
||||
for file in files {
|
||||
@ -120,13 +137,18 @@ impl EngineConnection {
|
||||
tcp_write,
|
||||
tcp_read_handle,
|
||||
export_notifier: export_notifier_clone,
|
||||
snapshot_notifier: snapshot_notifier_clone,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn wait_for_files(&self) {
|
||||
pub async fn wait_for_export(&self) {
|
||||
self.export_notifier.notified().await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_snapshot(&self) {
|
||||
self.snapshot_notifier.notified().await;
|
||||
}
|
||||
|
||||
pub async fn tcp_send(&mut self, msg: WebSocketRequest) -> Result<()> {
|
||||
let msg = serde_json::to_string(&msg)?;
|
||||
self.tcp_write.send(WsMsg::Text(msg)).await?;
|
||||
@ -140,72 +162,14 @@ impl EngineConnection {
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
futures::executor::block_on(
|
||||
self.tcp_send(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id }),
|
||||
)
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to send modeling command: {}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::executor::{execute, BodyType, ProgramMemory, SourceRange};
|
||||
|
||||
pub async fn parse_execute_export(code: &str) -> Result<()> {
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let export_dir_str = "/tmp/";
|
||||
let program = crate::parser::abstract_syntax_tree(&tokens)?;
|
||||
let mut mem: ProgramMemory = Default::default();
|
||||
let mut engine = EngineConnection::new(
|
||||
"wss://api.dev.kittycad.io/ws/modeling/commands?webrtc=false",
|
||||
std::env::var("KITTYCAD_API_TOKEN").unwrap().as_str(),
|
||||
"modeling-app-tests",
|
||||
export_dir_str,
|
||||
)
|
||||
.await?;
|
||||
let _ = execute(program, &mut mem, BodyType::Root, &mut engine)?;
|
||||
// Send an export request to the engine.
|
||||
engine.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
kittycad::types::ModelingCmd::Export {
|
||||
entity_ids: vec![],
|
||||
format: kittycad::types::OutputFormat::Gltf {
|
||||
presentation: kittycad::types::Presentation::Pretty,
|
||||
storage: kittycad::types::Storage::Embedded,
|
||||
},
|
||||
futures::executor::block_on(self.tcp_send(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id })).map_err(
|
||||
|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to send modeling command: {}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
engine.wait_for_files().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_export_file() {
|
||||
let ast = r#"const part001 = startSketchAt([-0.01, -0.06])
|
||||
|> line([0, 20], %)
|
||||
|> line([5, 0], %)
|
||||
|> line([-10, 5], %)
|
||||
|> close(%)
|
||||
|> extrude(4, %)
|
||||
|
||||
show(part001)"#;
|
||||
parse_execute_export(ast).await.unwrap();
|
||||
|
||||
// Ensure we have a file called "part001.gltf" in the export directory.
|
||||
let export_dir = std::path::Path::new("/tmp/");
|
||||
let export_file = export_dir.join("part001.gltf");
|
||||
assert!(export_file.exists());
|
||||
// Make sure the file is not empty.
|
||||
assert!(export_file.metadata().unwrap().len() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,16 +4,20 @@ use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
pub mod conn;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
pub use conn::EngineConnection;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
pub mod conn_wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
pub use conn_wasm::EngineConnection;
|
||||
|
||||
#[cfg(test)]
|
||||
@ -21,6 +25,13 @@ pub mod conn_mock;
|
||||
#[cfg(test)]
|
||||
pub use conn_mock::EngineConnection;
|
||||
|
||||
#[cfg(not(feature = "engine"))]
|
||||
#[cfg(not(test))]
|
||||
pub mod conn_mock;
|
||||
#[cfg(not(feature = "engine"))]
|
||||
#[cfg(not(test))]
|
||||
pub use conn_mock::EngineConnection;
|
||||
|
||||
use crate::executor::SourceRange;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -33,6 +44,7 @@ pub struct EngineManager {
|
||||
impl EngineManager {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub async fn new(manager: conn_wasm::EngineCommandManager) -> EngineManager {
|
||||
EngineManager {
|
||||
|
||||
@ -20,7 +20,7 @@ pub enum KclError {
|
||||
#[error("undefined value: {0:?}")]
|
||||
UndefinedValue(KclErrorDetails),
|
||||
#[error("invalid expression: {0:?}")]
|
||||
InvalidExpression(crate::math_parser::MathExpression),
|
||||
InvalidExpression(KclErrorDetails),
|
||||
#[error("engine: {0:?}")]
|
||||
Engine(KclErrorDetails),
|
||||
}
|
||||
@ -34,6 +34,35 @@ pub struct KclErrorDetails {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl KclError {
|
||||
/// Get the error message, line and column from the error and input code.
|
||||
pub fn get_message_line_column(&self, input: &str) -> (String, Option<usize>, Option<usize>) {
|
||||
let (type_, source_range, message) = match &self {
|
||||
KclError::Syntax(e) => ("syntax", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::Semantic(e) => ("semantic", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::Type(e) => ("type", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::Unimplemented(e) => ("unimplemented", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::Unexpected(e) => ("unexpected", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::ValueAlreadyDefined(e) => ("value already defined", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::UndefinedValue(e) => ("undefined value", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::InvalidExpression(e) => ("invalid expression", e.source_ranges.clone(), e.message.clone()),
|
||||
KclError::Engine(e) => ("engine", e.source_ranges.clone(), e.message.clone()),
|
||||
};
|
||||
|
||||
// Calculate the line and column of the error from the source range.
|
||||
let (line, column) = if let Some(range) = source_range.first() {
|
||||
let line = input[..range.0[0]].lines().count();
|
||||
let column = input[..range.0[0]].lines().last().map(|l| l.len()).unwrap_or_default();
|
||||
|
||||
(Some(line), Some(column))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
(format!("{}: {}", type_, message), line, column)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is different than to_string() in that it will serialize the Error
|
||||
/// the struct as JSON so we can deserialize it on the js side.
|
||||
impl From<KclError> for String {
|
||||
|
||||
@ -30,12 +30,7 @@ impl ProgramMemory {
|
||||
}
|
||||
|
||||
/// Add to the program memory.
|
||||
pub fn add(
|
||||
&mut self,
|
||||
key: &str,
|
||||
value: MemoryItem,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(), KclError> {
|
||||
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
||||
if self.root.get(key).is_some() {
|
||||
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
||||
message: format!("Cannot redefine {}", key),
|
||||
@ -169,12 +164,7 @@ impl MemoryItem {
|
||||
memory: &ProgramMemory,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<Option<ProgramReturn>, KclError> {
|
||||
if let MemoryItem::Function {
|
||||
func,
|
||||
expression,
|
||||
meta,
|
||||
} = self
|
||||
{
|
||||
if let MemoryItem::Function { func, expression, meta } = self {
|
||||
if let Some(func) = func {
|
||||
func(args, memory, expression, meta, engine)
|
||||
} else {
|
||||
@ -225,10 +215,7 @@ impl SketchGroup {
|
||||
if self.start.name == name {
|
||||
Some(&self.start)
|
||||
} else {
|
||||
self.value
|
||||
.iter()
|
||||
.find(|p| p.get_name() == name)
|
||||
.map(|p| p.get_base())
|
||||
self.value.iter().find(|p| p.get_name() == name).map(|p| p.get_base())
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,12 +298,24 @@ impl From<[f64; 2]> for Point2d {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[f64; 2]> for Point2d {
|
||||
fn from(p: &[f64; 2]) -> Self {
|
||||
Self { x: p[0], y: p[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point2d> for [f64; 2] {
|
||||
fn from(p: Point2d) -> Self {
|
||||
[p.x, p.y]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point2d> for kittycad::types::Point2D {
|
||||
fn from(p: Point2d) -> Self {
|
||||
Self { x: p.x, y: p.y }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct Point3d {
|
||||
@ -523,8 +522,7 @@ pub fn execute(
|
||||
match arg {
|
||||
Value::Literal(literal) => args.push(literal.into()),
|
||||
Value::Identifier(identifier) => {
|
||||
let memory_item =
|
||||
memory.get(&identifier.name, identifier.into())?;
|
||||
let memory_item = memory.get(&identifier.name, identifier.into())?;
|
||||
args.push(memory_item.clone());
|
||||
}
|
||||
// We do nothing for the rest.
|
||||
@ -539,8 +537,7 @@ pub fn execute(
|
||||
}));
|
||||
}
|
||||
|
||||
memory.return_ =
|
||||
Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||
} else if let Some(func) = memory.clone().root.get(&fn_name) {
|
||||
func.call_fn(&args, memory, engine)?;
|
||||
} else {
|
||||
@ -566,12 +563,7 @@ pub fn execute(
|
||||
memory.add(&var_name, value.clone(), source_range)?;
|
||||
}
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
let result = binary_expression.get_result(
|
||||
memory,
|
||||
&mut pipe_info,
|
||||
&stdlib,
|
||||
engine,
|
||||
)?;
|
||||
let result = binary_expression.get_result(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
@ -608,41 +600,28 @@ pub fn execute(
|
||||
)?;
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
let result =
|
||||
call_expression.execute(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
let result = call_expression.execute(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::PipeExpression(pipe_expression) => {
|
||||
let result = pipe_expression.get_result(
|
||||
memory,
|
||||
&mut pipe_info,
|
||||
&stdlib,
|
||||
engine,
|
||||
)?;
|
||||
let result = pipe_expression.get_result(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::PipeSubstitution(pipe_substitution) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("pipe substitution not implemented for declaration of variable {}", var_name),
|
||||
message: format!(
|
||||
"pipe substitution not implemented for declaration of variable {}",
|
||||
var_name
|
||||
),
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
}));
|
||||
}
|
||||
Value::ArrayExpression(array_expression) => {
|
||||
let result = array_expression.execute(
|
||||
memory,
|
||||
&mut pipe_info,
|
||||
&stdlib,
|
||||
engine,
|
||||
)?;
|
||||
let result = array_expression.execute(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::ObjectExpression(object_expression) => {
|
||||
let result = object_expression.execute(
|
||||
memory,
|
||||
&mut pipe_info,
|
||||
&stdlib,
|
||||
engine,
|
||||
)?;
|
||||
let result = object_expression.execute(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::MemberExpression(member_expression) => {
|
||||
@ -650,12 +629,7 @@ pub fn execute(
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::UnaryExpression(unary_expression) => {
|
||||
let result = unary_expression.get_result(
|
||||
memory,
|
||||
&mut pipe_info,
|
||||
&stdlib,
|
||||
engine,
|
||||
)?;
|
||||
let result = unary_expression.get_result(memory, &mut pipe_info, &stdlib, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
}
|
||||
@ -680,9 +654,10 @@ pub fn execute(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let program = crate::parser::abstract_syntax_tree(&tokens)?;
|
||||
@ -730,23 +705,13 @@ show(part001)"#,
|
||||
let memory = parse_execute(&ast_fn("-1")).await.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::json!(1.0 + 2.0f64.sqrt()),
|
||||
memory
|
||||
.root
|
||||
.get("intersect")
|
||||
.unwrap()
|
||||
.get_json_value()
|
||||
.unwrap()
|
||||
memory.root.get("intersect").unwrap().get_json_value().unwrap()
|
||||
);
|
||||
|
||||
let memory = parse_execute(&ast_fn("0")).await.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::json!(1.0000000000000002),
|
||||
memory
|
||||
.root
|
||||
.get("intersect")
|
||||
.unwrap()
|
||||
.get_json_value()
|
||||
.unwrap()
|
||||
memory.root.get("intersect").unwrap().get_json_value().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pub mod abstract_syntax_tree_types;
|
||||
mod docs;
|
||||
pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod errors;
|
||||
pub mod executor;
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::abstract_syntax_tree_types::{
|
||||
BinaryExpression, BinaryPart, CallExpression, Identifier, Literal,
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::{BinaryExpression, BinaryPart, CallExpression, Identifier, Literal, ValueMeta},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
parser::{find_closing_brace, is_not_code_token, make_call_expression},
|
||||
tokeniser::{Token, TokenType},
|
||||
};
|
||||
use crate::errors::{KclError, KclErrorDetails};
|
||||
use crate::parser::{find_closing_brace, is_not_code_token, make_call_expression};
|
||||
use crate::tokeniser::{Token, TokenType};
|
||||
|
||||
pub fn precedence(operator: &str) -> u8 {
|
||||
// might be useful for reference to make it match
|
||||
@ -182,6 +183,8 @@ pub struct ParenthesisToken {
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
crate::abstract_syntax_tree_types::impl_value_meta!(ParenthesisToken);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -195,10 +198,12 @@ pub struct ExtendedBinaryExpression {
|
||||
pub end_extended: Option<usize>,
|
||||
}
|
||||
|
||||
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedBinaryExpression);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct ExntendedLiteral {
|
||||
pub struct ExtendedLiteral {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub value: serde_json::Value,
|
||||
@ -207,11 +212,13 @@ pub struct ExntendedLiteral {
|
||||
pub end_extended: Option<usize>,
|
||||
}
|
||||
|
||||
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedLiteral);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum MathExpression {
|
||||
ExntendedLiteral(Box<ExntendedLiteral>),
|
||||
ExtendedLiteral(Box<ExtendedLiteral>),
|
||||
Identifier(Box<Identifier>),
|
||||
CallExpression(Box<CallExpression>),
|
||||
BinaryExpression(Box<BinaryExpression>),
|
||||
@ -219,6 +226,30 @@ pub enum MathExpression {
|
||||
ParenthesisToken(Box<ParenthesisToken>),
|
||||
}
|
||||
|
||||
impl MathExpression {
|
||||
pub fn start(&self) -> usize {
|
||||
match self {
|
||||
MathExpression::ExtendedLiteral(literal) => literal.start(),
|
||||
MathExpression::Identifier(identifier) => identifier.start(),
|
||||
MathExpression::CallExpression(call_expression) => call_expression.start(),
|
||||
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
|
||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
match self {
|
||||
MathExpression::ExtendedLiteral(literal) => literal.end(),
|
||||
MathExpression::Identifier(identifier) => identifier.end(),
|
||||
MathExpression::CallExpression(call_expression) => call_expression.end(),
|
||||
MathExpression::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
|
||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tree(
|
||||
reverse_polish_notation_tokens: &[Token],
|
||||
stack: Vec<MathExpression>,
|
||||
@ -241,80 +272,76 @@ fn build_tree(
|
||||
}),
|
||||
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(a.clone()));
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange([a.start(), a.end()])],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}
|
||||
};
|
||||
}
|
||||
let current_token = &reverse_polish_notation_tokens[0];
|
||||
if current_token.token_type == TokenType::Number
|
||||
|| current_token.token_type == TokenType::String
|
||||
{
|
||||
if current_token.token_type == TokenType::Number || current_token.token_type == TokenType::String {
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::ExntendedLiteral(Box::new(
|
||||
ExntendedLiteral {
|
||||
value: if current_token.token_type == TokenType::Number {
|
||||
if let Ok(value) = current_token.value.parse::<i64>() {
|
||||
serde_json::Value::Number(value.into())
|
||||
} else if let Ok(value) = current_token.value.parse::<f64>() {
|
||||
if let Some(n) = serde_json::Number::from_f64(value) {
|
||||
serde_json::Value::Number(n)
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("Invalid float: {}", current_token.value),
|
||||
}));
|
||||
}
|
||||
new_stack.push(MathExpression::ExtendedLiteral(Box::new(ExtendedLiteral {
|
||||
value: if current_token.token_type == TokenType::Number {
|
||||
if let Ok(value) = current_token.value.parse::<i64>() {
|
||||
serde_json::Value::Number(value.into())
|
||||
} else if let Ok(value) = current_token.value.parse::<f64>() {
|
||||
if let Some(n) = serde_json::Number::from_f64(value) {
|
||||
serde_json::Value::Number(n)
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("Invalid integer: {}", current_token.value),
|
||||
message: format!("Invalid float: {}", current_token.value),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
let mut str_val = current_token.value.clone();
|
||||
str_val.remove(0);
|
||||
str_val.pop();
|
||||
serde_json::Value::String(str_val)
|
||||
},
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
raw: current_token.value.clone(),
|
||||
end_extended: None,
|
||||
start_extended: None,
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("Invalid integer: {}", current_token.value),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
let mut str_val = current_token.value.clone();
|
||||
str_val.remove(0);
|
||||
str_val.pop();
|
||||
serde_json::Value::String(str_val)
|
||||
},
|
||||
)));
|
||||
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||
} else if current_token.token_type == TokenType::Word {
|
||||
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
||||
&& reverse_polish_notation_tokens[1].value == "("
|
||||
{
|
||||
let closing_brace = find_closing_brace(reverse_polish_notation_tokens, 1, 0, "")?;
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::CallExpression(Box::new(
|
||||
make_call_expression(reverse_polish_notation_tokens, 0)?.expression,
|
||||
)));
|
||||
return build_tree(
|
||||
&reverse_polish_notation_tokens[closing_brace + 1..],
|
||||
new_stack,
|
||||
);
|
||||
}
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||
name: current_token.value.clone(),
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
raw: current_token.value.clone(),
|
||||
end_extended: None,
|
||||
start_extended: None,
|
||||
})));
|
||||
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||
} else if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::ParenthesisToken(Box::new(
|
||||
ParenthesisToken {
|
||||
value: "(".to_string(),
|
||||
} else if current_token.token_type == TokenType::Word {
|
||||
if reverse_polish_notation_tokens.len() > 1 {
|
||||
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
||||
&& reverse_polish_notation_tokens[1].value == "("
|
||||
{
|
||||
let closing_brace = find_closing_brace(reverse_polish_notation_tokens, 1, 0, "")?;
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::CallExpression(Box::new(
|
||||
make_call_expression(reverse_polish_notation_tokens, 0)?.expression,
|
||||
)));
|
||||
return build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
|
||||
}
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||
name: current_token.value.clone(),
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
token_type: MathTokenType::Parenthesis,
|
||||
},
|
||||
)));
|
||||
})));
|
||||
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||
}
|
||||
} else if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::ParenthesisToken(Box::new(ParenthesisToken {
|
||||
value: "(".to_string(),
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
token_type: MathTokenType::Parenthesis,
|
||||
})));
|
||||
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||
} else if current_token.token_type == TokenType::Brace && current_token.value == ")" {
|
||||
let inner_node: MathExpression = match &stack[stack.len() - 1] {
|
||||
@ -340,14 +367,22 @@ fn build_tree(
|
||||
end_extended: None,
|
||||
}))
|
||||
}
|
||||
MathExpression::ExntendedLiteral(literal) => {
|
||||
MathExpression::ExntendedLiteral(literal.clone())
|
||||
MathExpression::ExtendedLiteral(literal) => MathExpression::ExtendedLiteral(literal.clone()),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}
|
||||
a => return Err(KclError::InvalidExpression(a.clone())),
|
||||
};
|
||||
let paran = match &stack[stack.len() - 2] {
|
||||
MathExpression::ParenthesisToken(paran) => paran.clone(),
|
||||
a => return Err(KclError::InvalidExpression(a.clone())),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let expression = match inner_node {
|
||||
MathExpression::ExtendedBinaryExpression(bin_exp) => {
|
||||
@ -372,22 +407,33 @@ fn build_tree(
|
||||
end_extended: Some(current_token.end),
|
||||
}))
|
||||
}
|
||||
MathExpression::ExntendedLiteral(literal) => {
|
||||
MathExpression::ExntendedLiteral(Box::new(ExntendedLiteral {
|
||||
value: literal.value.clone(),
|
||||
start: literal.start,
|
||||
end: literal.end,
|
||||
raw: literal.raw.clone(),
|
||||
end_extended: Some(current_token.end),
|
||||
start_extended: Some(paran.start),
|
||||
MathExpression::ExtendedLiteral(literal) => MathExpression::ExtendedLiteral(Box::new(ExtendedLiteral {
|
||||
value: literal.value.clone(),
|
||||
start: literal.start,
|
||||
end: literal.end,
|
||||
raw: literal.raw.clone(),
|
||||
end_extended: Some(current_token.end),
|
||||
start_extended: Some(paran.start),
|
||||
})),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}
|
||||
a => return Err(KclError::InvalidExpression(a.clone())),
|
||||
};
|
||||
let mut new_stack = stack[0..stack.len() - 2].to_vec();
|
||||
new_stack.push(expression);
|
||||
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||
}
|
||||
|
||||
if stack.len() < 2 {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "unexpected end of expression".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
let left: (BinaryPart, usize) = match &stack[stack.len() - 2] {
|
||||
MathExpression::ExtendedBinaryExpression(bin_exp) => (
|
||||
BinaryPart::BinaryExpression(Box::new(BinaryExpression {
|
||||
@ -399,7 +445,7 @@ fn build_tree(
|
||||
})),
|
||||
bin_exp.start_extended.unwrap_or(bin_exp.start),
|
||||
),
|
||||
MathExpression::ExntendedLiteral(lit) => (
|
||||
MathExpression::ExtendedLiteral(lit) => (
|
||||
BinaryPart::Literal(Box::new(Literal {
|
||||
value: lit.value.clone(),
|
||||
start: lit.start,
|
||||
@ -409,13 +455,14 @@ fn build_tree(
|
||||
lit.start_extended.unwrap_or(lit.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),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}
|
||||
MathExpression::BinaryExpression(bin_exp) => {
|
||||
(BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.start)
|
||||
}
|
||||
a => return Err(KclError::InvalidExpression(a.clone())),
|
||||
};
|
||||
let right = match &stack[stack.len() - 1] {
|
||||
MathExpression::ExtendedBinaryExpression(bin_exp) => (
|
||||
@ -428,7 +475,7 @@ fn build_tree(
|
||||
})),
|
||||
bin_exp.end_extended.unwrap_or(bin_exp.end),
|
||||
),
|
||||
MathExpression::ExntendedLiteral(lit) => (
|
||||
MathExpression::ExtendedLiteral(lit) => (
|
||||
BinaryPart::Literal(Box::new(Literal {
|
||||
value: lit.value.clone(),
|
||||
start: lit.start,
|
||||
@ -438,13 +485,14 @@ fn build_tree(
|
||||
lit.end_extended.unwrap_or(lit.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),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}
|
||||
MathExpression::BinaryExpression(bin_exp) => {
|
||||
(BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.end)
|
||||
}
|
||||
a => return Err(KclError::InvalidExpression(a.clone())),
|
||||
};
|
||||
|
||||
let right_end = match right.0.clone() {
|
||||
@ -458,11 +506,7 @@ fn build_tree(
|
||||
let tree = BinaryExpression {
|
||||
operator: current_token.value.clone(),
|
||||
start: left.1,
|
||||
end: if right.1 > right_end {
|
||||
right.1
|
||||
} else {
|
||||
right_end
|
||||
},
|
||||
end: if right.1 > right_end { right.1 } else { right_end },
|
||||
left: left.0,
|
||||
right: right.0,
|
||||
};
|
||||
@ -510,9 +554,10 @@ pub fn parse_expression(tokens: &[Token]) -> Result<BinaryExpression, KclError>
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_expression() {
|
||||
let tokens = crate::tokeniser::lexer("1 + 2");
|
||||
@ -833,8 +878,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_reverse_polish_notation_complex() {
|
||||
let result =
|
||||
reverse_polish_notation(&crate::tokeniser::lexer("1 + 2 * 3"), &[], &[]).unwrap();
|
||||
let result = reverse_polish_notation(&crate::tokeniser::lexer("1 + 2 * 3"), &[], &[]).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
@ -874,8 +918,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_reverse_polish_notation_complex_with_parentheses() {
|
||||
let result =
|
||||
reverse_polish_notation(&crate::tokeniser::lexer("1 * ( 2 + 3 )"), &[], &[]).unwrap();
|
||||
let result = reverse_polish_notation(&crate::tokeniser::lexer("1 * ( 2 + 3 )"), &[], &[]).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use crate::abstract_syntax_tree_types::{
|
||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
|
||||
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject,
|
||||
NoneCodeMeta, NoneCodeNode, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression,
|
||||
PipeSubstitution, Program, ReturnStatement, UnaryExpression, Value, VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::{
|
||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
|
||||
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta,
|
||||
NoneCodeNode, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution, Program,
|
||||
ReturnStatement, UnaryExpression, Value, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
math_parser::parse_expression,
|
||||
tokeniser::{Token, TokenType},
|
||||
};
|
||||
use crate::errors::{KclError, KclErrorDetails};
|
||||
use crate::math_parser::parse_expression;
|
||||
use crate::tokeniser::{Token, TokenType};
|
||||
|
||||
fn make_identifier(tokens: &[Token], index: usize) -> Identifier {
|
||||
let current_token = &tokens[index];
|
||||
@ -79,10 +80,7 @@ fn make_none_code_node(tokens: &[Token], index: usize) -> (Option<NoneCodeNode>,
|
||||
find_end_of_non_code_node(tokens, index)
|
||||
};
|
||||
let non_code_tokens = tokens[index..end_index].to_vec();
|
||||
let value = non_code_tokens
|
||||
.iter()
|
||||
.map(|t| t.value.clone())
|
||||
.collect::<String>();
|
||||
let value = non_code_tokens.iter().map(|t| t.value.clone()).collect::<String>();
|
||||
|
||||
let node = NoneCodeNode {
|
||||
start: current_token.start,
|
||||
@ -105,11 +103,7 @@ struct TokenReturnWithNonCode {
|
||||
non_code_node: Option<NoneCodeNode>,
|
||||
}
|
||||
|
||||
fn next_meaningful_token(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
offset: Option<usize>,
|
||||
) -> TokenReturnWithNonCode {
|
||||
fn next_meaningful_token(tokens: &[Token], index: usize, offset: Option<usize>) -> TokenReturnWithNonCode {
|
||||
let new_index = index + offset.unwrap_or(1);
|
||||
let _token = tokens.get(new_index);
|
||||
let token = if let Some(token) = _token {
|
||||
@ -150,11 +144,13 @@ pub fn find_closing_brace(
|
||||
brace_count: usize,
|
||||
search_opening_brace: &str,
|
||||
) -> Result<usize, KclError> {
|
||||
let closing_brace_map: HashMap<&str, &str> = [("(", ")"), ("{", "}"), ("[", "]")]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let current_token = &tokens[index];
|
||||
let closing_brace_map: HashMap<&str, &str> = [("(", ")"), ("{", "}"), ("[", "]")].iter().cloned().collect();
|
||||
let Some(current_token) = tokens.get(index) else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![tokens.last().unwrap().into()],
|
||||
message: "unexpected end".to_string(),
|
||||
}));
|
||||
};
|
||||
let mut search_opening_brace = search_opening_brace;
|
||||
let is_first_call = search_opening_brace.is_empty() && brace_count == 0;
|
||||
if is_first_call {
|
||||
@ -169,11 +165,9 @@ pub fn find_closing_brace(
|
||||
}));
|
||||
}
|
||||
}
|
||||
let found_closing_brace =
|
||||
brace_count == 1 && current_token.value == closing_brace_map[search_opening_brace];
|
||||
let found_closing_brace = brace_count == 1 && current_token.value == closing_brace_map[search_opening_brace];
|
||||
let found_another_opening_brace = current_token.value == search_opening_brace;
|
||||
let found_another_closing_brace =
|
||||
current_token.value == closing_brace_map[search_opening_brace];
|
||||
let found_another_closing_brace = current_token.value == closing_brace_map[search_opening_brace];
|
||||
if found_closing_brace {
|
||||
return Ok(index);
|
||||
}
|
||||
@ -218,9 +212,7 @@ fn find_next_declaration_keyword(tokens: &[Token], index: usize) -> Result<Token
|
||||
});
|
||||
}
|
||||
if let Some(token_val) = next_token.token {
|
||||
if token_val.token_type == TokenType::Word
|
||||
&& (token_val.value == "const" || token_val.value == "fn")
|
||||
{
|
||||
if token_val.token_type == TokenType::Word && (token_val.value == "const" || token_val.value == "fn") {
|
||||
return Ok(TokenReturn {
|
||||
token: Some(token_val),
|
||||
index: next_token.index,
|
||||
@ -275,8 +267,7 @@ fn has_pipe_operator(
|
||||
let current_token = &tokens[index];
|
||||
if current_token.token_type == TokenType::Brace && current_token.value == "{" {
|
||||
let closing_brace_index = find_closing_brace(tokens, index, 0, "")?;
|
||||
let token_after_closing_brace =
|
||||
next_meaningful_token(tokens, closing_brace_index, None);
|
||||
let token_after_closing_brace = next_meaningful_token(tokens, closing_brace_index, None);
|
||||
if let Some(token_after_closing_brace_val) = token_after_closing_brace.token {
|
||||
if token_after_closing_brace_val.token_type == TokenType::Operator
|
||||
&& token_after_closing_brace_val.value == "|>"
|
||||
@ -393,10 +384,7 @@ pub struct MemberExpressionReturn {
|
||||
pub last_index: usize,
|
||||
}
|
||||
|
||||
fn make_member_expression(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<MemberExpressionReturn, KclError> {
|
||||
fn make_member_expression(tokens: &[Token], index: usize) -> Result<MemberExpressionReturn, KclError> {
|
||||
let current_token = tokens[index].clone();
|
||||
let mut keys_info = collect_object_keys(tokens, index, None)?;
|
||||
let last_key = keys_info[keys_info.len() - 1].clone();
|
||||
@ -464,9 +452,7 @@ fn find_end_of_binary_expression(tokens: &[Token], index: usize) -> Result<usize
|
||||
}
|
||||
let maybe_operator = next_meaningful_token(tokens, index, None);
|
||||
if let Some(maybe_operator_token) = maybe_operator.token {
|
||||
if maybe_operator_token.token_type != TokenType::Operator
|
||||
|| maybe_operator_token.value == "|>"
|
||||
{
|
||||
if maybe_operator_token.token_type != TokenType::Operator || maybe_operator_token.value == "|>" {
|
||||
return Ok(index);
|
||||
}
|
||||
let next_right = next_meaningful_token(tokens, maybe_operator.index, None);
|
||||
@ -502,10 +488,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
|
||||
} else {
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!(
|
||||
"expression with token type {:?}",
|
||||
current_token.token_type
|
||||
),
|
||||
message: format!("expression with token type {:?}", current_token.token_type),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -564,9 +547,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
|
||||
last_index: index,
|
||||
});
|
||||
}
|
||||
if current_token.token_type == TokenType::Number
|
||||
|| current_token.token_type == TokenType::String
|
||||
{
|
||||
if current_token.token_type == TokenType::Number || current_token.token_type == TokenType::String {
|
||||
let literal = make_literal(tokens, index)?;
|
||||
return Ok(ValueReturn {
|
||||
value: Value::Literal(Box::new(literal)),
|
||||
@ -576,9 +557,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
|
||||
|
||||
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||
let closing_brace_index = find_closing_brace(tokens, index, 0, "")?;
|
||||
return if let Some(arrow_token) =
|
||||
next_meaningful_token(tokens, closing_brace_index, None).token
|
||||
{
|
||||
return if let Some(arrow_token) = next_meaningful_token(tokens, closing_brace_index, None).token {
|
||||
if arrow_token.token_type == TokenType::Operator && arrow_token.value == "=>" {
|
||||
let function_expression = make_function_expression(tokens, index)?;
|
||||
Ok(ValueReturn {
|
||||
@ -633,16 +612,12 @@ fn make_array_elements(
|
||||
let current_element = make_value(tokens, index)?;
|
||||
let next_token = next_meaningful_token(tokens, current_element.last_index, None);
|
||||
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;
|
||||
if !is_closing_brace && !is_comma {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![next_token_token.clone().into()],
|
||||
message: format!(
|
||||
"Expected a comma or closing brace, found {:?}",
|
||||
next_token_token.value
|
||||
),
|
||||
message: format!("Expected a comma or closing brace, found {:?}", next_token_token.value),
|
||||
}));
|
||||
}
|
||||
let next_call_index = if is_closing_brace {
|
||||
@ -711,10 +686,7 @@ fn make_pipe_body(
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!(
|
||||
"Expected a pipe value, found {:?}",
|
||||
current_token.token_type
|
||||
),
|
||||
message: format!("Expected a pipe value, found {:?}", current_token.token_type),
|
||||
}));
|
||||
}
|
||||
let next_pipe = has_pipe_operator(tokens, index, None)?;
|
||||
@ -730,20 +702,13 @@ fn make_pipe_body(
|
||||
let mut _non_code_meta: NoneCodeMeta;
|
||||
if let Some(node) = next_pipe.non_code_node {
|
||||
_non_code_meta = non_code_meta;
|
||||
_non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_values.len(), node);
|
||||
_non_code_meta.none_code_nodes.insert(previous_values.len(), node);
|
||||
} else {
|
||||
_non_code_meta = non_code_meta;
|
||||
}
|
||||
let mut _previous_values = previous_values;
|
||||
_previous_values.push(value);
|
||||
make_pipe_body(
|
||||
tokens,
|
||||
next_pipe.index,
|
||||
_previous_values,
|
||||
Some(_non_code_meta),
|
||||
)
|
||||
make_pipe_body(tokens, next_pipe.index, _previous_values, Some(_non_code_meta))
|
||||
}
|
||||
|
||||
struct BinaryExpressionReturn {
|
||||
@ -751,10 +716,7 @@ struct BinaryExpressionReturn {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_binary_expression(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<BinaryExpressionReturn, KclError> {
|
||||
fn make_binary_expression(tokens: &[Token], index: usize) -> Result<BinaryExpressionReturn, KclError> {
|
||||
let end_index = find_end_of_binary_expression(tokens, index)?;
|
||||
let expression = parse_expression(&tokens[index..end_index + 1])?;
|
||||
Ok(BinaryExpressionReturn {
|
||||
@ -768,11 +730,7 @@ struct ArgumentsReturn {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_arguments(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
previous_args: Vec<Value>,
|
||||
) -> Result<ArgumentsReturn, KclError> {
|
||||
fn make_arguments(tokens: &[Token], index: usize, previous_args: Vec<Value>) -> Result<ArgumentsReturn, KclError> {
|
||||
let brace_or_comma_token = &tokens[index];
|
||||
let should_finish_recursion =
|
||||
brace_or_comma_token.token_type == TokenType::Brace && brace_or_comma_token.value == ")";
|
||||
@ -788,40 +746,28 @@ fn make_arguments(
|
||||
if let Some(next_brace_or_comma_token) = next_brace_or_comma.token {
|
||||
let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma
|
||||
|| next_brace_or_comma_token.token_type == TokenType::Brace;
|
||||
if argument_token_token.token_type == TokenType::Brace
|
||||
&& argument_token_token.value == "["
|
||||
{
|
||||
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" {
|
||||
let array_expression = make_array_expression(tokens, argument_token.index)?;
|
||||
let next_comma_or_brace_token_index =
|
||||
next_meaningful_token(tokens, array_expression.last_index, None).index;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(Value::ArrayExpression(Box::new(
|
||||
array_expression.expression,
|
||||
)));
|
||||
_previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression)));
|
||||
return make_arguments(tokens, 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 = make_unary_expression(tokens, argument_token.index)?;
|
||||
let next_comma_or_brace_token_index =
|
||||
next_meaningful_token(tokens, unary_expression.last_index, None).index;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(Value::UnaryExpression(Box::new(
|
||||
unary_expression.expression,
|
||||
)));
|
||||
_previous_args.push(Value::UnaryExpression(Box::new(unary_expression.expression)));
|
||||
return make_arguments(tokens, 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 == "{" {
|
||||
let object_expression = make_object_expression(tokens, argument_token.index)?;
|
||||
let next_comma_or_brace_token_index =
|
||||
next_meaningful_token(tokens, object_expression.last_index, None).index;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(Value::ObjectExpression(Box::new(
|
||||
object_expression.expression,
|
||||
)));
|
||||
_previous_args.push(Value::ObjectExpression(Box::new(object_expression.expression)));
|
||||
return make_arguments(tokens, next_comma_or_brace_token_index, _previous_args);
|
||||
}
|
||||
if (argument_token_token.token_type == TokenType::Word
|
||||
@ -833,23 +779,17 @@ fn make_arguments(
|
||||
let next_comma_or_brace_token_index =
|
||||
next_meaningful_token(tokens, binary_expression.last_index, None).index;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(Value::BinaryExpression(Box::new(
|
||||
binary_expression.expression,
|
||||
)));
|
||||
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
|
||||
return make_arguments(tokens, next_comma_or_brace_token_index, _previous_args);
|
||||
}
|
||||
|
||||
if !is_identifier_or_literal {
|
||||
let binary_expression = make_binary_expression(tokens, next_brace_or_comma.index)?;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(Value::BinaryExpression(Box::new(
|
||||
binary_expression.expression,
|
||||
)));
|
||||
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
|
||||
return make_arguments(tokens, binary_expression.last_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 value = Value::PipeSubstitution(Box::new(PipeSubstitution {
|
||||
start: argument_token_token.start,
|
||||
end: argument_token_token.end,
|
||||
@ -864,28 +804,23 @@ fn make_arguments(
|
||||
&& next_brace_or_comma_token.value == "("
|
||||
{
|
||||
let closing_brace = find_closing_brace(tokens, next_brace_or_comma.index, 0, "")?;
|
||||
return if let Some(token_after_closing_brace) =
|
||||
next_meaningful_token(tokens, closing_brace, None).token
|
||||
return if let Some(token_after_closing_brace) = next_meaningful_token(tokens, closing_brace, None).token
|
||||
{
|
||||
if token_after_closing_brace.token_type == TokenType::Operator
|
||||
&& token_after_closing_brace.value != "|>"
|
||||
{
|
||||
let binary_expression =
|
||||
make_binary_expression(tokens, argument_token.index)?;
|
||||
let binary_expression = make_binary_expression(tokens, argument_token.index)?;
|
||||
let next_comma_or_brace_token_index =
|
||||
next_meaningful_token(tokens, binary_expression.last_index, None).index;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(Value::BinaryExpression(Box::new(
|
||||
binary_expression.expression,
|
||||
)));
|
||||
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
|
||||
make_arguments(tokens, next_comma_or_brace_token_index, _previous_args)
|
||||
} else {
|
||||
let call_expression = make_call_expression(tokens, argument_token.index)?;
|
||||
let next_comma_or_brace_token_index =
|
||||
next_meaningful_token(tokens, call_expression.last_index, None).index;
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args
|
||||
.push(Value::CallExpression(Box::new(call_expression.expression)));
|
||||
_previous_args.push(Value::CallExpression(Box::new(call_expression.expression)));
|
||||
make_arguments(tokens, next_comma_or_brace_token_index, _previous_args)
|
||||
}
|
||||
} else {
|
||||
@ -897,8 +832,7 @@ fn make_arguments(
|
||||
}
|
||||
|
||||
if argument_token_token.token_type == TokenType::Word {
|
||||
let identifier =
|
||||
Value::Identifier(Box::new(make_identifier(tokens, argument_token.index)));
|
||||
let identifier = Value::Identifier(Box::new(make_identifier(tokens, argument_token.index)));
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(identifier);
|
||||
return make_arguments(tokens, next_brace_or_comma.index, _previous_args);
|
||||
@ -909,9 +843,7 @@ fn make_arguments(
|
||||
let mut _previous_args = previous_args;
|
||||
_previous_args.push(literal);
|
||||
return make_arguments(tokens, next_brace_or_comma.index, _previous_args);
|
||||
} else if argument_token_token.token_type == TokenType::Brace
|
||||
&& argument_token_token.value == ")"
|
||||
{
|
||||
} else if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == ")" {
|
||||
return make_arguments(tokens, argument_token.index, previous_args);
|
||||
}
|
||||
|
||||
@ -938,10 +870,7 @@ pub struct CallExpressionResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
pub fn make_call_expression(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<CallExpressionResult, KclError> {
|
||||
pub fn make_call_expression(tokens: &[Token], index: usize) -> Result<CallExpressionResult, KclError> {
|
||||
let current_token = tokens[index].clone();
|
||||
let brace_token = next_meaningful_token(tokens, index, None);
|
||||
let callee = make_identifier(tokens, index);
|
||||
@ -1034,27 +963,20 @@ struct VariableDeclarationResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_variable_declaration(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<VariableDeclarationResult, KclError> {
|
||||
fn make_variable_declaration(tokens: &[Token], index: usize) -> Result<VariableDeclarationResult, KclError> {
|
||||
let current_token = tokens[index].clone();
|
||||
let declaration_start_token = next_meaningful_token(tokens, index, None);
|
||||
let variable_declarators_result =
|
||||
make_variable_declarators(tokens, declaration_start_token.index, vec![])?;
|
||||
let variable_declarators_result = make_variable_declarators(tokens, declaration_start_token.index, vec![])?;
|
||||
Ok(VariableDeclarationResult {
|
||||
declaration: VariableDeclaration {
|
||||
start: current_token.start,
|
||||
end: variable_declarators_result.declarations
|
||||
[variable_declarators_result.declarations.len() - 1]
|
||||
.end,
|
||||
kind: if current_token.value == "const" {
|
||||
"const".to_string()
|
||||
} else if current_token.value == "fn" {
|
||||
"fn".to_string()
|
||||
} else {
|
||||
"unkown".to_string()
|
||||
},
|
||||
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
|
||||
kind: VariableKind::from_str(¤t_token.value).map_err(|_| {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "Unexpected token".to_string(),
|
||||
})
|
||||
})?,
|
||||
declarations: variable_declarators_result.declarations,
|
||||
},
|
||||
last_index: variable_declarators_result.last_index,
|
||||
@ -1066,18 +988,12 @@ pub struct ParamsResult {
|
||||
pub last_index: usize,
|
||||
}
|
||||
|
||||
fn make_params(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
previous_params: Vec<Identifier>,
|
||||
) -> Result<ParamsResult, KclError> {
|
||||
fn make_params(tokens: &[Token], index: usize, previous_params: Vec<Identifier>) -> Result<ParamsResult, KclError> {
|
||||
let brace_or_comma_token = &tokens[index];
|
||||
let argument = next_meaningful_token(tokens, index, None);
|
||||
if let Some(argument_token) = argument.token {
|
||||
let should_finish_recursion = (argument_token.token_type == TokenType::Brace
|
||||
&& argument_token.value == ")")
|
||||
|| (brace_or_comma_token.token_type == TokenType::Brace
|
||||
&& brace_or_comma_token.value == ")");
|
||||
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,
|
||||
@ -1102,10 +1018,7 @@ struct UnaryExpressionResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_unary_expression(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<UnaryExpressionResult, KclError> {
|
||||
fn make_unary_expression(tokens: &[Token], index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||
let current_token = &tokens[index];
|
||||
let next_token = next_meaningful_token(tokens, index, None);
|
||||
let argument = make_value(tokens, next_token.index)?;
|
||||
@ -1120,17 +1033,11 @@ fn make_unary_expression(
|
||||
start: current_token.start,
|
||||
end: argument_token.end,
|
||||
argument: match argument.value {
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
BinaryPart::BinaryExpression(binary_expression)
|
||||
}
|
||||
Value::BinaryExpression(binary_expression) => BinaryPart::BinaryExpression(binary_expression),
|
||||
Value::Identifier(identifier) => BinaryPart::Identifier(identifier),
|
||||
Value::Literal(literal) => BinaryPart::Literal(literal),
|
||||
Value::UnaryExpression(unary_expression) => {
|
||||
BinaryPart::UnaryExpression(unary_expression)
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
BinaryPart::CallExpression(call_expression)
|
||||
}
|
||||
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
|
||||
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
|
||||
_ => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
@ -1149,10 +1056,7 @@ struct ExpressionStatementResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_expression_statement(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<ExpressionStatementResult, KclError> {
|
||||
fn make_expression_statement(tokens: &[Token], index: usize) -> Result<ExpressionStatementResult, KclError> {
|
||||
let current_token = &tokens[index];
|
||||
let next = next_meaningful_token(tokens, index, None);
|
||||
if let Some(next_token) = &next.token {
|
||||
@ -1252,10 +1156,7 @@ struct ObjectExpressionResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_object_expression(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<ObjectExpressionResult, KclError> {
|
||||
fn make_object_expression(tokens: &[Token], index: usize) -> Result<ObjectExpressionResult, KclError> {
|
||||
let opening_brace_token = &tokens[index];
|
||||
let first_property_token = next_meaningful_token(tokens, index, None);
|
||||
let object_properties = make_object_properties(tokens, first_property_token.index, vec![])?;
|
||||
@ -1274,10 +1175,7 @@ struct ReturnStatementResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_return_statement(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<ReturnStatementResult, KclError> {
|
||||
fn make_return_statement(tokens: &[Token], index: usize) -> Result<ReturnStatementResult, KclError> {
|
||||
let current_token = &tokens[index];
|
||||
let next_token = next_meaningful_token(tokens, index, None);
|
||||
let val = make_value(tokens, next_token.index)?;
|
||||
@ -1329,9 +1227,7 @@ fn make_body(
|
||||
if previous_body.is_empty() {
|
||||
non_code_meta.start = next_token.non_code_node;
|
||||
} else {
|
||||
non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
}
|
||||
return make_body(tokens, next_token.index, previous_body, non_code_meta);
|
||||
@ -1339,18 +1235,14 @@ fn make_body(
|
||||
|
||||
let next = next_meaningful_token(tokens, token_index, None);
|
||||
if let Some(node) = &next.non_code_node {
|
||||
non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
|
||||
if token.token_type == TokenType::Word && (token.value == *"const" || token.value == "fn") {
|
||||
let declaration = make_variable_declaration(tokens, token_index)?;
|
||||
let next_thing = next_meaningful_token(tokens, declaration.last_index, None);
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let mut _previous_body = previous_body;
|
||||
_previous_body.push(BodyItem::VariableDeclaration(VariableDeclaration {
|
||||
@ -1371,9 +1263,7 @@ fn make_body(
|
||||
let statement = make_return_statement(tokens, token_index)?;
|
||||
let next_thing = next_meaningful_token(tokens, statement.last_index, None);
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let mut _previous_body = previous_body;
|
||||
_previous_body.push(BodyItem::ReturnStatement(ReturnStatement {
|
||||
@ -1390,16 +1280,11 @@ fn make_body(
|
||||
}
|
||||
|
||||
if let Some(next_token) = next.token {
|
||||
if token.token_type == TokenType::Word
|
||||
&& next_token.token_type == TokenType::Brace
|
||||
&& next_token.value == "("
|
||||
{
|
||||
if token.token_type == TokenType::Word && next_token.token_type == TokenType::Brace && next_token.value == "(" {
|
||||
let expression = make_expression_statement(tokens, token_index)?;
|
||||
let next_thing = next_meaningful_token(tokens, expression.last_index, None);
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let mut _previous_body = previous_body;
|
||||
_previous_body.push(BodyItem::ExpressionStatement(ExpressionStatement {
|
||||
@ -1422,9 +1307,7 @@ fn make_body(
|
||||
&& next_thing_token.token_type == TokenType::Operator
|
||||
{
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta
|
||||
.none_code_nodes
|
||||
.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let expression = make_expression_statement(tokens, token_index)?;
|
||||
let mut _previous_body = previous_body;
|
||||
@ -1492,10 +1375,7 @@ struct FunctionExpressionResult {
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
fn make_function_expression(
|
||||
tokens: &[Token],
|
||||
index: usize,
|
||||
) -> Result<FunctionExpressionResult, KclError> {
|
||||
fn make_function_expression(tokens: &[Token], index: usize) -> Result<FunctionExpressionResult, KclError> {
|
||||
let current_token = &tokens[index];
|
||||
let closing_brace_index = find_closing_brace(tokens, index, 0, "")?;
|
||||
let arrow_token = next_meaningful_token(tokens, closing_brace_index, None);
|
||||
@ -1538,9 +1418,10 @@ pub fn abstract_syntax_tree(tokens: &[Token]) -> Result<Program, KclError> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_make_identifier() {
|
||||
let tokens = crate::tokeniser::lexer("a");
|
||||
@ -1701,8 +1582,7 @@ const key = 'c'"#,
|
||||
Some(NoneCodeNode {
|
||||
start: 106,
|
||||
end: 166,
|
||||
value: " /* this is\n a comment\n spanning a few lines */\n "
|
||||
.to_string(),
|
||||
value: " /* this is\n a comment\n spanning a few lines */\n ".to_string(),
|
||||
}),
|
||||
59,
|
||||
);
|
||||
@ -2226,8 +2106,7 @@ const key = 'c'"#,
|
||||
4
|
||||
);
|
||||
|
||||
let handles_non_zero_index =
|
||||
"(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)";
|
||||
let handles_non_zero_index = "(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)";
|
||||
assert_eq!(
|
||||
find_closing_brace(&crate::tokeniser::lexer(handles_non_zero_index), 2, 0, "").unwrap(),
|
||||
4
|
||||
@ -2279,10 +2158,7 @@ const key = 'c'"#,
|
||||
);
|
||||
assert_eq!(
|
||||
find_next_declaration_keyword(&tokens, 4).unwrap(),
|
||||
TokenReturn {
|
||||
token: None,
|
||||
index: 92,
|
||||
}
|
||||
TokenReturn { token: None, index: 92 }
|
||||
);
|
||||
|
||||
let tokens = crate::tokeniser::lexer(
|
||||
@ -2304,10 +2180,7 @@ const newVar = myVar + 1
|
||||
);
|
||||
assert_eq!(
|
||||
find_next_declaration_keyword(&tokens, 14).unwrap(),
|
||||
TokenReturn {
|
||||
token: None,
|
||||
index: 19,
|
||||
}
|
||||
TokenReturn { token: None, index: 19 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -2401,10 +2274,7 @@ const yo = myFunc(9()
|
||||
} |> rx(90, %)
|
||||
show(mySk1)"#;
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let token_with_my_path_index = tokens
|
||||
.iter()
|
||||
.position(|token| token.value == "myPath")
|
||||
.unwrap();
|
||||
let token_with_my_path_index = tokens.iter().position(|token| token.value == "myPath").unwrap();
|
||||
// loop through getting the token and it's index
|
||||
let token_with_line_to_index_for_var_dec_index = tokens
|
||||
.iter()
|
||||
@ -2613,7 +2483,7 @@ show(mySk1)"#;
|
||||
|> close(%)"#,
|
||||
);
|
||||
let result = make_variable_declaration(&tokens, 0).unwrap();
|
||||
assert_eq!(result.declaration.kind, "const");
|
||||
assert_eq!(result.declaration.kind.to_string(), "const");
|
||||
assert_eq!(result.declaration.declarations.len(), 1);
|
||||
assert_eq!(result.declaration.declarations[0].id.name, "yo");
|
||||
let declaration = result.declaration.declarations[0].clone();
|
||||
|
||||
@ -2,18 +2,14 @@
|
||||
//! The inverse of parsing (which generates an AST from the source code)
|
||||
|
||||
use crate::abstract_syntax_tree_types::{
|
||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, FunctionExpression,
|
||||
Literal, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression, PipeExpression,
|
||||
Program, UnaryExpression, Value,
|
||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, FunctionExpression, Literal,
|
||||
LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression, PipeExpression, Program, UnaryExpression,
|
||||
Value,
|
||||
};
|
||||
|
||||
fn recast_literal(literal: Literal) -> String {
|
||||
if let serde_json::Value::String(value) = literal.value {
|
||||
let quote = if literal.raw.trim().starts_with('"') {
|
||||
'"'
|
||||
} else {
|
||||
'\''
|
||||
};
|
||||
let quote = if literal.raw.trim().starts_with('"') { '"' } else { '\'' };
|
||||
format!("{}{}{}", quote, value, quote)
|
||||
} else {
|
||||
literal.value.to_string()
|
||||
@ -39,16 +35,13 @@ fn recast_binary_expression(expression: BinaryExpression) -> String {
|
||||
|
||||
let should_wrap_right = match expression.right.clone() {
|
||||
BinaryPart::BinaryExpression(bin_exp) => {
|
||||
precedence(&expression.operator) > precedence(&bin_exp.operator)
|
||||
|| expression.operator == "-"
|
||||
precedence(&expression.operator) > precedence(&bin_exp.operator) || expression.operator == "-"
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let should_wrap_left = match expression.left.clone() {
|
||||
BinaryPart::BinaryExpression(bin_exp) => {
|
||||
precedence(&expression.operator) > precedence(&bin_exp.operator)
|
||||
}
|
||||
BinaryPart::BinaryExpression(bin_exp) => precedence(&expression.operator) > precedence(&bin_exp.operator),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@ -64,12 +57,8 @@ fn recast_binary_part(part: BinaryPart) -> String {
|
||||
match part {
|
||||
BinaryPart::Literal(literal) => recast_literal(*literal),
|
||||
BinaryPart::Identifier(identifier) => identifier.name,
|
||||
BinaryPart::BinaryExpression(binary_expression) => {
|
||||
recast_binary_expression(*binary_expression)
|
||||
}
|
||||
BinaryPart::CallExpression(call_expression) => {
|
||||
recast_call_expression(&call_expression, "", false)
|
||||
}
|
||||
BinaryPart::BinaryExpression(binary_expression) => recast_binary_expression(*binary_expression),
|
||||
BinaryPart::CallExpression(call_expression) => recast_call_expression(&call_expression, "", false),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@ -79,15 +68,11 @@ fn recast_value(node: Value, _indentation: String, is_in_pipe_expression: bool)
|
||||
match node {
|
||||
Value::BinaryExpression(bin_exp) => recast_binary_expression(*bin_exp),
|
||||
Value::ArrayExpression(array_exp) => recast_array_expression(&array_exp, &indentation),
|
||||
Value::ObjectExpression(ref obj_exp) => {
|
||||
recast_object_expression(obj_exp, &indentation, is_in_pipe_expression)
|
||||
}
|
||||
Value::ObjectExpression(ref obj_exp) => recast_object_expression(obj_exp, &indentation, is_in_pipe_expression),
|
||||
Value::MemberExpression(mem_exp) => recast_member_expression(*mem_exp),
|
||||
Value::Literal(literal) => recast_literal(*literal),
|
||||
Value::FunctionExpression(func_exp) => recast_function(*func_exp),
|
||||
Value::CallExpression(call_exp) => {
|
||||
recast_call_expression(&call_exp, &indentation, is_in_pipe_expression)
|
||||
}
|
||||
Value::CallExpression(call_exp) => recast_call_expression(&call_exp, &indentation, is_in_pipe_expression),
|
||||
Value::Identifier(ident) => ident.name,
|
||||
Value::PipeExpression(pipe_exp) => recast_pipe_expression(&pipe_exp),
|
||||
Value::UnaryExpression(unary_exp) => recast_unary_expression(*unary_exp),
|
||||
@ -124,11 +109,7 @@ fn recast_array_expression(expression: &ArrayExpression, indentation: &str) -> S
|
||||
}
|
||||
}
|
||||
|
||||
fn recast_object_expression(
|
||||
expression: &ObjectExpression,
|
||||
indentation: &str,
|
||||
is_in_pipe_expression: bool,
|
||||
) -> String {
|
||||
fn recast_object_expression(expression: &ObjectExpression, indentation: &str, is_in_pipe_expression: bool) -> String {
|
||||
let flat_recast = format!(
|
||||
"{{ {} }}",
|
||||
expression
|
||||
@ -157,11 +138,7 @@ fn recast_object_expression(
|
||||
format!(
|
||||
"{}: {}",
|
||||
prop.key.name,
|
||||
recast_value(
|
||||
prop.value.clone(),
|
||||
_indentation.clone(),
|
||||
is_in_pipe_expression
|
||||
)
|
||||
recast_value(prop.value.clone(), _indentation.clone(), is_in_pipe_expression)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
@ -173,11 +150,7 @@ fn recast_object_expression(
|
||||
}
|
||||
}
|
||||
|
||||
fn recast_call_expression(
|
||||
expression: &CallExpression,
|
||||
indentation: &str,
|
||||
is_in_pipe_expression: bool,
|
||||
) -> String {
|
||||
fn recast_call_expression(expression: &CallExpression, indentation: &str, is_in_pipe_expression: bool) -> String {
|
||||
format!(
|
||||
"{}({})",
|
||||
expression.callee.name,
|
||||
@ -199,9 +172,7 @@ fn recast_argument(argument: Value, indentation: &str, is_in_pipe_expression: bo
|
||||
Value::ObjectExpression(object_exp) => {
|
||||
recast_object_expression(&object_exp, indentation, is_in_pipe_expression)
|
||||
}
|
||||
Value::CallExpression(call_exp) => {
|
||||
recast_call_expression(&call_exp, indentation, is_in_pipe_expression)
|
||||
}
|
||||
Value::CallExpression(call_exp) => recast_call_expression(&call_exp, indentation, is_in_pipe_expression),
|
||||
Value::FunctionExpression(function_exp) => recast_function(*function_exp),
|
||||
Value::PipeSubstitution(_) => "%".to_string(),
|
||||
Value::UnaryExpression(unary_exp) => recast_unary_expression(*unary_exp),
|
||||
@ -222,9 +193,7 @@ fn recast_member_expression(expression: MemberExpression) -> String {
|
||||
};
|
||||
|
||||
match expression.object {
|
||||
MemberObject::MemberExpression(member_exp) => {
|
||||
recast_member_expression(*member_exp) + key_str.as_str()
|
||||
}
|
||||
MemberObject::MemberExpression(member_exp) => recast_member_expression(*member_exp) + key_str.as_str(),
|
||||
MemberObject::Identifier(identifier) => identifier.name + key_str.as_str(),
|
||||
}
|
||||
}
|
||||
@ -261,9 +230,7 @@ fn recast_unary_expression(expression: UnaryExpression) -> String {
|
||||
let bin_part_val = match expression.argument {
|
||||
BinaryPart::Literal(literal) => Value::Literal(literal),
|
||||
BinaryPart::Identifier(identifier) => Value::Identifier(identifier),
|
||||
BinaryPart::BinaryExpression(binary_expression) => {
|
||||
Value::BinaryExpression(binary_expression)
|
||||
}
|
||||
BinaryPart::BinaryExpression(binary_expression) => Value::BinaryExpression(binary_expression),
|
||||
BinaryPart::CallExpression(call_expression) => Value::CallExpression(call_expression),
|
||||
BinaryPart::UnaryExpression(unary_expression) => Value::UnaryExpression(unary_expression),
|
||||
};
|
||||
@ -278,23 +245,13 @@ pub fn recast(ast: &Program, indentation: &str, is_with_block: bool) -> String {
|
||||
ast.body
|
||||
.iter()
|
||||
.map(|statement| match statement.clone() {
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
match expression_statement.expression {
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
recast_binary_expression(*binary_expression)
|
||||
}
|
||||
Value::ArrayExpression(array_expression) => {
|
||||
recast_array_expression(&array_expression, "")
|
||||
}
|
||||
Value::ObjectExpression(object_expression) => {
|
||||
recast_object_expression(&object_expression, "", false)
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
recast_call_expression(&call_expression, "", false)
|
||||
}
|
||||
_ => "Expression".to_string(),
|
||||
}
|
||||
}
|
||||
BodyItem::ExpressionStatement(expression_statement) => match expression_statement.expression {
|
||||
Value::BinaryExpression(binary_expression) => recast_binary_expression(*binary_expression),
|
||||
Value::ArrayExpression(array_expression) => recast_array_expression(&array_expression, ""),
|
||||
Value::ObjectExpression(object_expression) => recast_object_expression(&object_expression, "", false),
|
||||
Value::CallExpression(call_expression) => recast_call_expression(&call_expression, "", false),
|
||||
_ => "Expression".to_string(),
|
||||
},
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
|
||||
.declarations
|
||||
.iter()
|
||||
@ -308,22 +265,16 @@ pub fn recast(ast: &Program, indentation: &str, is_with_block: bool) -> String {
|
||||
})
|
||||
.collect::<String>(),
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
format!(
|
||||
"return {}",
|
||||
recast_argument(return_statement.argument, "", false)
|
||||
)
|
||||
format!("return {}", recast_argument(return_statement.argument, "", false))
|
||||
}
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(index, recast_str)| {
|
||||
let is_legit_custom_whitespace_or_comment =
|
||||
|str: String| str != " " && str != "\n" && str != " ";
|
||||
let is_legit_custom_whitespace_or_comment = |str: String| str != " " && str != "\n" && str != " ";
|
||||
|
||||
// determine the value of startString
|
||||
let last_white_space_or_comment = if index > 0 {
|
||||
let tmp = if let Some(non_code_node) =
|
||||
ast.non_code_meta.none_code_nodes.get(&(index - 1))
|
||||
{
|
||||
let tmp = if let Some(non_code_node) = ast.non_code_meta.none_code_nodes.get(&(index - 1)) {
|
||||
non_code_node.value.clone()
|
||||
} else {
|
||||
" ".to_string()
|
||||
@ -333,12 +284,11 @@ pub fn recast(ast: &Program, indentation: &str, is_with_block: bool) -> String {
|
||||
" ".to_string()
|
||||
};
|
||||
// indentation of this line will be covered by the previous if we're using a custom whitespace or comment
|
||||
let mut start_string =
|
||||
if is_legit_custom_whitespace_or_comment(last_white_space_or_comment) {
|
||||
String::new()
|
||||
} else {
|
||||
indentation.to_owned()
|
||||
};
|
||||
let mut start_string = if is_legit_custom_whitespace_or_comment(last_white_space_or_comment) {
|
||||
String::new()
|
||||
} else {
|
||||
indentation.to_owned()
|
||||
};
|
||||
if index == 0 {
|
||||
if let Some(start) = ast.non_code_meta.start.clone() {
|
||||
start_string = start.value;
|
||||
@ -356,13 +306,10 @@ pub fn recast(ast: &Program, indentation: &str, is_with_block: bool) -> String {
|
||||
} else {
|
||||
"\n".to_string()
|
||||
};
|
||||
let mut custom_white_space_or_comment =
|
||||
match ast.non_code_meta.none_code_nodes.get(&index) {
|
||||
Some(custom_white_space_or_comment) => {
|
||||
custom_white_space_or_comment.value.clone()
|
||||
}
|
||||
None => String::new(),
|
||||
};
|
||||
let mut custom_white_space_or_comment = match ast.non_code_meta.none_code_nodes.get(&index) {
|
||||
Some(custom_white_space_or_comment) => custom_white_space_or_comment.value.clone(),
|
||||
None => String::new(),
|
||||
};
|
||||
if !is_legit_custom_whitespace_or_comment(custom_white_space_or_comment.clone()) {
|
||||
custom_white_space_or_comment = String::new();
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
//! Functions related to extruding.
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{ExtrudeGroup, ExtrudeTransform, MemoryItem, SketchGroup},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
/// Extrudes by a given amount.
|
||||
pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (length, sketch_group) = args.get_number_sketch_group()?;
|
||||
@ -23,11 +23,7 @@ pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "extrude"
|
||||
}]
|
||||
fn inner_extrude(
|
||||
length: f64,
|
||||
sketch_group: SketchGroup,
|
||||
args: &mut Args,
|
||||
) -> Result<ExtrudeGroup, KclError> {
|
||||
fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Result<ExtrudeGroup, KclError> {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
let cmd = kittycad::types::ModelingCmd::Extrude {
|
||||
@ -65,17 +61,15 @@ fn inner_get_extrude_wall_transform(
|
||||
extrude_group: ExtrudeGroup,
|
||||
args: &mut Args,
|
||||
) -> Result<ExtrudeTransform, KclError> {
|
||||
let surface = extrude_group
|
||||
.get_path_by_name(surface_name)
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a surface name that exists in the given ExtrudeGroup, found `{}`",
|
||||
surface_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
let surface = extrude_group.get_path_by_name(surface_name).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a surface name that exists in the given ExtrudeGroup, found `{}`",
|
||||
surface_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(ExtrudeTransform {
|
||||
position: surface.get_position(),
|
||||
|
||||
@ -27,8 +27,7 @@ pub type FnMap = HashMap<String, StdFn>;
|
||||
pub type StdFn = fn(&mut Args) -> Result<MemoryItem, KclError>;
|
||||
|
||||
pub struct StdLib {
|
||||
#[allow(dead_code)]
|
||||
internal_fn_names: Vec<Box<(dyn crate::docs::StdLibFn)>>,
|
||||
pub internal_fn_names: Vec<Box<(dyn crate::docs::StdLibFn)>>,
|
||||
|
||||
pub fns: FnMap,
|
||||
}
|
||||
@ -64,20 +63,16 @@ impl StdLib {
|
||||
Box::new(crate::std::sketch::AngledLineThatIntersects),
|
||||
Box::new(crate::std::sketch::StartSketchAt),
|
||||
Box::new(crate::std::sketch::Close),
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
];
|
||||
|
||||
let mut fns = HashMap::new();
|
||||
for internal_fn_name in &internal_fn_names {
|
||||
fns.insert(
|
||||
internal_fn_name.name().to_string(),
|
||||
internal_fn_name.std_lib_fn(),
|
||||
);
|
||||
fns.insert(internal_fn_name.name().to_string(), internal_fn_name.std_lib_fn());
|
||||
}
|
||||
|
||||
Self {
|
||||
internal_fn_names,
|
||||
fns,
|
||||
}
|
||||
Self { internal_fn_names, fns }
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,22 +90,15 @@ pub struct Args<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Args<'a> {
|
||||
pub fn new(
|
||||
args: Vec<MemoryItem>,
|
||||
source_range: SourceRange,
|
||||
engine: &'a mut EngineConnection,
|
||||
) -> Self {
|
||||
pub fn new(args: Vec<MemoryItem>, source_range: SourceRange, engine: &'a mut EngineConnection) -> Self {
|
||||
Self {
|
||||
args,
|
||||
source_range,
|
||||
engine,
|
||||
}
|
||||
}
|
||||
pub fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
|
||||
pub fn send_modeling_cmd(&mut self, id: uuid::Uuid, cmd: kittycad::types::ModelingCmd) -> Result<(), KclError> {
|
||||
self.engine.send_modeling_cmd(id, self.source_range, cmd)
|
||||
}
|
||||
|
||||
@ -124,14 +112,14 @@ impl<'a> Args<'a> {
|
||||
}
|
||||
|
||||
fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {
|
||||
self.make_user_val_from_json(serde_json::Value::Number(
|
||||
serde_json::Number::from_f64(f).ok_or_else(|| {
|
||||
self.make_user_val_from_json(serde_json::Value::Number(serde_json::Number::from_f64(f).ok_or_else(
|
||||
|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Failed to convert `{}` to a number", f),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?,
|
||||
))
|
||||
},
|
||||
)?))
|
||||
}
|
||||
|
||||
fn get_number_array(&self) -> Result<Vec<f64>, KclError> {
|
||||
@ -164,10 +152,7 @@ impl<'a> Args<'a> {
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -177,20 +162,14 @@ impl<'a> Args<'a> {
|
||||
s.to_string()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
let second_value = self.args.get(1).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
@ -199,10 +178,7 @@ impl<'a> Args<'a> {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -213,10 +189,7 @@ impl<'a> Args<'a> {
|
||||
fn get_sketch_group(&self) -> Result<SketchGroup, KclError> {
|
||||
let first_value = self.args.first().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
@ -225,10 +198,7 @@ impl<'a> Args<'a> {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -242,10 +212,7 @@ impl<'a> Args<'a> {
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a struct as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -261,18 +228,13 @@ impl<'a> Args<'a> {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
) -> Result<(T, SketchGroup), KclError> {
|
||||
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, SketchGroup), KclError> {
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a struct as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -287,10 +249,7 @@ impl<'a> Args<'a> {
|
||||
|
||||
let second_value = self.args.get(1).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
@ -299,10 +258,7 @@ impl<'a> Args<'a> {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -310,9 +266,7 @@ impl<'a> Args<'a> {
|
||||
Ok((data, sketch_group))
|
||||
}
|
||||
|
||||
fn get_segment_name_to_number_sketch_group(
|
||||
&self,
|
||||
) -> Result<(String, f64, SketchGroup), KclError> {
|
||||
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, SketchGroup), KclError> {
|
||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||
// The second argument should be a number.
|
||||
// The third argument should be a SketchGroup.
|
||||
@ -321,10 +275,7 @@ impl<'a> Args<'a> {
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -334,10 +285,7 @@ impl<'a> Args<'a> {
|
||||
s.to_string()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -347,10 +295,7 @@ impl<'a> Args<'a> {
|
||||
.get(1)
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a number as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a number as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -360,10 +305,7 @@ impl<'a> Args<'a> {
|
||||
|
||||
let third_value = self.args.get(2).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the third argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the third argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
@ -372,10 +314,7 @@ impl<'a> Args<'a> {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the third argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the third argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -391,10 +330,7 @@ impl<'a> Args<'a> {
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a number as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a number as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -404,10 +340,7 @@ impl<'a> Args<'a> {
|
||||
|
||||
let second_value = self.args.get(1).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
@ -416,10 +349,7 @@ impl<'a> Args<'a> {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -435,10 +365,7 @@ impl<'a> Args<'a> {
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
@ -448,10 +375,7 @@ impl<'a> Args<'a> {
|
||||
s.to_string()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
@ -589,11 +513,7 @@ mod tests {
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.push_str(&format!(
|
||||
"\t* [`{}`](#{})\n",
|
||||
internal_fn.name(),
|
||||
internal_fn.name()
|
||||
));
|
||||
buf.push_str(&format!("\t* [`{}`](#{})\n", internal_fn.name(), internal_fn.name()));
|
||||
}
|
||||
|
||||
buf.push_str("\n\n");
|
||||
@ -617,25 +537,15 @@ mod tests {
|
||||
fn_docs.push_str(&format!("{}\n\n", internal_fn.description()));
|
||||
|
||||
fn_docs.push_str("```\n");
|
||||
fn_docs.push_str(&format!("{}(", internal_fn.name()));
|
||||
for (i, arg) in internal_fn.args().iter().enumerate() {
|
||||
if i > 0 {
|
||||
fn_docs.push_str(", ");
|
||||
}
|
||||
fn_docs.push_str(&format!("{}: {}", arg.name, arg.type_));
|
||||
}
|
||||
fn_docs.push_str(") -> ");
|
||||
fn_docs.push_str(&internal_fn.return_value().type_);
|
||||
let signature = internal_fn.fn_signature();
|
||||
fn_docs.push_str(&signature);
|
||||
fn_docs.push_str("\n```\n\n");
|
||||
|
||||
fn_docs.push_str("#### Arguments\n\n");
|
||||
for arg in internal_fn.args() {
|
||||
let (format, should_be_indented) = arg.get_type_string().unwrap();
|
||||
if let Some(description) = arg.description() {
|
||||
fn_docs.push_str(&format!(
|
||||
"* `{}`: `{}` - {}\n",
|
||||
arg.name, arg.type_, description
|
||||
));
|
||||
fn_docs.push_str(&format!("* `{}`: `{}` - {}\n", arg.name, arg.type_, description));
|
||||
} else {
|
||||
fn_docs.push_str(&format!("* `{}`: `{}`\n", arg.name, arg.type_));
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
//! Functions related to line segments.
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{MemoryItem, SketchGroup},
|
||||
std::{utils::get_angle, Args},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
/// Returns the segment end of x.
|
||||
pub fn segment_end_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
|
||||
@ -22,22 +22,16 @@ pub fn segment_end_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "segEndX",
|
||||
}]
|
||||
fn inner_segment_end_x(
|
||||
segment_name: &str,
|
||||
sketch_group: SketchGroup,
|
||||
args: &mut Args,
|
||||
) -> Result<f64, KclError> {
|
||||
let line = sketch_group
|
||||
.get_base_by_name_or_start(segment_name)
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
fn inner_segment_end_x(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> {
|
||||
let line = sketch_group.get_base_by_name_or_start(segment_name).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(line.to[0])
|
||||
}
|
||||
@ -54,22 +48,16 @@ pub fn segment_end_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "segEndY",
|
||||
}]
|
||||
fn inner_segment_end_y(
|
||||
segment_name: &str,
|
||||
sketch_group: SketchGroup,
|
||||
args: &mut Args,
|
||||
) -> Result<f64, KclError> {
|
||||
let line = sketch_group
|
||||
.get_base_by_name_or_start(segment_name)
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
fn inner_segment_end_y(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> {
|
||||
let line = sketch_group.get_base_by_name_or_start(segment_name).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a segment name that exists in the given SketchGroup, found `{}`",
|
||||
segment_name
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(line.to[1])
|
||||
}
|
||||
@ -145,11 +133,7 @@ pub fn segment_length(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "segLen",
|
||||
}]
|
||||
fn inner_segment_length(
|
||||
segment_name: &str,
|
||||
sketch_group: SketchGroup,
|
||||
args: &mut Args,
|
||||
) -> Result<f64, KclError> {
|
||||
fn inner_segment_length(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> {
|
||||
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
@ -178,11 +162,7 @@ pub fn segment_angle(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "segAng",
|
||||
}]
|
||||
fn inner_segment_angle(
|
||||
segment_name: &str,
|
||||
sketch_group: SketchGroup,
|
||||
args: &mut Args,
|
||||
) -> Result<f64, KclError> {
|
||||
fn inner_segment_angle(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> {
|
||||
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
//! Functions related to sketching.
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use kittycad::types::{ModelingCmd, Point3D};
|
||||
use schemars::JsonSchema;
|
||||
@ -9,13 +10,11 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{BasePath, GeoMeta, MemoryItem, Path, Point2d, Position, Rotation, SketchGroup},
|
||||
std::{
|
||||
utils::{get_x_component, get_y_component, intersection_with_parallel_line},
|
||||
utils::{arc_angles, arc_center_and_end, get_x_component, get_y_component, intersection_with_parallel_line},
|
||||
Args,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// Data to draw a line to a point.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -44,11 +43,7 @@ pub fn line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "lineTo",
|
||||
}]
|
||||
fn inner_line_to(
|
||||
data: LineToData,
|
||||
sketch_group: SketchGroup,
|
||||
args: &Args,
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
fn inner_line_to(data: LineToData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let to = match data {
|
||||
LineToData::PointWithTag { to, .. } => to,
|
||||
@ -56,6 +51,21 @@ fn inner_line_to(
|
||||
};
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::ExtendPath {
|
||||
path: sketch_group.id,
|
||||
segment: kittycad::types::PathSegment::Line {
|
||||
end: Point3D {
|
||||
x: to[0],
|
||||
y: to[1],
|
||||
z: 0.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
@ -106,18 +116,11 @@ pub fn x_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "xLineTo",
|
||||
}]
|
||||
fn inner_x_line_to(
|
||||
data: AxisLineToData,
|
||||
sketch_group: SketchGroup,
|
||||
args: &Args,
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
fn inner_x_line_to(data: AxisLineToData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let line_to_data = match data {
|
||||
AxisLineToData::PointWithTag { to, tag } => LineToData::PointWithTag {
|
||||
to: [to, from.y],
|
||||
tag,
|
||||
},
|
||||
AxisLineToData::PointWithTag { to, tag } => LineToData::PointWithTag { to: [to, from.y], tag },
|
||||
AxisLineToData::Point(data) => LineToData::Point([data, from.y]),
|
||||
};
|
||||
|
||||
@ -138,18 +141,11 @@ pub fn y_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "yLineTo",
|
||||
}]
|
||||
fn inner_y_line_to(
|
||||
data: AxisLineToData,
|
||||
sketch_group: SketchGroup,
|
||||
args: &Args,
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
fn inner_y_line_to(data: AxisLineToData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let line_to_data = match data {
|
||||
AxisLineToData::PointWithTag { to, tag } => LineToData::PointWithTag {
|
||||
to: [from.x, to],
|
||||
tag,
|
||||
},
|
||||
AxisLineToData::PointWithTag { to, tag } => LineToData::PointWithTag { to: [from.x, to], tag },
|
||||
AxisLineToData::Point(data) => LineToData::Point([from.x, data]),
|
||||
};
|
||||
|
||||
@ -207,11 +203,7 @@ pub fn line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "line",
|
||||
}]
|
||||
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 default = [0.2, 1.0];
|
||||
@ -289,11 +281,7 @@ pub fn x_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "xLine",
|
||||
}]
|
||||
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 {
|
||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([length, 0.0]),
|
||||
@ -318,11 +306,7 @@ pub fn y_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "yLine",
|
||||
}]
|
||||
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 {
|
||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([0.0, length]),
|
||||
@ -373,9 +357,7 @@ fn inner_angled_line(
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let (angle, length) = match &data {
|
||||
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
|
||||
AngledLineData::AngleAndLength(angle_and_length) => {
|
||||
(angle_and_length[0], angle_and_length[1])
|
||||
}
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
let to: [f64; 2] = [
|
||||
from.x + length * f64::cos(angle * std::f64::consts::PI / 180.0),
|
||||
@ -424,9 +406,7 @@ fn inner_angled_line_of_x_length(
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
let (angle, length) = match &data {
|
||||
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
|
||||
AngledLineData::AngleAndLength(angle_and_length) => {
|
||||
(angle_and_length[0], angle_and_length[1])
|
||||
}
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
|
||||
let to = get_y_component(angle, length);
|
||||
@ -494,10 +474,7 @@ fn inner_angled_line_to_x(
|
||||
|
||||
let new_sketch_group = inner_line_to(
|
||||
if let AngledLineToData::AngleWithTag { tag, .. } = data {
|
||||
LineToData::PointWithTag {
|
||||
to: [x_to, y_to],
|
||||
tag,
|
||||
}
|
||||
LineToData::PointWithTag { to: [x_to, y_to], tag }
|
||||
} else {
|
||||
LineToData::Point([x_to, y_to])
|
||||
},
|
||||
@ -527,9 +504,7 @@ fn inner_angled_line_of_y_length(
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
let (angle, length) = match &data {
|
||||
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
|
||||
AngledLineData::AngleAndLength(angle_and_length) => {
|
||||
(angle_and_length[0], angle_and_length[1])
|
||||
}
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
|
||||
let to = get_x_component(angle, length);
|
||||
@ -579,10 +554,7 @@ fn inner_angled_line_to_y(
|
||||
|
||||
let new_sketch_group = inner_line_to(
|
||||
if let AngledLineToData::AngleWithTag { tag, .. } = data {
|
||||
LineToData::PointWithTag {
|
||||
to: [x_to, y_to],
|
||||
tag,
|
||||
}
|
||||
LineToData::PointWithTag { to: [x_to, y_to], tag }
|
||||
} else {
|
||||
LineToData::Point([x_to, y_to])
|
||||
},
|
||||
@ -610,8 +582,7 @@ pub struct AngeledLineThatIntersectsData {
|
||||
|
||||
/// Draw an angled line that intersects with a given line.
|
||||
pub fn angled_line_that_intersects(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AngeledLineThatIntersectsData, SketchGroup) =
|
||||
args.get_data_and_sketch_group()?;
|
||||
let (data, sketch_group): (AngeledLineThatIntersectsData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
let new_sketch_group = inner_angled_line_that_intersects(data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
@ -760,13 +731,255 @@ fn inner_close(sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup
|
||||
Ok(new_sketch_group)
|
||||
}
|
||||
|
||||
/// Data to draw an arc.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum ArcData {
|
||||
/// Angles and radius with a tag.
|
||||
AnglesAndRadiusWithTag {
|
||||
/// The start angle.
|
||||
angle_start: f64,
|
||||
/// The end angle.
|
||||
angle_end: f64,
|
||||
/// The radius.
|
||||
radius: f64,
|
||||
/// The tag.
|
||||
tag: String,
|
||||
},
|
||||
/// Angles and radius.
|
||||
AnglesAndRadius {
|
||||
/// The start angle.
|
||||
angle_start: f64,
|
||||
/// The end angle.
|
||||
angle_end: f64,
|
||||
/// The radius.
|
||||
radius: f64,
|
||||
},
|
||||
/// Center, to and radius with a tag.
|
||||
CenterToRadiusWithTag {
|
||||
/// The center.
|
||||
center: [f64; 2],
|
||||
/// The to point.
|
||||
to: [f64; 2],
|
||||
/// The radius.
|
||||
radius: f64,
|
||||
/// The tag.
|
||||
tag: String,
|
||||
},
|
||||
/// Center, to and radius.
|
||||
CenterToRadius {
|
||||
/// The center.
|
||||
center: [f64; 2],
|
||||
/// The to point.
|
||||
to: [f64; 2],
|
||||
/// The radius.
|
||||
radius: f64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Draw an arc.
|
||||
pub fn arc(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (ArcData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let new_sketch_group = inner_arc(data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw an arc.
|
||||
#[stdlib {
|
||||
name = "arc",
|
||||
}]
|
||||
fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let (center, angle_start, angle_end, radius, end) = match &data {
|
||||
ArcData::AnglesAndRadiusWithTag {
|
||||
angle_start,
|
||||
angle_end,
|
||||
radius,
|
||||
..
|
||||
} => {
|
||||
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
|
||||
(center, *angle_start, *angle_end, *radius, end)
|
||||
}
|
||||
ArcData::AnglesAndRadius {
|
||||
angle_start,
|
||||
angle_end,
|
||||
radius,
|
||||
} => {
|
||||
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
|
||||
(center, *angle_start, *angle_end, *radius, end)
|
||||
}
|
||||
ArcData::CenterToRadiusWithTag { center, to, radius, .. } => {
|
||||
let (angle_start, angle_end) = arc_angles(&from, ¢er.into(), &to.into(), *radius, args.source_range)?;
|
||||
(center.into(), angle_start, angle_end, *radius, to.into())
|
||||
}
|
||||
ArcData::CenterToRadius { center, to, radius } => {
|
||||
let (angle_start, angle_end) = arc_angles(&from, ¢er.into(), &to.into(), *radius, args.source_range)?;
|
||||
(center.into(), angle_start, angle_end, *radius, to.into())
|
||||
}
|
||||
};
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::ExtendPath {
|
||||
path: sketch_group.id,
|
||||
segment: kittycad::types::PathSegment::Arc {
|
||||
angle_start,
|
||||
angle_end,
|
||||
center: center.into(),
|
||||
radius,
|
||||
},
|
||||
},
|
||||
)?;
|
||||
// Move the path pen to the end of the arc.
|
||||
// Since that is where we want to draw the next path.
|
||||
// TODO: the engine should automatically move the pen to the end of the arc.
|
||||
// This just seems inefficient.
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::MovePathPen {
|
||||
path: sketch_group.id,
|
||||
to: Point3D {
|
||||
x: end.x,
|
||||
y: end.y,
|
||||
z: 0.0,
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to: end.into(),
|
||||
name: match data {
|
||||
ArcData::AnglesAndRadiusWithTag { tag, .. } => tag.to_string(),
|
||||
ArcData::AnglesAndRadius { .. } => "".to_string(),
|
||||
ArcData::CenterToRadiusWithTag { tag, .. } => tag.to_string(),
|
||||
ArcData::CenterToRadius { .. } => "".to_string(),
|
||||
},
|
||||
geo_meta: GeoMeta {
|
||||
id,
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let mut new_sketch_group = sketch_group.clone();
|
||||
new_sketch_group.value.push(current_path);
|
||||
|
||||
Ok(new_sketch_group)
|
||||
}
|
||||
|
||||
/// Data to draw a bezier curve.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum BezierData {
|
||||
/// Points with a tag.
|
||||
PointsWithTag {
|
||||
/// The to point.
|
||||
to: [f64; 2],
|
||||
/// The first control point.
|
||||
control1: [f64; 2],
|
||||
/// The second control point.
|
||||
control2: [f64; 2],
|
||||
/// The tag.
|
||||
tag: String,
|
||||
},
|
||||
/// Points.
|
||||
Points {
|
||||
/// The to point.
|
||||
to: [f64; 2],
|
||||
/// The first control point.
|
||||
control1: [f64; 2],
|
||||
/// The second control point.
|
||||
control2: [f64; 2],
|
||||
},
|
||||
}
|
||||
|
||||
/// Draw a bezier curve.
|
||||
pub fn bezier_curve(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (BezierData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let new_sketch_group = inner_bezier_curve(data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a bezier curve.
|
||||
#[stdlib {
|
||||
name = "bezierCurve",
|
||||
}]
|
||||
fn inner_bezier_curve(data: BezierData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let (to, control1, control2) = match &data {
|
||||
BezierData::PointsWithTag {
|
||||
to, control1, control2, ..
|
||||
} => (to, control1, control2),
|
||||
BezierData::Points { to, control1, control2 } => (to, control1, control2),
|
||||
};
|
||||
|
||||
let to = [from.x + to[0], from.y + to[1]];
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::ExtendPath {
|
||||
path: sketch_group.id,
|
||||
segment: kittycad::types::PathSegment::Bezier {
|
||||
control1: Point3D {
|
||||
x: from.x + control1[0],
|
||||
y: from.y + control1[1],
|
||||
z: 0.0,
|
||||
},
|
||||
control2: Point3D {
|
||||
x: from.x + control2[0],
|
||||
y: from.y + control2[1],
|
||||
z: 0.0,
|
||||
},
|
||||
end: Point3D {
|
||||
x: to[0],
|
||||
y: to[1],
|
||||
z: 0.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to,
|
||||
name: if let BezierData::PointsWithTag { tag, .. } = data {
|
||||
tag.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
geo_meta: GeoMeta {
|
||||
id,
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let mut new_sketch_group = sketch_group.clone();
|
||||
new_sketch_group.value.push(current_path);
|
||||
|
||||
Ok(new_sketch_group)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::std::sketch::{LineData, PointOrDefault};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::std::sketch::{LineData, PointOrDefault};
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_line_data() {
|
||||
let mut str_json = "\"default\"".to_string();
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{Point2d, SourceRange},
|
||||
};
|
||||
|
||||
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
|
||||
let x = b[0] - a[0];
|
||||
let y = b[1] - a[1];
|
||||
@ -44,7 +49,10 @@ pub fn normalize_rad(angle: f64) -> f64 {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(kcl::std::utils::delta_angle(std::f64::consts::PI/8.0, std::f64::consts::PI/4.0), std::f64::consts::PI/8.0);
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::delta_angle(std::f64::consts::PI / 8.0, std::f64::consts::PI / 4.0),
|
||||
/// std::f64::consts::PI / 8.0
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn delta_angle(from_angle: f64, to_angle: f64) -> f64 {
|
||||
@ -69,8 +77,14 @@ pub fn delta_angle(from_angle: f64, to_angle: f64) -> f64 {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(kcl::std::utils::distance_between_points(&[0.0, 0.0], &[0.0, 5.0]), 5.0);
|
||||
/// assert_eq!(kcl::std::utils::distance_between_points(&[0.0, 0.0], &[3.0, 4.0]), 5.0);
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::distance_between_points(&[0.0, 0.0], &[0.0, 5.0]),
|
||||
/// 5.0
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::distance_between_points(&[0.0, 0.0], &[3.0, 4.0]),
|
||||
/// 5.0
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn distance_between_points(point_a: &[f64; 2], point_b: &[f64; 2]) -> f64 {
|
||||
@ -82,11 +96,7 @@ pub fn distance_between_points(point_a: &[f64; 2], point_b: &[f64; 2]) -> f64 {
|
||||
((y2 - y1).powi(2) + (x2 - x1).powi(2)).sqrt()
|
||||
}
|
||||
|
||||
pub fn calculate_intersection_of_two_lines(
|
||||
line1: &[[f64; 2]; 2],
|
||||
line2_angle: f64,
|
||||
line2_point: [f64; 2],
|
||||
) -> [f64; 2] {
|
||||
pub fn calculate_intersection_of_two_lines(line1: &[[f64; 2]; 2], line2_angle: f64, line2_point: [f64; 2]) -> [f64; 2] {
|
||||
let line2_point_b = [
|
||||
line2_point[0] + f64::cos(line2_angle * std::f64::consts::PI / 180.0) * 10.0,
|
||||
line2_point[1] + f64::sin(line2_angle * std::f64::consts::PI / 180.0) * 10.0,
|
||||
@ -117,27 +127,17 @@ pub fn intersection_with_parallel_line(
|
||||
line2_angle: f64,
|
||||
line2_point: [f64; 2],
|
||||
) -> [f64; 2] {
|
||||
calculate_intersection_of_two_lines(
|
||||
&offset_line(line1_offset, line1[0], line1[1]),
|
||||
line2_angle,
|
||||
line2_point,
|
||||
)
|
||||
calculate_intersection_of_two_lines(&offset_line(line1_offset, line1[0], line1[1]), line2_angle, line2_point)
|
||||
}
|
||||
|
||||
fn offset_line(offset: f64, p1: [f64; 2], p2: [f64; 2]) -> [[f64; 2]; 2] {
|
||||
if p1[0] == p2[0] {
|
||||
let direction = (p1[1] - p2[1]).signum();
|
||||
return [
|
||||
[p1[0] + offset * direction, p1[1]],
|
||||
[p2[0] + offset * direction, p2[1]],
|
||||
];
|
||||
return [[p1[0] + offset * direction, p1[1]], [p2[0] + offset * direction, p2[1]]];
|
||||
}
|
||||
if p1[1] == p2[1] {
|
||||
let direction = (p2[0] - p1[0]).signum();
|
||||
return [
|
||||
[p1[0], p1[1] + offset * direction],
|
||||
[p2[0], p2[1] + offset * direction],
|
||||
];
|
||||
return [[p1[0], p1[1] + offset * direction], [p2[0], p2[1] + offset * direction]];
|
||||
}
|
||||
let x_offset = offset / f64::sin(f64::atan2(p1[1] - p2[1], p1[0] - p2[0]));
|
||||
[[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]]
|
||||
@ -165,12 +165,81 @@ pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
|
||||
[sign * x_component, sign * y_component]
|
||||
}
|
||||
|
||||
pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f64, radius: f64) -> (Point2d, Point2d) {
|
||||
let start_angle = start_angle_deg * (std::f64::consts::PI / 180.0);
|
||||
let end_angle = end_angle_deg * (std::f64::consts::PI / 180.0);
|
||||
|
||||
let center = Point2d {
|
||||
x: -1.0 * (radius * start_angle.cos() - from.x),
|
||||
y: -1.0 * (radius * start_angle.sin() - from.y),
|
||||
};
|
||||
|
||||
let end = Point2d {
|
||||
x: center.x + radius * end_angle.cos(),
|
||||
y: center.y + radius * end_angle.sin(),
|
||||
};
|
||||
|
||||
(center, end)
|
||||
}
|
||||
|
||||
pub fn arc_angles(
|
||||
from: &Point2d,
|
||||
to: &Point2d,
|
||||
center: &Point2d,
|
||||
radius: f64,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(f64, f64), KclError> {
|
||||
// First make sure that the points are on the circumference of the circle.
|
||||
// If not, we'll return an error.
|
||||
if !is_on_circumference(center, from, radius) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
|
||||
from, center, radius
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
if !is_on_circumference(center, to, radius) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
|
||||
to, center, radius
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
let start_angle = (from.y - center.y).atan2(from.x - center.x);
|
||||
let end_angle = (to.y - center.y).atan2(to.x - center.x);
|
||||
|
||||
let start_angle_deg = start_angle * (180.0 / std::f64::consts::PI);
|
||||
let end_angle_deg = end_angle * (180.0 / std::f64::consts::PI);
|
||||
|
||||
Ok((start_angle_deg, end_angle_deg))
|
||||
}
|
||||
|
||||
pub fn is_on_circumference(center: &Point2d, point: &Point2d, radius: f64) -> bool {
|
||||
let dx = point.x - center.x;
|
||||
let dy = point.y - center.y;
|
||||
|
||||
let distance_squared = dx.powi(2) + dy.powi(2);
|
||||
|
||||
// We'll check if the distance squared is approximately equal to radius squared.
|
||||
// Due to potential floating point inaccuracies, we'll check if the difference
|
||||
// is very small (e.g., 1e-9) rather than checking for strict equality.
|
||||
(distance_squared - radius.powi(2)).abs() < 1e-9
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Here you can bring your functions into scope
|
||||
use super::{get_x_component, get_y_component};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::{get_x_component, get_y_component};
|
||||
use crate::executor::SourceRange;
|
||||
|
||||
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
|
||||
(-315, [1, 1]),
|
||||
(-225, [-1, 1]),
|
||||
@ -245,4 +314,77 @@ mod tests {
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_center_and_end() {
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 90.0, 1.0);
|
||||
assert_eq!(center.x.round(), -1.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -1.0);
|
||||
assert_eq!(end.y, 1.0);
|
||||
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 1.0);
|
||||
assert_eq!(center.x.round(), -1.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -2.0);
|
||||
assert_eq!(end.y.round(), 0.0);
|
||||
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 10.0);
|
||||
assert_eq!(center.x.round(), -10.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -20.0);
|
||||
assert_eq!(end.y.round(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_angles() {
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -1.0, y: 1.0 },
|
||||
&super::Point2d { x: -1.0, y: 0.0 },
|
||||
1.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 90.0);
|
||||
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -2.0, y: 0.0 },
|
||||
&super::Point2d { x: -1.0, y: 0.0 },
|
||||
1.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -20.0, y: 0.0 },
|
||||
&super::Point2d { x: -10.0, y: 0.0 },
|
||||
10.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
|
||||
let result = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 5.0 },
|
||||
&super::Point2d { x: 5.0, y: 5.0 },
|
||||
&super::Point2d { x: 10.0, y: -10.0 },
|
||||
10.0,
|
||||
SourceRange(Default::default()),
|
||||
);
|
||||
|
||||
if let Err(err) = result {
|
||||
assert!(err.to_string().contains( "Point Point2d { x: 0.0, y: 5.0 } is not on the circumference of the circle with center Point2d { x: 10.0, y: -10.0 } and radius 10."));
|
||||
} else {
|
||||
panic!("Expected error");
|
||||
}
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,8 +46,7 @@ lazy_static! {
|
||||
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
|
||||
static ref WORD: Regex = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*").unwrap();
|
||||
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
|
||||
static ref OPERATOR: Regex =
|
||||
Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
|
||||
static ref OPERATOR: Regex = Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
|
||||
static ref BLOCK_START: Regex = Regex::new(r"^\{").unwrap();
|
||||
static ref BLOCK_END: Regex = Regex::new(r"^\}").unwrap();
|
||||
static ref PARAN_START: Regex = Regex::new(r"^\(").unwrap();
|
||||
@ -114,9 +113,7 @@ fn is_block_comment(character: &str) -> bool {
|
||||
}
|
||||
|
||||
fn match_first(str: &str, regex: &Regex) -> Option<String> {
|
||||
regex
|
||||
.find(str)
|
||||
.map(|the_match| the_match.as_str().to_string())
|
||||
regex.find(str).map(|the_match| the_match.as_str().to_string())
|
||||
}
|
||||
|
||||
fn make_token(token_type: TokenType, value: &str, start: usize) -> Token {
|
||||
@ -251,11 +248,7 @@ fn return_token_at_index(str: &str, start_index: usize) -> Option<Token> {
|
||||
}
|
||||
|
||||
pub fn lexer(str: &str) -> Vec<Token> {
|
||||
fn recursively_tokenise(
|
||||
str: &str,
|
||||
current_index: usize,
|
||||
previous_tokens: Vec<Token>,
|
||||
) -> Vec<Token> {
|
||||
fn recursively_tokenise(str: &str, current_index: usize, previous_tokens: Vec<Token>) -> Vec<Token> {
|
||||
if current_index >= str.len() {
|
||||
return previous_tokens;
|
||||
}
|
||||
@ -273,9 +266,10 @@ pub fn lexer(str: &str) -> Vec<Token> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_number_test() {
|
||||
assert!(is_number("1"));
|
||||
|
||||
6
src/wasm-lib/rustfmt.toml
Normal file
6
src/wasm-lib/rustfmt.toml
Normal file
@ -0,0 +1,6 @@
|
||||
max_width = 120
|
||||
edition = "2018"
|
||||
format_code_in_doc_comments = true
|
||||
format_strings = false
|
||||
imports_granularity = "Crate"
|
||||
group_imports = "StdExternalCrate"
|
||||
@ -9,25 +9,19 @@ use wasm_bindgen::prelude::*;
|
||||
pub async fn execute_wasm(
|
||||
program_str: &str,
|
||||
memory_str: &str,
|
||||
manager: kcl::engine::conn_wasm::EngineCommandManager,
|
||||
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||
) -> Result<JsValue, String> {
|
||||
// deserialize the ast from a stringified json
|
||||
let program: kcl::abstract_syntax_tree_types::Program =
|
||||
let program: kcl_lib::abstract_syntax_tree_types::Program =
|
||||
serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||
let mut mem: kcl::executor::ProgramMemory =
|
||||
serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||
let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut engine = kcl::engine::EngineConnection::new(manager)
|
||||
let mut engine = kcl_lib::engine::EngineConnection::new(manager)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
let memory = kcl::executor::execute(
|
||||
program,
|
||||
&mut mem,
|
||||
kcl::executor::BodyType::Root,
|
||||
&mut engine,
|
||||
)
|
||||
.map_err(String::from)?;
|
||||
let memory = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)
|
||||
.map_err(String::from)?;
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&memory).map_err(|e| e.to_string())
|
||||
@ -37,35 +31,31 @@ pub async fn execute_wasm(
|
||||
pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
||||
let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?;
|
||||
|
||||
if !ws_resp.success {
|
||||
return Err(JsError::new(&format!(
|
||||
"Server returned error: {:?}",
|
||||
ws_resp.errors
|
||||
)));
|
||||
if let Some(success) = ws_resp.success {
|
||||
if !success {
|
||||
return Err(JsError::new(&format!("Server returned error: {:?}", ws_resp.errors)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(kittycad::types::OkWebSocketResponseData::Export { files }) = ws_resp.resp {
|
||||
return Ok(JsValue::from_serde(&files)?);
|
||||
}
|
||||
|
||||
Err(JsError::new(&format!(
|
||||
"Invalid response type, got: {:?}",
|
||||
ws_resp
|
||||
)))
|
||||
Err(JsError::new(&format!("Invalid response type, got: {:?}", ws_resp)))
|
||||
}
|
||||
|
||||
// wasm_bindgen wrapper for lexer
|
||||
// test for this function and by extension lexer are done in javascript land src/lang/tokeniser.test.ts
|
||||
#[wasm_bindgen]
|
||||
pub fn lexer_js(js: &str) -> Result<JsValue, JsError> {
|
||||
let tokens = kcl::tokeniser::lexer(js);
|
||||
let tokens = kcl_lib::tokeniser::lexer(js);
|
||||
Ok(JsValue::from_serde(&tokens)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_js(js: &str) -> Result<JsValue, String> {
|
||||
let tokens = kcl::tokeniser::lexer(js);
|
||||
let program = kcl::parser::abstract_syntax_tree(&tokens).map_err(String::from)?;
|
||||
let tokens = kcl_lib::tokeniser::lexer(js);
|
||||
let program = kcl_lib::parser::abstract_syntax_tree(&tokens).map_err(String::from)?;
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&program).map_err(|e| e.to_string())
|
||||
@ -76,9 +66,9 @@ pub fn parse_js(js: &str) -> Result<JsValue, String> {
|
||||
#[wasm_bindgen]
|
||||
pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
||||
// deserialize the ast from a stringified json
|
||||
let program: kcl::abstract_syntax_tree_types::Program =
|
||||
let program: kcl_lib::abstract_syntax_tree_types::Program =
|
||||
serde_json::from_str(json_str).map_err(JsError::from)?;
|
||||
|
||||
let result = kcl::recast::recast(&program, "", false);
|
||||
let result = kcl_lib::recast::recast(&program, "", false);
|
||||
Ok(JsValue::from_serde(&result)?)
|
||||
}
|
||||
|
||||
79
yarn.lock
79
yarn.lock
@ -1752,10 +1752,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
|
||||
integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
|
||||
|
||||
"@kittycad/lib@^0.0.34":
|
||||
version "0.0.34"
|
||||
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-0.0.34.tgz#c1f1021f6c77bd9f47caa685cfbff0ef358a0316"
|
||||
integrity sha512-9pUUuspJB/rayW4adfF7UqRYLw1pugBy3t0+V6qK3sWttG9flgv54fPw3JKewn7VFoEjRtNtoREMAoWb4ZrUIw==
|
||||
"@kittycad/lib@^0.0.35":
|
||||
version "0.0.35"
|
||||
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-0.0.35.tgz#bde8868048f9fd53f8309e7308aeba622898b935"
|
||||
integrity sha512-qM8AyP2QUlDfPWNxb1Fs/Pq9AebGVDN1OHjByxbGomKCy0jFdN2TsyDdhQH/CAZGfBCgPEfr5bq6rkUBGSXcNw==
|
||||
dependencies:
|
||||
node-fetch "3.3.2"
|
||||
openapi-types "^12.0.0"
|
||||
@ -1986,6 +1986,70 @@
|
||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf"
|
||||
integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==
|
||||
|
||||
"@sentry-internal/tracing@7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.65.0.tgz#f7c56885d10c753ef03a25405dae13728916c0f5"
|
||||
integrity sha512-TEYkiq5vKr1Y79YIu+UYr1sO3vEMttQOBsOZLziDbqiC7TvKUARBR4W5XWfb9qBVDeon87EFNKluW0/+7rzYWw==
|
||||
dependencies:
|
||||
"@sentry/core" "7.65.0"
|
||||
"@sentry/types" "7.65.0"
|
||||
"@sentry/utils" "7.65.0"
|
||||
tslib "^2.4.1 || ^1.9.3"
|
||||
|
||||
"@sentry/browser@7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.65.0.tgz#fb2009d6f8f1e5e3e1c616ce0ea70dd728c46ce7"
|
||||
integrity sha512-TUzZPAXNJ/Y1yakFODYhsEtdDpLdkgjTfrx5i9MOnXQLrcRR0C4TC1KitqbP6Tv7Xha9WiR0TDZkh7gS/9RxEA==
|
||||
dependencies:
|
||||
"@sentry-internal/tracing" "7.65.0"
|
||||
"@sentry/core" "7.65.0"
|
||||
"@sentry/replay" "7.65.0"
|
||||
"@sentry/types" "7.65.0"
|
||||
"@sentry/utils" "7.65.0"
|
||||
tslib "^2.4.1 || ^1.9.3"
|
||||
|
||||
"@sentry/core@7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.65.0.tgz#01c1320b4e7c62ccf757258c1622d07cc743468a"
|
||||
integrity sha512-EwZABW8CtAbRGXV69FqeCqcNApA+Jbq308dko0W+MFdFe+9t2RGubUkpPxpJcbWy/dN2j4LiuENu1T7nWn0ZAQ==
|
||||
dependencies:
|
||||
"@sentry/types" "7.65.0"
|
||||
"@sentry/utils" "7.65.0"
|
||||
tslib "^2.4.1 || ^1.9.3"
|
||||
|
||||
"@sentry/react@^7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.65.0.tgz#98c044bc2d7a99da7dfdef2686c3214d8f2f4ee0"
|
||||
integrity sha512-1ABxHwEHw5J4avUr8TBch3l7UszbNIroWergwiLPSy+EJU8WuB3Fdx0zSU+hS4Sujf8HNcRgu1JyWThZFTnIMA==
|
||||
dependencies:
|
||||
"@sentry/browser" "7.65.0"
|
||||
"@sentry/types" "7.65.0"
|
||||
"@sentry/utils" "7.65.0"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
tslib "^2.4.1 || ^1.9.3"
|
||||
|
||||
"@sentry/replay@7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.65.0.tgz#e73a8a577c8b492c3f18ab769db15993b96e77fe"
|
||||
integrity sha512-vhlk5F9RrhMQ+gOjNlLoWXamAPLNIT6wNII1O9ae+DRhZFmiUYirP5ag6dH5lljvNZndKl+xw+lJGJ3YdjXKlQ==
|
||||
dependencies:
|
||||
"@sentry/core" "7.65.0"
|
||||
"@sentry/types" "7.65.0"
|
||||
"@sentry/utils" "7.65.0"
|
||||
|
||||
"@sentry/types@7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.65.0.tgz#f0f4e6583c631408d15ee5fb46901fd195fa1cc4"
|
||||
integrity sha512-YYq7IDLLhpSBTmHoyWFtq/5ZDaEJ01r7xGuhB0aSIq33cm2I7im/B3ipzoOP/ukGZSIhuYVW9t531xZEO0+6og==
|
||||
|
||||
"@sentry/utils@7.65.0":
|
||||
version "7.65.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.65.0.tgz#a7929c5b019fa33e819b08a99744fa27cd38c85f"
|
||||
integrity sha512-2JEBf4jzRSClhp+LJpX/E3QgHEeKvXqFMeNhmwQ07qqd6szhfH2ckYFj4gXk6YiGGY4Act3C6oxLfdZovG71bw==
|
||||
dependencies:
|
||||
"@sentry/types" "7.65.0"
|
||||
tslib "^2.4.1 || ^1.9.3"
|
||||
|
||||
"@sinclair/typebox@^0.27.8":
|
||||
version "0.27.8"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
|
||||
@ -4031,7 +4095,7 @@ he@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hoist-non-react-statics@^3.3.0:
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
@ -5763,6 +5827,11 @@ tslib@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
|
||||
integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
|
||||
|
||||
"tslib@^2.4.1 || ^1.9.3":
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
|
||||
tslib@~2.4:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||
|
||||
Reference in New Issue
Block a user