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