diff --git a/.github/workflows/cargo-build.yml b/.github/workflows/cargo-build.yml index e798e0575..9dc45794d 100644 --- a/.github/workflows/cargo-build.yml +++ b/.github/workflows/cargo-build.yml @@ -43,7 +43,5 @@ jobs: - name: Run cargo build run: | cd "${{ matrix.dir }}" - cargo build --all --no-default-features --features noweb - cargo build --all --no-default-features --features web cargo build --all shell: bash diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index 60e59c2e6..316723804 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -45,4 +45,7 @@ jobs: shell: bash run: |- cd "${{ matrix.dir }}" - cargo llvm-cov nextest --lcov --output-path lcov.info --test-threads=1 --no-fail-fast + cargo test --all + env: + KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} + diff --git a/.gitignore b/.gitignore index c573abdf9..ee994879c 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,6 @@ yarn-error.log* # rust src/wasm-lib/target src/wasm-lib/bindings +src/wasm-lib/kcl/bindings public/wasm_lib_bg.wasm src/wasm-lib/lcov.info diff --git a/package.json b/package.json index e7d62d0ee..7c806d062 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "simpleserver": "http-server ./public --cors -p 3000", "fmt": "prettier --write ./src", "fmt-check": "prettier --check ./src", - "build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg --no-default-features --features web && cargo test --all) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta", + "build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test --all) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta", "remove-importmeta": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"", - "wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/bindings", + "wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings", "lint": "eslint --fix src", "bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json" }, diff --git a/src/lang/abstractSyntaxTree.ts b/src/lang/abstractSyntaxTree.ts index 0d1159115..4fc708c38 100644 --- a/src/lang/abstractSyntaxTree.ts +++ b/src/lang/abstractSyntaxTree.ts @@ -3,7 +3,7 @@ import { parse_js } from '../wasm-lib/pkg/wasm_lib' import { initPromise } from './rust' import { Token } from './tokeniser' import { KCLError } from './errors' -import { KclError as RustKclError } from '../wasm-lib/bindings/KclError' +import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' export const rangeTypeFix = (ranges: number[][]): [number, number][] => ranges.map(([start, end]) => [start, end]) diff --git a/src/lang/abstractSyntaxTreeTypes.ts b/src/lang/abstractSyntaxTreeTypes.ts index 7a89957cd..28c6a1562 100644 --- a/src/lang/abstractSyntaxTreeTypes.ts +++ b/src/lang/abstractSyntaxTreeTypes.ts @@ -1,20 +1,20 @@ -export type { Program } from '../wasm-lib/bindings/Program' -export type { Value } from '../wasm-lib/bindings/Value' -export type { ObjectExpression } from '../wasm-lib/bindings/ObjectExpression' -export type { MemberExpression } from '../wasm-lib/bindings/MemberExpression' -export type { PipeExpression } from '../wasm-lib/bindings/PipeExpression' -export type { VariableDeclaration } from '../wasm-lib/bindings/VariableDeclaration' -export type { PipeSubstitution } from '../wasm-lib/bindings/PipeSubstitution' -export type { Identifier } from '../wasm-lib/bindings/Identifier' -export type { UnaryExpression } from '../wasm-lib/bindings/UnaryExpression' -export type { BinaryExpression } from '../wasm-lib/bindings/BinaryExpression' -export type { ReturnStatement } from '../wasm-lib/bindings/ReturnStatement' -export type { ExpressionStatement } from '../wasm-lib/bindings/ExpressionStatement' -export type { CallExpression } from '../wasm-lib/bindings/CallExpression' -export type { VariableDeclarator } from '../wasm-lib/bindings/VariableDeclarator' -export type { BinaryPart } from '../wasm-lib/bindings/BinaryPart' -export type { Literal } from '../wasm-lib/bindings/Literal' -export type { ArrayExpression } from '../wasm-lib/bindings/ArrayExpression' +export type { Program } from '../wasm-lib/kcl/bindings/Program' +export type { Value } from '../wasm-lib/kcl/bindings/Value' +export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression' +export type { MemberExpression } from '../wasm-lib/kcl/bindings/MemberExpression' +export type { PipeExpression } from '../wasm-lib/kcl/bindings/PipeExpression' +export type { VariableDeclaration } from '../wasm-lib/kcl/bindings/VariableDeclaration' +export type { PipeSubstitution } from '../wasm-lib/kcl/bindings/PipeSubstitution' +export type { Identifier } from '../wasm-lib/kcl/bindings/Identifier' +export type { UnaryExpression } from '../wasm-lib/kcl/bindings/UnaryExpression' +export type { BinaryExpression } from '../wasm-lib/kcl/bindings/BinaryExpression' +export type { ReturnStatement } from '../wasm-lib/kcl/bindings/ReturnStatement' +export type { ExpressionStatement } from '../wasm-lib/kcl/bindings/ExpressionStatement' +export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression' +export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator' +export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart' +export type { Literal } from '../wasm-lib/kcl/bindings/Literal' +export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression' export type SyntaxType = | 'Program' diff --git a/src/lang/errors.ts b/src/lang/errors.ts index 606c187fc..b20cd3e5a 100644 --- a/src/lang/errors.ts +++ b/src/lang/errors.ts @@ -1,5 +1,5 @@ import { Diagnostic } from '@codemirror/lint' -import { KclError as RustKclError } from '../wasm-lib/bindings/KclError' +import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' type ExtractKind = T extends { kind: infer K } ? K : never export class KCLError { diff --git a/src/lang/executor.ts b/src/lang/executor.ts index 47039ed6d..06e203c92 100644 --- a/src/lang/executor.ts +++ b/src/lang/executor.ts @@ -4,10 +4,10 @@ import { ArtifactMap, SourceRangeMap, } from './std/engineConnection' -import { ProgramReturn } from '../wasm-lib/bindings/ProgramReturn' +import { ProgramReturn } from '../wasm-lib/kcl/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 { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { rangeTypeFix } from './abstractSyntaxTree' export type SourceRange = [number, number] diff --git a/src/lang/tokeniser.ts b/src/lang/tokeniser.ts index 7c7cf0c7c..f74e95562 100644 --- a/src/lang/tokeniser.ts +++ b/src/lang/tokeniser.ts @@ -1,8 +1,8 @@ import { lexer_js } from '../wasm-lib/pkg/wasm_lib' import { initPromise } from './rust' -import { Token } from '../wasm-lib/bindings/Token' +import { Token } from '../wasm-lib/kcl/bindings/Token' -export type { Token } from '../wasm-lib/bindings/Token' +export type { Token } from '../wasm-lib/kcl/bindings/Token' export async function asyncLexer(str: string): Promise { await initPromise diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index 67b3c0626..567b61a05 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -27,6 +27,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.4" @@ -109,6 +120,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.2" @@ -147,6 +164,18 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -156,6 +185,28 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bson" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aeb8bae494e49dbc330dd23cf78f6f7accee22f640ce3ab17841badaa4ce232" +dependencies = [ + "ahash", + "base64 0.13.1", + "bitvec", + "chrono", + "hex", + "indexmap 1.9.3", + "js-sys", + "lazy_static", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time 0.3.27", + "uuid", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -281,6 +332,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -488,6 +549,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -606,28 +673,6 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -[[package]] -name = "gloo-events" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-file" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" -dependencies = [ - "gloo-events", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gloo-utils" version = "0.2.0" @@ -693,6 +738,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.5" @@ -881,13 +932,41 @@ dependencies = [ ] [[package]] -name = "kittycad" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0443a9f76cee80d5a43d076028d3ce39d2f6f6b66fc5c1a0ce24f8d7caf733b9" +name = "kcl" +version = "0.1.0" dependencies = [ "anyhow", - "base64", + "bson", + "derive-docs", + "expectorate", + "futures", + "js-sys", + "kittycad", + "lazy_static", + "parse-display", + "pretty_assertions", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "ts-rs", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "kittycad" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71916b50966110cb9f70aa6c310748a153fdcb0183a02615324a6f457fd18e8" +dependencies = [ + "anyhow", + "base64 0.21.2", "bytes", "chrono", "data-encoding", @@ -1151,6 +1230,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "os_str_bytes" version = "6.5.1" @@ -1244,9 +1329,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1321,6 +1406,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1433,7 +1524,7 @@ version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -1519,9 +1610,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" dependencies = [ "bitflags 2.4.0", "errno", @@ -1542,13 +1633,25 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64", + "base64 0.21.2", ] [[package]] @@ -1582,6 +1685,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "schemars" version = "0.8.12" @@ -1627,6 +1739,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -1647,18 +1782,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.186" +version = "1.0.187" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" +checksum = "30a7fe14252655bd1e578af19f5fa00fe02fd0013b100ca6b49fde31c41bae4c" dependencies = [ "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.186" +name = "serde_bytes" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.187" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8" dependencies = [ "proc-macro2", "quote", @@ -1682,6 +1826,7 @@ version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ + "indexmap 2.0.0", "itoa", "ryu", "serde", @@ -1925,6 +2070,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "task-local-extensions" version = "0.1.4" @@ -2110,7 +2261,10 @@ checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" dependencies = [ "futures-util", "log", + "rustls", + "rustls-native-certs", "tokio", + "tokio-rustls", "tungstenite", ] @@ -2227,6 +2381,7 @@ dependencies = [ "httparse", "log", "rand", + "rustls", "sha1", "thiserror", "url", @@ -2435,30 +2590,11 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" name = "wasm-lib" version = "0.1.0" dependencies = [ - "anyhow", - "backtrace", - "bincode", - "derive-docs", - "expectorate", - "futures", - "gloo-file", + "bson", "gloo-utils", - "http", - "httparse", - "js-sys", + "kcl", "kittycad", - "lazy_static", - "parse-display", - "pretty_assertions", - "regex", - "schemars", - "serde", "serde_json", - "thiserror", - "tokio", - "tokio-tungstenite", - "ts-rs", - "uuid", "wasm-bindgen", "wasm-bindgen-futures", ] @@ -2661,6 +2797,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/src/wasm-lib/Cargo.toml b/src/wasm-lib/Cargo.toml index f2b473ef2..e6db97f86 100644 --- a/src/wasm-lib/Cargo.toml +++ b/src/wasm-lib/Cargo.toml @@ -8,28 +8,11 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -anyhow = "1.0.75" -backtrace = "0.3" -bincode = "1.3.3" -derive-docs = { path = "derive-docs" } -futures = { version = "0.3.28", optional = true } -gloo-file = { version = "0.3.0", optional = true } +bson = { version = "2.6.1", features = ["uuid-1", "chrono"] } gloo-utils = "0.2.0" -http = "0.2.9" -httparse = { version = "1.8.0", optional = true } -js-sys = { version = "0.3.64", optional = true } +kcl = { path = "kcl" } kittycad = { version = "0.2.15", default-features = false, features = ["js"] } -lazy_static = "1.4.0" -parse-display = "0.8.2" -regex = "1.7.1" -schemars = { version = "0.8", features = ["url", "uuid1"] } -serde = {version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" -thiserror = "1.0.47" -tokio = { version = "1.32.0", features = ["full"], optional = true } -tokio-tungstenite = { version = "0.20.0", optional = true } -ts-rs = { git = "https://github.com/kittycad/ts-rs.git", branch = "serde_json", features = ["serde-json-impl", "uuid-impl"] } -uuid = { version = "1.4.1", features = ["v4", "js", "serde"] } wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4.37" @@ -37,17 +20,8 @@ wasm-bindgen-futures = "0.4.37" panic = "abort" debug = true -[dev-dependencies] -expectorate = "1.0.7" -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"] -noweb = ["dep:futures", "dep:httparse", "dep:tokio", "dep:tokio-tungstenite"] - [workspace] members = [ - "derive-docs" + "derive-docs", + "kcl" ] diff --git a/src/wasm-lib/kcl/Cargo.toml b/src/wasm-lib/kcl/Cargo.toml new file mode 100644 index 000000000..382105482 --- /dev/null +++ b/src/wasm-lib/kcl/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "kcl" +description = "KittyCAD Language" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +derive-docs = { path = "../derive-docs" } +kittycad = { version = "0.2.15", default-features = false, features = ["js"] } +lazy_static = "1.4.0" +parse-display = "0.8.2" +regex = "1.7.1" +schemars = { version = "0.8", features = ["url", "uuid1"] } +serde = {version = "1.0.152", features = ["derive"] } +serde_json = "1.0.93" +thiserror = "1.0.47" +ts-rs = { git = "https://github.com/kittycad/ts-rs.git", branch = "serde_json", features = ["serde-json-impl", "uuid-impl"] } +uuid = { version = "1.4.1", features = ["v4", "js", "serde"] } +wasm-bindgen = "0.2.87" +wasm-bindgen-futures = "0.4.37" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = { version = "0.3.64" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +bson = { version = "2.6.1", features = ["uuid-1", "chrono"] } +futures = { version = "0.3.28" } +reqwest = { version = "0.11.20", default-features = false } +tokio = { version = "1.32.0", features = ["full"] } +tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] } + +[profile.release] +panic = "abort" +debug = true + +[dev-dependencies] +expectorate = "1.0.7" +pretty_assertions = "1.4.0" +tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] } diff --git a/src/wasm-lib/src/abstract_syntax_tree_types.rs b/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs similarity index 100% rename from src/wasm-lib/src/abstract_syntax_tree_types.rs rename to src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs diff --git a/src/wasm-lib/src/docs.rs b/src/wasm-lib/kcl/src/docs.rs similarity index 100% rename from src/wasm-lib/src/docs.rs rename to src/wasm-lib/kcl/src/docs.rs diff --git a/src/wasm-lib/kcl/src/engine/conn.rs b/src/wasm-lib/kcl/src/engine/conn.rs new file mode 100644 index 000000000..6d10e6501 --- /dev/null +++ b/src/wasm-lib/kcl/src/engine/conn.rs @@ -0,0 +1,211 @@ +//! Functions for setting up our WebSocket and WebRTC connections for communications with the +//! engine. + +use std::sync::Arc; + +use anyhow::Result; +use futures::{SinkExt, StreamExt}; +use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse}; +use tokio_tungstenite::tungstenite::Message as WsMsg; + +use crate::errors::{KclError, KclErrorDetails}; + +#[derive(Debug)] +pub struct EngineConnection { + tcp_write: + futures::stream::SplitSink, WsMsg>, + tcp_read_handle: tokio::task::JoinHandle>, + export_notifier: Arc, +} + +impl Drop for EngineConnection { + fn drop(&mut self) { + // Drop the read handle. + self.tcp_read_handle.abort(); + } +} + +pub struct TcpRead { + stream: futures::stream::SplitStream>, +} + +impl TcpRead { + pub async fn read(&mut self) -> Result { + let msg = self.stream.next().await.unwrap()?; + let msg: WebSocketResponse = match msg { + WsMsg::Text(text) => serde_json::from_str(&text)?, + WsMsg::Binary(bin) => bson::from_slice(&bin)?, + other => anyhow::bail!("Unexpected websocket message from server: {}", other), + }; + Ok(msg) + } +} + +impl EngineConnection { + pub async fn new(ws: reqwest::Upgraded, export_dir: &str) -> Result { + // Make sure the export directory exists and that it is a directory. + let export_dir = std::path::Path::new(export_dir).to_owned(); + if !export_dir.exists() { + anyhow::bail!("Export directory does not exist: {}", export_dir.display()); + } + // Make sure it is a directory. + if !export_dir.is_dir() { + anyhow::bail!( + "Export directory is not a directory: {}", + export_dir.display() + ); + } + + let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket( + ws, + tokio_tungstenite::tungstenite::protocol::Role::Client, + None, + ) + .await; + + let (tcp_write, tcp_read) = ws_stream.split(); + + let mut tcp_read = TcpRead { stream: tcp_read }; + + let export_notifier = Arc::new(tokio::sync::Notify::new()); + let export_notifier_clone = export_notifier.clone(); + + let tcp_read_handle = tokio::spawn(async move { + // Get Websocket messages from API server + loop { + match tcp_read.read().await { + Ok(ws_resp) => { + if !ws_resp.success { + println!("got ws errors: {:?}", ws_resp.errors); + export_notifier.notify_one(); + continue; + } + + if let Some(msg) = ws_resp.resp { + match msg { + OkWebSocketResponseData::IceServerInfo { ice_servers } => { + println!("got ice server info: {:?}", ice_servers); + } + OkWebSocketResponseData::SdpAnswer { answer } => { + println!("got sdp answer: {:?}", answer); + } + OkWebSocketResponseData::TrickleIce { candidate } => { + println!("got trickle ice: {:?}", candidate); + } + OkWebSocketResponseData::Modeling { .. } => {} + OkWebSocketResponseData::Export { files } => { + // Save the files to our export directory. + for file in files { + let path = export_dir.join(file.name); + std::fs::write(&path, file.contents)?; + println!("Wrote file: {}", path.display()); + } + + // Tell the export notifier that we have new files. + export_notifier.notify_one(); + } + } + } + } + Err(e) => { + println!("got ws error: {:?}", e); + export_notifier.notify_one(); + continue; + } + } + } + }); + + Ok(EngineConnection { + tcp_write, + tcp_read_handle, + export_notifier: export_notifier_clone, + }) + } + + pub async fn wait_for_files(&self) { + self.export_notifier.notified().await; + } + + pub async fn tcp_send(&mut self, msg: WebSocketRequest) -> Result<()> { + let msg = serde_json::to_string(&msg)?; + self.tcp_write.send(WsMsg::Text(msg)).await?; + + 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(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id }), + ) + .map_err(|e| { + KclError::Engine(KclErrorDetails { + message: format!("Failed to send modeling command: {}", e), + source_ranges: vec![source_range], + }) + })?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::executor::{execute, BodyType, ProgramMemory, SourceRange}; + + pub async fn parse_execute_export(code: &str) -> Result<()> { + let tokens = crate::tokeniser::lexer(code); + let export_dir_str = "/tmp/"; + let program = crate::parser::abstract_syntax_tree(&tokens)?; + let mut mem: ProgramMemory = Default::default(); + let mut engine = EngineConnection::new( + "wss://api.dev.kittycad.io/ws/modeling/commands?webrtc=false", + std::env::var("KITTYCAD_API_TOKEN").unwrap().as_str(), + "modeling-app-tests", + export_dir_str, + ) + .await?; + let _ = execute(program, &mut mem, BodyType::Root, &mut engine)?; + // Send an export request to the engine. + engine.send_modeling_cmd( + uuid::Uuid::new_v4(), + SourceRange::default(), + kittycad::types::ModelingCmd::Export { + entity_ids: vec![], + format: kittycad::types::OutputFormat::Gltf { + presentation: kittycad::types::Presentation::Pretty, + storage: kittycad::types::Storage::Embedded, + }, + }, + )?; + + engine.wait_for_files().await; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_export_file() { + let ast = r#"const part001 = startSketchAt([-0.01, -0.06]) + |> line([0, 20], %) + |> line([5, 0], %) + |> line([-10, 5], %) + |> close(%) + |> extrude(4, %) + +show(part001)"#; + parse_execute_export(ast).await.unwrap(); + + // Ensure we have a file called "part001.gltf" in the export directory. + let export_dir = std::path::Path::new("/tmp/"); + let export_file = export_dir.join("part001.gltf"); + assert!(export_file.exists()); + // Make sure the file is not empty. + assert!(export_file.metadata().unwrap().len() != 0); + } +} diff --git a/src/wasm-lib/src/engine/conn_mock.rs b/src/wasm-lib/kcl/src/engine/conn_mock.rs similarity index 78% rename from src/wasm-lib/src/engine/conn_mock.rs rename to src/wasm-lib/kcl/src/engine/conn_mock.rs index 326b8c18a..cbed6aecc 100644 --- a/src/wasm-lib/src/engine/conn_mock.rs +++ b/src/wasm-lib/kcl/src/engine/conn_mock.rs @@ -9,11 +9,7 @@ use crate::errors::KclError; pub struct EngineConnection {} impl EngineConnection { - pub async fn new( - _conn_str: &str, - _auth_token: &str, - _origin: &str, - ) -> Result { + pub async fn new() -> Result { Ok(EngineConnection {}) } diff --git a/src/wasm-lib/src/engine/conn_web.rs b/src/wasm-lib/kcl/src/engine/conn_wasm.rs similarity index 96% rename from src/wasm-lib/src/engine/conn_web.rs rename to src/wasm-lib/kcl/src/engine/conn_wasm.rs index 94c4bcb24..d814dcf67 100644 --- a/src/wasm-lib/src/engine/conn_web.rs +++ b/src/wasm-lib/kcl/src/engine/conn_wasm.rs @@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*; use crate::errors::{KclError, KclErrorDetails}; -#[wasm_bindgen(module = "/../lang/std/engineConnection.ts")] +#[wasm_bindgen(module = "/../../lang/std/engineConnection.ts")] extern "C" { #[derive(Debug, Clone)] pub type EngineCommandManager; diff --git a/src/wasm-lib/src/engine/mod.rs b/src/wasm-lib/kcl/src/engine/mod.rs similarity index 60% rename from src/wasm-lib/src/engine/mod.rs rename to src/wasm-lib/kcl/src/engine/mod.rs index 59c849653..1a2473259 100644 --- a/src/wasm-lib/src/engine/mod.rs +++ b/src/wasm-lib/kcl/src/engine/mod.rs @@ -2,19 +2,19 @@ use wasm_bindgen::prelude::*; -#[cfg(feature = "noweb")] +#[cfg(not(target_arch = "wasm32"))] #[cfg(not(test))] -pub mod conn_noweb; -#[cfg(feature = "noweb")] +pub mod conn; +#[cfg(not(target_arch = "wasm32"))] #[cfg(not(test))] -pub use conn_noweb::EngineConnection; +pub use conn::EngineConnection; -#[cfg(feature = "web")] +#[cfg(target_arch = "wasm32")] #[cfg(not(test))] -pub mod conn_web; -#[cfg(feature = "web")] +pub mod conn_wasm; +#[cfg(target_arch = "wasm32")] #[cfg(not(test))] -pub use conn_web::EngineConnection; +pub use conn_wasm::EngineConnection; #[cfg(test)] pub mod conn_mock; @@ -31,27 +31,16 @@ pub struct EngineManager { #[wasm_bindgen] impl EngineManager { - #[cfg(feature = "web")] + #[cfg(target_arch = "wasm32")] #[cfg(not(test))] #[wasm_bindgen(constructor)] - pub async fn new(manager: conn_web::EngineCommandManager) -> EngineManager { + pub async fn new(manager: conn_wasm::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())?; diff --git a/src/wasm-lib/src/errors.rs b/src/wasm-lib/kcl/src/errors.rs similarity index 100% rename from src/wasm-lib/src/errors.rs rename to src/wasm-lib/kcl/src/errors.rs diff --git a/src/wasm-lib/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs similarity index 92% rename from src/wasm-lib/src/executor.rs rename to src/wasm-lib/kcl/src/executor.rs index 40e2e222b..9c778a433 100644 --- a/src/wasm-lib/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -6,9 +6,6 @@ use anyhow::Result; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[cfg(not(test))] -use wasm_bindgen::prelude::*; - use crate::{ abstract_syntax_tree_types::{BodyItem, FunctionExpression, Value}, engine::EngineConnection, @@ -506,7 +503,7 @@ impl Default for PipeInfo { } /// Execute a AST's program. -fn execute( +pub fn execute( program: crate::abstract_syntax_tree_types::Program, memory: &mut ProgramMemory, options: BodyType, @@ -681,53 +678,6 @@ fn execute( 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 { - 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 { - 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::*; @@ -737,7 +687,7 @@ mod tests { let tokens = crate::tokeniser::lexer(code); let program = crate::parser::abstract_syntax_tree(&tokens)?; let mut mem: ProgramMemory = Default::default(); - let mut engine = EngineConnection::new("dev.kittycad.io", "some-token", "").await?; + let mut engine = EngineConnection::new().await?; let memory = execute(program, &mut mem, BodyType::Root, &mut engine)?; Ok(memory) diff --git a/src/wasm-lib/kcl/src/lib.rs b/src/wasm-lib/kcl/src/lib.rs new file mode 100644 index 000000000..ca268fda8 --- /dev/null +++ b/src/wasm-lib/kcl/src/lib.rs @@ -0,0 +1,10 @@ +pub mod abstract_syntax_tree_types; +mod docs; +pub mod engine; +pub mod errors; +pub mod executor; +pub mod math_parser; +pub mod parser; +pub mod recast; +pub mod std; +pub mod tokeniser; diff --git a/src/wasm-lib/src/math_parser.rs b/src/wasm-lib/kcl/src/math_parser.rs similarity index 100% rename from src/wasm-lib/src/math_parser.rs rename to src/wasm-lib/kcl/src/math_parser.rs diff --git a/src/wasm-lib/src/parser.rs b/src/wasm-lib/kcl/src/parser.rs similarity index 97% rename from src/wasm-lib/src/parser.rs rename to src/wasm-lib/kcl/src/parser.rs index fee32d9e8..83e149332 100644 --- a/src/wasm-lib/src/parser.rs +++ b/src/wasm-lib/kcl/src/parser.rs @@ -9,12 +9,8 @@ use crate::abstract_syntax_tree_types::{ }; use crate::errors::{KclError, KclErrorDetails}; use crate::math_parser::parse_expression; -use crate::tokeniser::lexer; use crate::tokeniser::{Token, TokenType}; -use gloo_utils::format::JsValueSerdeExt; -use wasm_bindgen::prelude::*; - fn make_identifier(tokens: &[Token], index: usize) -> Identifier { let current_token = &tokens[index]; Identifier { @@ -1540,15 +1536,6 @@ pub fn abstract_syntax_tree(tokens: &[Token]) -> Result { }) } -#[wasm_bindgen] -pub fn parse_js(js: &str) -> Result { - let tokens = lexer(js); - let program = abstract_syntax_tree(&tokens).map_err(String::from)?; - // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the - // gloo-serialize crate instead. - JsValue::from_serde(&program).map_err(|e| e.to_string()) -} - #[cfg(test)] mod tests { use super::*; @@ -1556,7 +1543,7 @@ mod tests { #[test] fn test_make_identifier() { - let tokens = lexer("a"); + let tokens = crate::tokeniser::lexer("a"); let identifier = make_identifier(&tokens, 0); assert_eq!( Identifier { @@ -1570,7 +1557,7 @@ mod tests { #[test] fn test_make_identifier_with_const_myvar_equals_5_and_index_2() { - let tokens = lexer("const myVar = 5"); + let tokens = crate::tokeniser::lexer("const myVar = 5"); let identifier = make_identifier(&tokens, 2); assert_eq!( Identifier { @@ -1584,7 +1571,7 @@ mod tests { #[test] fn test_make_identifier_multiline() { - let tokens = lexer("const myVar = 5\nconst newVar = myVar + 1"); + let tokens = crate::tokeniser::lexer("const myVar = 5\nconst newVar = myVar + 1"); let identifier = make_identifier(&tokens, 2); assert_eq!( Identifier { @@ -1607,7 +1594,7 @@ mod tests { #[test] fn test_make_identifier_call_expression() { - let tokens = lexer("log(5, \"hello\", aIdentifier)"); + let tokens = crate::tokeniser::lexer("log(5, \"hello\", aIdentifier)"); let identifier = make_identifier(&tokens, 0); assert_eq!( Identifier { @@ -1629,7 +1616,7 @@ mod tests { } #[test] fn test_make_none_code_node() { - let tokens = lexer("log(5, \"hello\", aIdentifier)"); + let tokens = crate::tokeniser::lexer("log(5, \"hello\", aIdentifier)"); let index = 4; let expected_output = ( Some(NoneCodeNode { @@ -1651,7 +1638,7 @@ mod tests { 7, ); assert_eq!(make_none_code_node(&tokens, index), expected_output); - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#" const yo = { a: { b: { c: '123' } } } // this is a comment @@ -1700,7 +1687,7 @@ const key = 'c'"#, 31, ); assert_eq!(make_none_code_node(&tokens, index), expected_output); - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const mySketch = startSketchAt([0,0]) |> lineTo({ to: [0, 1], tag: 'myPath' }, %) |> lineTo([1, 1], %) /* this is @@ -1724,7 +1711,7 @@ const key = 'c'"#, #[test] fn test_collect_object_keys() { - let tokens = lexer("const prop = yo.one[\"two\"]"); + let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]"); let keys_info = collect_object_keys(&tokens, 6, None).unwrap(); assert_eq!(keys_info.len(), 2); let first_key = match keys_info[0].key.clone() { @@ -1743,7 +1730,7 @@ const key = 'c'"#, #[test] fn test_make_literal_call_expression() { - let tokens = lexer("log(5, \"hello\", aIdentifier)"); + let tokens = crate::tokeniser::lexer("log(5, \"hello\", aIdentifier)"); let literal = make_literal(&tokens, 2).unwrap(); assert_eq!( Literal { @@ -1833,7 +1820,7 @@ const key = 'c'"#, #[test] fn test_next_meaningful_token() { let _offset = 1; - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const mySketch = startSketchAt([0,0]) |> lineTo({ to: [0, 1], tag: 'myPath' }, %) |> lineTo([1, 1], %) /* this is @@ -2218,7 +2205,7 @@ const key = 'c'"#, #[test] fn test_find_closing_brace() { - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const mySketch = startSketchAt([0,0]) |> lineTo({ to: [0, 1], tag: 'myPath' }, %) |> lineTo([1, 1], %) /* this is @@ -2234,22 +2221,25 @@ const key = 'c'"#, assert_eq!(find_closing_brace(&tokens, 90, 0, "").unwrap(), 92); let basic = "( hey )"; - assert_eq!(find_closing_brace(&lexer(basic), 0, 0, "").unwrap(), 4); + assert_eq!( + find_closing_brace(&crate::tokeniser::lexer(basic), 0, 0, "").unwrap(), + 4 + ); let handles_non_zero_index = "(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)"; assert_eq!( - find_closing_brace(&lexer(handles_non_zero_index), 2, 0, "").unwrap(), + find_closing_brace(&crate::tokeniser::lexer(handles_non_zero_index), 2, 0, "").unwrap(), 4 ); assert_eq!( - find_closing_brace(&lexer(handles_non_zero_index), 0, 0, "").unwrap(), + find_closing_brace(&crate::tokeniser::lexer(handles_non_zero_index), 0, 0, "").unwrap(), 6 ); let handles_nested = "{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }"; assert_eq!( - find_closing_brace(&lexer(handles_nested), 0, 0, "").unwrap(), + find_closing_brace(&crate::tokeniser::lexer(handles_nested), 0, 0, "").unwrap(), 18 ); @@ -2258,7 +2248,7 @@ const key = 'c'"#, #[test] fn test_is_call_expression() { - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const mySketch = startSketchAt([0,0]) |> lineTo({ to: [0, 1], tag: 'myPath' }, %) |> lineTo([1, 1], %) /* this is @@ -2278,7 +2268,7 @@ const key = 'c'"#, #[test] fn test_find_next_declaration_keyword() { - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const mySketch = startSketchAt([0,0]) |> lineTo({ to: [0, 1], tag: 'myPath' }, %) |> lineTo([1, 1], %) /* this is @@ -2295,7 +2285,7 @@ const key = 'c'"#, } ); - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const myVar = 5 const newVar = myVar + 1 "#, @@ -2327,7 +2317,7 @@ const newVar = myVar + 1 lineTo(2, 3) } |> rx(45, %) "#; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); assert_eq!( has_pipe_operator(&tokens, 0, None).unwrap(), TokenReturnWithNonCode { @@ -2349,7 +2339,7 @@ const newVar = myVar + 1 lineTo(2, 3) } |> rx(45, %) |> rx(45, %) "#; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); assert_eq!( has_pipe_operator(&tokens, 0, None).unwrap(), TokenReturnWithNonCode { @@ -2374,7 +2364,7 @@ const newVar = myVar + 1 const yo = myFunc(9() |> rx(45, %) "#; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); assert_eq!( has_pipe_operator(&tokens, 0, None).unwrap(), TokenReturnWithNonCode { @@ -2385,7 +2375,7 @@ const yo = myFunc(9() ); let code = "const myVar2 = 5 + 1 |> myFn(%)"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); assert_eq!( has_pipe_operator(&tokens, 1, None).unwrap(), TokenReturnWithNonCode { @@ -2410,7 +2400,7 @@ const yo = myFunc(9() lineTo(1,1) } |> rx(90, %) show(mySk1)"#; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let token_with_my_path_index = tokens .iter() .position(|token| token.value == "myPath") @@ -2454,7 +2444,7 @@ show(mySk1)"#; #[test] fn test_make_member_expression() { - let tokens = lexer("const prop = yo.one[\"two\"]"); + let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]"); let member_expression_return = make_member_expression(&tokens, 6).unwrap(); let member_expression = member_expression_return.expression; let last_index = member_expression_return.last_index; @@ -2495,12 +2485,12 @@ show(mySk1)"#; #[test] fn test_find_end_of_binary_expression() { let code = "1 + 2 * 3\nconst yo = 5"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 0).unwrap(); assert_eq!(tokens[end].value, "3"); let code = "(1 + 25) / 5 - 3\nconst yo = 5"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 0).unwrap(); assert_eq!(tokens[end].value, "3"); let index_of_5 = code.find('5').unwrap(); @@ -2508,44 +2498,44 @@ show(mySk1)"#; assert_eq!(end_starting_at_the_5, end); // whole thing wraped let code = "((1 + 2) / 5 - 3)\nconst yo = 5"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 0).unwrap(); assert_eq!(tokens[end].end, code.find("3)").unwrap() + 2); // whole thing wraped but given index after the first brace let code = "((1 + 2) / 5 - 3)\nconst yo = 5"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 1).unwrap(); assert_eq!(tokens[end].value, "3"); // given the index of a small wrapped section i.e. `1 + 2` in ((1 + 2) / 5 - 3)' let code = "((1 + 2) / 5 - 3)\nconst yo = 5"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 2).unwrap(); assert_eq!(tokens[end].value, "2"); // lots of silly nesting let code = "(1 + 2) / (5 - (3))\nconst yo = 5"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 0).unwrap(); assert_eq!(tokens[end].end, code.find("))").unwrap() + 2); // with pipe operator at the end let code = "(1 + 2) / (5 - (3))\n |> fn(%)"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 0).unwrap(); assert_eq!(tokens[end].end, code.find("))").unwrap() + 2); // with call expression at the start of binary expression let code = "yo(2) + 3\n |> fn(%)"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let end = find_end_of_binary_expression(&tokens, 0).unwrap(); assert_eq!(tokens[end].value, "3"); // with call expression at the end of binary expression let code = "3 + yo(2)\n |> fn(%)"; - let tokens = lexer(code); + let tokens = crate::tokeniser::lexer(code); let _end = find_end_of_binary_expression(&tokens, 0).unwrap(); } #[test] fn test_make_array_expression() { // input_index: 6, output_index: 14, output: {"type":"ArrayExpression","start":11,"end":26,"elements":[{"type":"Literal","start":12,"end":15,"value":"1","raw":"\"1\""},{"type":"Literal","start":17,"end":18,"value":2,"raw":"2"},{"type":"Identifier","start":20,"end":25,"name":"three"}]} - let tokens = lexer("const yo = [\"1\", 2, three]"); + let tokens = crate::tokeniser::lexer("const yo = [\"1\", 2, three]"); let array_expression = make_array_expression(&tokens, 6).unwrap(); let expression = array_expression.expression; assert_eq!(array_expression.last_index, 14); @@ -2583,7 +2573,7 @@ show(mySk1)"#; #[test] fn test_make_call_expression() { - let tokens = lexer("foo(\"a\", a, 3)"); + let tokens = crate::tokeniser::lexer("foo(\"a\", a, 3)"); let result = make_call_expression(&tokens, 0).unwrap(); assert_eq!(result.last_index, 9); assert_eq!(result.expression.start, 0); @@ -2616,7 +2606,7 @@ show(mySk1)"#; #[test] fn test_make_variable_declaration() { - let tokens = lexer( + let tokens = crate::tokeniser::lexer( r#"const yo = startSketch([0, 0]) |> lineTo([1, myVar], %) |> foo(myVar2, %) @@ -2685,7 +2675,7 @@ show(mySk1)"#; #[test] fn test_make_body() { - let tokens = lexer("const myVar = 5"); + let tokens = crate::tokeniser::lexer("const myVar = 5"); let body = make_body( &tokens, 0, @@ -2702,7 +2692,7 @@ show(mySk1)"#; #[test] fn test_abstract_syntax_tree() { let code = "5 +6"; - let result = abstract_syntax_tree(&lexer(code)).unwrap(); + let result = abstract_syntax_tree(&crate::tokeniser::lexer(code)).unwrap(); let expected_result = Program { start: 0, end: 4, diff --git a/src/wasm-lib/src/recast.rs b/src/wasm-lib/kcl/src/recast.rs similarity index 96% rename from src/wasm-lib/src/recast.rs rename to src/wasm-lib/kcl/src/recast.rs index 1070cb280..245f308f8 100644 --- a/src/wasm-lib/src/recast.rs +++ b/src/wasm-lib/kcl/src/recast.rs @@ -1,7 +1,5 @@ //! Generates source code from the AST. //! The inverse of parsing (which generates an AST from the source code) -use gloo_utils::format::JsValueSerdeExt; -use wasm_bindgen::prelude::*; use crate::abstract_syntax_tree_types::{ ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, FunctionExpression, @@ -400,14 +398,3 @@ pub fn recast_function(expression: FunctionExpression) -> String { ) ) } - -// wasm_bindgen wrapper for recast -// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts -#[wasm_bindgen] -pub fn recast_wasm(json_str: &str) -> Result { - // deserialize the ast from a stringified json - let program: Program = serde_json::from_str(json_str).map_err(JsError::from)?; - - let result = recast(&program, "", false); - Ok(JsValue::from_serde(&result)?) -} diff --git a/src/wasm-lib/src/std/extrude.rs b/src/wasm-lib/kcl/src/std/extrude.rs similarity index 100% rename from src/wasm-lib/src/std/extrude.rs rename to src/wasm-lib/kcl/src/std/extrude.rs diff --git a/src/wasm-lib/src/std/mod.rs b/src/wasm-lib/kcl/src/std/mod.rs similarity index 99% rename from src/wasm-lib/src/std/mod.rs rename to src/wasm-lib/kcl/src/std/mod.rs index 93d135d4a..d04b82544 100644 --- a/src/wasm-lib/src/std/mod.rs +++ b/src/wasm-lib/kcl/src/std/mod.rs @@ -1,9 +1,9 @@ //! Functions implemented for language execution. -mod extrude; -mod segment; -mod sketch; -mod utils; +pub mod extrude; +pub mod segment; +pub mod sketch; +pub mod utils; // TODO: Something that would be nice is if we could generate docs for Kcl based on the // actual stdlib functions below. @@ -663,7 +663,7 @@ mod tests { buf.push_str(&fn_docs); } - expectorate::assert_contents("../../docs/kcl.md", &buf); + expectorate::assert_contents("../../../docs/kcl.md", &buf); } #[test] @@ -677,7 +677,7 @@ mod tests { } expectorate::assert_contents( - "../../docs/kcl.json", + "../../../docs/kcl.json", &serde_json::to_string_pretty(&json_data).unwrap(), ); } diff --git a/src/wasm-lib/src/std/segment.rs b/src/wasm-lib/kcl/src/std/segment.rs similarity index 100% rename from src/wasm-lib/src/std/segment.rs rename to src/wasm-lib/kcl/src/std/segment.rs diff --git a/src/wasm-lib/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs similarity index 100% rename from src/wasm-lib/src/std/sketch.rs rename to src/wasm-lib/kcl/src/std/sketch.rs diff --git a/src/wasm-lib/src/std/utils.rs b/src/wasm-lib/kcl/src/std/utils.rs similarity index 96% rename from src/wasm-lib/src/std/utils.rs rename to src/wasm-lib/kcl/src/std/utils.rs index 938c0c120..3ea8da2bc 100644 --- a/src/wasm-lib/src/std/utils.rs +++ b/src/wasm-lib/kcl/src/std/utils.rs @@ -44,7 +44,7 @@ pub fn normalize_rad(angle: f64) -> f64 { /// # Examples /// /// ``` -/// assert_eq!(delta_angle(std::f64::consts::PI/8.0, std::f64::consts::PI/4.0), std::f64::consts::PI/8.0); +/// assert_eq!(kcl::std::utils::delta_angle(std::f64::consts::PI/8.0, std::f64::consts::PI/4.0), std::f64::consts::PI/8.0); /// ``` #[allow(dead_code)] pub fn delta_angle(from_angle: f64, to_angle: f64) -> f64 { @@ -69,8 +69,8 @@ pub fn delta_angle(from_angle: f64, to_angle: f64) -> f64 { /// # 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); +/// assert_eq!(kcl::std::utils::distance_between_points(&[0.0, 0.0], &[0.0, 5.0]), 5.0); +/// assert_eq!(kcl::std::utils::distance_between_points(&[0.0, 0.0], &[3.0, 4.0]), 5.0); /// ``` #[allow(dead_code)] pub fn distance_between_points(point_a: &[f64; 2], point_b: &[f64; 2]) -> f64 { diff --git a/src/wasm-lib/src/tokeniser.rs b/src/wasm-lib/kcl/src/tokeniser.rs similarity index 97% rename from src/wasm-lib/src/tokeniser.rs rename to src/wasm-lib/kcl/src/tokeniser.rs index f1a5605eb..00e2c7080 100644 --- a/src/wasm-lib/src/tokeniser.rs +++ b/src/wasm-lib/kcl/src/tokeniser.rs @@ -1,8 +1,6 @@ -use gloo_utils::format::JsValueSerdeExt; use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; #[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize, ts_rs::TS)] #[ts(export)] @@ -273,14 +271,6 @@ pub fn lexer(str: &str) -> Vec { recursively_tokenise(str, 0, Vec::new()) } -// wasm_bindgen wrapper for lexer -// test for this function and by extension lexer are done in javascript land src/lang/tokeniser.test.ts -#[wasm_bindgen] -pub fn lexer_js(str: &str) -> Result { - let tokens = lexer(str); - Ok(JsValue::from_serde(&tokens)?) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/wasm-lib/src/engine/conn_noweb.rs b/src/wasm-lib/src/engine/conn_noweb.rs deleted file mode 100644 index 441c9fa2c..000000000 --- a/src/wasm-lib/src/engine/conn_noweb.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! Functions for setting up our WebSocket and WebRTC connections for communications with the -//! engine. - -use anyhow::Result; -use futures::{SinkExt, StreamExt}; -use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse}; -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, - >, - 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, - >, - >, -} - -impl TcpRead { - pub async fn read(&mut self) -> Result { - 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::(&msg)?; - Ok(msg) - } -} - -impl EngineConnection { - pub async fn new(conn_str: &str, auth_token: &str, origin: &str) -> Result { - 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(ws_resp) = tcp_read.read().await { - if !ws_resp.success { - println!("got ws errors: {:?}", ws_resp.errors); - continue; - } - - if let Some(msg) = ws_resp.resp { - match msg { - OkWebSocketResponseData::IceServerInfo { ice_servers } => { - println!("got ice server info: {:?}", ice_servers); - } - OkWebSocketResponseData::SdpAnswer { answer } => { - println!("got sdp answer: {:?}", answer); - } - OkWebSocketResponseData::TrickleIce { candidate } => { - println!("got trickle ice: {:?}", candidate); - } - OkWebSocketResponseData::Modeling { .. } => {} - OkWebSocketResponseData::Export { .. } => {} - } - } - } - }); - - Ok(EngineConnection { - tcp_write, - tcp_read_handle, - }) - } - - pub async fn tcp_send(&mut self, msg: WebSocketRequest) -> Result<()> { - let msg = serde_json::to_string(&msg)?; - self.tcp_write.send(WsMsg::Text(msg)).await?; - - 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(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id }), - ) - .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to send modeling command: {}", e), - source_ranges: vec![source_range], - }) - })?; - Ok(()) - } -} - -/// 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(), - }, - ] -} diff --git a/src/wasm-lib/src/export.rs b/src/wasm-lib/src/export.rs deleted file mode 100644 index 2cd120bb1..000000000 --- a/src/wasm-lib/src/export.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Functions for exported files from the server. - -use gloo_utils::format::JsValueSerdeExt; -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub fn deserialize_files(data: &[u8]) -> Result { - let ws_resp: kittycad::types::WebSocketResponse = bincode::deserialize(data)?; - - if !ws_resp.success { - return Err(JsError::new(&format!( - "Server returned error: {:?}", - ws_resp.errors - ))); - } - - if let Some(kittycad::types::OkWebSocketResponseData::Export { files }) = ws_resp.resp { - return Ok(JsValue::from_serde(&files)?); - } - - Err(JsError::new(&format!( - "Invalid response type, got: {:?}", - ws_resp - ))) -} diff --git a/src/wasm-lib/src/lib.rs b/src/wasm-lib/src/lib.rs index 3dd4455af..38fd84a2d 100644 --- a/src/wasm-lib/src/lib.rs +++ b/src/wasm-lib/src/lib.rs @@ -1,11 +1,84 @@ -mod abstract_syntax_tree_types; -mod docs; -mod engine; -mod errors; -mod executor; -mod export; -mod math_parser; -mod parser; -mod recast; -mod std; -mod tokeniser; +//! Wasm bindings for `kcl`. + +use gloo_utils::format::JsValueSerdeExt; +use wasm_bindgen::prelude::*; + +// wasm_bindgen wrapper for execute +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub async fn execute_wasm( + program_str: &str, + memory_str: &str, + manager: kcl::engine::conn_wasm::EngineCommandManager, +) -> Result { + // deserialize the ast from a stringified json + let program: kcl::abstract_syntax_tree_types::Program = + serde_json::from_str(program_str).map_err(|e| e.to_string())?; + let mut mem: kcl::executor::ProgramMemory = + serde_json::from_str(memory_str).map_err(|e| e.to_string())?; + + let mut engine = kcl::engine::EngineConnection::new(manager) + .await + .map_err(|e| format!("{:?}", e))?; + + let memory = kcl::executor::execute( + program, + &mut mem, + kcl::executor::BodyType::Root, + &mut engine, + ) + .map_err(String::from)?; + // 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] +pub fn deserialize_files(data: &[u8]) -> Result { + let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?; + + if !ws_resp.success { + return Err(JsError::new(&format!( + "Server returned error: {:?}", + ws_resp.errors + ))); + } + + if let Some(kittycad::types::OkWebSocketResponseData::Export { files }) = ws_resp.resp { + return Ok(JsValue::from_serde(&files)?); + } + + Err(JsError::new(&format!( + "Invalid response type, got: {:?}", + ws_resp + ))) +} + +// wasm_bindgen wrapper for lexer +// test for this function and by extension lexer are done in javascript land src/lang/tokeniser.test.ts +#[wasm_bindgen] +pub fn lexer_js(js: &str) -> Result { + let tokens = kcl::tokeniser::lexer(js); + Ok(JsValue::from_serde(&tokens)?) +} + +#[wasm_bindgen] +pub fn parse_js(js: &str) -> Result { + let tokens = kcl::tokeniser::lexer(js); + let program = kcl::parser::abstract_syntax_tree(&tokens).map_err(String::from)?; + // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the + // gloo-serialize crate instead. + JsValue::from_serde(&program).map_err(|e| e.to_string()) +} + +// wasm_bindgen wrapper for recast +// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts +#[wasm_bindgen] +pub fn recast_wasm(json_str: &str) -> Result { + // deserialize the ast from a stringified json + let program: kcl::abstract_syntax_tree_types::Program = + serde_json::from_str(json_str).map_err(JsError::from)?; + + let result = kcl::recast::recast(&program, "", false); + Ok(JsValue::from_serde(&result)?) +}