Port executor (#287)
* parent initial types Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> more port Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> use the function Signed-off-by: Jess Frazelle <github@jessfraz.com> ipdates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> pipe sjhit Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup and pipes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> attempt to call the function Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> better Signed-off-by: Jess Frazelle <github@jessfraz.com> add first function Signed-off-by: Jess Frazelle <github@jessfraz.com> start of stdlib Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> organize better Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> boilerplace Signed-off-by: Jess Frazelle <github@jessfraz.com> boilerplace Signed-off-by: Jess Frazelle <github@jessfraz.com> more functions Signed-off-by: Jess Frazelle <github@jessfraz.com> more stuff Signed-off-by: Jess Frazelle <github@jessfraz.com> more path segment functions Signed-off-by: Jess Frazelle <github@jessfraz.com> reorganize files Signed-off-by: Jess Frazelle <github@jessfraz.com> extrude boilerplate Signed-off-by: Jess Frazelle <github@jessfraz.com> extrude Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> sketch boilerplate Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> comment out extrude for now Signed-off-by: Jess Frazelle <github@jessfraz.com> more executor test passing Signed-off-by: Jess Frazelle <github@jessfraz.com> rename meta Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup unneeded deps Signed-off-by: Jess Frazelle <github@jessfraz.com> generate executor typoes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> remove path to node Signed-off-by: Jess Frazelle <github@jessfraz.com> updates for tests js Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> ignore wasm file Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> start of websocket connection Signed-off-by: Jess Frazelle <github@jessfraz.com> boilerplate for engine connection Signed-off-by: Jess Frazelle <github@jessfraz.com> fix Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> send the modeling cmd Signed-off-by: Jess Frazelle <github@jessfraz.com> implement close Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> remove refid Signed-off-by: Jess Frazelle <github@jessfraz.com> remove refid Signed-off-by: Jess Frazelle <github@jessfraz.com> do sketch start Signed-off-by: Jess Frazelle <github@jessfraz.com> almost done w sketch port Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> add more tests Signed-off-by: Jess Frazelle <github@jessfraz.com> fix deserialize and tests Signed-off-by: Jess Frazelle <github@jessfraz.com> fix tests remove logging Signed-off-by: Jess Frazelle <github@jessfraz.com> fix the return type Signed-off-by: Jess Frazelle <github@jessfraz.com> make compile Signed-off-by: Jess Frazelle <github@jessfraz.com> more tests pass Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> expect any string Signed-off-by: Jess Frazelle <github@jessfraz.com> add failing test Signed-off-by: Jess Frazelle <github@jessfraz.com> fix the tests Signed-off-by: Jess Frazelle <github@jessfraz.com> fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> fix more tests Signed-off-by: Jess Frazelle <github@jessfraz.com> replace wasm_execute Signed-off-by: Jess Frazelle <github@jessfraz.com> fix more tests Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> add more tests Signed-off-by: Jess Frazelle <github@jessfraz.com> make all tests pass Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fix remaining tests Signed-off-by: Jess Frazelle <github@jessfraz.com> add a warpper Signed-off-by: Jess Frazelle <github@jessfraz.com> start of server side ws/webrtc Signed-off-by: Jess Frazelle <github@jessfraz.com> more nonweb working Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> add test mock Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> mutable engine Signed-off-by: Jess Frazelle <github@jessfraz.com> blocking snd engine cmd Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> tmp Signed-off-by: Jess Frazelle <github@jessfraz.com> * tmp Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * build wasm only Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix cargo builds Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * more logging Signed-off-by: Jess Frazelle <github@jessfraz.com> * push Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
src/wasm-lib/pkg/wasm_lib.js
|
4
.github/workflows/cargo-build.yml
vendored
4
.github/workflows/cargo-build.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib', 'src-tauri']
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@ -43,5 +43,7 @@ jobs:
|
||||
- name: Run cargo build
|
||||
run: |
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo build --all --no-default-features --features noweb
|
||||
cargo build --all --no-default-features --features web
|
||||
cargo build --all
|
||||
shell: bash
|
||||
|
2
.github/workflows/cargo-clippy.yml
vendored
2
.github/workflows/cargo-clippy.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib', 'src-tauri']
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install latest rust
|
||||
|
2
.github/workflows/cargo-test.yml
vendored
2
.github/workflows/cargo-test.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
strategy:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib', 'src-tauri']
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install latest rust
|
||||
|
@ -60,7 +60,7 @@
|
||||
"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 --no-default-features --features web && cargo test --all) && 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/bindings",
|
||||
"lint": "eslint --fix src",
|
||||
|
17
src/App.tsx
17
src/App.tsx
@ -295,18 +295,6 @@ export function App() {
|
||||
_ast,
|
||||
{
|
||||
root: {
|
||||
log: {
|
||||
type: 'userVal',
|
||||
value: (a: any) => {
|
||||
addLog(a)
|
||||
},
|
||||
__meta: [
|
||||
{
|
||||
pathToNode: [],
|
||||
sourceRange: [0, 0],
|
||||
},
|
||||
],
|
||||
},
|
||||
_0: {
|
||||
type: 'userVal',
|
||||
value: 0,
|
||||
@ -328,11 +316,8 @@ export function App() {
|
||||
__meta: [],
|
||||
},
|
||||
},
|
||||
pendingMemory: {},
|
||||
},
|
||||
engineCommandManager,
|
||||
{ bodyType: 'root' },
|
||||
[]
|
||||
engineCommandManager
|
||||
)
|
||||
|
||||
const { artifactMap, sourceRangeMap } =
|
||||
|
@ -28,30 +28,20 @@ describe('processMemory', () => {
|
||||
show(theExtrude, theSketch)`
|
||||
const ast = parser_wasm(code)
|
||||
const programMemory = await enginelessExecutor(ast, {
|
||||
root: {
|
||||
log: {
|
||||
type: 'userVal',
|
||||
value: (a: any) => {
|
||||
console.log('raw log', a)
|
||||
},
|
||||
__meta: [],
|
||||
},
|
||||
},
|
||||
pendingMemory: {},
|
||||
root: {},
|
||||
})
|
||||
const output = processMemory(programMemory)
|
||||
expect(output.myVar).toEqual(5)
|
||||
expect(output.myFn).toEqual('__function__')
|
||||
expect(output.otherVar).toEqual(3)
|
||||
expect(output).toEqual({
|
||||
myVar: 5,
|
||||
myFn: '__function__',
|
||||
myFn: undefined,
|
||||
otherVar: 3,
|
||||
theExtrude: [],
|
||||
theSketch: [
|
||||
{ type: 'toPoint', to: [-3.35, 0.17], from: [0, 0] },
|
||||
{ type: 'toPoint', to: [0.98, 5.16], from: [-3.35, 0.17] },
|
||||
{ type: 'toPoint', to: [2.15, 4.32], from: [0.98, 5.16] },
|
||||
{ type: 'toPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
||||
{ type: 'toPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
||||
{ type: 'toPoint', to: [2.15, 4.32], from: [0.98, 5.16], name: '' },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
@ -214,7 +214,6 @@ describe('testing function declaration', () => {
|
||||
id: null,
|
||||
params: [],
|
||||
body: {
|
||||
type: 'BlockStatement',
|
||||
start: 17,
|
||||
end: 19,
|
||||
body: [],
|
||||
@ -267,7 +266,6 @@ describe('testing function declaration', () => {
|
||||
},
|
||||
],
|
||||
body: {
|
||||
type: 'BlockStatement',
|
||||
start: 21,
|
||||
end: 39,
|
||||
body: [
|
||||
@ -344,7 +342,6 @@ const myVar = funcN(1, 2)`
|
||||
},
|
||||
],
|
||||
body: {
|
||||
type: 'BlockStatement',
|
||||
start: 21,
|
||||
end: 37,
|
||||
body: [
|
||||
@ -1585,9 +1582,8 @@ const key = 'c'`
|
||||
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
||||
type: 'NoneCodeNode',
|
||||
start: 106,
|
||||
end: 168,
|
||||
value:
|
||||
' /* this is \n a comment \n spanning a few lines */\n ',
|
||||
end: 166,
|
||||
value: ' /* this is\n a comment\n spanning a few lines */\n ',
|
||||
})
|
||||
})
|
||||
it('comments in a pipe expression', () => {
|
||||
|
@ -5,7 +5,7 @@ import { Token } from './tokeniser'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/bindings/KclError'
|
||||
|
||||
const rangeTypeFix = (ranges: number[][]): [number, number][] =>
|
||||
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
|
||||
ranges.map(([start, end]) => [start, end])
|
||||
|
||||
export const parser_wasm = (code: string): Program => {
|
||||
|
@ -22,7 +22,6 @@ export type SyntaxType =
|
||||
| 'BinaryExpression'
|
||||
| 'CallExpression'
|
||||
| 'Identifier'
|
||||
| 'BlockStatement'
|
||||
| 'ReturnStatement'
|
||||
| 'VariableDeclaration'
|
||||
| 'VariableDeclarator'
|
||||
|
@ -14,48 +14,49 @@ const mySketch001 = startSketchAt([0, 0])
|
||||
// |> rx(45, %)
|
||||
show(mySketch001)`
|
||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
||||
// @ts-ignore
|
||||
const shown = programMemory?.return?.map(
|
||||
// @ts-ignore
|
||||
(a) => programMemory?.root?.[a.name]
|
||||
)
|
||||
expect(shown).toEqual([
|
||||
{
|
||||
type: 'sketchGroup',
|
||||
start: {
|
||||
type: 'base',
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: '66366561-6465-4734-a463-366330356563',
|
||||
id: expect.any(String),
|
||||
sourceRange: [21, 42],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
value: [
|
||||
{
|
||||
type: 'toPoint',
|
||||
name: '',
|
||||
to: [-1.59, -1.54],
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [48, 73],
|
||||
id: '30366338-6462-4330-a364-303935626163',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'toPoint',
|
||||
to: [0.46, -5.82],
|
||||
from: [-1.59, -1.54],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
sourceRange: [79, 103],
|
||||
id: '32653334-6331-4231-b162-663334363535',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
],
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
id: '39643164-6130-4734-b432-623638393262',
|
||||
__meta: [{ sourceRange: [21, 42], pathToNode: [] }],
|
||||
id: expect.any(String),
|
||||
__meta: [{ sourceRange: [21, 42] }],
|
||||
},
|
||||
])
|
||||
})
|
||||
@ -69,21 +70,20 @@ const mySketch001 = startSketchAt([0, 0])
|
||||
|> extrude(2, %)
|
||||
show(mySketch001)`
|
||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
||||
// @ts-ignore
|
||||
const shown = programMemory?.return?.map(
|
||||
// @ts-ignore
|
||||
(a) => programMemory?.root?.[a.name]
|
||||
)
|
||||
expect(shown).toEqual([
|
||||
{
|
||||
type: 'extrudeGroup',
|
||||
id: '65383433-3839-4333-b836-343263636638',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
height: 2,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
__meta: [
|
||||
{ sourceRange: [127, 140], pathToNode: [] },
|
||||
{ sourceRange: [21, 42], pathToNode: [] },
|
||||
],
|
||||
__meta: [{ sourceRange: [21, 42] }],
|
||||
},
|
||||
])
|
||||
})
|
||||
@ -110,33 +110,29 @@ const sk2 = startSketchAt([0, 0])
|
||||
|
||||
show(theExtrude, sk2)`
|
||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
||||
// @ts-ignore
|
||||
const geos = programMemory?.return?.map(
|
||||
// @ts-ignore
|
||||
({ name }) => programMemory?.root?.[name]
|
||||
)
|
||||
expect(geos).toEqual([
|
||||
{
|
||||
type: 'extrudeGroup',
|
||||
id: '63333330-3631-4230-b664-623132643731',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
height: 2,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
__meta: [
|
||||
{ sourceRange: [212, 227], pathToNode: [] },
|
||||
{ sourceRange: [13, 34], pathToNode: [] },
|
||||
],
|
||||
__meta: [{ sourceRange: [13, 34] }],
|
||||
},
|
||||
{
|
||||
type: 'extrudeGroup',
|
||||
id: '33316639-3438-4661-a334-663262383737',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
height: 2,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
__meta: [
|
||||
{ sourceRange: [453, 466], pathToNode: [] },
|
||||
{ sourceRange: [302, 323], pathToNode: [] },
|
||||
],
|
||||
__meta: [{ sourceRange: [302, 323] }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
@ -5,7 +5,7 @@ import { ProgramMemory } from './executor'
|
||||
import { initPromise } from './rust'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { vi } from 'vitest'
|
||||
import { KCLUndefinedValueError } from './errors'
|
||||
import { KCLError } from './errors'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
|
||||
@ -30,29 +30,6 @@ const newVar = myVar + 1`
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar.value).toBe('a str another str')
|
||||
})
|
||||
it('test with function call', async () => {
|
||||
const code = `
|
||||
const myVar = "hello"
|
||||
log(5, myVar)`
|
||||
const programMemoryOverride: ProgramMemory['root'] = {
|
||||
log: {
|
||||
type: 'userVal',
|
||||
value: vi.fn(),
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [0, 0],
|
||||
pathToNode: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
const { root } = await enginelessExecutor(parser_wasm(code), {
|
||||
root: programMemoryOverride,
|
||||
pendingMemory: {},
|
||||
})
|
||||
expect(root.myVar.value).toBe('hello')
|
||||
expect(programMemoryOverride.log.value).toHaveBeenCalledWith(5, 'hello')
|
||||
})
|
||||
it('fn funcN = () => {} execute', async () => {
|
||||
const { root } = await exe(
|
||||
[
|
||||
@ -84,8 +61,7 @@ show(mySketch)
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [43, 80],
|
||||
id: '37333036-3033-4432-b530-643030303837',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
name: 'myPath',
|
||||
},
|
||||
@ -93,10 +69,10 @@ show(mySketch)
|
||||
type: 'toPoint',
|
||||
to: [2, 3],
|
||||
from: [0, 2],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
sourceRange: [86, 102],
|
||||
id: '32343136-3330-4134-a462-376437386365',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -105,8 +81,7 @@ show(mySketch)
|
||||
from: [2, 3],
|
||||
__geoMeta: {
|
||||
sourceRange: [108, 151],
|
||||
id: '32306132-6130-4138-b832-636363326330',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
name: 'rightPath',
|
||||
},
|
||||
@ -170,13 +145,12 @@ show(mySketch)
|
||||
expect(root.mySk1).toEqual({
|
||||
type: 'sketchGroup',
|
||||
start: {
|
||||
type: 'base',
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: '37663863-3664-4366-a637-623739336334',
|
||||
id: expect.any(String),
|
||||
sourceRange: [14, 34],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
value: [
|
||||
@ -184,10 +158,10 @@ show(mySketch)
|
||||
type: 'toPoint',
|
||||
to: [1, 1],
|
||||
from: [0, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
sourceRange: [40, 56],
|
||||
id: '34356231-3362-4363-b935-393033353034',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -196,8 +170,7 @@ show(mySketch)
|
||||
from: [1, 1],
|
||||
__geoMeta: {
|
||||
sourceRange: [62, 100],
|
||||
id: '39623339-3538-4366-b633-356630326639',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
name: 'myPath',
|
||||
},
|
||||
@ -205,17 +178,17 @@ show(mySketch)
|
||||
type: 'toPoint',
|
||||
to: [1, 1],
|
||||
from: [0, 1],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
sourceRange: [106, 122],
|
||||
id: '30636135-6232-4335-b665-366562303161',
|
||||
pathToNode: [],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
],
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
id: '30376661-3039-4965-b532-653665313731',
|
||||
__meta: [{ sourceRange: [14, 34], pathToNode: [] }],
|
||||
id: expect.any(String),
|
||||
__meta: [{ sourceRange: [14, 34] }],
|
||||
})
|
||||
})
|
||||
it('execute array expression', async () => {
|
||||
@ -230,13 +203,6 @@ show(mySketch)
|
||||
value: 3,
|
||||
__meta: [
|
||||
{
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclaration'],
|
||||
],
|
||||
sourceRange: [14, 15],
|
||||
},
|
||||
],
|
||||
@ -246,25 +212,8 @@ show(mySketch)
|
||||
value: [1, '2', 3, 9],
|
||||
__meta: [
|
||||
{
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclaration'],
|
||||
],
|
||||
sourceRange: [27, 49],
|
||||
},
|
||||
{
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclaration'],
|
||||
],
|
||||
sourceRange: [14, 15],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
@ -280,13 +229,6 @@ show(mySketch)
|
||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
||||
__meta: [
|
||||
{
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclaration'],
|
||||
],
|
||||
sourceRange: [27, 83],
|
||||
},
|
||||
],
|
||||
@ -302,13 +244,6 @@ show(mySketch)
|
||||
value: '123',
|
||||
__meta: [
|
||||
{
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclaration'],
|
||||
],
|
||||
sourceRange: [41, 50],
|
||||
},
|
||||
],
|
||||
@ -451,17 +386,18 @@ const theExtrude = startSketchAt([0, 0])
|
||||
|> extrude(4, %)
|
||||
show(theExtrude)`
|
||||
await expect(exe(code)).rejects.toEqual(
|
||||
new KCLUndefinedValueError('Memory item myVarZ not found', [[100, 106]])
|
||||
new KCLError(
|
||||
'undefined_value',
|
||||
'memory item key `myVarZ` is not defined',
|
||||
[[100, 106]]
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// helpers
|
||||
|
||||
async function exe(
|
||||
code: string,
|
||||
programMemory: ProgramMemory = { root: {}, pendingMemory: {} }
|
||||
) {
|
||||
async function exe(code: string, programMemory: ProgramMemory = { root: {} }) {
|
||||
const ast = parser_wasm(code)
|
||||
|
||||
const result = await enginelessExecutor(ast, programMemory)
|
||||
|
@ -1,35 +1,19 @@
|
||||
import {
|
||||
Program,
|
||||
BinaryPart,
|
||||
BinaryExpression,
|
||||
PipeExpression,
|
||||
ObjectExpression,
|
||||
MemberExpression,
|
||||
Identifier,
|
||||
CallExpression,
|
||||
ArrayExpression,
|
||||
UnaryExpression,
|
||||
} from './abstractSyntaxTreeTypes'
|
||||
import { InternalFnNames } from './std/stdTypes'
|
||||
import { internalFns } from './std/std'
|
||||
import {
|
||||
KCLUndefinedValueError,
|
||||
KCLValueAlreadyDefined,
|
||||
KCLSyntaxError,
|
||||
KCLSemanticError,
|
||||
KCLTypeError,
|
||||
} from './errors'
|
||||
import { Program } from './abstractSyntaxTreeTypes'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
ArtifactMap,
|
||||
SourceRangeMap,
|
||||
} from './std/engineConnection'
|
||||
import { ProgramReturn } from '../wasm-lib/bindings/ProgramReturn'
|
||||
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/bindings/KclError'
|
||||
import { rangeTypeFix } from './abstractSyntaxTree'
|
||||
|
||||
export type SourceRange = [number, number]
|
||||
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
|
||||
export type Metadata = {
|
||||
sourceRange: SourceRange
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
export type Position = [number, number, number]
|
||||
export type Rotation = [number, number, number, number]
|
||||
@ -41,7 +25,6 @@ interface BasePath {
|
||||
__geoMeta: {
|
||||
id: string
|
||||
sourceRange: SourceRange
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,9 +51,7 @@ export interface AngledLineTo extends BasePath {
|
||||
interface GeoMeta {
|
||||
__geoMeta: {
|
||||
id: string
|
||||
refId?: string
|
||||
sourceRange: SourceRange
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,69 +99,16 @@ type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
|
||||
interface Memory {
|
||||
[key: string]: MemoryItem
|
||||
}
|
||||
interface PendingMemory {
|
||||
[key: string]: Promise<MemoryItem>
|
||||
}
|
||||
|
||||
export interface ProgramMemory {
|
||||
root: Memory
|
||||
pendingMemory: Partial<PendingMemory>
|
||||
return?: Identifier[]
|
||||
}
|
||||
|
||||
const addItemToMemory = (
|
||||
programMemory: ProgramMemory,
|
||||
key: string,
|
||||
sourceRange: [[number, number]],
|
||||
value: MemoryItem | Promise<MemoryItem>
|
||||
) => {
|
||||
const _programMemory = programMemory
|
||||
if (_programMemory.root[key] || _programMemory.pendingMemory[key]) {
|
||||
throw new KCLValueAlreadyDefined(key, sourceRange)
|
||||
}
|
||||
if (value instanceof Promise) {
|
||||
_programMemory.pendingMemory[key] = value
|
||||
value.then((resolvedValue) => {
|
||||
_programMemory.root[key] = resolvedValue
|
||||
delete _programMemory.pendingMemory[key]
|
||||
})
|
||||
} else {
|
||||
_programMemory.root[key] = value
|
||||
}
|
||||
return _programMemory
|
||||
}
|
||||
|
||||
const promisifyMemoryItem = async (obj: MemoryItem) => {
|
||||
if (obj.value instanceof Promise) {
|
||||
const resolvedGuy = await obj.value
|
||||
return {
|
||||
...obj,
|
||||
value: resolvedGuy,
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
const getMemoryItem = async (
|
||||
programMemory: ProgramMemory,
|
||||
key: string,
|
||||
sourceRanges: [number, number][]
|
||||
): Promise<MemoryItem> => {
|
||||
if (programMemory.root[key]) {
|
||||
return programMemory.root[key]
|
||||
}
|
||||
if (programMemory.pendingMemory[key]) {
|
||||
return programMemory.pendingMemory[key] as Promise<MemoryItem>
|
||||
}
|
||||
throw new KCLUndefinedValueError(`Memory item ${key} not found`, sourceRanges)
|
||||
return?: ProgramReturn
|
||||
}
|
||||
|
||||
export const executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {}, pendingMemory: {} },
|
||||
programMemory: ProgramMemory = { root: {} },
|
||||
engineCommandManager: EngineCommandManager,
|
||||
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
|
||||
previousPathToNode: PathToNode = [],
|
||||
// work around while the gemotry is still be stored on the frontend
|
||||
// will be removed when the stream UI is added.
|
||||
tempMapCallback: (a: {
|
||||
@ -192,9 +120,7 @@ export const executor = async (
|
||||
const _programMemory = await _executor(
|
||||
node,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
options,
|
||||
previousPathToNode
|
||||
engineCommandManager
|
||||
)
|
||||
const { artifactMap, sourceRangeMap } =
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
@ -206,840 +132,27 @@ export const executor = async (
|
||||
|
||||
export const _executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {}, pendingMemory: {} },
|
||||
engineCommandManager: EngineCommandManager,
|
||||
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
|
||||
previousPathToNode: PathToNode = []
|
||||
programMemory: ProgramMemory = { root: {} },
|
||||
engineCommandManager: EngineCommandManager
|
||||
): Promise<ProgramMemory> => {
|
||||
let _programMemory: ProgramMemory = {
|
||||
root: {
|
||||
...programMemory.root,
|
||||
},
|
||||
pendingMemory: {
|
||||
...programMemory.pendingMemory,
|
||||
},
|
||||
return: programMemory.return,
|
||||
}
|
||||
const { body } = node
|
||||
const proms: Promise<any>[] = []
|
||||
for (let bodyIndex = 0; bodyIndex < body.length; bodyIndex++) {
|
||||
const statement = body[bodyIndex]
|
||||
if (statement.type === 'VariableDeclaration') {
|
||||
for (let index = 0; index < statement.declarations.length; index++) {
|
||||
const declaration = statement.declarations[index]
|
||||
const variableName = declaration.id.name
|
||||
const pathToNode: PathToNode = [
|
||||
...previousPathToNode,
|
||||
['body', ''],
|
||||
[bodyIndex, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[index, 'index'],
|
||||
['init', 'VariableDeclaration'],
|
||||
]
|
||||
const sourceRange: SourceRange = [
|
||||
declaration.init.start,
|
||||
declaration.init.end,
|
||||
]
|
||||
const __meta: Metadata[] = [
|
||||
{
|
||||
pathToNode,
|
||||
sourceRange,
|
||||
},
|
||||
]
|
||||
try {
|
||||
const memory: ProgramMemory = await execute_wasm(
|
||||
JSON.stringify(node),
|
||||
JSON.stringify(programMemory),
|
||||
engineCommandManager
|
||||
)
|
||||
return memory
|
||||
} catch (e: any) {
|
||||
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)
|
||||
)
|
||||
|
||||
if (declaration.init.type === 'PipeExpression') {
|
||||
const prom = getPipeExpressionResult(
|
||||
declaration.init,
|
||||
_programMemory,
|
||||
engineCommandManager,
|
||||
pathToNode
|
||||
)
|
||||
proms.push(prom)
|
||||
const value = await prom
|
||||
if (value?.type === 'sketchGroup' || value?.type === 'extrudeGroup') {
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
value
|
||||
)
|
||||
} else {
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value,
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
}
|
||||
} else if (declaration.init.type === 'Identifier') {
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: _programMemory.root[declaration.init.name].value,
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'Literal') {
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: declaration.init.value,
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'BinaryExpression') {
|
||||
const prom = getBinaryExpressionResult(
|
||||
declaration.init,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
__meta,
|
||||
})
|
||||
)
|
||||
} else if (declaration.init.type === 'UnaryExpression') {
|
||||
const prom = getUnaryExpressionResult(
|
||||
declaration.init,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
__meta,
|
||||
})
|
||||
)
|
||||
} else if (declaration.init.type === 'ArrayExpression') {
|
||||
const valueInfo: Promise<{ value: any; __meta?: Metadata }>[] =
|
||||
declaration.init.elements.map(
|
||||
async (element): Promise<{ value: any; __meta?: Metadata }> => {
|
||||
if (element.type === 'Literal') {
|
||||
return {
|
||||
value: element.value,
|
||||
}
|
||||
} else if (element.type === 'BinaryExpression') {
|
||||
const prom = getBinaryExpressionResult(
|
||||
element,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
return {
|
||||
value: await prom,
|
||||
}
|
||||
} else if (element.type === 'PipeExpression') {
|
||||
const prom = getPipeExpressionResult(
|
||||
element,
|
||||
_programMemory,
|
||||
engineCommandManager,
|
||||
pathToNode
|
||||
)
|
||||
proms.push(prom)
|
||||
return {
|
||||
value: await prom,
|
||||
}
|
||||
} else if (element.type === 'Identifier') {
|
||||
const node = await getMemoryItem(
|
||||
_programMemory,
|
||||
element.name,
|
||||
[[element.start, element.end]]
|
||||
)
|
||||
return {
|
||||
value: node.value,
|
||||
__meta: node.__meta[node.__meta.length - 1],
|
||||
}
|
||||
} else if (element.type === 'UnaryExpression') {
|
||||
const prom = getUnaryExpressionResult(
|
||||
element,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
return {
|
||||
value: await prom,
|
||||
}
|
||||
} else {
|
||||
throw new KCLSyntaxError(
|
||||
`Unexpected element type ${element.type} in array expression`,
|
||||
// TODO: Refactor this whole block into a `switch` so that we have a specific
|
||||
// type here and can put a sourceRange.
|
||||
[]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
const awaitedValueInfo = await Promise.all(valueInfo)
|
||||
const meta = awaitedValueInfo
|
||||
.filter(({ __meta }) => __meta)
|
||||
.map(({ __meta }) => __meta) as Metadata[]
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: awaitedValueInfo.map(({ value }) => value),
|
||||
__meta: [...__meta, ...meta],
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'ObjectExpression') {
|
||||
const prom = executeObjectExpression(
|
||||
_programMemory,
|
||||
declaration.init,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
__meta,
|
||||
})
|
||||
)
|
||||
} else if (declaration.init.type === 'FunctionExpression') {
|
||||
const fnInit = declaration.init
|
||||
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
declaration.id.name,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: async (...args: any[]) => {
|
||||
let fnMemory: ProgramMemory = {
|
||||
root: {
|
||||
..._programMemory.root,
|
||||
},
|
||||
pendingMemory: {
|
||||
..._programMemory.pendingMemory,
|
||||
},
|
||||
}
|
||||
if (args.length > fnInit.params.length) {
|
||||
throw new KCLSyntaxError(
|
||||
`Too many arguments passed to function ${declaration.id.name}`,
|
||||
[[declaration.start, declaration.end]]
|
||||
)
|
||||
} else if (args.length < fnInit.params.length) {
|
||||
throw new KCLSyntaxError(
|
||||
`Too few arguments passed to function ${declaration.id.name}`,
|
||||
[[declaration.start, declaration.end]]
|
||||
)
|
||||
}
|
||||
fnInit.params.forEach((param, index) => {
|
||||
fnMemory = addItemToMemory(
|
||||
fnMemory,
|
||||
param.name,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: args[index],
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
})
|
||||
const prom = _executor(
|
||||
fnInit.body,
|
||||
fnMemory,
|
||||
engineCommandManager,
|
||||
{
|
||||
bodyType: 'block',
|
||||
}
|
||||
)
|
||||
proms.push(prom)
|
||||
const result = (await prom).return
|
||||
return result
|
||||
},
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'MemberExpression') {
|
||||
await Promise.all([...proms]) // TODO wait for previous promises, does that makes sense?
|
||||
const prom = getMemberExpressionResult(
|
||||
declaration.init,
|
||||
_programMemory
|
||||
)
|
||||
proms.push(prom)
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
__meta,
|
||||
})
|
||||
)
|
||||
} else if (declaration.init.type === 'CallExpression') {
|
||||
const prom = executeCallExpression(
|
||||
_programMemory,
|
||||
declaration.init,
|
||||
engineCommandManager,
|
||||
previousPathToNode
|
||||
)
|
||||
proms.push(prom)
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
prom.then((a) => {
|
||||
return a?.type === 'sketchGroup' || a?.type === 'extrudeGroup'
|
||||
? a
|
||||
: {
|
||||
type: 'userVal',
|
||||
value: a,
|
||||
__meta,
|
||||
}
|
||||
})
|
||||
)
|
||||
} else {
|
||||
throw new KCLSyntaxError(
|
||||
'Unsupported declaration type: ' + declaration.init.type,
|
||||
[[declaration.start, declaration.end]]
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (statement.type === 'ExpressionStatement') {
|
||||
const expression = statement.expression
|
||||
if (expression.type === 'CallExpression') {
|
||||
const functionName = expression.callee.name
|
||||
const args = expression.arguments.map((arg) => {
|
||||
if (arg.type === 'Literal') {
|
||||
return arg.value
|
||||
} else if (arg.type === 'Identifier') {
|
||||
return _programMemory.root[arg.name]?.value
|
||||
}
|
||||
})
|
||||
if ('show' === functionName) {
|
||||
if (options.bodyType !== 'root') {
|
||||
throw new KCLSemanticError(
|
||||
`Cannot call ${functionName} outside of a root`,
|
||||
[[statement.start, statement.end]]
|
||||
)
|
||||
}
|
||||
_programMemory.return = expression.arguments as any // todo memory redo
|
||||
} else {
|
||||
if (_programMemory.root[functionName] === undefined) {
|
||||
throw new KCLSemanticError(`No such name ${functionName} defined`, [
|
||||
[statement.start, statement.end],
|
||||
])
|
||||
}
|
||||
_programMemory.root[functionName].value(...args)
|
||||
}
|
||||
}
|
||||
} else if (statement.type === 'ReturnStatement') {
|
||||
if (statement.argument.type === 'BinaryExpression') {
|
||||
const prom = getBinaryExpressionResult(
|
||||
statement.argument,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
_programMemory.return = await prom
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(proms)
|
||||
return _programMemory
|
||||
}
|
||||
|
||||
function getMemberExpressionResult(
|
||||
expression: MemberExpression,
|
||||
programMemory: ProgramMemory
|
||||
) {
|
||||
const propertyName = (
|
||||
expression.property.type === 'Identifier'
|
||||
? expression.property.name
|
||||
: expression.property.value
|
||||
) as any
|
||||
const object: any =
|
||||
expression.object.type === 'MemberExpression'
|
||||
? getMemberExpressionResult(expression.object, programMemory)
|
||||
: programMemory.root[expression.object.name]?.value
|
||||
return object?.[propertyName]
|
||||
}
|
||||
|
||||
async function getBinaryExpressionResult(
|
||||
expression: BinaryExpression,
|
||||
programMemory: ProgramMemory,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
pipeInfo: {
|
||||
isInPipe: boolean
|
||||
previousResults: any[]
|
||||
expressionIndex: number
|
||||
body: PipeExpression['body']
|
||||
sourceRangeOverride?: SourceRange
|
||||
} = {
|
||||
isInPipe: false,
|
||||
previousResults: [],
|
||||
expressionIndex: 0,
|
||||
body: [],
|
||||
}
|
||||
) {
|
||||
const _pipeInfo = {
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
const left = await getBinaryPartResult(
|
||||
expression.left,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
const right = await getBinaryPartResult(
|
||||
expression.right,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
if (expression.operator === '+') return left + right
|
||||
if (expression.operator === '-') return left - right
|
||||
if (expression.operator === '*') return left * right
|
||||
if (expression.operator === '/') return left / right
|
||||
if (expression.operator === '%') return left % right
|
||||
}
|
||||
|
||||
async function getBinaryPartResult(
|
||||
part: BinaryPart,
|
||||
programMemory: ProgramMemory,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
pipeInfo: {
|
||||
isInPipe: boolean
|
||||
previousResults: any[]
|
||||
expressionIndex: number
|
||||
body: PipeExpression['body']
|
||||
sourceRangeOverride?: SourceRange
|
||||
} = {
|
||||
isInPipe: false,
|
||||
previousResults: [],
|
||||
expressionIndex: 0,
|
||||
body: [],
|
||||
}
|
||||
): Promise<any> {
|
||||
const _pipeInfo = {
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
if (part.type === 'Literal') {
|
||||
return part.value
|
||||
} else if (part.type === 'Identifier') {
|
||||
return programMemory.root[part.name].value
|
||||
} else if (part.type === 'BinaryExpression') {
|
||||
const prom = getBinaryExpressionResult(
|
||||
part,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
const result = await prom
|
||||
return result
|
||||
} else if (part.type === 'CallExpression') {
|
||||
const result = await executeCallExpression(
|
||||
programMemory,
|
||||
part,
|
||||
engineCommandManager,
|
||||
[],
|
||||
_pipeInfo
|
||||
)
|
||||
return result
|
||||
console.log(kclError)
|
||||
throw kclError
|
||||
}
|
||||
}
|
||||
|
||||
async function getUnaryExpressionResult(
|
||||
expression: UnaryExpression,
|
||||
programMemory: ProgramMemory,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
pipeInfo: {
|
||||
isInPipe: boolean
|
||||
previousResults: any[]
|
||||
expressionIndex: number
|
||||
body: PipeExpression['body']
|
||||
sourceRangeOverride?: SourceRange
|
||||
} = {
|
||||
isInPipe: false,
|
||||
previousResults: [],
|
||||
expressionIndex: 0,
|
||||
body: [],
|
||||
}
|
||||
) {
|
||||
return -(await getBinaryPartResult(
|
||||
expression.argument,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
{
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
async function getPipeExpressionResult(
|
||||
expression: PipeExpression,
|
||||
programMemory: ProgramMemory,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
previousPathToNode: PathToNode = []
|
||||
) {
|
||||
const executedBody = await executePipeBody(
|
||||
expression.body,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
previousPathToNode
|
||||
)
|
||||
const result = executedBody[executedBody.length - 1]
|
||||
return result
|
||||
}
|
||||
|
||||
async function executePipeBody(
|
||||
body: PipeExpression['body'],
|
||||
programMemory: ProgramMemory,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
previousPathToNode: PathToNode = [],
|
||||
expressionIndex = 0,
|
||||
previousResults: any[] = []
|
||||
): Promise<any[]> {
|
||||
if (expressionIndex === body.length) {
|
||||
return previousResults
|
||||
}
|
||||
const expression = body[expressionIndex]
|
||||
if (expression.type === 'BinaryExpression') {
|
||||
const result = await getBinaryExpressionResult(
|
||||
expression,
|
||||
programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
return executePipeBody(
|
||||
body,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
previousPathToNode,
|
||||
expressionIndex + 1,
|
||||
[...previousResults, result]
|
||||
)
|
||||
} else if (expression.type === 'CallExpression') {
|
||||
return await executeCallExpression(
|
||||
programMemory,
|
||||
expression,
|
||||
engineCommandManager,
|
||||
previousPathToNode,
|
||||
{
|
||||
isInPipe: true,
|
||||
previousResults,
|
||||
expressionIndex,
|
||||
body,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
throw new KCLSyntaxError('Invalid pipe expression', [
|
||||
[expression.start, expression.end],
|
||||
])
|
||||
}
|
||||
|
||||
async function executeObjectExpression(
|
||||
_programMemory: ProgramMemory,
|
||||
objExp: ObjectExpression,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
pipeInfo: {
|
||||
isInPipe: boolean
|
||||
previousResults: any[]
|
||||
expressionIndex: number
|
||||
body: PipeExpression['body']
|
||||
sourceRangeOverride?: SourceRange
|
||||
} = {
|
||||
isInPipe: false,
|
||||
previousResults: [],
|
||||
expressionIndex: 0,
|
||||
body: [],
|
||||
}
|
||||
) {
|
||||
const _pipeInfo = {
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
const obj: { [key: string]: any } = {}
|
||||
const proms: Promise<any>[] = []
|
||||
objExp.properties.forEach(async (property) => {
|
||||
if (property.type === 'ObjectProperty') {
|
||||
if (property.value.type === 'Literal') {
|
||||
obj[property.key.name] = property.value.value
|
||||
} else if (property.value.type === 'BinaryExpression') {
|
||||
const prom = getBinaryExpressionResult(
|
||||
property.value,
|
||||
_programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
proms.push(prom)
|
||||
obj[property.key.name] = await prom
|
||||
} else if (property.value.type === 'PipeExpression') {
|
||||
const prom = getPipeExpressionResult(
|
||||
property.value,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
obj[property.key.name] = await prom
|
||||
} else if (property.value.type === 'Identifier') {
|
||||
obj[property.key.name] = (
|
||||
await getMemoryItem(_programMemory, property.value.name, [
|
||||
[property.value.start, property.value.end],
|
||||
])
|
||||
).value
|
||||
} else if (property.value.type === 'ObjectExpression') {
|
||||
const prom = executeObjectExpression(
|
||||
_programMemory,
|
||||
property.value,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
obj[property.key.name] = await prom
|
||||
} else if (property.value.type === 'ArrayExpression') {
|
||||
const prom = executeArrayExpression(
|
||||
_programMemory,
|
||||
property.value,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
obj[property.key.name] = await prom
|
||||
} else if (property.value.type === 'CallExpression') {
|
||||
const prom = executeCallExpression(
|
||||
_programMemory,
|
||||
property.value,
|
||||
engineCommandManager,
|
||||
[],
|
||||
_pipeInfo
|
||||
)
|
||||
proms.push(prom)
|
||||
const result = await prom
|
||||
obj[property.key.name] = result
|
||||
} else if (property.value.type === 'UnaryExpression') {
|
||||
const prom = getUnaryExpressionResult(
|
||||
property.value,
|
||||
_programMemory,
|
||||
engineCommandManager
|
||||
)
|
||||
proms.push(prom)
|
||||
obj[property.key.name] = await prom
|
||||
} else {
|
||||
throw new KCLSyntaxError(
|
||||
`Unexpected property type ${property.value.type} in object expression`,
|
||||
[[property.value.start, property.value.end]]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new KCLSyntaxError(
|
||||
`Unexpected property type ${property.type} in object expression`,
|
||||
[[property.value.start, property.value.end]]
|
||||
)
|
||||
}
|
||||
})
|
||||
await Promise.all(proms)
|
||||
return obj
|
||||
}
|
||||
|
||||
async function executeArrayExpression(
|
||||
_programMemory: ProgramMemory,
|
||||
arrExp: ArrayExpression,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
pipeInfo: {
|
||||
isInPipe: boolean
|
||||
previousResults: any[]
|
||||
expressionIndex: number
|
||||
body: PipeExpression['body']
|
||||
sourceRangeOverride?: SourceRange
|
||||
} = {
|
||||
isInPipe: false,
|
||||
previousResults: [],
|
||||
expressionIndex: 0,
|
||||
body: [],
|
||||
}
|
||||
) {
|
||||
const _pipeInfo = {
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
return await Promise.all(
|
||||
arrExp.elements.map((el) => {
|
||||
if (el.type === 'Literal') {
|
||||
return el.value
|
||||
} else if (el.type === 'Identifier') {
|
||||
return _programMemory.root?.[el.name]?.value
|
||||
} else if (el.type === 'BinaryExpression') {
|
||||
return getBinaryExpressionResult(
|
||||
el,
|
||||
_programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
} else if (el.type === 'ObjectExpression') {
|
||||
return executeObjectExpression(_programMemory, el, engineCommandManager)
|
||||
} else if (el.type === 'CallExpression') {
|
||||
const result: any = executeCallExpression(
|
||||
_programMemory,
|
||||
el,
|
||||
engineCommandManager,
|
||||
[],
|
||||
_pipeInfo
|
||||
)
|
||||
return result
|
||||
} else if (el.type === 'UnaryExpression') {
|
||||
return getUnaryExpressionResult(
|
||||
el,
|
||||
_programMemory,
|
||||
engineCommandManager,
|
||||
{
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
)
|
||||
}
|
||||
throw new KCLTypeError('Invalid argument type', [[el.start, el.end]])
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function executeCallExpression(
|
||||
programMemory: ProgramMemory,
|
||||
expression: CallExpression,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
previousPathToNode: PathToNode = [],
|
||||
pipeInfo: {
|
||||
isInPipe: boolean
|
||||
previousResults: any[]
|
||||
expressionIndex: number
|
||||
body: PipeExpression['body']
|
||||
sourceRangeOverride?: SourceRange
|
||||
} = {
|
||||
isInPipe: false,
|
||||
previousResults: [],
|
||||
expressionIndex: 0,
|
||||
body: [],
|
||||
}
|
||||
) {
|
||||
const {
|
||||
isInPipe,
|
||||
previousResults,
|
||||
expressionIndex,
|
||||
body,
|
||||
sourceRangeOverride,
|
||||
} = pipeInfo
|
||||
const functionName = expression?.callee?.name
|
||||
const _pipeInfo = {
|
||||
...pipeInfo,
|
||||
isInPipe: false,
|
||||
}
|
||||
const fnArgs = await Promise.all(
|
||||
expression?.arguments?.map(async (arg) => {
|
||||
if (arg.type === 'Literal') {
|
||||
return arg.value
|
||||
} else if (arg.type === 'Identifier') {
|
||||
await new Promise((r) => setTimeout(r)) // push into next even loop, but also probably should fix this
|
||||
const temp = await getMemoryItem(programMemory, arg.name, [
|
||||
[arg.start, arg.end],
|
||||
])
|
||||
return temp?.type === 'userVal' ? temp.value : temp
|
||||
} else if (arg.type === 'PipeSubstitution') {
|
||||
return previousResults[expressionIndex - 1]
|
||||
} else if (arg.type === 'ArrayExpression') {
|
||||
return await executeArrayExpression(
|
||||
programMemory,
|
||||
arg,
|
||||
engineCommandManager,
|
||||
pipeInfo
|
||||
)
|
||||
} else if (arg.type === 'CallExpression') {
|
||||
const result: any = await executeCallExpression(
|
||||
programMemory,
|
||||
arg,
|
||||
engineCommandManager,
|
||||
previousPathToNode,
|
||||
_pipeInfo
|
||||
)
|
||||
return result
|
||||
} else if (arg.type === 'ObjectExpression') {
|
||||
return await executeObjectExpression(
|
||||
programMemory,
|
||||
arg,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
} else if (arg.type === 'UnaryExpression') {
|
||||
return getUnaryExpressionResult(
|
||||
arg,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
} else if (arg.type === 'BinaryExpression') {
|
||||
return getBinaryExpressionResult(
|
||||
arg,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
_pipeInfo
|
||||
)
|
||||
}
|
||||
throw new KCLSyntaxError('Invalid argument type in function call', [
|
||||
[arg.start, arg.end],
|
||||
])
|
||||
})
|
||||
)
|
||||
if (functionName in internalFns) {
|
||||
const fnNameWithSketchOrExtrude = functionName as InternalFnNames
|
||||
const result = await internalFns[fnNameWithSketchOrExtrude](
|
||||
{
|
||||
programMemory,
|
||||
sourceRange: sourceRangeOverride || [expression.start, expression.end],
|
||||
engineCommandManager,
|
||||
code: JSON.stringify(expression),
|
||||
},
|
||||
fnArgs[0],
|
||||
fnArgs[1],
|
||||
fnArgs[2]
|
||||
)
|
||||
return isInPipe
|
||||
? await executePipeBody(
|
||||
body,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
previousPathToNode,
|
||||
expressionIndex + 1,
|
||||
[...previousResults, result]
|
||||
)
|
||||
: result
|
||||
}
|
||||
const result = await programMemory.root[functionName].value(...fnArgs)
|
||||
return isInPipe
|
||||
? await executePipeBody(
|
||||
body,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
previousPathToNode,
|
||||
expressionIndex + 1,
|
||||
[...previousResults, result]
|
||||
)
|
||||
: result
|
||||
}
|
||||
|
@ -182,14 +182,14 @@ describe('Testing moveValueIntoNewVariable', () => {
|
||||
const code = `${fn('def')}${fn('ghi')}${fn('jkl')}${fn('hmm')}
|
||||
const abc = 3
|
||||
const identifierGuy = 5
|
||||
const yo = 5 + 6
|
||||
const part001 = startSketchAt([-1.2, 4.83])
|
||||
|> line([2.8, 0], %)
|
||||
|> angledLine([100 + 100, 3.09], %)
|
||||
|> angledLine([abc, 3.09], %)
|
||||
|> angledLine([def('yo'), 3.09], %)
|
||||
|> angledLine([def(yo), 3.09], %)
|
||||
|> angledLine([ghi(%), 3.09], %)
|
||||
|> angledLine([jkl('yo') + 2, 3.09], %)
|
||||
const yo = 5 + 6
|
||||
|> angledLine([jkl(yo) + 2, 3.09], %)
|
||||
const yo2 = hmm([identifierGuy + 5])
|
||||
show(part001)`
|
||||
it('should move a binary expression into a new variable', async () => {
|
||||
@ -231,7 +231,7 @@ show(part001)`
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const newVar = def('yo')`)
|
||||
expect(newCode).toContain(`const newVar = def(yo)`)
|
||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||
})
|
||||
it('should move a binary expression with call expression into a new variable', async () => {
|
||||
@ -245,7 +245,7 @@ show(part001)`
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const newVar = jkl('yo') + 2`)
|
||||
expect(newCode).toContain(`const newVar = jkl(yo) + 2`)
|
||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||
})
|
||||
it('should move a identifier into a new variable', async () => {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Program } from './abstractSyntaxTreeTypes'
|
||||
import { recast_js } from '../wasm-lib/pkg/wasm_lib'
|
||||
import { recast_wasm } from '../wasm-lib/pkg/wasm_lib'
|
||||
|
||||
export const recast = (ast: Program): string => {
|
||||
try {
|
||||
const s: string = recast_js(JSON.stringify(ast))
|
||||
const s: string = recast_wasm(JSON.stringify(ast))
|
||||
return s
|
||||
} catch (e) {
|
||||
// TODO: do something real with the error.
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { SourceRange } from '../executor'
|
||||
import { Selections } from '../../useStore'
|
||||
import {
|
||||
VITE_KC_API_WS_MODELING_URL,
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS,
|
||||
} from '../../env'
|
||||
import { SourceRange } from 'lang/executor'
|
||||
import { Selections } from 'useStore'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { exportSave } from '../../lib/exportSave'
|
||||
import { exportSave } from 'lib/exportSave'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
interface ResultCommand {
|
||||
@ -557,6 +554,26 @@ export class EngineCommandManager {
|
||||
}
|
||||
return promise
|
||||
}
|
||||
sendModelingCommandFromWasm(
|
||||
id: string,
|
||||
rangeStr: string,
|
||||
commandStr: string
|
||||
): Promise<any> {
|
||||
if (id === undefined) {
|
||||
throw new Error('id is undefined')
|
||||
}
|
||||
if (rangeStr === undefined) {
|
||||
throw new Error('rangeStr is undefined')
|
||||
}
|
||||
if (commandStr === undefined) {
|
||||
throw new Error('commandStr is undefined')
|
||||
}
|
||||
console.log('sendModelingCommandFromWasm', id, rangeStr, commandStr)
|
||||
const command: EngineCommand = JSON.parse(commandStr)
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
|
||||
return this.sendModelingCommand({ id, range, command })
|
||||
}
|
||||
commandResult(id: string): Promise<any> {
|
||||
const command = this.artifactMap[id]
|
||||
if (!command) {
|
||||
|
@ -55,11 +55,9 @@ export const extrude: InternalFn = (
|
||||
__meta: [
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
{
|
||||
sourceRange: sketchVal.__meta[0].sourceRange,
|
||||
pathToNode: sketchVal.__meta[0].pathToNode,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -143,7 +143,6 @@ export const lineTo: SketchLineHelper = {
|
||||
__geoMeta: {
|
||||
sourceRange,
|
||||
id,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
}
|
||||
if ('tag' in data) {
|
||||
@ -280,7 +279,6 @@ export const line: SketchLineHelper = {
|
||||
__geoMeta: {
|
||||
id,
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
}
|
||||
if (data !== 'default' && 'tag' in data) {
|
||||
@ -674,7 +672,6 @@ export const angledLine: SketchLineHelper = {
|
||||
__geoMeta: {
|
||||
id,
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
}
|
||||
if ('tag' in data) {
|
||||
@ -1559,7 +1556,6 @@ export const close: InternalFn = (
|
||||
__geoMeta: {
|
||||
id,
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
}
|
||||
const newValue = [...sketchGroup.value]
|
||||
@ -1633,7 +1629,6 @@ export const startSketchAt: InternalFn = (
|
||||
__geoMeta: {
|
||||
id,
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
}
|
||||
if (data !== 'default' && 'tag' in data) {
|
||||
@ -1649,7 +1644,6 @@ export const startSketchAt: InternalFn = (
|
||||
__meta: [
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -391,6 +391,7 @@ show(part001)`
|
||||
type: 'toPoint',
|
||||
to: [5.62, 1.79],
|
||||
from: [3.48, 0.44],
|
||||
name: '',
|
||||
})
|
||||
})
|
||||
it('verify it works when the segment is in the `start` property', async () => {
|
||||
@ -400,6 +401,6 @@ show(part001)`
|
||||
programMemory.root['part001'] as SketchGroup,
|
||||
[index, index]
|
||||
).segment
|
||||
expect(segment).toEqual({ type: 'base', to: [0, 0.04], from: [0, 0.04] })
|
||||
expect(segment).toEqual({ to: [0, 0.04], from: [0, 0.04], name: '' })
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { Program } from '../lang/abstractSyntaxTreeTypes'
|
||||
import { ProgramMemory, _executor } from '../lang/executor'
|
||||
import { EngineCommandManager } from '../lang/std/engineConnection'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
EngineCommand,
|
||||
} from '../lang/std/engineConnection'
|
||||
import { SourceRange } from 'lang/executor'
|
||||
|
||||
class MockEngineCommandManager {
|
||||
constructor(mockParams: {
|
||||
@ -10,13 +14,43 @@ class MockEngineCommandManager {
|
||||
startNewSession() {}
|
||||
waitForAllCommands() {}
|
||||
waitForReady = new Promise<void>((resolve) => resolve())
|
||||
sendModelingCommand() {}
|
||||
sendModelingCommand({
|
||||
id,
|
||||
range,
|
||||
command,
|
||||
}: {
|
||||
id: string
|
||||
range: SourceRange
|
||||
command: EngineCommand
|
||||
}): Promise<any> {
|
||||
return Promise.resolve()
|
||||
}
|
||||
sendModelingCommandFromWasm(
|
||||
id: string,
|
||||
rangeStr: string,
|
||||
commandStr: string
|
||||
): Promise<any> {
|
||||
if (id === undefined) {
|
||||
throw new Error('id is undefined')
|
||||
}
|
||||
if (rangeStr === undefined) {
|
||||
throw new Error('rangeStr is undefined')
|
||||
}
|
||||
if (commandStr === undefined) {
|
||||
throw new Error('commandStr is undefined')
|
||||
}
|
||||
console.log('sendModelingCommandFromWasm', id, rangeStr, commandStr)
|
||||
const command: EngineCommand = JSON.parse(commandStr)
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
|
||||
return this.sendModelingCommand({ id, range, command })
|
||||
}
|
||||
sendSceneCommand() {}
|
||||
}
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Program,
|
||||
pm: ProgramMemory = { root: {}, pendingMemory: {} }
|
||||
pm: ProgramMemory = { root: {} }
|
||||
): Promise<ProgramMemory> {
|
||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
@ -31,7 +65,7 @@ export async function enginelessExecutor(
|
||||
|
||||
export async function executor(
|
||||
ast: Program,
|
||||
pm: ProgramMemory = { root: {}, pendingMemory: {} }
|
||||
pm: ProgramMemory = { root: {} }
|
||||
): Promise<ProgramMemory> {
|
||||
const engineCommandManager = new EngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
|
548
src/wasm-lib/Cargo.lock
generated
548
src/wasm-lib/Cargo.lock
generated
@ -80,12 +80,33 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.4.0"
|
||||
@ -118,6 +139,25 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.4.0"
|
||||
@ -130,6 +170,16 @@ version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.13"
|
||||
@ -157,6 +207,105 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
@ -176,6 +325,28 @@ version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-events"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-file"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f"
|
||||
dependencies = [
|
||||
"gloo-events",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
@ -189,6 +360,29 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
@ -263,6 +457,16 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
@ -302,6 +506,17 @@ dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -321,6 +536,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.31.1"
|
||||
@ -342,6 +567,29 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-display"
|
||||
version = "0.8.2"
|
||||
@ -394,6 +642,24 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
@ -431,6 +697,45 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.3"
|
||||
@ -519,35 +824,29 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.185"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
version = "1.0.185"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -572,6 +871,51 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structmeta"
|
||||
version = "0.2.0"
|
||||
@ -661,6 +1005,48 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "7.0.0"
|
||||
@ -684,6 +1070,31 @@ dependencies = [
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
@ -717,6 +1128,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.1"
|
||||
@ -728,6 +1145,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -759,6 +1182,18 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.87"
|
||||
@ -795,17 +1230,26 @@ dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
"bincode",
|
||||
"futures",
|
||||
"gloo-file",
|
||||
"gloo-utils",
|
||||
"http",
|
||||
"httparse",
|
||||
"js-sys",
|
||||
"kittycad",
|
||||
"lazy_static",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"ts-rs",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -849,6 +1293,72 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
|
@ -11,16 +11,50 @@ crate-type = ["cdylib"]
|
||||
anyhow = "1.0.75"
|
||||
backtrace = "0.3"
|
||||
bincode = "1.3.3"
|
||||
futures = { version = "0.3.28", optional = true }
|
||||
gloo-file = { version = "0.3.0", optional = true }
|
||||
gloo-utils = "0.2.0"
|
||||
http = "0.2.9"
|
||||
httparse = { version = "1.8.0", optional = true }
|
||||
js-sys = { version = "0.3.64", optional = true }
|
||||
kittycad = { version = "0.2.15", default-features = false, features = ["js"] }
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.7.1"
|
||||
serde = {version = "1.0.152", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.3.0"
|
||||
serde_json = "1.0.93"
|
||||
thiserror = "1.0.47"
|
||||
tokio = { version = "1.32.0", features = ["full"], optional = true }
|
||||
tokio-tungstenite = { version = "0.20.0", optional = true }
|
||||
ts-rs = { git = "https://github.com/kittycad/ts-rs.git", branch = "serde_json", 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"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.64"
|
||||
features = [
|
||||
"BinaryType",
|
||||
"Blob",
|
||||
"CloseEvent",
|
||||
"ErrorEvent",
|
||||
"FileReader",
|
||||
"MessageEvent",
|
||||
"ProgressEvent",
|
||||
"RtcConfiguration",
|
||||
"RtcIceServer",
|
||||
"RtcIceTransportPolicy",
|
||||
"RtcPeerConnection",
|
||||
"RtcSignalingState",
|
||||
"RtcSdpType",
|
||||
"RtcSessionDescriptionInit",
|
||||
"RtcPeerConnectionIceEvent",
|
||||
"RtcIceCandidate",
|
||||
"RtcDataChannel",
|
||||
"RtcDataChannelEvent",
|
||||
"RtcRtpTransceiver",
|
||||
"WebSocket",
|
||||
]
|
||||
optional = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
@ -28,3 +62,9 @@ debug = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
|
||||
[features]
|
||||
default = ["web"]
|
||||
web = ["dep:gloo-file", "dep:js-sys", "dep:web-sys"]
|
||||
noweb = ["dep:futures", "dep:httparse", "dep:tokio", "dep:tokio-tungstenite"]
|
||||
|
@ -3,8 +3,15 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Map;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
use crate::{
|
||||
engine::EngineConnection,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Program {
|
||||
@ -40,6 +47,90 @@ pub enum Value {
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
}
|
||||
|
||||
pub trait ValueMeta {
|
||||
fn start(&self) -> usize;
|
||||
|
||||
fn end(&self) -> usize;
|
||||
}
|
||||
|
||||
macro_rules! impl_value_meta {
|
||||
{$name:ident} => {
|
||||
impl ValueMeta for $name {
|
||||
fn start(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
|
||||
fn end(&self) -> usize {
|
||||
self.end
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for crate::executor::SourceRange {
|
||||
fn from(v: $name) -> Self {
|
||||
Self([v.start, v.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&$name> for crate::executor::SourceRange {
|
||||
fn from(v: &$name) -> Self {
|
||||
Self([v.start, v.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Box<$name>> for crate::executor::SourceRange {
|
||||
fn from(v: &Box<$name>) -> Self {
|
||||
Self([v.start, v.end])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn start(&self) -> usize {
|
||||
match self {
|
||||
Value::Literal(literal) => literal.start(),
|
||||
Value::Identifier(identifier) => identifier.start(),
|
||||
Value::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||
Value::FunctionExpression(function_expression) => function_expression.start(),
|
||||
Value::CallExpression(call_expression) => call_expression.start(),
|
||||
Value::PipeExpression(pipe_expression) => pipe_expression.start(),
|
||||
Value::PipeSubstitution(pipe_substitution) => pipe_substitution.start(),
|
||||
Value::ArrayExpression(array_expression) => array_expression.start(),
|
||||
Value::ObjectExpression(object_expression) => object_expression.start(),
|
||||
Value::MemberExpression(member_expression) => member_expression.start(),
|
||||
Value::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
match self {
|
||||
Value::Literal(literal) => literal.end(),
|
||||
Value::Identifier(identifier) => identifier.end(),
|
||||
Value::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||
Value::FunctionExpression(function_expression) => function_expression.end(),
|
||||
Value::CallExpression(call_expression) => call_expression.end(),
|
||||
Value::PipeExpression(pipe_expression) => pipe_expression.end(),
|
||||
Value::PipeSubstitution(pipe_substitution) => pipe_substitution.end(),
|
||||
Value::ArrayExpression(array_expression) => array_expression.end(),
|
||||
Value::ObjectExpression(object_expression) => object_expression.end(),
|
||||
Value::MemberExpression(member_expression) => member_expression.end(),
|
||||
Value::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for crate::executor::SourceRange {
|
||||
fn from(value: Value) -> Self {
|
||||
Self([value.start(), value.end()])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Value> for crate::executor::SourceRange {
|
||||
fn from(value: &Value) -> Self {
|
||||
Self([value.start(), value.end()])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -50,6 +141,71 @@ pub enum BinaryPart {
|
||||
CallExpression(Box<CallExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
}
|
||||
impl From<BinaryPart> for crate::executor::SourceRange {
|
||||
fn from(value: BinaryPart) -> Self {
|
||||
Self([value.start(), value.end()])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BinaryPart> for crate::executor::SourceRange {
|
||||
fn from(value: &BinaryPart) -> Self {
|
||||
Self([value.start(), value.end()])
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
pub fn start(&self) -> usize {
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => literal.start(),
|
||||
BinaryPart::Identifier(identifier) => identifier.start(),
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => literal.end(),
|
||||
BinaryPart::Identifier(identifier) => identifier.end(),
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_result(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
pipe_info.is_in_pipe = false;
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => Ok(literal.into()),
|
||||
BinaryPart::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
Ok(value.clone())
|
||||
}
|
||||
BinaryPart::BinaryExpression(binary_expression) => {
|
||||
binary_expression.get_result(memory, pipe_info, engine)
|
||||
}
|
||||
BinaryPart::CallExpression(call_expression) => {
|
||||
call_expression.execute(memory, pipe_info, 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
|
||||
),
|
||||
source_ranges: vec![unary_expression.into()],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -103,6 +259,8 @@ pub struct ExpressionStatement {
|
||||
pub expression: Value,
|
||||
}
|
||||
|
||||
impl_value_meta!(ExpressionStatement);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -114,6 +272,134 @@ pub struct CallExpression {
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
impl_value_meta!(CallExpression);
|
||||
|
||||
impl CallExpression {
|
||||
pub fn execute(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
let fn_name = self.callee.name.clone();
|
||||
|
||||
let mut fn_args: Vec<MemoryItem> = Vec::with_capacity(self.arguments.len());
|
||||
|
||||
for arg in &self.arguments {
|
||||
let result: MemoryItem = match arg {
|
||||
Value::Literal(literal) => literal.into(),
|
||||
Value::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
binary_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
pipe_info.is_in_pipe = false;
|
||||
call_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::UnaryExpression(unary_expression) => {
|
||||
unary_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::ObjectExpression(object_expression) => {
|
||||
object_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::ArrayExpression(array_expression) => {
|
||||
array_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::PipeExpression(pipe_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"PipeExpression not implemented here: {:?}",
|
||||
pipe_expression
|
||||
),
|
||||
source_ranges: vec![pipe_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::PipeSubstitution(pipe_substitution) => pipe_info
|
||||
.previous_results
|
||||
.get(&pipe_info.index - 1)
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
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
|
||||
),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"FunctionExpression not implemented here: {:?}",
|
||||
function_expression
|
||||
),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
fn_args.push(result);
|
||||
}
|
||||
|
||||
if let Some(func) = crate::std::INTERNAL_FNS.get(&fn_name) {
|
||||
// Attempt to call the function.
|
||||
let mut args = crate::std::Args::new(fn_args, self.into(), engine);
|
||||
let result = func(&mut args)?;
|
||||
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(),
|
||||
engine,
|
||||
)
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
} else {
|
||||
let mem = memory.clone();
|
||||
let func = mem.get(&fn_name, self.into())?;
|
||||
let result = func.call_fn(&fn_args, memory, engine)?.ok_or_else(|| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Result of function {} is undefined", fn_name),
|
||||
source_ranges: vec![self.into()],
|
||||
})
|
||||
})?;
|
||||
|
||||
let result = result.get_value()?;
|
||||
|
||||
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(),
|
||||
engine,
|
||||
)
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -124,6 +410,8 @@ pub struct VariableDeclaration {
|
||||
pub kind: String, // Change to enum if there are specific values
|
||||
}
|
||||
|
||||
impl_value_meta!(VariableDeclaration);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -134,6 +422,8 @@ pub struct VariableDeclarator {
|
||||
pub init: Value,
|
||||
}
|
||||
|
||||
impl_value_meta!(VariableDeclarator);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -144,6 +434,30 @@ pub struct Literal {
|
||||
pub raw: String,
|
||||
}
|
||||
|
||||
impl_value_meta!(Literal);
|
||||
|
||||
impl From<Literal> for MemoryItem {
|
||||
fn from(literal: Literal) -> Self {
|
||||
MemoryItem::UserVal {
|
||||
value: literal.value.clone(),
|
||||
meta: vec![Metadata {
|
||||
source_range: literal.into(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Box<Literal>> for MemoryItem {
|
||||
fn from(literal: &Box<Literal>) -> Self {
|
||||
MemoryItem::UserVal {
|
||||
value: literal.value.clone(),
|
||||
meta: vec![Metadata {
|
||||
source_range: literal.into(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -153,6 +467,8 @@ pub struct Identifier {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl_value_meta!(Identifier);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -161,6 +477,8 @@ pub struct PipeSubstitution {
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl_value_meta!(PipeSubstitution);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -170,6 +488,85 @@ pub struct ArrayExpression {
|
||||
pub elements: Vec<Value>,
|
||||
}
|
||||
|
||||
impl_value_meta!(ArrayExpression);
|
||||
|
||||
impl ArrayExpression {
|
||||
pub fn execute(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
let mut results = Vec::with_capacity(self.elements.len());
|
||||
|
||||
for element in &self.elements {
|
||||
let result = match element {
|
||||
Value::Literal(literal) => literal.into(),
|
||||
Value::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
binary_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
pipe_info.is_in_pipe = false;
|
||||
call_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::UnaryExpression(unary_expression) => {
|
||||
unary_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::ObjectExpression(object_expression) => {
|
||||
object_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::ArrayExpression(array_expression) => {
|
||||
array_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::PipeExpression(pipe_expression) => {
|
||||
pipe_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::PipeSubstitution(pipe_substitution) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
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
|
||||
),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"FunctionExpression not implemented here: {:?}",
|
||||
function_expression
|
||||
),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
}
|
||||
.get_json_value()?;
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
Ok(MemoryItem::UserVal {
|
||||
value: results.into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -179,6 +576,83 @@ pub struct ObjectExpression {
|
||||
pub properties: Vec<ObjectProperty>,
|
||||
}
|
||||
|
||||
impl ObjectExpression {
|
||||
pub fn execute(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
let mut object = Map::new();
|
||||
for property in &self.properties {
|
||||
let result = match &property.value {
|
||||
Value::Literal(literal) => literal.into(),
|
||||
Value::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
binary_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
pipe_info.is_in_pipe = false;
|
||||
call_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::UnaryExpression(unary_expression) => {
|
||||
unary_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::ObjectExpression(object_expression) => {
|
||||
object_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::ArrayExpression(array_expression) => {
|
||||
array_expression.execute(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::PipeExpression(pipe_expression) => {
|
||||
pipe_expression.get_result(memory, pipe_info, engine)?
|
||||
}
|
||||
Value::PipeSubstitution(pipe_substitution) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
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
|
||||
),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"FunctionExpression not implemented here: {:?}",
|
||||
function_expression
|
||||
),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
object.insert(property.key.name.clone(), result.get_json_value()?);
|
||||
}
|
||||
|
||||
Ok(MemoryItem::UserVal {
|
||||
value: object.into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl_value_meta!(ObjectExpression);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -189,6 +663,8 @@ pub struct ObjectProperty {
|
||||
pub value: Value,
|
||||
}
|
||||
|
||||
impl_value_meta!(ObjectProperty);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -216,6 +692,61 @@ pub struct MemberExpression {
|
||||
pub computed: bool,
|
||||
}
|
||||
|
||||
impl_value_meta!(MemberExpression);
|
||||
|
||||
impl MemberExpression {
|
||||
pub fn get_result(&self, memory: &mut ProgramMemory) -> Result<MemoryItem, KclError> {
|
||||
let property_name = match &self.property {
|
||||
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
|
||||
LiteralIdentifier::Literal(literal) => {
|
||||
let value = literal.value.clone();
|
||||
// Parse this as a string.
|
||||
if let serde_json::Value::String(string) = value {
|
||||
string
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected string literal for property name, found {:?}",
|
||||
value
|
||||
),
|
||||
source_ranges: vec![literal.into()],
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let object = match &self.object {
|
||||
MemberObject::MemberExpression(member_expr) => member_expr.get_result(memory)?,
|
||||
MemberObject::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
.get_json_value()?;
|
||||
|
||||
if let serde_json::Value::Object(map) = object {
|
||||
if let Some(value) = map.get(&property_name) {
|
||||
Ok(MemoryItem::UserVal {
|
||||
value: value.clone(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
} else {
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Property {} not found in object", property_name),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("MemberExpression object is not an object: {:?}", object),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct ObjectKeyInfo {
|
||||
@ -230,11 +761,101 @@ pub struct ObjectKeyInfo {
|
||||
pub struct BinaryExpression {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
// TODO: operator should be a type not a string.
|
||||
pub operator: String,
|
||||
pub left: BinaryPart,
|
||||
pub right: BinaryPart,
|
||||
}
|
||||
|
||||
impl_value_meta!(BinaryExpression);
|
||||
|
||||
impl BinaryExpression {
|
||||
pub fn get_result(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
pipe_info.is_in_pipe = false;
|
||||
|
||||
let left_json_value = self
|
||||
.left
|
||||
.get_result(memory, pipe_info, engine)?
|
||||
.get_json_value()?;
|
||||
let right_json_value = self
|
||||
.right
|
||||
.get_result(memory, pipe_info, engine)?
|
||||
.get_json_value()?;
|
||||
|
||||
// First check if we are doing string concatenation.
|
||||
if self.operator == "+" {
|
||||
if let (Some(left), Some(right)) = (
|
||||
parse_json_value_as_string(&left_json_value),
|
||||
parse_json_value_as_string(&right_json_value),
|
||||
) {
|
||||
let value = serde_json::Value::String(format!("{}{}", left, right));
|
||||
return Ok(MemoryItem::UserVal {
|
||||
value,
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let left = parse_json_number_as_f64(&left_json_value, self.left.clone().into())?;
|
||||
let right = parse_json_number_as_f64(&right_json_value, self.right.clone().into())?;
|
||||
|
||||
let value: serde_json::Value = match self.operator.as_str() {
|
||||
"+" => (left + right).into(),
|
||||
"-" => (left - right).into(),
|
||||
"*" => (left * right).into(),
|
||||
"/" => (left / right).into(),
|
||||
"%" => (left % right).into(),
|
||||
_ => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![self.into()],
|
||||
message: format!("Invalid operator: {}", self.operator),
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(MemoryItem::UserVal {
|
||||
value,
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
source_ranges: vec![source_range],
|
||||
message: format!("Invalid number: {}", j),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![source_range],
|
||||
message: format!("Invalid number: {}", j),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
||||
if let serde_json::Value::String(n) = &j {
|
||||
Some(n.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -245,6 +866,33 @@ pub struct UnaryExpression {
|
||||
pub argument: BinaryPart,
|
||||
}
|
||||
|
||||
impl_value_meta!(UnaryExpression);
|
||||
|
||||
impl UnaryExpression {
|
||||
pub fn get_result(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
pipe_info.is_in_pipe = false;
|
||||
|
||||
let num = parse_json_number_as_f64(
|
||||
&self
|
||||
.argument
|
||||
.get_result(memory, pipe_info, engine)?
|
||||
.get_json_value()?,
|
||||
self.into(),
|
||||
)?;
|
||||
Ok(MemoryItem::UserVal {
|
||||
value: (-(num)).into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
@ -255,6 +903,72 @@ pub struct PipeExpression {
|
||||
pub non_code_meta: NoneCodeMeta,
|
||||
}
|
||||
|
||||
impl_value_meta!(PipeExpression);
|
||||
|
||||
impl PipeExpression {
|
||||
pub fn get_result(
|
||||
&self,
|
||||
memory: &mut ProgramMemory,
|
||||
pipe_info: &mut PipeInfo,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
// Reset the previous results.
|
||||
pipe_info.previous_results = vec![];
|
||||
pipe_info.index = 0;
|
||||
execute_pipe_body(memory, &self.body, pipe_info, self.into(), engine)
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_pipe_body(
|
||||
memory: &mut ProgramMemory,
|
||||
body: &[Value],
|
||||
pipe_info: &mut PipeInfo,
|
||||
source_range: SourceRange,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<MemoryItem, KclError> {
|
||||
if pipe_info.index == body.len() {
|
||||
pipe_info.is_in_pipe = false;
|
||||
return Ok(pipe_info
|
||||
.previous_results
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: "Pipe body results should have at least one expression".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?
|
||||
.clone());
|
||||
}
|
||||
|
||||
let expression = body.get(pipe_info.index).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Invalid index for pipe: {}", pipe_info.index),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
match expression {
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
let result = binary_expression.get_result(memory, pipe_info, engine)?;
|
||||
pipe_info.previous_results.push(result);
|
||||
pipe_info.index += 1;
|
||||
execute_pipe_body(memory, body, pipe_info, source_range, engine)
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
pipe_info.is_in_pipe = true;
|
||||
pipe_info.body = body.to_vec();
|
||||
call_expression.execute(memory, pipe_info, engine)
|
||||
}
|
||||
_ => {
|
||||
// Return an error this should not happen.
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("PipeExpression not implemented here: {:?}", expression),
|
||||
source_ranges: vec![expression.into()],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -263,18 +977,10 @@ pub struct FunctionExpression {
|
||||
pub end: usize,
|
||||
pub id: Option<Identifier>,
|
||||
pub params: Vec<Identifier>,
|
||||
pub body: BlockStatement,
|
||||
pub body: Program,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub struct BlockStatement {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub body: Vec<BodyItem>,
|
||||
pub non_code_meta: NoneCodeMeta,
|
||||
}
|
||||
impl_value_meta!(FunctionExpression);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -284,3 +990,5 @@ pub struct ReturnStatement {
|
||||
pub end: usize,
|
||||
pub argument: Value,
|
||||
}
|
||||
|
||||
impl_value_meta!(ReturnStatement);
|
||||
|
28
src/wasm-lib/src/engine/conn_mock.rs
Normal file
28
src/wasm-lib/src/engine/conn_mock.rs
Normal file
@ -0,0 +1,28 @@
|
||||
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
||||
//! engine.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::errors::KclError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EngineConnection {}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(
|
||||
_conn_str: &str,
|
||||
_auth_token: &str,
|
||||
_origin: &str,
|
||||
) -> Result<EngineConnection> {
|
||||
Ok(EngineConnection {})
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
&mut self,
|
||||
_id: uuid::Uuid,
|
||||
_source_range: crate::executor::SourceRange,
|
||||
_cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
152
src/wasm-lib/src/engine/conn_noweb.rs
Normal file
152
src/wasm-lib/src/engine/conn_noweb.rs
Normal file
@ -0,0 +1,152 @@
|
||||
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
||||
//! engine.
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use kittycad::types::{WebSocketMessages, WebSocketResponses};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
|
||||
use crate::errors::{KclError, KclErrorDetails};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EngineConnection {
|
||||
tcp_write: futures::stream::SplitSink<
|
||||
tokio_tungstenite::WebSocketStream<
|
||||
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
|
||||
>,
|
||||
WsMsg,
|
||||
>,
|
||||
tcp_read_handle: tokio::task::JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Drop for EngineConnection {
|
||||
fn drop(&mut self) {
|
||||
// Drop the read handle.
|
||||
self.tcp_read_handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TcpRead {
|
||||
stream: futures::stream::SplitStream<
|
||||
tokio_tungstenite::WebSocketStream<
|
||||
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl TcpRead {
|
||||
pub async fn read(&mut self) -> Result<WebSocketResponses> {
|
||||
let msg = self.stream.next().await.unwrap()?;
|
||||
let msg = match msg {
|
||||
WsMsg::Text(text) => text,
|
||||
WsMsg::Binary(bin) => bincode::deserialize(&bin)?,
|
||||
other => anyhow::bail!("Unexpected websocket message from server: {}", other),
|
||||
};
|
||||
let msg = serde_json::from_str::<WebSocketResponses>(&msg)?;
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(conn_str: &str, auth_token: &str, origin: &str) -> Result<EngineConnection> {
|
||||
let method = http::Method::GET.to_string();
|
||||
let key = tokio_tungstenite::tungstenite::handshake::client::generate_key();
|
||||
|
||||
// Establish a websocket connection.
|
||||
let (ws_stream, _) = tokio_tungstenite::connect_async(httparse::Request {
|
||||
method: Some(&method),
|
||||
path: Some(conn_str),
|
||||
// TODO pass in the origin from elsewhere.
|
||||
headers: &mut websocket_headers(auth_token, &key, origin),
|
||||
version: Some(1), // HTTP/1.1
|
||||
})
|
||||
.await?;
|
||||
|
||||
let (tcp_write, tcp_read) = ws_stream.split();
|
||||
|
||||
let mut tcp_read = TcpRead { stream: tcp_read };
|
||||
|
||||
let tcp_read_handle = tokio::spawn(async move {
|
||||
// Get Websocket messages from API server
|
||||
while let Ok(msg) = tcp_read.read().await {
|
||||
match msg {
|
||||
WebSocketResponses::IceServerInfo { ice_servers } => {
|
||||
println!("got ice server info: {:?}", ice_servers);
|
||||
}
|
||||
WebSocketResponses::SdpAnswer { answer } => {
|
||||
println!("got sdp answer: {:?}", answer);
|
||||
}
|
||||
WebSocketResponses::TrickleIce { candidate } => {
|
||||
println!("got trickle ice: {:?}", candidate);
|
||||
}
|
||||
WebSocketResponses::Modeling { .. } => {}
|
||||
WebSocketResponses::Export { .. } => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(EngineConnection {
|
||||
tcp_write,
|
||||
tcp_read_handle,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn tcp_send(&mut self, msg: WebSocketMessages) -> Result<()> {
|
||||
let msg = serde_json::to_string(&msg)?;
|
||||
self.tcp_write.send(WsMsg::Text(msg)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
futures::executor::block_on(
|
||||
self.tcp_send(WebSocketMessages::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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Headers for starting a websocket session with api-deux.
|
||||
fn websocket_headers<'a>(
|
||||
token: &'a str,
|
||||
key: &'a str,
|
||||
origin: &'a str,
|
||||
) -> [httparse::Header<'a>; 6] {
|
||||
[
|
||||
httparse::Header {
|
||||
name: "Authorization",
|
||||
value: token.as_bytes(),
|
||||
},
|
||||
httparse::Header {
|
||||
name: "Connection",
|
||||
value: b"Upgrade",
|
||||
},
|
||||
httparse::Header {
|
||||
name: "Upgrade",
|
||||
value: b"websocket",
|
||||
},
|
||||
httparse::Header {
|
||||
name: "Sec-WebSocket-Version",
|
||||
value: b"13",
|
||||
},
|
||||
httparse::Header {
|
||||
name: "Sec-WebSocket-Key",
|
||||
value: key.as_bytes(),
|
||||
},
|
||||
httparse::Header {
|
||||
name: "Host",
|
||||
value: origin.as_bytes(),
|
||||
},
|
||||
]
|
||||
}
|
398
src/wasm-lib/src/engine/conn_web.rs
Normal file
398
src/wasm-lib/src/engine/conn_web.rs
Normal file
@ -0,0 +1,398 @@
|
||||
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
||||
//! engine.
|
||||
|
||||
use anyhow::Result;
|
||||
use kittycad::types::WebSocketMessages;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::errors::{KclError, KclErrorDetails};
|
||||
|
||||
#[wasm_bindgen(module = "/../lang/std/engineConnection.ts")]
|
||||
extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub type EngineCommandManager;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn sendModelingCommandFromWasm(
|
||||
this: &EngineCommandManager,
|
||||
id: String,
|
||||
rangeStr: String,
|
||||
cmdStr: String,
|
||||
) -> js_sys::Promise;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
manager: EngineCommandManager,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
||||
Ok(EngineConnection { manager })
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let ws_msg = WebSocketMessages::ModelingCmdReq { cmd, cmd_id: id };
|
||||
let cmd_str = serde_json::to_string(&ws_msg).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let _ = self
|
||||
.manager
|
||||
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/*use anyhow::Result;
|
||||
use kittycad::types::{WebSocketMessages, WebSocketResponses};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{CloseEvent, ErrorEvent, MessageEvent, RtcDataChannel, RtcPeerConnection, WebSocket};
|
||||
|
||||
use crate::errors::{KclError, KclErrorDetails};
|
||||
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
ws: WebSocket,
|
||||
pc: RtcPeerConnection,
|
||||
lossy_data_channel: RtcDataChannel,
|
||||
|
||||
ready: bool,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(
|
||||
conn_str: &str,
|
||||
auth_token: &str,
|
||||
_origin: &str,
|
||||
) -> Result<EngineConnection, JsValue> {
|
||||
// Setup the websocket connection.
|
||||
let ws = WebSocket::new(conn_str)?;
|
||||
|
||||
// Setup the WebRTC connection.
|
||||
let pc = RtcPeerConnection::new()?;
|
||||
let lossy_data_channel = pc.create_data_channel("unreliable_modeling_cmds");
|
||||
|
||||
let cloned_ws = ws.clone();
|
||||
let cloned_auth_token = auth_token.to_owned();
|
||||
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
|
||||
println!("Connected to websocket, waiting for ICE servers");
|
||||
|
||||
// Send our token for auth.
|
||||
cloned_ws
|
||||
.send_with_str(&cloned_auth_token)
|
||||
.expect("failed to send auth token");
|
||||
});
|
||||
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
||||
onopen_callback.forget();
|
||||
|
||||
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
|
||||
console_log!("error event: {:?}", e);
|
||||
});
|
||||
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
||||
onerror_callback.forget();
|
||||
|
||||
// For small binary messages, like CBOR, Arraybuffer is more efficient than Blob handling
|
||||
// Export is huge so let's use Blob.
|
||||
ws.set_binary_type(web_sys::BinaryType::Blob);
|
||||
|
||||
let engine_conn = EngineConnection {
|
||||
ws,
|
||||
pc,
|
||||
lossy_data_channel,
|
||||
ready: false,
|
||||
};
|
||||
|
||||
let mut cloned_engine_conn = engine_conn.clone();
|
||||
let onclose_callback = Closure::<dyn FnMut(_)>::new(move |e: CloseEvent| {
|
||||
console_log!("close event: {:?}", e);
|
||||
cloned_engine_conn
|
||||
.close()
|
||||
.expect("failed to close engine connection");
|
||||
});
|
||||
engine_conn
|
||||
.ws
|
||||
.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||
onclose_callback.forget();
|
||||
|
||||
let mut cloned_engine_conn = engine_conn.clone();
|
||||
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |msg: MessageEvent| {
|
||||
// Parse the message as our types.
|
||||
let msg = match parse_message(msg) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
console_log!("Failed to parse message: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match msg {
|
||||
WebSocketResponses::IceServerInfo { ice_servers } => {
|
||||
// When we set the Configuration, we want to always force
|
||||
// iceTransportPolicy to 'relay', since we know the topology
|
||||
// of the ICE/STUN/TUN server and the engine. We don't wish to
|
||||
// talk to the engine in any configuration /other/ than relay
|
||||
// from a infra POV.
|
||||
let mut config = web_sys::RtcConfiguration::new();
|
||||
let converted_ice_servers = js_sys::Array::new();
|
||||
for server in ice_servers {
|
||||
let mut ice_server = web_sys::RtcIceServer::new();
|
||||
let urls = js_sys::Array::new();
|
||||
for url in server.urls {
|
||||
urls.push(&JsValue::from(url));
|
||||
}
|
||||
ice_server.urls(&urls.into());
|
||||
if let Some(credential) = server.credential {
|
||||
ice_server.credential(&credential);
|
||||
}
|
||||
if let Some(username) = server.username {
|
||||
ice_server.username(&username);
|
||||
}
|
||||
converted_ice_servers.push(&ice_server.into());
|
||||
}
|
||||
config.ice_servers(&converted_ice_servers.into());
|
||||
config.ice_transport_policy(web_sys::RtcIceTransportPolicy::Relay);
|
||||
cloned_engine_conn.pc = web_sys::RtcPeerConnection::new_with_configuration(
|
||||
&config,
|
||||
)
|
||||
.expect("Failed to create RtcPeerConnection with our custom configuration");
|
||||
|
||||
// We have an ICE Servers set now. We just setConfiguration, so let's
|
||||
// start adding things we care about to the PeerConnection and let
|
||||
// ICE negotiation happen in the background. Everything from here
|
||||
// until the end of this function is setup of our end of the
|
||||
// PeerConnection and waiting for events to fire our callbacks.
|
||||
|
||||
let mut cloned_engine_conn2 = cloned_engine_conn.clone();
|
||||
let onicecandidate_callback =
|
||||
Closure::<dyn FnMut(_)>::new(move |ev: MessageEvent| {
|
||||
if !cloned_engine_conn2.ready {
|
||||
return;
|
||||
}
|
||||
|
||||
let ev: web_sys::RtcPeerConnectionIceEvent = ev
|
||||
.dyn_into()
|
||||
.expect("Failed to cast to RtcPeerConnectionIceEvent");
|
||||
|
||||
if let Some(candidate) = ev.candidate() {
|
||||
console_log!("sending trickle ice candidate");
|
||||
cloned_engine_conn2
|
||||
.ws_send(WebSocketMessages::TrickleIce {
|
||||
candidate: kittycad::types::RtcIceCandidateInit {
|
||||
candidate: candidate.candidate(),
|
||||
sdp_mid: candidate.sdp_mid(),
|
||||
sdp_m_line_index: candidate.sdp_m_line_index(),
|
||||
username_fragment: Default::default(),
|
||||
},
|
||||
})
|
||||
.expect("failed to send trickle ice candidate");
|
||||
}
|
||||
});
|
||||
cloned_engine_conn
|
||||
.pc
|
||||
.set_onicecandidate(Some(onicecandidate_callback.as_ref().unchecked_ref()));
|
||||
onicecandidate_callback.forget();
|
||||
|
||||
let onconnectionstatechange_callback =
|
||||
Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
|
||||
console_log!("connection state changed: {:?}", e);
|
||||
});
|
||||
cloned_engine_conn.pc.set_onconnectionstatechange(Some(
|
||||
onconnectionstatechange_callback.as_ref().unchecked_ref(),
|
||||
));
|
||||
onconnectionstatechange_callback.forget();
|
||||
|
||||
// Offer to receive 1 video track
|
||||
cloned_engine_conn.pc.add_transceiver_with_str("video");
|
||||
|
||||
// Finally (but actually firstly!), to kick things off, we're going to
|
||||
// generate our SDP, set it on our PeerConnection, and let the server
|
||||
// know about our capabilities.
|
||||
let cloned_engine_conn2 = cloned_engine_conn.clone();
|
||||
let create_offer_callback = Closure::<dyn FnMut(_)>::new(move |v: JsValue| {
|
||||
let desc: web_sys::RtcSessionDescriptionInit = v.into();
|
||||
let _ = cloned_engine_conn2.pc.set_local_description(&desc);
|
||||
console_log!("sent sdp_offer begin");
|
||||
let object: js_sys::Object = desc.into();
|
||||
console_log!("got offer object: {:?}", object);
|
||||
/*cloned_engine_conn2
|
||||
.send(WebSocketMessages::SdpOffer {
|
||||
offer: kittycad::types::RtcSessionDescription {
|
||||
sdp: desc.sdp(),
|
||||
type_: kittycad::types::RtcSdpType::Offer,
|
||||
},
|
||||
})
|
||||
.expect("failed to send sdp offer");*/
|
||||
});
|
||||
let create_offer_catch = Closure::<dyn FnMut(_)>::new(move |v: JsValue| {
|
||||
console_log!("Failed to create offer: {:?}", v);
|
||||
});
|
||||
let _ = cloned_engine_conn
|
||||
.pc
|
||||
.create_offer()
|
||||
.then(&create_offer_callback)
|
||||
.catch(&create_offer_catch);
|
||||
create_offer_callback.forget();
|
||||
create_offer_catch.forget();
|
||||
}
|
||||
WebSocketResponses::SdpAnswer { answer } => {
|
||||
if answer.type_ == kittycad::types::RtcSdpType::Unspecified {
|
||||
console_log!("Received an unspecified rtc sdp answer, ignoring");
|
||||
} else if cloned_engine_conn.pc.signaling_state()
|
||||
!= web_sys::RtcSignalingState::Stable
|
||||
{
|
||||
// If the connection is stable, we shouldn't bother updating the
|
||||
// SDP, since we have a stable connection to the backend. If we
|
||||
// need to renegotiate, the whole PeerConnection needs to get
|
||||
// tore down.
|
||||
let mut desc =
|
||||
web_sys::RtcSessionDescriptionInit::new(match answer.type_ {
|
||||
kittycad::types::RtcSdpType::Offer => web_sys::RtcSdpType::Offer,
|
||||
kittycad::types::RtcSdpType::Pranswer => {
|
||||
web_sys::RtcSdpType::Pranswer
|
||||
}
|
||||
kittycad::types::RtcSdpType::Answer => web_sys::RtcSdpType::Answer,
|
||||
kittycad::types::RtcSdpType::Rollback => {
|
||||
web_sys::RtcSdpType::Rollback
|
||||
}
|
||||
kittycad::types::RtcSdpType::Unspecified => {
|
||||
unreachable!(
|
||||
"Unspecified RtcSdpType should have been handled above"
|
||||
)
|
||||
}
|
||||
});
|
||||
desc.sdp(answer.sdp.as_str());
|
||||
let _ = cloned_engine_conn.pc.set_remote_description(&desc);
|
||||
}
|
||||
}
|
||||
WebSocketResponses::TrickleIce { candidate } => {
|
||||
console_log!("got trickle ice candidate: {:?}", candidate);
|
||||
// this.pc?.addIceCandidate(message.candidate as RTCIceCandidateInit)
|
||||
/*let mut candidate_init = web_sys::RtcIceCandidateInit::new(candidate.candidate);
|
||||
cloned_engine_conn
|
||||
.pc
|
||||
.add_ice_candidate_with_opt_rtc_ice_candidate_init(&candidate_init);*/
|
||||
todo!()
|
||||
}
|
||||
WebSocketResponses::Modeling { .. } => {}
|
||||
WebSocketResponses::Export { .. } => {}
|
||||
}
|
||||
});
|
||||
// set message event handler on WebSocket
|
||||
engine_conn
|
||||
.ws
|
||||
.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
|
||||
// forget the callback to keep it alive
|
||||
onmessage_callback.forget();
|
||||
|
||||
Ok(engine_conn)
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Result<(), JsValue> {
|
||||
self.ready = false;
|
||||
self.ws.close()?;
|
||||
self.pc.close();
|
||||
self.lossy_data_channel.close();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ws_send(&mut self, msg: kittycad::types::WebSocketMessages) -> Result<(), JsValue> {
|
||||
self.ws.send_with_str(
|
||||
serde_json::to_string(&msg)
|
||||
.map_err(|err| JsValue::from(err.to_string()))?
|
||||
.as_str(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lossy_send(&mut self, msg: kittycad::types::WebSocketMessages) -> Result<(), JsValue> {
|
||||
self.lossy_data_channel.send_with_str(
|
||||
serde_json::to_string(&msg)
|
||||
.map_err(|err| JsValue::from(err.to_string()))?
|
||||
.as_str(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_lossy_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
self.lossy_send(WebSocketMessages::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(())
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
self.ws_send(WebSocketMessages::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(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_message(msg: MessageEvent) -> Result<WebSocketResponses> {
|
||||
if let Ok(abuf) = msg.data().dyn_into::<js_sys::ArrayBuffer>() {
|
||||
let array = js_sys::Uint8Array::new(&abuf);
|
||||
Ok(serde_json::from_slice(&array.to_vec())?)
|
||||
} else if let Ok(blob) = msg.data().dyn_into::<web_sys::Blob>() {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
gloo_file::callbacks::read_as_bytes(&blob.into(), move |res| {
|
||||
sender.send(res).unwrap();
|
||||
});
|
||||
let value = receiver.recv()??;
|
||||
Ok(serde_json::from_slice(&value)?)
|
||||
} else if let Ok(txt) = msg.data().dyn_into::<js_sys::JsString>() {
|
||||
console_log!("message event, received Text: {:?}", txt);
|
||||
let s = txt
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to convert JsString: {:?}", txt))?;
|
||||
Ok(serde_json::from_str(&s)?)
|
||||
} else {
|
||||
console_log!("message event, received Unknown: {:?}", msg.data());
|
||||
anyhow::bail!("message event, received Unknown: {:?}", msg.data());
|
||||
}
|
||||
}*/
|
64
src/wasm-lib/src/engine/mod.rs
Normal file
64
src/wasm-lib/src/engine/mod.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! Functions for managing engine communications.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "noweb")]
|
||||
#[cfg(not(test))]
|
||||
pub mod conn_noweb;
|
||||
#[cfg(feature = "noweb")]
|
||||
#[cfg(not(test))]
|
||||
pub use conn_noweb::EngineConnection;
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg(not(test))]
|
||||
pub mod conn_web;
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg(not(test))]
|
||||
pub use conn_web::EngineConnection;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod conn_mock;
|
||||
#[cfg(test)]
|
||||
pub use conn_mock::EngineConnection;
|
||||
|
||||
use crate::executor::SourceRange;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen]
|
||||
pub struct EngineManager {
|
||||
connection: EngineConnection,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EngineManager {
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg(not(test))]
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub async fn new(manager: conn_web::EngineCommandManager) -> EngineManager {
|
||||
EngineManager {
|
||||
// This unwrap is safe because the connection is always created.
|
||||
connection: EngineConnection::new(manager).await.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "web"))]
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub async fn new(conn_str: &str, auth_token: &str, origin: &str) -> EngineManager {
|
||||
EngineManager {
|
||||
// TODO: fix unwrap.
|
||||
connection: EngineConnection::new(conn_str, auth_token, origin)
|
||||
.await
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(&mut self, id_str: &str, cmd_str: &str) -> Result<(), String> {
|
||||
let id = uuid::Uuid::parse_str(id_str).map_err(|e| e.to_string())?;
|
||||
let cmd = serde_json::from_str(cmd_str).map_err(|e| e.to_string())?;
|
||||
self.connection
|
||||
.send_modeling_cmd(id, SourceRange::default(), cmd)
|
||||
.map_err(String::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -21,13 +21,15 @@ pub enum KclError {
|
||||
UndefinedValue(KclErrorDetails),
|
||||
#[error("invalid expression: {0:?}")]
|
||||
InvalidExpression(crate::math_parser::MathExpression),
|
||||
#[error("engine: {0:?}")]
|
||||
Engine(KclErrorDetails),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct KclErrorDetails {
|
||||
#[serde(rename = "sourceRanges")]
|
||||
pub source_ranges: Vec<[i32; 2]>,
|
||||
pub source_ranges: Vec<crate::executor::SourceRange>,
|
||||
#[serde(rename = "msg")]
|
||||
pub message: String,
|
||||
}
|
||||
@ -39,3 +41,9 @@ impl From<KclError> for String {
|
||||
serde_json::to_string(&error).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for KclError {
|
||||
fn from(error: String) -> Self {
|
||||
serde_json::from_str(&error).unwrap()
|
||||
}
|
||||
}
|
||||
|
801
src/wasm-lib/src/executor.rs
Normal file
801
src/wasm-lib/src/executor.rs
Normal file
@ -0,0 +1,801 @@
|
||||
//! The executor for the AST.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(test))]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::{BodyItem, FunctionExpression, Value},
|
||||
engine::EngineConnection,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramMemory {
|
||||
pub root: HashMap<String, MemoryItem>,
|
||||
#[serde(rename = "return")]
|
||||
pub return_: Option<ProgramReturn>,
|
||||
}
|
||||
|
||||
impl ProgramMemory {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root: HashMap::new(),
|
||||
return_: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add to the program memory.
|
||||
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),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
self.root.insert(key.to_string(), value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a value from the program memory.
|
||||
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
||||
self.root.get(key).ok_or_else(|| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("memory item key `{}` is not defined", key),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProgramMemory {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum ProgramReturn {
|
||||
Arguments(Vec<Value>),
|
||||
Value(MemoryItem),
|
||||
}
|
||||
|
||||
impl From<ProgramReturn> for Vec<SourceRange> {
|
||||
fn from(item: ProgramReturn) -> Self {
|
||||
match item {
|
||||
ProgramReturn::Arguments(args) => args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let r: SourceRange = arg.into();
|
||||
r
|
||||
})
|
||||
.collect(),
|
||||
ProgramReturn::Value(v) => v.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProgramReturn {
|
||||
pub fn get_value(&self) -> Result<MemoryItem, KclError> {
|
||||
match self {
|
||||
ProgramReturn::Value(v) => Ok(v.clone()),
|
||||
ProgramReturn::Arguments(args) => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Cannot get value from arguments: {:?}", args),
|
||||
source_ranges: self.clone().into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum MemoryItem {
|
||||
UserVal {
|
||||
value: serde_json::Value,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
SketchGroup(SketchGroup),
|
||||
ExtrudeGroup(ExtrudeGroup),
|
||||
ExtrudeTransform {
|
||||
position: Position,
|
||||
rotation: Rotation,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Function {
|
||||
#[serde(skip)]
|
||||
func: Option<MemoryFunction>,
|
||||
expression: Box<FunctionExpression>,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
}
|
||||
|
||||
pub type MemoryFunction = fn(
|
||||
s: &[MemoryItem],
|
||||
memory: &ProgramMemory,
|
||||
expression: &FunctionExpression,
|
||||
metadata: &[Metadata],
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<Option<ProgramReturn>, KclError>;
|
||||
|
||||
impl From<MemoryItem> for Vec<SourceRange> {
|
||||
fn from(item: MemoryItem) -> Self {
|
||||
match item {
|
||||
MemoryItem::UserVal { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::ExtrudeTransform { meta, .. } => {
|
||||
meta.iter().map(|m| m.source_range).collect()
|
||||
}
|
||||
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryItem {
|
||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||
if let MemoryItem::UserVal { value, .. } = self {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Not a user value: {:?}", self),
|
||||
source_ranges: self.clone().into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_fn(
|
||||
&self,
|
||||
args: &[MemoryItem],
|
||||
memory: &ProgramMemory,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<Option<ProgramReturn>, KclError> {
|
||||
if let MemoryItem::Function {
|
||||
func,
|
||||
expression,
|
||||
meta,
|
||||
} = self
|
||||
{
|
||||
if let Some(func) = func {
|
||||
func(args, memory, expression, meta, engine)
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Not a function: {:?}", self),
|
||||
source_ranges: vec![],
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("not a function: {:?}", self),
|
||||
source_ranges: vec![],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SketchGroup {
|
||||
pub id: uuid::Uuid,
|
||||
pub value: Vec<Path>,
|
||||
pub start: BasePath,
|
||||
pub position: Position,
|
||||
pub rotation: Rotation,
|
||||
#[serde(rename = "__meta")]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
impl SketchGroup {
|
||||
pub fn get_path_by_id(&self, id: &uuid::Uuid) -> Option<&Path> {
|
||||
self.value.iter().find(|p| p.get_id() == *id)
|
||||
}
|
||||
|
||||
pub fn get_path_by_name(&self, name: &str) -> Option<&Path> {
|
||||
self.value.iter().find(|p| p.get_name() == name)
|
||||
}
|
||||
|
||||
pub fn get_base_by_name_or_start(&self, name: &str) -> Option<&BasePath> {
|
||||
if self.start.name == name {
|
||||
Some(&self.start)
|
||||
} else {
|
||||
self.value
|
||||
.iter()
|
||||
.find(|p| p.get_name() == name)
|
||||
.map(|p| p.get_base())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_coords_from_paths(&self) -> Result<Point2d, KclError> {
|
||||
if self.value.is_empty() {
|
||||
return Ok(self.start.to.into());
|
||||
}
|
||||
|
||||
let index = self.value.len() - 1;
|
||||
if let Some(path) = self.value.get(index) {
|
||||
let base = path.get_base();
|
||||
Ok(base.to.into())
|
||||
} else {
|
||||
Ok(self.start.to.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExtrudeGroup {
|
||||
pub id: uuid::Uuid,
|
||||
pub value: Vec<ExtrudeSurface>,
|
||||
pub height: f64,
|
||||
pub position: Position,
|
||||
pub rotation: Rotation,
|
||||
#[serde(rename = "__meta")]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
impl ExtrudeGroup {
|
||||
pub fn get_path_by_id(&self, id: &uuid::Uuid) -> Option<&ExtrudeSurface> {
|
||||
self.value.iter().find(|p| p.get_id() == *id)
|
||||
}
|
||||
|
||||
pub fn get_path_by_name(&self, name: &str) -> Option<&ExtrudeSurface> {
|
||||
self.value.iter().find(|p| p.get_name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BodyType {
|
||||
Root,
|
||||
Sketch,
|
||||
Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct Position(pub [f64; 3]);
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct Rotation(pub [f64; 4]);
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct SourceRange(pub [usize; 2]);
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct Point2d {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
pub struct Point3d {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Metadata {
|
||||
pub source_range: SourceRange,
|
||||
}
|
||||
|
||||
impl From<SourceRange> for Metadata {
|
||||
fn from(source_range: SourceRange) -> Self {
|
||||
Self { source_range }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BasePath {
|
||||
pub from: [f64; 2],
|
||||
pub to: [f64; 2],
|
||||
pub name: String,
|
||||
#[serde(rename = "__geoMeta")]
|
||||
pub geo_meta: GeoMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GeoMeta {
|
||||
pub id: uuid::Uuid,
|
||||
#[serde(flatten)]
|
||||
pub metadata: Metadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum Path {
|
||||
ToPoint {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
},
|
||||
Horizontal {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
x: f64,
|
||||
},
|
||||
AngledLineTo {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
x: Option<f64>,
|
||||
y: Option<f64>,
|
||||
},
|
||||
Base {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
},
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn get_id(&self) -> uuid::Uuid {
|
||||
match self {
|
||||
Path::ToPoint { base } => base.geo_meta.id,
|
||||
Path::Horizontal { base, .. } => base.geo_meta.id,
|
||||
Path::AngledLineTo { base, .. } => base.geo_meta.id,
|
||||
Path::Base { base } => base.geo_meta.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> String {
|
||||
match self {
|
||||
Path::ToPoint { base } => base.name.clone(),
|
||||
Path::Horizontal { base, .. } => base.name.clone(),
|
||||
Path::AngledLineTo { base, .. } => base.name.clone(),
|
||||
Path::Base { base } => base.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_base(&self) -> &BasePath {
|
||||
match self {
|
||||
Path::ToPoint { base } => base,
|
||||
Path::Horizontal { base, .. } => base,
|
||||
Path::AngledLineTo { base, .. } => base,
|
||||
Path::Base { base } => base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum ExtrudeSurface {
|
||||
ExtrudePlane {
|
||||
position: Position,
|
||||
rotation: Rotation,
|
||||
name: String,
|
||||
#[serde(flatten)]
|
||||
geo_meta: GeoMeta,
|
||||
},
|
||||
}
|
||||
|
||||
impl ExtrudeSurface {
|
||||
pub fn get_id(&self) -> uuid::Uuid {
|
||||
match self {
|
||||
ExtrudeSurface::ExtrudePlane { geo_meta, .. } => geo_meta.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> String {
|
||||
match self {
|
||||
ExtrudeSurface::ExtrudePlane { name, .. } => name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> Position {
|
||||
match self {
|
||||
ExtrudeSurface::ExtrudePlane { position, .. } => *position,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rotation(&self) -> Rotation {
|
||||
match self {
|
||||
ExtrudeSurface::ExtrudePlane { rotation, .. } => *rotation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PipeInfo {
|
||||
pub previous_results: Vec<MemoryItem>,
|
||||
pub is_in_pipe: bool,
|
||||
pub index: usize,
|
||||
pub body: Vec<Value>,
|
||||
}
|
||||
|
||||
impl PipeInfo {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
previous_results: Vec::new(),
|
||||
is_in_pipe: false,
|
||||
index: 0,
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PipeInfo {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a AST's program.
|
||||
fn execute(
|
||||
program: crate::abstract_syntax_tree_types::Program,
|
||||
memory: &mut ProgramMemory,
|
||||
options: BodyType,
|
||||
engine: &mut EngineConnection,
|
||||
) -> Result<ProgramMemory, KclError> {
|
||||
let mut pipe_info = PipeInfo::default();
|
||||
|
||||
// Iterate over the body of the program.
|
||||
for statement in &program.body {
|
||||
match statement {
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
if let Value::CallExpression(call_expr) = &expression_statement.expression {
|
||||
let fn_name = call_expr.callee.name.to_string();
|
||||
let mut args: Vec<MemoryItem> = Vec::new();
|
||||
for arg in &call_expr.arguments {
|
||||
match arg {
|
||||
Value::Literal(literal) => args.push(literal.into()),
|
||||
Value::Identifier(identifier) => {
|
||||
let memory_item =
|
||||
memory.get(&identifier.name, identifier.into())?;
|
||||
args.push(memory_item.clone());
|
||||
}
|
||||
// We do nothing for the rest.
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if fn_name == "show" {
|
||||
if options != BodyType::Root {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot call show outside of a root".to_string(),
|
||||
source_ranges: vec![call_expr.into()],
|
||||
}));
|
||||
}
|
||||
|
||||
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 {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("No such name {} defined", fn_name),
|
||||
source_ranges: vec![call_expr.into()],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||
for declaration in &variable_declaration.declarations {
|
||||
let var_name = declaration.id.name.to_string();
|
||||
let source_range: SourceRange = declaration.init.clone().into();
|
||||
let metadata = Metadata { source_range };
|
||||
|
||||
match &declaration.init {
|
||||
Value::Literal(literal) => {
|
||||
memory.add(&var_name, literal.into(), source_range)?;
|
||||
}
|
||||
Value::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
memory.add(&var_name, value.clone(), source_range)?;
|
||||
}
|
||||
Value::BinaryExpression(binary_expression) => {
|
||||
let result =
|
||||
binary_expression.get_result(memory, &mut pipe_info, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
memory.add(
|
||||
&var_name,
|
||||
MemoryItem::Function{
|
||||
expression: function_expression.clone(),
|
||||
meta: vec![metadata],
|
||||
func: Some(|args: &[MemoryItem], memory: &ProgramMemory, function_expression: &FunctionExpression, _metadata: &[Metadata], engine: &mut EngineConnection| -> Result<Option<ProgramReturn>, KclError> {
|
||||
let mut fn_memory = memory.clone();
|
||||
|
||||
if args.len() != function_expression.params.len() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Expected {} arguments, got {}", function_expression.params.len(), args.len()),
|
||||
source_ranges: vec![function_expression.into()],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the arguments to the memory.
|
||||
for (index, param) in function_expression.params.iter().enumerate() {
|
||||
fn_memory.add(
|
||||
¶m.name,
|
||||
args.clone().get(index).unwrap().clone(),
|
||||
param.into(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let result = execute(function_expression.body.clone(), &mut fn_memory, BodyType::Block, engine)?;
|
||||
|
||||
Ok(result.return_)
|
||||
})
|
||||
},
|
||||
source_range,
|
||||
)?;
|
||||
}
|
||||
Value::CallExpression(call_expression) => {
|
||||
let result = call_expression.execute(memory, &mut pipe_info, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::PipeExpression(pipe_expression) => {
|
||||
let result =
|
||||
pipe_expression.get_result(memory, &mut pipe_info, 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),
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
}));
|
||||
}
|
||||
Value::ArrayExpression(array_expression) => {
|
||||
let result =
|
||||
array_expression.execute(memory, &mut pipe_info, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::ObjectExpression(object_expression) => {
|
||||
let result =
|
||||
object_expression.execute(memory, &mut pipe_info, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::MemberExpression(member_expression) => {
|
||||
let result = member_expression.get_result(memory)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
Value::UnaryExpression(unary_expression) => {
|
||||
let result =
|
||||
unary_expression.get_result(memory, &mut pipe_info, engine)?;
|
||||
memory.add(&var_name, result, source_range)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
|
||||
Value::BinaryExpression(bin_expr) => {
|
||||
let result = bin_expr.get_result(memory, &mut pipe_info, engine)?;
|
||||
memory.return_ = Some(ProgramReturn::Value(result));
|
||||
}
|
||||
Value::Identifier(identifier) => {
|
||||
let value = memory.get(&identifier.name, identifier.into())?.clone();
|
||||
memory.return_ = Some(ProgramReturn::Value(value));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(memory.clone())
|
||||
}
|
||||
|
||||
// wasm_bindgen wrapper for execute
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg(not(test))]
|
||||
#[wasm_bindgen]
|
||||
pub async fn execute_wasm(
|
||||
program_str: &str,
|
||||
memory_str: &str,
|
||||
manager: crate::engine::conn_web::EngineCommandManager,
|
||||
) -> Result<JsValue, String> {
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
|
||||
// deserialize the ast from a stringified json
|
||||
let program: crate::abstract_syntax_tree_types::Program =
|
||||
serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||
let mut mem: ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut engine = EngineConnection::new(manager)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
let memory = execute(program, &mut mem, 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())
|
||||
}
|
||||
|
||||
// wasm_bindgen wrapper for execute
|
||||
#[cfg(not(feature = "web"))]
|
||||
#[wasm_bindgen]
|
||||
pub async fn execute_wasm(program_str: &str, memory_str: &str) -> Result<JsValue, String> {
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
|
||||
// deserialize the ast from a stringified json
|
||||
let program: crate::abstract_syntax_tree_types::Program =
|
||||
serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||
let mut mem: ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut engine = EngineConnection::new("dev.kittycad.io", "some-token", "")
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
let memory = execute(program, &mut mem, 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())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let program = crate::parser::abstract_syntax_tree(&tokens)?;
|
||||
println!("{:#?}", program);
|
||||
let mut mem: ProgramMemory = Default::default();
|
||||
let mut engine = EngineConnection::new("dev.kittycad.io", "some-token", "").await?;
|
||||
let memory = execute(program, &mut mem, BodyType::Root, &mut engine)?;
|
||||
|
||||
Ok(memory)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_assign_two_variables() {
|
||||
let ast = r#"const myVar = 5
|
||||
const newVar = myVar + 1"#;
|
||||
let memory = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::json!(5),
|
||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::json!(6.0),
|
||||
memory.root.get("newVar").unwrap().get_json_value().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_angled_line_that_intersects() {
|
||||
let ast_fn = |offset: &str| -> String {
|
||||
format!(
|
||||
r#"const part001 = startSketchAt([0, 0])
|
||||
|> lineTo({{to:[2, 2], tag: "yo"}}, %)
|
||||
|> lineTo([3, 1], %)
|
||||
|> angledLineThatIntersects({{
|
||||
angle: 180,
|
||||
intersectTag: 'yo',
|
||||
offset: {},
|
||||
tag: "yo2"
|
||||
}}, %)
|
||||
const intersect = segEndX('yo2', part001)
|
||||
show(part001)"#,
|
||||
offset
|
||||
)
|
||||
};
|
||||
|
||||
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()
|
||||
);
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_fn_definitions() {
|
||||
let ast = r#"const def = (x) => {
|
||||
return x
|
||||
}
|
||||
const ghi = (x) => {
|
||||
return x
|
||||
}
|
||||
const jkl = (x) => {
|
||||
return x
|
||||
}
|
||||
const hmm = (x) => {
|
||||
return x
|
||||
}
|
||||
|
||||
const yo = 5 + 6
|
||||
|
||||
const abc = 3
|
||||
const identifierGuy = 5
|
||||
const part001 = startSketchAt([-1.2, 4.83])
|
||||
|> line([2.8, 0], %)
|
||||
|> angledLine([100 + 100, 3.01], %)
|
||||
|> angledLine([abc, 3.02], %)
|
||||
|> angledLine([def(yo), 3.03], %)
|
||||
|> angledLine([ghi(2), 3.04], %)
|
||||
|> angledLine([jkl(yo) + 2, 3.05], %)
|
||||
|> close(%)
|
||||
const yo2 = hmm([identifierGuy + 5])
|
||||
show(part001)"#;
|
||||
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_with_pipe_substitutions_unary() {
|
||||
let ast = r#"const myVar = 3
|
||||
const part001 = startSketchAt([0, 0])
|
||||
|> line({ to: [3, 4], tag: 'seg01' }, %)
|
||||
|> line([
|
||||
min(segLen('seg01', %), myVar),
|
||||
-legLen(segLen('seg01', %), myVar)
|
||||
], %)
|
||||
|
||||
show(part001)"#;
|
||||
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_with_pipe_substitutions() {
|
||||
let ast = r#"const myVar = 3
|
||||
const part001 = startSketchAt([0, 0])
|
||||
|> line({ to: [3, 4], tag: 'seg01' }, %)
|
||||
|> line([
|
||||
min(segLen('seg01', %), myVar),
|
||||
legLen(segLen('seg01', %), myVar)
|
||||
], %)
|
||||
|
||||
show(part001)"#;
|
||||
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
//! Functions for exported files from the server.
|
||||
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -7,7 +8,7 @@ pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
||||
let ws_resp: kittycad::types::WebSocketResponses = bincode::deserialize(data)?;
|
||||
|
||||
if let kittycad::types::WebSocketResponses::Export { files } = ws_resp {
|
||||
return Ok(serde_wasm_bindgen::to_value(&files)?);
|
||||
return Ok(JsValue::from_serde(&files)?);
|
||||
}
|
||||
|
||||
Err(JsError::new(&format!(
|
||||
|
@ -1,7 +1,10 @@
|
||||
mod abstract_syntax_tree_types;
|
||||
mod engine;
|
||||
mod errors;
|
||||
mod executor;
|
||||
mod export;
|
||||
mod math_parser;
|
||||
mod parser;
|
||||
mod recast;
|
||||
mod std;
|
||||
mod tokeniser;
|
||||
|
@ -147,7 +147,7 @@ pub fn reverse_polish_notation(
|
||||
}
|
||||
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!(
|
||||
"Unexpected token: {} {:?}",
|
||||
current_token.value, current_token.token_type
|
||||
@ -260,19 +260,13 @@ fn build_tree(
|
||||
serde_json::Value::Number(n)
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
current_token.start as i32,
|
||||
current_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("Invalid float: {}", current_token.value),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
current_token.start as i32,
|
||||
current_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("Invalid integer: {}", current_token.value),
|
||||
}));
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::abstract_syntax_tree_types::{
|
||||
ArrayExpression, BinaryExpression, BinaryPart, BlockStatement, BodyItem, CallExpression,
|
||||
ExpressionStatement, FunctionExpression, Identifier, Literal, LiteralIdentifier,
|
||||
MemberExpression, MemberObject, NoneCodeMeta, NoneCodeNode, ObjectExpression, ObjectKeyInfo,
|
||||
ObjectProperty, PipeExpression, PipeSubstitution, Program, ReturnStatement, UnaryExpression,
|
||||
Value, VariableDeclaration, VariableDeclarator,
|
||||
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::errors::{KclError, KclErrorDetails};
|
||||
use crate::math_parser::parse_expression;
|
||||
@ -34,13 +34,13 @@ pub fn make_literal(tokens: &[Token], index: usize) -> Result<Literal, KclError>
|
||||
serde_json::Value::Number(n)
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[token.start as i32, token.end as i32]],
|
||||
source_ranges: vec![token.into()],
|
||||
message: format!("Invalid float: {}", token.value),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[token.start as i32, token.end as i32]],
|
||||
source_ranges: vec![token.into()],
|
||||
message: format!("Invalid integer: {}", token.value),
|
||||
}));
|
||||
}
|
||||
@ -165,7 +165,7 @@ pub fn find_closing_brace(
|
||||
search_opening_brace = ¤t_token.value;
|
||||
if !["(", "{", "["].contains(&search_opening_brace) {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!(
|
||||
"expected to be started on a opening brace ( {{ [, instead found '{}'",
|
||||
search_opening_brace
|
||||
@ -380,10 +380,7 @@ fn collect_object_keys(
|
||||
collect_object_keys(tokens, key_token.index, Some(new_previous_keys))
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
period_or_opening_bracket_token.start as i32,
|
||||
period_or_opening_bracket_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![period_or_opening_bracket_token.clone().into()],
|
||||
message: format!(
|
||||
"expression with token type {:?}",
|
||||
period_or_opening_bracket_token.token_type
|
||||
@ -508,7 +505,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
|
||||
}
|
||||
} else {
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!(
|
||||
"expression with token type {:?}",
|
||||
current_token.token_type
|
||||
@ -593,16 +590,16 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
|
||||
last_index: function_expression.last_index,
|
||||
})
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "expression with braces".to_string(),
|
||||
}))
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "expression with braces".to_string(),
|
||||
}))
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@ -615,7 +612,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
|
||||
}
|
||||
|
||||
Err(KclError::Unexpected(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", current_token.token_type),
|
||||
}))
|
||||
}
|
||||
@ -645,7 +642,7 @@ fn make_array_elements(
|
||||
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.start as i32, next_token_token.end as i32]],
|
||||
source_ranges: vec![next_token_token.clone().into()],
|
||||
message: format!(
|
||||
"Expected a comma or closing brace, found {:?}",
|
||||
next_token_token.value
|
||||
@ -662,10 +659,7 @@ fn make_array_elements(
|
||||
make_array_elements(tokens, next_call_index, _previous_elements)
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
first_element_token.start as i32,
|
||||
first_element_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![first_element_token.into()],
|
||||
message: "no next token".to_string(),
|
||||
}))
|
||||
}
|
||||
@ -720,7 +714,7 @@ fn make_pipe_body(
|
||||
last_index = val.last_index;
|
||||
} else {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!(
|
||||
"Expected a pipe value, found {:?}",
|
||||
current_token.token_type
|
||||
@ -899,13 +893,10 @@ fn make_arguments(
|
||||
make_arguments(tokens, next_comma_or_brace_token_index, _previous_args)
|
||||
}
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
argument_token_token.start as i32,
|
||||
argument_token_token.end as i32,
|
||||
]],
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![argument_token_token.clone().into()],
|
||||
message: format!("Unexpected token {} ", argument_token_token.value),
|
||||
}))
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@ -929,27 +920,18 @@ fn make_arguments(
|
||||
}
|
||||
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
argument_token_token.start as i32,
|
||||
argument_token_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![argument_token_token.clone().into()],
|
||||
message: format!("Unexpected token {} ", argument_token_token.value),
|
||||
}))
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
brace_or_comma_token.start as i32,
|
||||
brace_or_comma_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![brace_or_comma_token.into()],
|
||||
message: format!("Unexpected token {} ", brace_or_comma_token.value),
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
brace_or_comma_token.start as i32,
|
||||
brace_or_comma_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![brace_or_comma_token.into()],
|
||||
message: format!("Unexpected token {} ", brace_or_comma_token.value),
|
||||
}))
|
||||
}
|
||||
@ -1045,7 +1027,7 @@ fn make_variable_declarators(
|
||||
})
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.clone().into()],
|
||||
message: format!("Unexpected token {} ", current_token.value),
|
||||
}))
|
||||
}
|
||||
@ -1113,10 +1095,7 @@ fn make_params(
|
||||
make_params(tokens, next_brace_or_comma_token.index, _previous_params)
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
brace_or_comma_token.start as i32,
|
||||
brace_or_comma_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![brace_or_comma_token.into()],
|
||||
message: format!("Unexpected token {} ", brace_or_comma_token.value),
|
||||
}))
|
||||
}
|
||||
@ -1158,7 +1137,7 @@ fn make_unary_expression(
|
||||
}
|
||||
_ => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "Invalid argument for unary expression".to_string(),
|
||||
}));
|
||||
}
|
||||
@ -1205,7 +1184,7 @@ fn make_expression_statement(
|
||||
})
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[current_token.start as i32, current_token.end as i32]],
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "make_expression_statement".to_string(),
|
||||
}))
|
||||
}
|
||||
@ -1266,10 +1245,7 @@ fn make_object_properties(
|
||||
make_object_properties(tokens, next_key_index, _previous_properties)
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![[
|
||||
property_key_token.start as i32,
|
||||
property_key_token.end as i32,
|
||||
]],
|
||||
source_ranges: vec![property_key_token.into()],
|
||||
message: "make_object_properties".to_string(),
|
||||
}))
|
||||
}
|
||||
@ -1470,13 +1446,13 @@ fn make_body(
|
||||
}
|
||||
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![[token.start as i32, token.end as i32]],
|
||||
source_ranges: vec![token.into()],
|
||||
message: "unexpected token".to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
struct BlockStatementResult {
|
||||
block: BlockStatement,
|
||||
block: Program,
|
||||
last_index: usize,
|
||||
}
|
||||
|
||||
@ -1505,7 +1481,7 @@ fn make_block_statement(tokens: &[Token], index: usize) -> Result<BlockStatement
|
||||
)?
|
||||
};
|
||||
Ok(BlockStatementResult {
|
||||
block: BlockStatement {
|
||||
block: Program {
|
||||
start: opening_curly.start,
|
||||
end: tokens[body.last_index].end,
|
||||
body: body.body,
|
||||
@ -1564,14 +1540,6 @@ pub fn abstract_syntax_tree(tokens: &[Token]) -> Result<Program, KclError> {
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
// Use `js_namespace` here to bind `console.log(..)` instead of just
|
||||
// `log(..)`
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_js(js: &str) -> Result<JsValue, String> {
|
||||
let tokens = lexer(js);
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! Generates source code from the AST.
|
||||
//! The inverse of parsing (which generates an AST from the source code)
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::abstract_syntax_tree_types::{
|
||||
@ -403,10 +404,10 @@ pub fn recast_function(expression: FunctionExpression) -> String {
|
||||
// wasm_bindgen wrapper for recast
|
||||
// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts
|
||||
#[wasm_bindgen]
|
||||
pub fn recast_js(json_str: &str) -> Result<JsValue, JsError> {
|
||||
pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
||||
// deserialize the ast from a stringified json
|
||||
let program: Program = serde_json::from_str(json_str).map_err(JsError::from)?;
|
||||
|
||||
let result = recast(&program, "", false);
|
||||
Ok(serde_wasm_bindgen::to_value(&result)?)
|
||||
Ok(JsValue::from_serde(&result)?)
|
||||
}
|
||||
|
56
src/wasm-lib/src/std/extrude.rs
Normal file
56
src/wasm-lib/src/std/extrude.rs
Normal file
@ -0,0 +1,56 @@
|
||||
//! Functions related to extruding.
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{ExtrudeGroup, MemoryItem},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// Extrudes by a given amount.
|
||||
pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (length, sketch_group) = args.get_number_sketch_group()?;
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
let cmd = kittycad::types::ModelingCmd::Extrude {
|
||||
target: sketch_group.id,
|
||||
distance: length,
|
||||
cap: true,
|
||||
};
|
||||
args.send_modeling_cmd(id, cmd)?;
|
||||
|
||||
Ok(MemoryItem::ExtrudeGroup(ExtrudeGroup {
|
||||
id,
|
||||
// TODO, this is just an empty array now, should be deleted. This
|
||||
// comment was originally in the JS code.
|
||||
value: Default::default(),
|
||||
height: length,
|
||||
position: sketch_group.position,
|
||||
rotation: sketch_group.rotation,
|
||||
meta: sketch_group.meta,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns the extrude wall transform.
|
||||
pub fn get_extrude_wall_transform(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (surface_name, extrude_group) = args.get_path_name_extrude_group()?;
|
||||
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(MemoryItem::ExtrudeTransform {
|
||||
position: surface.get_position(),
|
||||
rotation: surface.get_rotation(),
|
||||
meta: extrude_group.meta,
|
||||
})
|
||||
}
|
504
src/wasm-lib/src/std/mod.rs
Normal file
504
src/wasm-lib/src/std/mod.rs
Normal file
@ -0,0 +1,504 @@
|
||||
//! Functions implemented for language execution.
|
||||
|
||||
mod extrude;
|
||||
mod segment;
|
||||
mod sketch;
|
||||
mod utils;
|
||||
|
||||
// TODO: Something that would be nice is if we could generate docs for Kcl based on the
|
||||
// actual stdlib functions below.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::parse_json_number_as_f64,
|
||||
engine::EngineConnection,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
|
||||
std::extrude::{extrude, get_extrude_wall_transform},
|
||||
std::segment::{
|
||||
angle_to_match_length_x, angle_to_match_length_y, last_segment_x, last_segment_y,
|
||||
segment_angle, segment_end_x, segment_end_y, segment_length,
|
||||
},
|
||||
std::sketch::{
|
||||
angled_line, angled_line_of_x_length, angled_line_of_y_length, angled_line_that_intersects,
|
||||
angled_line_to_x, angled_line_to_y, close, line, line_to, start_sketch_at, x_line,
|
||||
x_line_to, y_line, y_line_to,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
pub type FnMap = HashMap<String, StdFn>;
|
||||
pub type StdFn = fn(&mut Args) -> Result<MemoryItem, KclError>;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref INTERNAL_FNS: FnMap =
|
||||
{
|
||||
HashMap::from([
|
||||
// Extrude functions.
|
||||
("extrude".to_string(), extrude as StdFn),
|
||||
("getExtrudeWallTransform".to_string(), get_extrude_wall_transform as StdFn),
|
||||
|
||||
("min".to_string(), min as StdFn),
|
||||
("legLen".to_string(), leg_length as StdFn),
|
||||
("legAngX".to_string(),leg_angle_x as StdFn),
|
||||
("legAngY".to_string(), leg_angle_y as StdFn),
|
||||
// Sketch segment functions.
|
||||
("segEndX".to_string(), segment_end_x as StdFn),
|
||||
("segEndY".to_string(), segment_end_y as StdFn),
|
||||
("lastSegX".to_string(), last_segment_x as StdFn),
|
||||
("lastSegY".to_string(), last_segment_y as StdFn),
|
||||
("segLen".to_string(), segment_length as StdFn),
|
||||
("segAng".to_string(), segment_angle as StdFn),
|
||||
("angleToMatchLengthX".to_string(), angle_to_match_length_x as StdFn),
|
||||
("angleToMatchLengthY".to_string(), angle_to_match_length_y as StdFn),
|
||||
|
||||
// Sketch functions.
|
||||
("lineTo".to_string(), line_to as StdFn),
|
||||
("xLineTo".to_string(), x_line_to as StdFn),
|
||||
("yLineTo".to_string(), y_line_to as StdFn),
|
||||
("line".to_string(), line as StdFn),
|
||||
("xLine".to_string(), x_line as StdFn),
|
||||
("yLine".to_string(), y_line as StdFn),
|
||||
("angledLine".to_string(), angled_line as StdFn),
|
||||
("angledLineOfXLength".to_string(), angled_line_of_x_length as StdFn),
|
||||
("angledLineToX".to_string(), angled_line_to_x as StdFn),
|
||||
("angledLineOfYLength".to_string(), angled_line_of_y_length as StdFn),
|
||||
("angledLineToY".to_string(), angled_line_to_y as StdFn),
|
||||
("angledLineThatIntersects".to_string(), angled_line_that_intersects as StdFn),
|
||||
("startSketchAt".to_string(), start_sketch_at as StdFn),
|
||||
("close".to_string(), close as StdFn),
|
||||
])
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Args<'a> {
|
||||
pub args: Vec<MemoryItem>,
|
||||
pub source_range: SourceRange,
|
||||
engine: &'a mut EngineConnection,
|
||||
}
|
||||
|
||||
impl<'a> Args<'a> {
|
||||
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> {
|
||||
self.engine.send_modeling_cmd(id, self.source_range, cmd)
|
||||
}
|
||||
|
||||
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
||||
Ok(MemoryItem::UserVal {
|
||||
value: j,
|
||||
meta: vec![Metadata {
|
||||
source_range: self.source_range,
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
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(|| {
|
||||
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> {
|
||||
let mut numbers: Vec<f64> = Vec::new();
|
||||
for arg in &self.args {
|
||||
let parsed = arg.get_json_value()?;
|
||||
numbers.push(parse_json_number_as_f64(&parsed, self.source_range)?);
|
||||
}
|
||||
Ok(numbers)
|
||||
}
|
||||
|
||||
fn get_hypotenuse_leg(&self) -> Result<(f64, f64), KclError> {
|
||||
let numbers = self.get_number_array()?;
|
||||
|
||||
if numbers.len() != 2 {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!("Expected a number array of length 2, found `{:?}`", numbers),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
Ok((numbers[0], numbers[1]))
|
||||
}
|
||||
|
||||
fn get_segment_name_sketch_group(&self) -> Result<(String, SketchGroup), KclError> {
|
||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||
// The second argument should be a SketchGroup.
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let segment_name = if let serde_json::Value::String(s) = first_value {
|
||||
s.to_string()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok((segment_name, sketch_group))
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let sketch_group = if let MemoryItem::SketchGroup(sg) = first_value {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok(sketch_group)
|
||||
}
|
||||
|
||||
fn get_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, 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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let data: T = serde_json::from_value(first_value).map_err(|e| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let data: T = serde_json::from_value(first_value).map_err(|e| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||
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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok((data, sketch_group))
|
||||
}
|
||||
|
||||
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.
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let segment_name = if let serde_json::Value::String(s) = first_value {
|
||||
s.to_string()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
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 number as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let to_number = parse_json_number_as_f64(&second_value, self.source_range)?;
|
||||
|
||||
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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let sketch_group = if let MemoryItem::SketchGroup(sg) = third_value {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the third argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok((segment_name, to_number, sketch_group))
|
||||
}
|
||||
|
||||
fn get_number_sketch_group(&self) -> Result<(f64, SketchGroup), KclError> {
|
||||
// Iterate over our args, the first argument should be a number.
|
||||
// The second argument should be a SketchGroup.
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a number as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let number = parse_json_number_as_f64(&first_value, 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
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok((number, sketch_group))
|
||||
}
|
||||
|
||||
fn get_path_name_extrude_group(&self) -> Result<(String, ExtrudeGroup), KclError> {
|
||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||
// The second argument should be a ExtrudeGroup.
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a string as the first argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let path_name = if let serde_json::Value::String(s) = first_value {
|
||||
s.to_string()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
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 ExtrudeGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let extrude_group = if let MemoryItem::ExtrudeGroup(sg) = second_value {
|
||||
sg.clone()
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a ExtrudeGroup as the second argument, found `{:?}`",
|
||||
self.args
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok((path_name, extrude_group))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum of the given arguments.
|
||||
pub fn min(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let mut min = std::f64::MAX;
|
||||
for arg in args.get_number_array()? {
|
||||
if arg < min {
|
||||
min = arg;
|
||||
}
|
||||
}
|
||||
|
||||
args.make_user_val_from_f64(min)
|
||||
}
|
||||
|
||||
/// Returns the length of the given leg.
|
||||
pub fn leg_length(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
|
||||
let result = (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt();
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Returns the angle of the given leg for x.
|
||||
pub fn leg_angle_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
|
||||
let result = (leg.min(hypotenuse) / hypotenuse).acos() * 180.0 / std::f64::consts::PI;
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Returns the angle of the given leg for y.
|
||||
pub fn leg_angle_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
|
||||
let result = (leg.min(hypotenuse) / hypotenuse).asin() * 180.0 / std::f64::consts::PI;
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
211
src/wasm-lib/src/std/segment.rs
Normal file
211
src/wasm-lib/src/std/segment.rs
Normal file
@ -0,0 +1,211 @@
|
||||
//! Functions related to line segments.
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::MemoryItem,
|
||||
std::{utils::get_angle, Args},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// 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()?;
|
||||
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],
|
||||
})
|
||||
})?;
|
||||
|
||||
args.make_user_val_from_f64(line.to[0])
|
||||
}
|
||||
|
||||
/// Returns the segment end of y.
|
||||
pub fn segment_end_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
|
||||
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],
|
||||
})
|
||||
})?;
|
||||
|
||||
args.make_user_val_from_f64(line.to[1])
|
||||
}
|
||||
|
||||
/// Returns the last segment of x.
|
||||
pub fn last_segment_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let sketch_group = args.get_sketch_group()?;
|
||||
let last_line = sketch_group
|
||||
.value
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup with at least one segment, found `{:?}`",
|
||||
sketch_group
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?
|
||||
.get_base();
|
||||
|
||||
args.make_user_val_from_f64(last_line.to[0])
|
||||
}
|
||||
|
||||
/// Returns the last segment of y.
|
||||
pub fn last_segment_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let sketch_group = args.get_sketch_group()?;
|
||||
let last_line = sketch_group
|
||||
.value
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup with at least one segment, found `{:?}`",
|
||||
sketch_group
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?
|
||||
.get_base();
|
||||
|
||||
args.make_user_val_from_f64(last_line.to[1])
|
||||
}
|
||||
|
||||
/// Returns the length of the segment.
|
||||
pub fn segment_length(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
|
||||
let path = sketch_group
|
||||
.get_path_by_name(&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],
|
||||
})
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let result = ((line.from[1] - line.to[1]).powi(2) + (line.from[0] - line.to[0]).powi(2)).sqrt();
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Returns the angle of the segment.
|
||||
pub fn segment_angle(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (segment_name, sketch_group) = args.get_segment_name_sketch_group()?;
|
||||
let path = sketch_group
|
||||
.get_path_by_name(&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],
|
||||
})
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let result = get_angle(&line.from, &line.to);
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Returns the angle to match the given length for x.
|
||||
pub fn angle_to_match_length_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (segment_name, to, sketch_group) = args.get_segment_name_to_number_sketch_group()?;
|
||||
let path = sketch_group
|
||||
.get_path_by_name(&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],
|
||||
})
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let length = ((line.from[1] - line.to[1]).powi(2) + (line.from[0] - line.to[0]).powi(2)).sqrt();
|
||||
|
||||
let last_line = sketch_group
|
||||
.value
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup with at least one segment, found `{:?}`",
|
||||
sketch_group
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?
|
||||
.get_base();
|
||||
|
||||
let diff = (to - last_line.to[0]).abs();
|
||||
|
||||
let angle_r = diff / length.acos();
|
||||
|
||||
if diff > length {
|
||||
args.make_user_val_from_f64(0.0)
|
||||
} else {
|
||||
args.make_user_val_from_f64(angle_r * 180.0 / std::f64::consts::PI)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the angle to match the given length for y.
|
||||
pub fn angle_to_match_length_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (segment_name, to, sketch_group) = args.get_segment_name_to_number_sketch_group()?;
|
||||
let path = sketch_group
|
||||
.get_path_by_name(&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],
|
||||
})
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let length = ((line.from[1] - line.to[1]).powi(2) + (line.from[0] - line.to[0]).powi(2)).sqrt();
|
||||
|
||||
let last_line = sketch_group
|
||||
.value
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a SketchGroup with at least one segment, found `{:?}`",
|
||||
sketch_group
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?
|
||||
.get_base();
|
||||
|
||||
let diff = (to - last_line.to[1]).abs();
|
||||
|
||||
let angle_r = diff / length.asin();
|
||||
|
||||
if diff > length {
|
||||
args.make_user_val_from_f64(0.0)
|
||||
} else {
|
||||
args.make_user_val_from_f64(angle_r * 180.0 / std::f64::consts::PI)
|
||||
}
|
||||
}
|
578
src/wasm-lib/src/std/sketch.rs
Normal file
578
src/wasm-lib/src/std/sketch.rs
Normal file
@ -0,0 +1,578 @@
|
||||
//! Functions related to sketching.
|
||||
|
||||
use kittycad::types::{ModelingCmd, Point3D};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
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},
|
||||
Args,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum LineToData {
|
||||
PointWithTag { to: [f64; 2], tag: String },
|
||||
Point([f64; 2]),
|
||||
}
|
||||
|
||||
/// Draw a line to a point.
|
||||
pub fn line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (LineToData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let new_sketch_group = inner_line_to(data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line to a point.
|
||||
fn inner_line_to(
|
||||
data: LineToData,
|
||||
sketch_group: SketchGroup,
|
||||
args: &Args,
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let to = match data {
|
||||
LineToData::PointWithTag { to, .. } => to,
|
||||
LineToData::Point(to) => to,
|
||||
};
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to,
|
||||
name: if let LineToData::PointWithTag { 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)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum AxisLineToData {
|
||||
PointWithTag { to: f64, tag: String },
|
||||
Point(f64),
|
||||
}
|
||||
|
||||
/// Draw a line to a point on the x-axis.
|
||||
pub fn x_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AxisLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
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::Point(data) => LineToData::Point([data, from.y]),
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line to a point on the y-axis.
|
||||
pub fn y_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AxisLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
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::Point(data) => LineToData::Point([from.x, data]),
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum LineData {
|
||||
PointWithTag { to: PointOrDefault, tag: String },
|
||||
Point([f64; 2]),
|
||||
Default(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum PointOrDefault {
|
||||
Point([f64; 2]),
|
||||
Default(String),
|
||||
}
|
||||
|
||||
impl PointOrDefault {
|
||||
fn get_point_with_default(&self, default: [f64; 2]) -> [f64; 2] {
|
||||
match self {
|
||||
PointOrDefault::Point(point) => *point,
|
||||
PointOrDefault::Default(_) => default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a line.
|
||||
pub fn line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (LineData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let new_sketch_group = inner_line(data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
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];
|
||||
let inner_args = match &data {
|
||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
||||
LineData::Point(to) => *to,
|
||||
LineData::Default(_) => default,
|
||||
};
|
||||
|
||||
let to = [from.x + inner_args[0], from.y + inner_args[1]];
|
||||
|
||||
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(),
|
||||
to,
|
||||
name: if let LineData::PointWithTag { 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)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum AxisLineData {
|
||||
PointWithTag { length: f64, tag: String },
|
||||
Point(f64),
|
||||
}
|
||||
|
||||
/// Draw a line on the x-axis.
|
||||
pub fn x_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AxisLineData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let line_data = match data {
|
||||
AxisLineData::PointWithTag { length, tag } => LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([length, 0.0]),
|
||||
tag,
|
||||
},
|
||||
AxisLineData::Point(length) => LineData::Point([length, 0.0]),
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line(line_data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line on the y-axis.
|
||||
pub fn y_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AxisLineData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let line_data = match data {
|
||||
AxisLineData::PointWithTag { length, tag } => LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([0.0, length]),
|
||||
tag,
|
||||
},
|
||||
AxisLineData::Point(length) => LineData::Point([0.0, length]),
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line(line_data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum AngledLineData {
|
||||
AngleWithTag {
|
||||
angle: f64,
|
||||
length: f64,
|
||||
tag: String,
|
||||
},
|
||||
AngleAndLength([f64; 2]),
|
||||
}
|
||||
|
||||
/// Draw an angled line.
|
||||
pub fn angled_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AngledLineData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
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])
|
||||
}
|
||||
};
|
||||
let to: [f64; 2] = [
|
||||
from.x + length * f64::cos(angle * std::f64::consts::PI / 180.0),
|
||||
from.y + length * f64::sin(angle * std::f64::consts::PI / 180.0),
|
||||
];
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to,
|
||||
name: if let AngledLineData::AngleWithTag { 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(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw an angled line of a given x length.
|
||||
pub fn angled_line_of_x_length(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AngledLineData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let (angle, length) = match &data {
|
||||
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
|
||||
AngledLineData::AngleAndLength(angle_and_length) => {
|
||||
(angle_and_length[0], angle_and_length[1])
|
||||
}
|
||||
};
|
||||
|
||||
let to = get_y_component(angle, length);
|
||||
|
||||
let new_sketch_group = inner_line(
|
||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||
LineData::PointWithTag {
|
||||
to: PointOrDefault::Point(to),
|
||||
tag,
|
||||
}
|
||||
} else {
|
||||
LineData::Point(to)
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
)?;
|
||||
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum AngledLineToData {
|
||||
AngleWithTag { angle: f64, to: f64, tag: String },
|
||||
AngleAndPoint([f64; 2]),
|
||||
}
|
||||
|
||||
/// Draw an angled line to a given x coordinate.
|
||||
pub fn angled_line_to_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AngledLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let (angle, x_to) = match &data {
|
||||
AngledLineToData::AngleWithTag { angle, to, .. } => (*angle, *to),
|
||||
AngledLineToData::AngleAndPoint(angle_and_to) => (angle_and_to[0], angle_and_to[1]),
|
||||
};
|
||||
|
||||
let x_component = x_to - from.x;
|
||||
let y_component = x_component * f64::tan(angle * std::f64::consts::PI / 180.0);
|
||||
let y_to = from.y + y_component;
|
||||
|
||||
let new_sketch_group = inner_line_to(
|
||||
if let AngledLineToData::AngleWithTag { tag, .. } = data {
|
||||
LineToData::PointWithTag {
|
||||
to: [x_to, y_to],
|
||||
tag,
|
||||
}
|
||||
} else {
|
||||
LineToData::Point([x_to, y_to])
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw an angled line of a given y length.
|
||||
pub fn angled_line_of_y_length(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AngledLineData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let (angle, length) = match &data {
|
||||
AngledLineData::AngleWithTag { angle, length, .. } => (*angle, *length),
|
||||
AngledLineData::AngleAndLength(angle_and_length) => {
|
||||
(angle_and_length[0], angle_and_length[1])
|
||||
}
|
||||
};
|
||||
|
||||
let to = get_x_component(angle, length);
|
||||
|
||||
let new_sketch_group = inner_line(
|
||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||
LineData::PointWithTag {
|
||||
to: PointOrDefault::Point(to),
|
||||
tag,
|
||||
}
|
||||
} else {
|
||||
LineData::Point(to)
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
)?;
|
||||
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw an angled line to a given y coordinate.
|
||||
pub fn angled_line_to_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (data, sketch_group): (AngledLineToData, SketchGroup) = args.get_data_and_sketch_group()?;
|
||||
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let (angle, y_to) = match &data {
|
||||
AngledLineToData::AngleWithTag { angle, to, .. } => (*angle, *to),
|
||||
AngledLineToData::AngleAndPoint(angle_and_to) => (angle_and_to[0], angle_and_to[1]),
|
||||
};
|
||||
|
||||
let y_component = y_to - from.y;
|
||||
let x_component = y_component / f64::tan(angle * std::f64::consts::PI / 180.0);
|
||||
let x_to = from.x + x_component;
|
||||
|
||||
let new_sketch_group = inner_line_to(
|
||||
if let AngledLineToData::AngleWithTag { tag, .. } = data {
|
||||
LineToData::PointWithTag {
|
||||
to: [x_to, y_to],
|
||||
tag,
|
||||
}
|
||||
} else {
|
||||
LineToData::Point([x_to, y_to])
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
// TODO: make sure the docs on the args below are correct.
|
||||
pub struct AngeledLineThatIntersectsData {
|
||||
/// The angle of the line.
|
||||
pub angle: f64,
|
||||
/// The tag of the line to intersect with.
|
||||
pub intersect_tag: String,
|
||||
/// The offset from the intersecting line.
|
||||
pub offset: Option<f64>,
|
||||
/// The tag.
|
||||
pub tag: Option<String>,
|
||||
}
|
||||
|
||||
/// 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 intersect_path = sketch_group
|
||||
.get_path_by_name(&data.intersect_tag)
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a line that exists in the given SketchGroup, found `{}`",
|
||||
data.intersect_tag
|
||||
),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?
|
||||
.get_base();
|
||||
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let to = intersection_with_parallel_line(
|
||||
&[intersect_path.from, intersect_path.to],
|
||||
data.offset.unwrap_or_default(),
|
||||
data.angle,
|
||||
from.into(),
|
||||
);
|
||||
|
||||
let line_to_data = if let Some(tag) = data.tag {
|
||||
LineToData::PointWithTag { to, tag }
|
||||
} else {
|
||||
LineToData::Point(to)
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Start a sketch at a given point.
|
||||
pub fn start_sketch_at(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let data: LineData = args.get_data()?;
|
||||
|
||||
let default = [0.0, 0.0];
|
||||
let to = match &data {
|
||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
||||
LineData::Point(to) => *to,
|
||||
LineData::Default(_) => default,
|
||||
};
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let path_id = uuid::Uuid::new_v4();
|
||||
|
||||
args.send_modeling_cmd(path_id, ModelingCmd::StartPath {})?;
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::MovePathPen {
|
||||
path: path_id,
|
||||
to: Point3D {
|
||||
x: to[0],
|
||||
y: to[1],
|
||||
z: 0.0,
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
let current_path = BasePath {
|
||||
from: to,
|
||||
to,
|
||||
name: if let LineData::PointWithTag { tag, .. } = data {
|
||||
tag.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
geo_meta: GeoMeta {
|
||||
id,
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
};
|
||||
|
||||
let sketch_group = SketchGroup {
|
||||
id: path_id,
|
||||
position: Position([0.0, 0.0, 0.0]),
|
||||
rotation: Rotation([0.0, 0.0, 0.0, 1.0]),
|
||||
value: vec![],
|
||||
start: current_path,
|
||||
meta: vec![args.source_range.into()],
|
||||
};
|
||||
Ok(MemoryItem::SketchGroup(sketch_group))
|
||||
}
|
||||
|
||||
/// Close the current sketch.
|
||||
pub fn close(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let sketch_group = args.get_sketch_group()?;
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let to: Point2d = sketch_group.start.from.into();
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::ClosePath {
|
||||
path_id: sketch_group.id,
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut new_sketch_group = sketch_group.clone();
|
||||
new_sketch_group.value.push(Path::ToPoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to: to.into(),
|
||||
// TODO: should we use a different name?
|
||||
name: "".into(),
|
||||
geo_meta: GeoMeta {
|
||||
id,
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::std::sketch::{LineData, PointOrDefault};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_line_data() {
|
||||
let mut str_json = "\"default\"".to_string();
|
||||
let data: LineData = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, LineData::Default("default".to_string()));
|
||||
|
||||
let data = LineData::Point([0.0, 1.0]);
|
||||
str_json = serde_json::to_string(&data).unwrap();
|
||||
assert_eq!(str_json, "[0.0,1.0]");
|
||||
|
||||
str_json = "[0, 1]".to_string();
|
||||
let data: LineData = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, LineData::Point([0.0, 1.0]));
|
||||
|
||||
str_json = "{ \"to\": [0.0, 1.0], \"tag\": \"thing\" }".to_string();
|
||||
let data: LineData = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(
|
||||
data,
|
||||
LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([0.0, 1.0]),
|
||||
tag: "thing".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
248
src/wasm-lib/src/std/utils.rs
Normal file
248
src/wasm-lib/src/std/utils.rs
Normal file
@ -0,0 +1,248 @@
|
||||
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
|
||||
let x = b[0] - a[0];
|
||||
let y = b[1] - a[1];
|
||||
normalise_angle(y.atan2(x) * 180.0 / std::f64::consts::PI)
|
||||
}
|
||||
|
||||
pub fn normalise_angle(angle: f64) -> f64 {
|
||||
let result = ((angle % 360.0) + 360.0) % 360.0;
|
||||
if result > 180.0 {
|
||||
result - 360.0
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clockwise_sign(points: &[[f64; 2]]) -> i32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..points.len() {
|
||||
let current_point = points[i];
|
||||
let next_point = points[(i + 1) % points.len()];
|
||||
sum += (next_point[0] - current_point[0]) * (next_point[1] + current_point[1]);
|
||||
}
|
||||
if sum >= 0.0 {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn normalize_rad(angle: f64) -> f64 {
|
||||
let draft = angle % (2.0 * std::f64::consts::PI);
|
||||
if draft < 0.0 {
|
||||
draft + 2.0 * std::f64::consts::PI
|
||||
} else {
|
||||
draft
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives the ▲-angle between from and to angles (shortest path), use radians.
|
||||
///
|
||||
/// Sign of the returned angle denotes direction, positive means counterClockwise 🔄
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(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 {
|
||||
let norm_from_angle = normalize_rad(from_angle);
|
||||
let norm_to_angle = normalize_rad(to_angle);
|
||||
let provisional = norm_to_angle - norm_from_angle;
|
||||
|
||||
if provisional > -std::f64::consts::PI && provisional <= std::f64::consts::PI {
|
||||
return provisional;
|
||||
}
|
||||
if provisional > std::f64::consts::PI {
|
||||
return provisional - 2.0 * std::f64::consts::PI;
|
||||
}
|
||||
if provisional < -std::f64::consts::PI {
|
||||
return provisional + 2.0 * std::f64::consts::PI;
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
/// Calculates the distance between two points.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(distance_between_points(&[0.0, 0.0], &[0.0, 5.0]), 5.0);
|
||||
/// assert_eq!(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 {
|
||||
let x1 = point_a[0];
|
||||
let y1 = point_a[1];
|
||||
let x2 = point_b[0];
|
||||
let y2 = point_b[1];
|
||||
|
||||
((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] {
|
||||
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,
|
||||
];
|
||||
intersect(line1[0], line1[1], line2_point, line2_point_b)
|
||||
}
|
||||
|
||||
pub fn intersect(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2], p4: [f64; 2]) -> [f64; 2] {
|
||||
let slope = |p1: [f64; 2], p2: [f64; 2]| (p1[1] - p2[1]) / (p1[0] - p2[0]);
|
||||
let constant = |p1: [f64; 2], p2: [f64; 2]| p1[1] - slope(p1, p2) * p1[0];
|
||||
let get_y = |for_x: f64, p1: [f64; 2], p2: [f64; 2]| slope(p1, p2) * for_x + constant(p1, p2);
|
||||
|
||||
if p1[0] == p2[0] {
|
||||
return [p1[0], get_y(p1[0], p3, p4)];
|
||||
}
|
||||
if p3[0] == p4[0] {
|
||||
return [p3[0], get_y(p3[0], p1, p2)];
|
||||
}
|
||||
|
||||
let x = (constant(p3, p4) - constant(p1, p2)) / (slope(p1, p2) - slope(p3, p4));
|
||||
let y = get_y(x, p1, p2);
|
||||
[x, y]
|
||||
}
|
||||
|
||||
pub fn intersection_with_parallel_line(
|
||||
line1: &[[f64; 2]; 2],
|
||||
line1_offset: f64,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
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]],
|
||||
];
|
||||
}
|
||||
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],
|
||||
];
|
||||
}
|
||||
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]]]
|
||||
}
|
||||
|
||||
pub fn get_y_component(angle_degree: f64, x_component: f64) -> [f64; 2] {
|
||||
let normalised_angle = ((angle_degree % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let y_component = x_component * f64::tan(normalised_angle * std::f64::consts::PI / 180.0);
|
||||
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
[sign * x_component, sign * y_component]
|
||||
}
|
||||
|
||||
pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
|
||||
let normalised_angle = ((angle_degree % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let x_component = y_component / f64::tan(normalised_angle * std::f64::consts::PI / 180.0);
|
||||
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
[sign * x_component, sign * y_component]
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
|
||||
(-315, [1, 1]),
|
||||
(-225, [-1, 1]),
|
||||
(-135, [-1, -1]),
|
||||
(-45, [1, -1]),
|
||||
(45, [1, 1]),
|
||||
(135, [-1, 1]),
|
||||
(225, [-1, -1]),
|
||||
(315, [1, -1]),
|
||||
(405, [1, 1]),
|
||||
(495, [-1, 1]),
|
||||
(585, [-1, -1]),
|
||||
(675, [1, -1]),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_get_y_component() {
|
||||
let mut expected = Vec::new();
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &(angle, expected_result) in EACH_QUAD.iter() {
|
||||
let res = get_y_component(angle as f64, 1.0);
|
||||
results.push([res[0].round() as i32, res[1].round() as i32]);
|
||||
expected.push(expected_result);
|
||||
}
|
||||
|
||||
assert_eq!(results, expected);
|
||||
|
||||
let result = get_y_component(0.0, 1.0);
|
||||
assert_eq!(result[0] as i32, 1);
|
||||
assert_eq!(result[1] as i32, 0);
|
||||
|
||||
let result = get_y_component(90.0, 1.0);
|
||||
assert_eq!(result[0] as i32, 1);
|
||||
assert!(result[1] > 100000.0);
|
||||
|
||||
let result = get_y_component(180.0, 1.0);
|
||||
assert_eq!(result[0] as i32, -1);
|
||||
assert!((result[1] - 0.0).abs() < f64::EPSILON);
|
||||
|
||||
let result = get_y_component(270.0, 1.0);
|
||||
assert_eq!(result[0] as i32, -1);
|
||||
assert!(result[1] < -100000.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_x_component() {
|
||||
let mut expected = Vec::new();
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &(angle, expected_result) in EACH_QUAD.iter() {
|
||||
let res = get_x_component(angle as f64, 1.0);
|
||||
results.push([res[0].round() as i32, res[1].round() as i32]);
|
||||
expected.push(expected_result);
|
||||
}
|
||||
|
||||
assert_eq!(results, expected);
|
||||
|
||||
let result = get_x_component(0.0, 1.0);
|
||||
assert!(result[0] > 100000.0);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
|
||||
let result = get_x_component(90.0, 1.0);
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
|
||||
let result = get_x_component(180.0, 1.0);
|
||||
assert!(result[0] < -100000.0);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
|
||||
let result = get_x_component(270.0, 1.0);
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, -1);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -30,6 +31,18 @@ pub struct Token {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<Token> for crate::executor::SourceRange {
|
||||
fn from(token: Token) -> Self {
|
||||
Self([token.start, token.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Token> for crate::executor::SourceRange {
|
||||
fn from(token: &Token) -> Self {
|
||||
Self([token.start, token.end])
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref NUMBER: Regex = Regex::new(r"^-?\d+(\.\d+)?").unwrap();
|
||||
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
|
||||
@ -265,7 +278,7 @@ pub fn lexer(str: &str) -> Vec<Token> {
|
||||
#[wasm_bindgen]
|
||||
pub fn lexer_js(str: &str) -> Result<JsValue, JsError> {
|
||||
let tokens = lexer(str);
|
||||
Ok(serde_wasm_bindgen::to_value(&tokens)?)
|
||||
Ok(JsValue::from_serde(&tokens)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"types": ["vite/client", "@types/wicg-file-system-access"],
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
|
Reference in New Issue
Block a user