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:
Jess Frazelle
2023-08-24 15:34:51 -07:00
committed by GitHub
parent d33ddb2f1b
commit de255acc59
41 changed files with 4560 additions and 1211 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
src/wasm-lib/pkg/wasm_lib.js

View File

@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
dir: ['src/wasm-lib', 'src-tauri'] dir: ['src/wasm-lib']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -43,5 +43,7 @@ jobs:
- name: Run cargo build - name: Run cargo build
run: | run: |
cd "${{ matrix.dir }}" cd "${{ matrix.dir }}"
cargo build --all --no-default-features --features noweb
cargo build --all --no-default-features --features web
cargo build --all cargo build --all
shell: bash shell: bash

View File

@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
dir: ['src/wasm-lib', 'src-tauri'] dir: ['src/wasm-lib']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install latest rust - name: Install latest rust

View File

@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest-8-cores runs-on: ubuntu-latest-8-cores
strategy: strategy:
matrix: matrix:
dir: ['src/wasm-lib', 'src-tauri'] dir: ['src/wasm-lib']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install latest rust - name: Install latest rust

View File

@ -60,7 +60,7 @@
"simpleserver": "http-server ./public --cors -p 3000", "simpleserver": "http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src", "fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./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\"", "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", "wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/bindings",
"lint": "eslint --fix src", "lint": "eslint --fix src",

View File

@ -295,18 +295,6 @@ export function App() {
_ast, _ast,
{ {
root: { root: {
log: {
type: 'userVal',
value: (a: any) => {
addLog(a)
},
__meta: [
{
pathToNode: [],
sourceRange: [0, 0],
},
],
},
_0: { _0: {
type: 'userVal', type: 'userVal',
value: 0, value: 0,
@ -328,11 +316,8 @@ export function App() {
__meta: [], __meta: [],
}, },
}, },
pendingMemory: {},
}, },
engineCommandManager, engineCommandManager
{ bodyType: 'root' },
[]
) )
const { artifactMap, sourceRangeMap } = const { artifactMap, sourceRangeMap } =

View File

@ -28,30 +28,20 @@ describe('processMemory', () => {
show(theExtrude, theSketch)` show(theExtrude, theSketch)`
const ast = parser_wasm(code) const ast = parser_wasm(code)
const programMemory = await enginelessExecutor(ast, { const programMemory = await enginelessExecutor(ast, {
root: { root: {},
log: {
type: 'userVal',
value: (a: any) => {
console.log('raw log', a)
},
__meta: [],
},
},
pendingMemory: {},
}) })
const output = processMemory(programMemory) const output = processMemory(programMemory)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)
expect(output.myFn).toEqual('__function__')
expect(output.otherVar).toEqual(3) expect(output.otherVar).toEqual(3)
expect(output).toEqual({ expect(output).toEqual({
myVar: 5, myVar: 5,
myFn: '__function__', myFn: undefined,
otherVar: 3, otherVar: 3,
theExtrude: [], theExtrude: [],
theSketch: [ theSketch: [
{ type: 'toPoint', to: [-3.35, 0.17], from: [0, 0] }, { type: 'toPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
{ type: 'toPoint', to: [0.98, 5.16], from: [-3.35, 0.17] }, { 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] }, { type: 'toPoint', to: [2.15, 4.32], from: [0.98, 5.16], name: '' },
], ],
}) })
}) })

View File

@ -214,7 +214,6 @@ describe('testing function declaration', () => {
id: null, id: null,
params: [], params: [],
body: { body: {
type: 'BlockStatement',
start: 17, start: 17,
end: 19, end: 19,
body: [], body: [],
@ -267,7 +266,6 @@ describe('testing function declaration', () => {
}, },
], ],
body: { body: {
type: 'BlockStatement',
start: 21, start: 21,
end: 39, end: 39,
body: [ body: [
@ -344,7 +342,6 @@ const myVar = funcN(1, 2)`
}, },
], ],
body: { body: {
type: 'BlockStatement',
start: 21, start: 21,
end: 37, end: 37,
body: [ body: [
@ -1585,9 +1582,8 @@ const key = 'c'`
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({ expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
type: 'NoneCodeNode', type: 'NoneCodeNode',
start: 106, start: 106,
end: 168, end: 166,
value: value: ' /* this is\n a comment\n spanning a few lines */\n ',
' /* this is \n a comment \n spanning a few lines */\n ',
}) })
}) })
it('comments in a pipe expression', () => { it('comments in a pipe expression', () => {

View File

@ -5,7 +5,7 @@ import { Token } from './tokeniser'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/bindings/KclError' 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]) ranges.map(([start, end]) => [start, end])
export const parser_wasm = (code: string): Program => { export const parser_wasm = (code: string): Program => {

View File

@ -22,7 +22,6 @@ export type SyntaxType =
| 'BinaryExpression' | 'BinaryExpression'
| 'CallExpression' | 'CallExpression'
| 'Identifier' | 'Identifier'
| 'BlockStatement'
| 'ReturnStatement' | 'ReturnStatement'
| 'VariableDeclaration' | 'VariableDeclaration'
| 'VariableDeclarator' | 'VariableDeclarator'

View File

@ -14,48 +14,49 @@ const mySketch001 = startSketchAt([0, 0])
// |> rx(45, %) // |> rx(45, %)
show(mySketch001)` show(mySketch001)`
const programMemory = await enginelessExecutor(parser_wasm(code)) const programMemory = await enginelessExecutor(parser_wasm(code))
// @ts-ignore
const shown = programMemory?.return?.map( const shown = programMemory?.return?.map(
// @ts-ignore
(a) => programMemory?.root?.[a.name] (a) => programMemory?.root?.[a.name]
) )
expect(shown).toEqual([ expect(shown).toEqual([
{ {
type: 'sketchGroup', type: 'sketchGroup',
start: { start: {
type: 'base',
to: [0, 0], to: [0, 0],
from: [0, 0], from: [0, 0],
name: '',
__geoMeta: { __geoMeta: {
id: '66366561-6465-4734-a463-366330356563', id: expect.any(String),
sourceRange: [21, 42], sourceRange: [21, 42],
pathToNode: [],
}, },
}, },
value: [ value: [
{ {
type: 'toPoint', type: 'toPoint',
name: '',
to: [-1.59, -1.54], to: [-1.59, -1.54],
from: [0, 0], from: [0, 0],
__geoMeta: { __geoMeta: {
sourceRange: [48, 73], sourceRange: [48, 73],
id: '30366338-6462-4330-a364-303935626163', id: expect.any(String),
pathToNode: [],
}, },
}, },
{ {
type: 'toPoint', type: 'toPoint',
to: [0.46, -5.82], to: [0.46, -5.82],
from: [-1.59, -1.54], from: [-1.59, -1.54],
name: '',
__geoMeta: { __geoMeta: {
sourceRange: [79, 103], sourceRange: [79, 103],
id: '32653334-6331-4231-b162-663334363535', id: expect.any(String),
pathToNode: [],
}, },
}, },
], ],
position: [0, 0, 0], position: [0, 0, 0],
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
id: '39643164-6130-4734-b432-623638393262', id: expect.any(String),
__meta: [{ sourceRange: [21, 42], pathToNode: [] }], __meta: [{ sourceRange: [21, 42] }],
}, },
]) ])
}) })
@ -69,21 +70,20 @@ const mySketch001 = startSketchAt([0, 0])
|> extrude(2, %) |> extrude(2, %)
show(mySketch001)` show(mySketch001)`
const programMemory = await enginelessExecutor(parser_wasm(code)) const programMemory = await enginelessExecutor(parser_wasm(code))
// @ts-ignore
const shown = programMemory?.return?.map( const shown = programMemory?.return?.map(
// @ts-ignore
(a) => programMemory?.root?.[a.name] (a) => programMemory?.root?.[a.name]
) )
expect(shown).toEqual([ expect(shown).toEqual([
{ {
type: 'extrudeGroup', type: 'extrudeGroup',
id: '65383433-3839-4333-b836-343263636638', id: expect.any(String),
value: [], value: [],
height: 2, height: 2,
position: [0, 0, 0], position: [0, 0, 0],
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
__meta: [ __meta: [{ sourceRange: [21, 42] }],
{ sourceRange: [127, 140], pathToNode: [] },
{ sourceRange: [21, 42], pathToNode: [] },
],
}, },
]) ])
}) })
@ -110,33 +110,29 @@ const sk2 = startSketchAt([0, 0])
show(theExtrude, sk2)` show(theExtrude, sk2)`
const programMemory = await enginelessExecutor(parser_wasm(code)) const programMemory = await enginelessExecutor(parser_wasm(code))
// @ts-ignore
const geos = programMemory?.return?.map( const geos = programMemory?.return?.map(
// @ts-ignore
({ name }) => programMemory?.root?.[name] ({ name }) => programMemory?.root?.[name]
) )
expect(geos).toEqual([ expect(geos).toEqual([
{ {
type: 'extrudeGroup', type: 'extrudeGroup',
id: '63333330-3631-4230-b664-623132643731', id: expect.any(String),
value: [], value: [],
height: 2, height: 2,
position: [0, 0, 0], position: [0, 0, 0],
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
__meta: [ __meta: [{ sourceRange: [13, 34] }],
{ sourceRange: [212, 227], pathToNode: [] },
{ sourceRange: [13, 34], pathToNode: [] },
],
}, },
{ {
type: 'extrudeGroup', type: 'extrudeGroup',
id: '33316639-3438-4661-a334-663262383737', id: expect.any(String),
value: [], value: [],
height: 2, height: 2,
position: [0, 0, 0], position: [0, 0, 0],
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
__meta: [ __meta: [{ sourceRange: [302, 323] }],
{ sourceRange: [453, 466], pathToNode: [] },
{ sourceRange: [302, 323], pathToNode: [] },
],
}, },
]) ])
}) })

View File

@ -5,7 +5,7 @@ import { ProgramMemory } from './executor'
import { initPromise } from './rust' import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { vi } from 'vitest' import { vi } from 'vitest'
import { KCLUndefinedValueError } from './errors' import { KCLError } from './errors'
beforeAll(() => initPromise) beforeAll(() => initPromise)
@ -30,29 +30,6 @@ const newVar = myVar + 1`
const { root } = await exe(code) const { root } = await exe(code)
expect(root.myVar.value).toBe('a str another str') 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 () => { it('fn funcN = () => {} execute', async () => {
const { root } = await exe( const { root } = await exe(
[ [
@ -84,8 +61,7 @@ show(mySketch)
from: [0, 0], from: [0, 0],
__geoMeta: { __geoMeta: {
sourceRange: [43, 80], sourceRange: [43, 80],
id: '37333036-3033-4432-b530-643030303837', id: expect.any(String),
pathToNode: [],
}, },
name: 'myPath', name: 'myPath',
}, },
@ -93,10 +69,10 @@ show(mySketch)
type: 'toPoint', type: 'toPoint',
to: [2, 3], to: [2, 3],
from: [0, 2], from: [0, 2],
name: '',
__geoMeta: { __geoMeta: {
sourceRange: [86, 102], sourceRange: [86, 102],
id: '32343136-3330-4134-a462-376437386365', id: expect.any(String),
pathToNode: [],
}, },
}, },
{ {
@ -105,8 +81,7 @@ show(mySketch)
from: [2, 3], from: [2, 3],
__geoMeta: { __geoMeta: {
sourceRange: [108, 151], sourceRange: [108, 151],
id: '32306132-6130-4138-b832-636363326330', id: expect.any(String),
pathToNode: [],
}, },
name: 'rightPath', name: 'rightPath',
}, },
@ -170,13 +145,12 @@ show(mySketch)
expect(root.mySk1).toEqual({ expect(root.mySk1).toEqual({
type: 'sketchGroup', type: 'sketchGroup',
start: { start: {
type: 'base',
to: [0, 0], to: [0, 0],
from: [0, 0], from: [0, 0],
name: '',
__geoMeta: { __geoMeta: {
id: '37663863-3664-4366-a637-623739336334', id: expect.any(String),
sourceRange: [14, 34], sourceRange: [14, 34],
pathToNode: [],
}, },
}, },
value: [ value: [
@ -184,10 +158,10 @@ show(mySketch)
type: 'toPoint', type: 'toPoint',
to: [1, 1], to: [1, 1],
from: [0, 0], from: [0, 0],
name: '',
__geoMeta: { __geoMeta: {
sourceRange: [40, 56], sourceRange: [40, 56],
id: '34356231-3362-4363-b935-393033353034', id: expect.any(String),
pathToNode: [],
}, },
}, },
{ {
@ -196,8 +170,7 @@ show(mySketch)
from: [1, 1], from: [1, 1],
__geoMeta: { __geoMeta: {
sourceRange: [62, 100], sourceRange: [62, 100],
id: '39623339-3538-4366-b633-356630326639', id: expect.any(String),
pathToNode: [],
}, },
name: 'myPath', name: 'myPath',
}, },
@ -205,17 +178,17 @@ show(mySketch)
type: 'toPoint', type: 'toPoint',
to: [1, 1], to: [1, 1],
from: [0, 1], from: [0, 1],
name: '',
__geoMeta: { __geoMeta: {
sourceRange: [106, 122], sourceRange: [106, 122],
id: '30636135-6232-4335-b665-366562303161', id: expect.any(String),
pathToNode: [],
}, },
}, },
], ],
position: [0, 0, 0], position: [0, 0, 0],
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
id: '30376661-3039-4965-b532-653665313731', id: expect.any(String),
__meta: [{ sourceRange: [14, 34], pathToNode: [] }], __meta: [{ sourceRange: [14, 34] }],
}) })
}) })
it('execute array expression', async () => { it('execute array expression', async () => {
@ -230,13 +203,6 @@ show(mySketch)
value: 3, value: 3,
__meta: [ __meta: [
{ {
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [14, 15], sourceRange: [14, 15],
}, },
], ],
@ -246,25 +212,8 @@ show(mySketch)
value: [1, '2', 3, 9], value: [1, '2', 3, 9],
__meta: [ __meta: [
{ {
pathToNode: [
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [27, 49], 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 }, value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
__meta: [ __meta: [
{ {
pathToNode: [
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [27, 83], sourceRange: [27, 83],
}, },
], ],
@ -302,13 +244,6 @@ show(mySketch)
value: '123', value: '123',
__meta: [ __meta: [
{ {
pathToNode: [
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [41, 50], sourceRange: [41, 50],
}, },
], ],
@ -451,17 +386,18 @@ const theExtrude = startSketchAt([0, 0])
|> extrude(4, %) |> extrude(4, %)
show(theExtrude)` show(theExtrude)`
await expect(exe(code)).rejects.toEqual( 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 // helpers
async function exe( async function exe(code: string, programMemory: ProgramMemory = { root: {} }) {
code: string,
programMemory: ProgramMemory = { root: {}, pendingMemory: {} }
) {
const ast = parser_wasm(code) const ast = parser_wasm(code)
const result = await enginelessExecutor(ast, programMemory) const result = await enginelessExecutor(ast, programMemory)

View File

@ -1,35 +1,19 @@
import { import { Program } from './abstractSyntaxTreeTypes'
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 { import {
EngineCommandManager, EngineCommandManager,
ArtifactMap, ArtifactMap,
SourceRangeMap, SourceRangeMap,
} from './std/engineConnection' } 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 SourceRange = [number, number]
export type PathToNode = [string | number, string][] // [pathKey, nodeType][] export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
export type Metadata = { export type Metadata = {
sourceRange: SourceRange sourceRange: SourceRange
pathToNode: PathToNode
} }
export type Position = [number, number, number] export type Position = [number, number, number]
export type Rotation = [number, number, number, number] export type Rotation = [number, number, number, number]
@ -41,7 +25,6 @@ interface BasePath {
__geoMeta: { __geoMeta: {
id: string id: string
sourceRange: SourceRange sourceRange: SourceRange
pathToNode: PathToNode
} }
} }
@ -68,9 +51,7 @@ export interface AngledLineTo extends BasePath {
interface GeoMeta { interface GeoMeta {
__geoMeta: { __geoMeta: {
id: string id: string
refId?: string
sourceRange: SourceRange sourceRange: SourceRange
pathToNode: PathToNode
} }
} }
@ -118,69 +99,16 @@ type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
interface Memory { interface Memory {
[key: string]: MemoryItem [key: string]: MemoryItem
} }
interface PendingMemory {
[key: string]: Promise<MemoryItem>
}
export interface ProgramMemory { export interface ProgramMemory {
root: Memory root: Memory
pendingMemory: Partial<PendingMemory> return?: ProgramReturn
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)
} }
export const executor = async ( export const executor = async (
node: Program, node: Program,
programMemory: ProgramMemory = { root: {}, pendingMemory: {} }, programMemory: ProgramMemory = { root: {} },
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
previousPathToNode: PathToNode = [],
// work around while the gemotry is still be stored on the frontend // work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added. // will be removed when the stream UI is added.
tempMapCallback: (a: { tempMapCallback: (a: {
@ -192,9 +120,7 @@ export const executor = async (
const _programMemory = await _executor( const _programMemory = await _executor(
node, node,
programMemory, programMemory,
engineCommandManager, engineCommandManager
options,
previousPathToNode
) )
const { artifactMap, sourceRangeMap } = const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()
@ -206,840 +132,27 @@ export const executor = async (
export const _executor = async ( export const _executor = async (
node: Program, node: Program,
programMemory: ProgramMemory = { root: {}, pendingMemory: {} }, programMemory: ProgramMemory = { root: {} },
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
previousPathToNode: PathToNode = []
): Promise<ProgramMemory> => { ): Promise<ProgramMemory> => {
let _programMemory: ProgramMemory = { try {
root: { const memory: ProgramMemory = await execute_wasm(
...programMemory.root, JSON.stringify(node),
}, JSON.stringify(programMemory),
pendingMemory: { engineCommandManager
...programMemory.pendingMemory, )
}, return memory
return: programMemory.return, } catch (e: any) {
} const parsed: RustKclError = JSON.parse(e.toString())
const { body } = node const kclError = new KCLError(
const proms: Promise<any>[] = [] parsed.kind,
for (let bodyIndex = 0; bodyIndex < body.length; bodyIndex++) { parsed.kind === 'invalid_expression' ? parsed.kind : parsed.msg,
const statement = body[bodyIndex] parsed.kind === 'invalid_expression'
if (statement.type === 'VariableDeclaration') { ? [[parsed.start, parsed.end]]
for (let index = 0; index < statement.declarations.length; index++) { : rangeTypeFix(parsed.sourceRanges)
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,
},
]
if (declaration.init.type === 'PipeExpression') { console.log(kclError)
const prom = getPipeExpressionResult( throw kclError
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
} }
} }
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
}

View File

@ -182,14 +182,14 @@ describe('Testing moveValueIntoNewVariable', () => {
const code = `${fn('def')}${fn('ghi')}${fn('jkl')}${fn('hmm')} const code = `${fn('def')}${fn('ghi')}${fn('jkl')}${fn('hmm')}
const abc = 3 const abc = 3
const identifierGuy = 5 const identifierGuy = 5
const yo = 5 + 6
const part001 = startSketchAt([-1.2, 4.83]) const part001 = startSketchAt([-1.2, 4.83])
|> line([2.8, 0], %) |> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %) |> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %) |> angledLine([abc, 3.09], %)
|> angledLine([def('yo'), 3.09], %) |> angledLine([def(yo), 3.09], %)
|> angledLine([ghi(%), 3.09], %) |> angledLine([ghi(%), 3.09], %)
|> angledLine([jkl('yo') + 2, 3.09], %) |> angledLine([jkl(yo) + 2, 3.09], %)
const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5]) const yo2 = hmm([identifierGuy + 5])
show(part001)` show(part001)`
it('should move a binary expression into a new variable', async () => { it('should move a binary expression into a new variable', async () => {
@ -231,7 +231,7 @@ show(part001)`
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) 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], %)`) expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
}) })
it('should move a binary expression with call expression into a new variable', async () => { it('should move a binary expression with call expression into a new variable', async () => {
@ -245,7 +245,7 @@ show(part001)`
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) 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], %)`) expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
}) })
it('should move a identifier into a new variable', async () => { it('should move a identifier into a new variable', async () => {

View File

@ -1,9 +1,9 @@
import { Program } from './abstractSyntaxTreeTypes' 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 => { export const recast = (ast: Program): string => {
try { try {
const s: string = recast_js(JSON.stringify(ast)) const s: string = recast_wasm(JSON.stringify(ast))
return s return s
} catch (e) { } catch (e) {
// TODO: do something real with the error. // TODO: do something real with the error.

View File

@ -1,11 +1,8 @@
import { SourceRange } from '../executor' import { SourceRange } from 'lang/executor'
import { Selections } from '../../useStore' import { Selections } from 'useStore'
import { import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
VITE_KC_API_WS_MODELING_URL,
VITE_KC_CONNECTION_TIMEOUT_MS,
} from '../../env'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { exportSave } from '../../lib/exportSave' import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
interface ResultCommand { interface ResultCommand {
@ -557,6 +554,26 @@ export class EngineCommandManager {
} }
return promise 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> { commandResult(id: string): Promise<any> {
const command = this.artifactMap[id] const command = this.artifactMap[id]
if (!command) { if (!command) {

View File

@ -55,11 +55,9 @@ export const extrude: InternalFn = (
__meta: [ __meta: [
{ {
sourceRange, sourceRange,
pathToNode: [], // TODO
}, },
{ {
sourceRange: sketchVal.__meta[0].sourceRange, sourceRange: sketchVal.__meta[0].sourceRange,
pathToNode: sketchVal.__meta[0].pathToNode,
}, },
], ],
} }

View File

@ -143,7 +143,6 @@ export const lineTo: SketchLineHelper = {
__geoMeta: { __geoMeta: {
sourceRange, sourceRange,
id, id,
pathToNode: [], // TODO
}, },
} }
if ('tag' in data) { if ('tag' in data) {
@ -280,7 +279,6 @@ export const line: SketchLineHelper = {
__geoMeta: { __geoMeta: {
id, id,
sourceRange, sourceRange,
pathToNode: [], // TODO
}, },
} }
if (data !== 'default' && 'tag' in data) { if (data !== 'default' && 'tag' in data) {
@ -674,7 +672,6 @@ export const angledLine: SketchLineHelper = {
__geoMeta: { __geoMeta: {
id, id,
sourceRange, sourceRange,
pathToNode: [], // TODO
}, },
} }
if ('tag' in data) { if ('tag' in data) {
@ -1559,7 +1556,6 @@ export const close: InternalFn = (
__geoMeta: { __geoMeta: {
id, id,
sourceRange, sourceRange,
pathToNode: [], // TODO
}, },
} }
const newValue = [...sketchGroup.value] const newValue = [...sketchGroup.value]
@ -1633,7 +1629,6 @@ export const startSketchAt: InternalFn = (
__geoMeta: { __geoMeta: {
id, id,
sourceRange, sourceRange,
pathToNode: [], // TODO
}, },
} }
if (data !== 'default' && 'tag' in data) { if (data !== 'default' && 'tag' in data) {
@ -1649,7 +1644,6 @@ export const startSketchAt: InternalFn = (
__meta: [ __meta: [
{ {
sourceRange, sourceRange,
pathToNode: [], // TODO
}, },
], ],
} }

View File

@ -391,6 +391,7 @@ show(part001)`
type: 'toPoint', type: 'toPoint',
to: [5.62, 1.79], to: [5.62, 1.79],
from: [3.48, 0.44], from: [3.48, 0.44],
name: '',
}) })
}) })
it('verify it works when the segment is in the `start` property', async () => { it('verify it works when the segment is in the `start` property', async () => {
@ -400,6 +401,6 @@ show(part001)`
programMemory.root['part001'] as SketchGroup, programMemory.root['part001'] as SketchGroup,
[index, index] [index, index]
).segment ).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: '' })
}) })
}) })

View File

@ -1,6 +1,10 @@
import { Program } from '../lang/abstractSyntaxTreeTypes' import { Program } from '../lang/abstractSyntaxTreeTypes'
import { ProgramMemory, _executor } from '../lang/executor' 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 { class MockEngineCommandManager {
constructor(mockParams: { constructor(mockParams: {
@ -10,13 +14,43 @@ class MockEngineCommandManager {
startNewSession() {} startNewSession() {}
waitForAllCommands() {} waitForAllCommands() {}
waitForReady = new Promise<void>((resolve) => resolve()) 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() {} sendSceneCommand() {}
} }
export async function enginelessExecutor( export async function enginelessExecutor(
ast: Program, ast: Program,
pm: ProgramMemory = { root: {}, pendingMemory: {} } pm: ProgramMemory = { root: {} }
): Promise<ProgramMemory> { ): Promise<ProgramMemory> {
const mockEngineCommandManager = new MockEngineCommandManager({ const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {}, setIsStreamReady: () => {},
@ -31,7 +65,7 @@ export async function enginelessExecutor(
export async function executor( export async function executor(
ast: Program, ast: Program,
pm: ProgramMemory = { root: {}, pendingMemory: {} } pm: ProgramMemory = { root: {} }
): Promise<ProgramMemory> { ): Promise<ProgramMemory> {
const engineCommandManager = new EngineCommandManager({ const engineCommandManager = new EngineCommandManager({
setIsStreamReady: () => {}, setIsStreamReady: () => {},

548
src/wasm-lib/Cargo.lock generated
View File

@ -80,12 +80,33 @@ dependencies = [
"serde", "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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.12.0" version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.4.0" version = "1.4.0"
@ -118,6 +139,25 @@ dependencies = [
"serde", "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]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.4.0" version = "2.4.0"
@ -130,6 +170,16 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 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]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.13" version = "1.0.13"
@ -157,6 +207,105 @@ dependencies = [
"percent-encoding", "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]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.10" version = "0.2.10"
@ -176,6 +325,28 @@ version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 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]] [[package]]
name = "gloo-utils" name = "gloo-utils"
version = "0.2.0" version = "0.2.0"
@ -189,6 +360,29 @@ dependencies = [
"web-sys", "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]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "0.4.0"
@ -263,6 +457,16 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 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]] [[package]]
name = "log" name = "log"
version = "0.4.17" version = "0.4.17"
@ -302,6 +506,17 @@ dependencies = [
"adler", "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]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -321,6 +536,16 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "object" name = "object"
version = "0.31.1" version = "0.31.1"
@ -342,6 +567,29 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2" 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]] [[package]]
name = "parse-display" name = "parse-display"
version = "0.8.2" version = "0.8.2"
@ -394,6 +642,24 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.4.0" version = "1.4.0"
@ -431,6 +697,45 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "regex" name = "regex"
version = "1.9.3" version = "1.9.3"
@ -519,35 +824,29 @@ dependencies = [
] ]
[[package]] [[package]]
name = "serde" name = "scopeguard"
version = "1.0.152" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"serde_derive", "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]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.152" version = "1.0.185"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.107", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -572,6 +871,51 @@ dependencies = [
"serde", "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]] [[package]]
name = "structmeta" name = "structmeta"
version = "0.2.0" version = "0.2.0"
@ -661,6 +1005,48 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 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]] [[package]]
name = "ts-rs" name = "ts-rs"
version = "7.0.0" version = "7.0.0"
@ -684,6 +1070,31 @@ dependencies = [
"termcolor", "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]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.13" version = "0.3.13"
@ -717,6 +1128,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.4.1" version = "1.4.1"
@ -728,6 +1145,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -759,6 +1182,18 @@ dependencies = [
"wasm-bindgen-shared", "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]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.87" version = "0.2.87"
@ -795,17 +1230,26 @@ dependencies = [
"anyhow", "anyhow",
"backtrace", "backtrace",
"bincode", "bincode",
"futures",
"gloo-file",
"gloo-utils", "gloo-utils",
"http",
"httparse",
"js-sys",
"kittycad", "kittycad",
"lazy_static", "lazy_static",
"pretty_assertions", "pretty_assertions",
"regex", "regex",
"serde", "serde",
"serde-wasm-bindgen",
"serde_json", "serde_json",
"thiserror", "thiserror",
"tokio",
"tokio-tungstenite",
"ts-rs", "ts-rs",
"uuid",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
] ]
[[package]] [[package]]
@ -849,6 +1293,72 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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]] [[package]]
name = "yansi" name = "yansi"
version = "0.5.1" version = "0.5.1"

View File

@ -11,16 +11,50 @@ crate-type = ["cdylib"]
anyhow = "1.0.75" anyhow = "1.0.75"
backtrace = "0.3" backtrace = "0.3"
bincode = "1.3.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" 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"] } kittycad = { version = "0.2.15", default-features = false, features = ["js"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
regex = "1.7.1" regex = "1.7.1"
serde = {version = "1.0.152", features = ["derive"] } serde = {version = "1.0.152", features = ["derive"] }
serde-wasm-bindgen = "0.3.0"
serde_json = "1.0.93" serde_json = "1.0.93"
thiserror = "1.0.47" 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"] } 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 = "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] [profile.release]
panic = "abort" panic = "abort"
@ -28,3 +62,9 @@ debug = true
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" 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"]

View File

@ -3,8 +3,15 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; 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)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Program { pub struct Program {
@ -40,6 +47,90 @@ pub enum Value {
UnaryExpression(Box<UnaryExpression>), 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -50,6 +141,71 @@ pub enum BinaryPart {
CallExpression(Box<CallExpression>), CallExpression(Box<CallExpression>),
UnaryExpression(Box<UnaryExpression>), 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
@ -103,6 +259,8 @@ pub struct ExpressionStatement {
pub expression: Value, pub expression: Value,
} }
impl_value_meta!(ExpressionStatement);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -114,6 +272,134 @@ pub struct CallExpression {
pub optional: bool, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -124,6 +410,8 @@ pub struct VariableDeclaration {
pub kind: String, // Change to enum if there are specific values pub kind: String, // Change to enum if there are specific values
} }
impl_value_meta!(VariableDeclaration);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -134,6 +422,8 @@ pub struct VariableDeclarator {
pub init: Value, pub init: Value,
} }
impl_value_meta!(VariableDeclarator);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -144,6 +434,30 @@ pub struct Literal {
pub raw: String, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -153,6 +467,8 @@ pub struct Identifier {
pub name: String, pub name: String,
} }
impl_value_meta!(Identifier);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -161,6 +477,8 @@ pub struct PipeSubstitution {
pub end: usize, pub end: usize,
} }
impl_value_meta!(PipeSubstitution);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -170,6 +488,85 @@ pub struct ArrayExpression {
pub elements: Vec<Value>, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -179,6 +576,83 @@ pub struct ObjectExpression {
pub properties: Vec<ObjectProperty>, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -189,6 +663,8 @@ pub struct ObjectProperty {
pub value: Value, pub value: Value,
} }
impl_value_meta!(ObjectProperty);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -216,6 +692,61 @@ pub struct MemberExpression {
pub computed: bool, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
pub struct ObjectKeyInfo { pub struct ObjectKeyInfo {
@ -230,11 +761,101 @@ pub struct ObjectKeyInfo {
pub struct BinaryExpression { pub struct BinaryExpression {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
// TODO: operator should be a type not a string.
pub operator: String, pub operator: String,
pub left: BinaryPart, pub left: BinaryPart,
pub right: 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -245,6 +866,33 @@ pub struct UnaryExpression {
pub argument: BinaryPart, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase", tag = "type")] #[serde(rename_all = "camelCase", tag = "type")]
@ -255,6 +903,72 @@ pub struct PipeExpression {
pub non_code_meta: NoneCodeMeta, 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -263,18 +977,10 @@ pub struct FunctionExpression {
pub end: usize, pub end: usize,
pub id: Option<Identifier>, pub id: Option<Identifier>,
pub params: Vec<Identifier>, pub params: Vec<Identifier>,
pub body: BlockStatement, pub body: Program,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] impl_value_meta!(FunctionExpression);
#[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,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
@ -284,3 +990,5 @@ pub struct ReturnStatement {
pub end: usize, pub end: usize,
pub argument: Value, pub argument: Value,
} }
impl_value_meta!(ReturnStatement);

View 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(())
}
}

View 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(),
},
]
}

View 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());
}
}*/

View 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(())
}
}

View File

@ -21,13 +21,15 @@ pub enum KclError {
UndefinedValue(KclErrorDetails), UndefinedValue(KclErrorDetails),
#[error("invalid expression: {0:?}")] #[error("invalid expression: {0:?}")]
InvalidExpression(crate::math_parser::MathExpression), InvalidExpression(crate::math_parser::MathExpression),
#[error("engine: {0:?}")]
Engine(KclErrorDetails),
} }
#[derive(Debug, Serialize, Deserialize, ts_rs::TS)] #[derive(Debug, Serialize, Deserialize, ts_rs::TS)]
#[ts(export)] #[ts(export)]
pub struct KclErrorDetails { pub struct KclErrorDetails {
#[serde(rename = "sourceRanges")] #[serde(rename = "sourceRanges")]
pub source_ranges: Vec<[i32; 2]>, pub source_ranges: Vec<crate::executor::SourceRange>,
#[serde(rename = "msg")] #[serde(rename = "msg")]
pub message: String, pub message: String,
} }
@ -39,3 +41,9 @@ impl From<KclError> for String {
serde_json::to_string(&error).unwrap() serde_json::to_string(&error).unwrap()
} }
} }
impl From<String> for KclError {
fn from(error: String) -> Self {
serde_json::from_str(&error).unwrap()
}
}

View 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(
&param.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();
}
}

View File

@ -1,5 +1,6 @@
//! Functions for exported files from the server. //! Functions for exported files from the server.
use gloo_utils::format::JsValueSerdeExt;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
@ -7,7 +8,7 @@ pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
let ws_resp: kittycad::types::WebSocketResponses = bincode::deserialize(data)?; let ws_resp: kittycad::types::WebSocketResponses = bincode::deserialize(data)?;
if let kittycad::types::WebSocketResponses::Export { files } = ws_resp { 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!( Err(JsError::new(&format!(

View File

@ -1,7 +1,10 @@
mod abstract_syntax_tree_types; mod abstract_syntax_tree_types;
mod engine;
mod errors; mod errors;
mod executor;
mod export; mod export;
mod math_parser; mod math_parser;
mod parser; mod parser;
mod recast; mod recast;
mod std;
mod tokeniser; mod tokeniser;

View File

@ -147,7 +147,7 @@ pub fn reverse_polish_notation(
} }
Err(KclError::Syntax(KclErrorDetails { 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!( message: format!(
"Unexpected token: {} {:?}", "Unexpected token: {} {:?}",
current_token.value, current_token.token_type current_token.value, current_token.token_type
@ -260,19 +260,13 @@ fn build_tree(
serde_json::Value::Number(n) serde_json::Value::Number(n)
} else { } else {
return Err(KclError::Syntax(KclErrorDetails { return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![current_token.into()],
current_token.start as i32,
current_token.end as i32,
]],
message: format!("Invalid float: {}", current_token.value), message: format!("Invalid float: {}", current_token.value),
})); }));
} }
} else { } else {
return Err(KclError::Syntax(KclErrorDetails { return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![current_token.into()],
current_token.start as i32,
current_token.end as i32,
]],
message: format!("Invalid integer: {}", current_token.value), message: format!("Invalid integer: {}", current_token.value),
})); }));
} }

View File

@ -1,11 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::abstract_syntax_tree_types::{ use crate::abstract_syntax_tree_types::{
ArrayExpression, BinaryExpression, BinaryPart, BlockStatement, BodyItem, CallExpression, ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
ExpressionStatement, FunctionExpression, Identifier, Literal, LiteralIdentifier, FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject,
MemberExpression, MemberObject, NoneCodeMeta, NoneCodeNode, ObjectExpression, ObjectKeyInfo, NoneCodeMeta, NoneCodeNode, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression,
ObjectProperty, PipeExpression, PipeSubstitution, Program, ReturnStatement, UnaryExpression, PipeSubstitution, Program, ReturnStatement, UnaryExpression, Value, VariableDeclaration,
Value, VariableDeclaration, VariableDeclarator, VariableDeclarator,
}; };
use crate::errors::{KclError, KclErrorDetails}; use crate::errors::{KclError, KclErrorDetails};
use crate::math_parser::parse_expression; 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) serde_json::Value::Number(n)
} else { } else {
return Err(KclError::Syntax(KclErrorDetails { 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), message: format!("Invalid float: {}", token.value),
})); }));
} }
} else { } else {
return Err(KclError::Syntax(KclErrorDetails { 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), message: format!("Invalid integer: {}", token.value),
})); }));
} }
@ -165,7 +165,7 @@ pub fn find_closing_brace(
search_opening_brace = &current_token.value; search_opening_brace = &current_token.value;
if !["(", "{", "["].contains(&search_opening_brace) { if !["(", "{", "["].contains(&search_opening_brace) {
return Err(KclError::Syntax(KclErrorDetails { 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!( message: format!(
"expected to be started on a opening brace ( {{ [, instead found '{}'", "expected to be started on a opening brace ( {{ [, instead found '{}'",
search_opening_brace search_opening_brace
@ -380,10 +380,7 @@ fn collect_object_keys(
collect_object_keys(tokens, key_token.index, Some(new_previous_keys)) collect_object_keys(tokens, key_token.index, Some(new_previous_keys))
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![period_or_opening_bracket_token.clone().into()],
period_or_opening_bracket_token.start as i32,
period_or_opening_bracket_token.end as i32,
]],
message: format!( message: format!(
"expression with token type {:?}", "expression with token type {:?}",
period_or_opening_bracket_token.token_type period_or_opening_bracket_token.token_type
@ -508,7 +505,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
} }
} else { } else {
return Err(KclError::Unimplemented(KclErrorDetails { 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!( message: format!(
"expression with token type {:?}", "expression with token type {:?}",
current_token.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, last_index: function_expression.last_index,
}) })
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { 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: "expression with braces".to_string(), message: "expression with braces".to_string(),
})) }));
} }
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { 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: "expression with braces".to_string(), message: "expression with braces".to_string(),
})) }));
}; };
} }
@ -615,7 +612,7 @@ fn make_value(tokens: &[Token], index: usize) -> Result<ValueReturn, KclError> {
} }
Err(KclError::Unexpected(KclErrorDetails { 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), message: format!("{:?}", current_token.token_type),
})) }))
} }
@ -645,7 +642,7 @@ fn make_array_elements(
let is_comma = next_token_token.token_type == TokenType::Comma; let is_comma = next_token_token.token_type == TokenType::Comma;
if !is_closing_brace && !is_comma { if !is_closing_brace && !is_comma {
return Err(KclError::Syntax(KclErrorDetails { 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!( message: format!(
"Expected a comma or closing brace, found {:?}", "Expected a comma or closing brace, found {:?}",
next_token_token.value next_token_token.value
@ -662,10 +659,7 @@ fn make_array_elements(
make_array_elements(tokens, next_call_index, _previous_elements) make_array_elements(tokens, next_call_index, _previous_elements)
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![first_element_token.into()],
first_element_token.start as i32,
first_element_token.end as i32,
]],
message: "no next token".to_string(), message: "no next token".to_string(),
})) }))
} }
@ -720,7 +714,7 @@ fn make_pipe_body(
last_index = val.last_index; last_index = val.last_index;
} else { } else {
return Err(KclError::Syntax(KclErrorDetails { 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!( message: format!(
"Expected a pipe value, found {:?}", "Expected a pipe value, found {:?}",
current_token.token_type current_token.token_type
@ -899,13 +893,10 @@ fn make_arguments(
make_arguments(tokens, next_comma_or_brace_token_index, _previous_args) make_arguments(tokens, next_comma_or_brace_token_index, _previous_args)
} }
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { return Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![argument_token_token.clone().into()],
argument_token_token.start as i32,
argument_token_token.end as i32,
]],
message: format!("Unexpected token {} ", argument_token_token.value), message: format!("Unexpected token {} ", argument_token_token.value),
})) }));
}; };
} }
@ -929,27 +920,18 @@ fn make_arguments(
} }
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![argument_token_token.clone().into()],
argument_token_token.start as i32,
argument_token_token.end as i32,
]],
message: format!("Unexpected token {} ", argument_token_token.value), message: format!("Unexpected token {} ", argument_token_token.value),
})) }))
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![brace_or_comma_token.into()],
brace_or_comma_token.start as i32,
brace_or_comma_token.end as i32,
]],
message: format!("Unexpected token {} ", brace_or_comma_token.value), message: format!("Unexpected token {} ", brace_or_comma_token.value),
})) }))
} }
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![brace_or_comma_token.into()],
brace_or_comma_token.start as i32,
brace_or_comma_token.end as i32,
]],
message: format!("Unexpected token {} ", brace_or_comma_token.value), message: format!("Unexpected token {} ", brace_or_comma_token.value),
})) }))
} }
@ -1045,7 +1027,7 @@ fn make_variable_declarators(
}) })
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { 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), 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) make_params(tokens, next_brace_or_comma_token.index, _previous_params)
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![brace_or_comma_token.into()],
brace_or_comma_token.start as i32,
brace_or_comma_token.end as i32,
]],
message: format!("Unexpected token {} ", brace_or_comma_token.value), message: format!("Unexpected token {} ", brace_or_comma_token.value),
})) }))
} }
@ -1158,7 +1137,7 @@ fn make_unary_expression(
} }
_ => { _ => {
return Err(KclError::Syntax(KclErrorDetails { 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(), message: "Invalid argument for unary expression".to_string(),
})); }));
} }
@ -1205,7 +1184,7 @@ fn make_expression_statement(
}) })
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { 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(), message: "make_expression_statement".to_string(),
})) }))
} }
@ -1266,10 +1245,7 @@ fn make_object_properties(
make_object_properties(tokens, next_key_index, _previous_properties) make_object_properties(tokens, next_key_index, _previous_properties)
} else { } else {
Err(KclError::Unimplemented(KclErrorDetails { Err(KclError::Unimplemented(KclErrorDetails {
source_ranges: vec![[ source_ranges: vec![property_key_token.into()],
property_key_token.start as i32,
property_key_token.end as i32,
]],
message: "make_object_properties".to_string(), message: "make_object_properties".to_string(),
})) }))
} }
@ -1470,13 +1446,13 @@ fn make_body(
} }
Err(KclError::Syntax(KclErrorDetails { 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(), message: "unexpected token".to_string(),
})) }))
} }
struct BlockStatementResult { struct BlockStatementResult {
block: BlockStatement, block: Program,
last_index: usize, last_index: usize,
} }
@ -1505,7 +1481,7 @@ fn make_block_statement(tokens: &[Token], index: usize) -> Result<BlockStatement
)? )?
}; };
Ok(BlockStatementResult { Ok(BlockStatementResult {
block: BlockStatement { block: Program {
start: opening_curly.start, start: opening_curly.start,
end: tokens[body.last_index].end, end: tokens[body.last_index].end,
body: body.body, 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] #[wasm_bindgen]
pub fn parse_js(js: &str) -> Result<JsValue, String> { pub fn parse_js(js: &str) -> Result<JsValue, String> {
let tokens = lexer(js); let tokens = lexer(js);

View File

@ -1,5 +1,6 @@
//! Generates source code from the AST. //! Generates source code from the AST.
//! The inverse of parsing (which generates an AST from the source code) //! The inverse of parsing (which generates an AST from the source code)
use gloo_utils::format::JsValueSerdeExt;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use crate::abstract_syntax_tree_types::{ use crate::abstract_syntax_tree_types::{
@ -403,10 +404,10 @@ pub fn recast_function(expression: FunctionExpression) -> String {
// wasm_bindgen wrapper for recast // wasm_bindgen wrapper for recast
// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts // test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts
#[wasm_bindgen] #[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 // deserialize the ast from a stringified json
let program: Program = serde_json::from_str(json_str).map_err(JsError::from)?; let program: Program = serde_json::from_str(json_str).map_err(JsError::from)?;
let result = recast(&program, "", false); let result = recast(&program, "", false);
Ok(serde_wasm_bindgen::to_value(&result)?) Ok(JsValue::from_serde(&result)?)
} }

View 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
View 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)
}

View 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)
}
}

View 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()
}
);
}
}

View 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);
}
}

View File

@ -1,3 +1,4 @@
use gloo_utils::format::JsValueSerdeExt;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -30,6 +31,18 @@ pub struct Token {
pub value: String, 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! { lazy_static! {
static ref NUMBER: Regex = Regex::new(r"^-?\d+(\.\d+)?").unwrap(); static ref NUMBER: Regex = Regex::new(r"^-?\d+(\.\d+)?").unwrap();
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap(); static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
@ -265,7 +278,7 @@ pub fn lexer(str: &str) -> Vec<Token> {
#[wasm_bindgen] #[wasm_bindgen]
pub fn lexer_js(str: &str) -> Result<JsValue, JsError> { pub fn lexer_js(str: &str) -> Result<JsValue, JsError> {
let tokens = lexer(str); let tokens = lexer(str);
Ok(serde_wasm_bindgen::to_value(&tokens)?) Ok(JsValue::from_serde(&tokens)?)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": "src",
"types": ["vite/client", "@types/wicg-file-system-access"], "types": ["vite/client", "@types/wicg-file-system-access"],
"target": "esnext", "target": "esnext",
"lib": [ "lib": [