Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
66ba60dc8e | |||
8fcc8cdd17 | |||
bba9bdc563 | |||
760a180f56 | |||
0eeff8cb45 | |||
3c76721159 | |||
6ac79ae645 | |||
90d7c33c92 | |||
e02bc76bdb | |||
0466f04d82 | |||
f8ed830b60 | |||
b7ca91bf6d | |||
2261f92b0b | |||
bbe9e621b1 | |||
bf087d760b | |||
a4353c63fd | |||
c438d11c3d | |||
43284e33c8 | |||
77dce7f0dd | |||
d559862051 | |||
7382ed87ba | |||
3324ed31de | |||
ba9dbc2205 | |||
b0028d4874 | |||
9e6be9651c | |||
b145ab0106 | |||
84e0fbb70f | |||
990605bbea | |||
d075c4ad13 | |||
a3f41f5519 | |||
cb173e2850 | |||
87cd3b67f4 | |||
fe3ee3806e | |||
c9ed6c724c | |||
a5fa259d55 | |||
33822b5a19 | |||
a2a4daebe3 | |||
a17ede50bd | |||
2d452f80d1 | |||
cf39c08428 | |||
2f25564fcc | |||
fd2ed8acbd | |||
5f3e1cfb6c | |||
ee767afc3f | |||
8071eb6f8a | |||
11f789e980 | |||
3f82522fe9 | |||
c5cb0e2fd4 |
@ -1,6 +1,6 @@
|
|||||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
||||||
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||||
VITE_KC_SENTRY_DSN=
|
VITE_KC_SENTRY_DSN=
|
||||||
|
11
.github/workflows/cargo-clippy.yml
vendored
11
.github/workflows/cargo-clippy.yml
vendored
@ -40,6 +40,17 @@ jobs:
|
|||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
uses: Swatinem/rust-cache@v2.6.1
|
||||||
|
|
||||||
|
- name: Install ffmpeg
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install \
|
||||||
|
ffmpeg \
|
||||||
|
libavformat-dev \
|
||||||
|
libavutil-dev \
|
||||||
|
libclang-dev \
|
||||||
|
libswscale-dev \
|
||||||
|
--no-install-recommends
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
|
10
.github/workflows/cargo-test.yml
vendored
10
.github/workflows/cargo-test.yml
vendored
@ -41,6 +41,16 @@ jobs:
|
|||||||
- uses: taiki-e/install-action@nextest
|
- uses: taiki-e/install-action@nextest
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
uses: Swatinem/rust-cache@v2.6.1
|
||||||
|
- name: Install ffmpeg
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install \
|
||||||
|
ffmpeg \
|
||||||
|
libavformat-dev \
|
||||||
|
libavutil-dev \
|
||||||
|
libclang-dev \
|
||||||
|
libswscale-dev \
|
||||||
|
--no-install-recommends
|
||||||
- name: cargo test
|
- name: cargo test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
|
99
.github/workflows/ci.yml
vendored
99
.github/workflows/ci.yml
vendored
@ -147,8 +147,8 @@ jobs:
|
|||||||
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
|
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
|
||||||
|
|
||||||
|
|
||||||
publish-apps-release:
|
sign-windows-msi:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: windows-latest
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
needs: [build-test-web, build-apps]
|
needs: [build-test-web, build-apps]
|
||||||
env:
|
env:
|
||||||
@ -157,23 +157,80 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
|
|
||||||
|
- name: Setup Certificate
|
||||||
|
run: |
|
||||||
|
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||||
|
cat /d/Certificate_pkcs12.p12
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set variables
|
||||||
|
id: variables
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
||||||
|
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
||||||
|
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
||||||
|
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||||
|
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
|
||||||
|
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||||
|
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||||
|
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup SSM KSP on windows latest
|
||||||
|
run: |
|
||||||
|
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||||
|
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||||
|
smksp_registrar.exe list
|
||||||
|
smctl.exe keypair ls
|
||||||
|
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||||
|
smksp_cert_sync.exe
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Signing using Signtool
|
||||||
|
run: |
|
||||||
|
signtool.exe sign /sha1 ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "artifact\msi\*.msi"
|
||||||
|
signtool.exe verify /v /pa "artifact\msi\*.msi"
|
||||||
|
|
||||||
|
# TODO: for the updater, investigate if we need to also replace what's in the .zip, and what to do about the .sig file
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
path: artifact/*
|
||||||
|
|
||||||
|
|
||||||
|
publish-apps-release:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
needs: [build-test-web, build-apps, sign-windows-msi]
|
||||||
|
env:
|
||||||
|
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
||||||
|
PUB_DATE: ${{ github.event.release.created_at }}
|
||||||
|
NOTES: ${{ github.event.release.body }}
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
|
||||||
- name: Generate the update static endpoint
|
- name: Generate the update static endpoint
|
||||||
run: |
|
run: |
|
||||||
ls -l artifact/*/*itty*
|
ls -l artifact/*/*itty*
|
||||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
||||||
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
|
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
|
||||||
WINDOWS_SIG=`cat artifact/nsis/*.nsis.zip.sig`
|
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
|
||||||
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
||||||
jq --null-input \
|
jq --null-input \
|
||||||
--arg version "v${VERSION_NO_V}" \
|
--arg version "v${VERSION_NO_V}" \
|
||||||
|
--arg pub_date "${PUB_DATE}" \
|
||||||
|
--arg notes "${NOTES}" \
|
||||||
--arg darwin_sig "$DARWIN_SIG" \
|
--arg darwin_sig "$DARWIN_SIG" \
|
||||||
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
||||||
--arg linux_sig "$LINUX_SIG" \
|
--arg linux_sig "$LINUX_SIG" \
|
||||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
||||||
--arg windows_sig "$WINDOWS_SIG" \
|
--arg windows_sig "$WINDOWS_SIG" \
|
||||||
--arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \
|
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
|
"pub_date": $pub_date,
|
||||||
|
"notes": $notes,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"darwin-x86_64": {
|
"darwin-x86_64": {
|
||||||
"signature": $darwin_sig,
|
"signature": $darwin_sig,
|
||||||
@ -195,6 +252,34 @@ jobs:
|
|||||||
}' > last_update.json
|
}' > last_update.json
|
||||||
cat last_update.json
|
cat last_update.json
|
||||||
|
|
||||||
|
- name: Generate the download static endpoint
|
||||||
|
run: |
|
||||||
|
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
||||||
|
jq --null-input \
|
||||||
|
--arg version "v${VERSION_NO_V}" \
|
||||||
|
--arg pub_date "${PUB_DATE}" \
|
||||||
|
--arg notes "${NOTES}" \
|
||||||
|
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
|
||||||
|
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
|
||||||
|
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi" \
|
||||||
|
'{
|
||||||
|
"version": $version,
|
||||||
|
"pub_date": $pub_date,
|
||||||
|
"notes": $notes,
|
||||||
|
"platforms": {
|
||||||
|
"dmg-universal": {
|
||||||
|
"url": $darwin_url
|
||||||
|
},
|
||||||
|
"appimage-x86_64": {
|
||||||
|
"url": $linux_url
|
||||||
|
},
|
||||||
|
"msi-x86_64": {
|
||||||
|
"url": $windows_url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' > last_download.json
|
||||||
|
cat last_download.json
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v1.1.1'
|
uses: 'google-github-actions/auth@v1.1.1'
|
||||||
with:
|
with:
|
||||||
@ -219,6 +304,12 @@ jobs:
|
|||||||
path: last_update.json
|
path: last_update.json
|
||||||
destination: dl.kittycad.io/releases/modeling-app
|
destination: dl.kittycad.io/releases/modeling-app
|
||||||
|
|
||||||
|
- name: Upload download endpoint to public bucket
|
||||||
|
uses: google-github-actions/upload-cloud-storage@v1.0.3
|
||||||
|
with:
|
||||||
|
path: last_download.json
|
||||||
|
destination: dl.kittycad.io/releases/modeling-app
|
||||||
|
|
||||||
- name: Upload release files to Github
|
- name: Upload release files to Github
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
102
README.md
102
README.md
@ -1,48 +1,72 @@
|
|||||||
## Kurt demo project
|

|
||||||
|
|
||||||
|
## KittyCAD Modeling App
|
||||||
|
|
||||||
live at [app.kittycad.io](https://app.kittycad.io/)
|
live at [app.kittycad.io](https://app.kittycad.io/)
|
||||||
|
|
||||||
Not sure what to call this, it's both a language/interpreter and a UI that uses the language as the source of truth model the user build with direct-manipulation with the UI.
|
A CAD application from the future, brought to you by the [KittyCAD team](https://kittycad.io).
|
||||||
|
|
||||||
It might make sense to split this repo up at some point, but not the lang and the UI are all togther in a react app
|
The KittyCAD modeling app is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
|
||||||
|
|
||||||
Originally Presented on 10/01/2023
|
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
|
||||||
|
- This makes version control—which is a solved problem in software engineering—trivial for CAD
|
||||||
|
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
|
||||||
|
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in KittyCAD Modeling App
|
||||||
|
- Everything graphics _has_ to be built for the GPU
|
||||||
|
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
|
||||||
|
- Make the resource-intensive pieces of an application auto-scaling
|
||||||
|
- One of the bottlenecks of today's hardware design tools is that they all rely on the local machine's resources to do the hardest parts, which include geometry rendering and analysis. Our geometry engine parallelizes rendering and just sends video frames back to the app (seriously, inspect source, it's just a `<video>` element), and our API will offload analysis as we build it in
|
||||||
|
|
||||||
[Video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
|
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
|
||||||
|
|
||||||
[demo-slides.pdf](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
|
KittyCAD Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
|
||||||
|
|
||||||
## To run, there are a couple steps since we're compiling rust to WASM, you'll need to have rust stuff installed, then
|
The 3D view in KittyCAD Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
- UI
|
||||||
|
- [React](https://react.dev/)
|
||||||
|
- [Headless UI](https://headlessui.com/)
|
||||||
|
- [TailwindCSS](https://tailwindcss.com/)
|
||||||
|
- Networking
|
||||||
|
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
|
||||||
|
- Code Editor
|
||||||
|
- [CodeMirror](https://codemirror.net/)
|
||||||
|
- Custom WASM LSP Server
|
||||||
|
- Modeling
|
||||||
|
- [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts)
|
||||||
|
|
||||||
|
[Original demo video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
|
||||||
|
|
||||||
|
[Original demo slides](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
We recommend downloading the latest application binary from [our Releases page](https://github.com/KittyCAD/modeling-app/releases). If you don't see your platform or architecture supported there, please file an issue.
|
||||||
|
|
||||||
|
## Running a development build
|
||||||
|
|
||||||
|
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. Then, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
then
|
|
||||||
|
followed by:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn build:wasm
|
yarn build:wasm
|
||||||
```
|
```
|
||||||
|
|
||||||
That will build the WASM binary and put in the `public` dir (though gitignored)
|
That will build the WASM binary and put in the `public` dir (though gitignored)
|
||||||
|
|
||||||
finally
|
finally, to run the web app only, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
and `yarn test` you would have need to have built the WASM previously. The tests need to download the binary from a server, so if you've already got `yarn start` running, that will work, otherwise running
|
|
||||||
```
|
|
||||||
yarn simpleserver
|
|
||||||
```
|
|
||||||
in one terminal
|
|
||||||
and
|
|
||||||
```
|
|
||||||
yarn test
|
|
||||||
```
|
|
||||||
in another.
|
|
||||||
|
|
||||||
If you want to edit the rust files, you can cd into `src/wasm-lib` and then use the usual rust commands, `cargo build`, `cargo test`, when you want to bring the changes back to the web-app, a fresh `yarn build:wasm` in the root will be needed.
|
|
||||||
|
|
||||||
Worth noting that the integration of the WASM into this project is very hacky because I'm really pushing create-react-app further than what's practical, but focusing on features atm rather than the setup.
|
|
||||||
|
|
||||||
## Developing in Chrome
|
## Developing in Chrome
|
||||||
|
|
||||||
Chrome is in the process of rolling out a new default which
|
Chrome is in the process of rolling out a new default which
|
||||||
@ -52,12 +76,26 @@ enable third-party cookies. You can enable third-party cookies by clicking on
|
|||||||
the eye with a slash through it in the URL bar, and clicking on "Enable
|
the eye with a slash through it in the URL bar, and clicking on "Enable
|
||||||
Third-Party Cookies".
|
Third-Party Cookies".
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
First, start the dev server following "Running a development build" above.
|
||||||
|
|
||||||
|
Then in another terminal tab, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||||
|
|
||||||
## Tauri
|
## Tauri
|
||||||
|
|
||||||
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn tauri dev
|
yarn tauri dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writting they can conflict.
|
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writting they can conflict.
|
||||||
|
|
||||||
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
|
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
|
||||||
@ -67,11 +105,22 @@ To build, run `yarn tauri build`, or `yarn tauri build --debug` to keep access t
|
|||||||
Note that these became separate apps on Macos, so make sure you open the right one after a build 😉
|
Note that these became separate apps on Macos, so make sure you open the right one after a build 😉
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
<img width="1232" alt="image" src="https://user-images.githubusercontent.com/29681384/211947063-46164bb4-7bdd-45cb-9a76-2f40c71a24aa.png">
|
<img width="1232" alt="image" src="https://user-images.githubusercontent.com/29681384/211947063-46164bb4-7bdd-45cb-9a76-2f40c71a24aa.png">
|
||||||
|
|
||||||
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
||||||
|
|
||||||
|
## Before submitting a PR
|
||||||
|
|
||||||
|
Before you submit a contribution PR to this repo, please ensure that:
|
||||||
|
|
||||||
|
- There is a corresponding issue for the changes you want to make, so that discussion of approach can be had before work begins.
|
||||||
|
- You have separated out refactoring commits from feature commits as much as possible
|
||||||
|
- You have run all of the following commands locally:
|
||||||
|
- `yarn fmt`
|
||||||
|
- `yarn tsc`
|
||||||
|
- `yarn test`
|
||||||
|
- Here they are all together: `yarn fmt && yarn tsc && yarn test`
|
||||||
|
|
||||||
## Release a new version
|
## Release a new version
|
||||||
|
|
||||||
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
|
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
|
||||||
@ -79,6 +128,7 @@ Note that these became separate apps on Macos, so make sure you open the right o
|
|||||||
```bash
|
```bash
|
||||||
VERSION=x.y.z yarn run bump-jsons
|
VERSION=x.y.z yarn run bump-jsons
|
||||||
```
|
```
|
||||||
|
|
||||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
||||||
|
|
||||||
2. Merge the PR
|
2. Merge the PR
|
||||||
@ -105,5 +155,5 @@ $ cargo fuzz list
|
|||||||
$ cargo +nightly fuzz run parser
|
$ cargo +nightly fuzz run parser
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information on fuzzing you can check out
|
For more information on fuzzing you can check out
|
||||||
[this guide](https://rust-fuzz.github.io/book/cargo-fuzz.html).
|
[this guide](https://rust-fuzz.github.io/book/cargo-fuzz.html).
|
||||||
|
@ -9322,6 +9322,34 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "cos",
|
||||||
|
"summary": "Computes the sine of a number (in radians).",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "extrude",
|
"name": "extrude",
|
||||||
"summary": "Extrudes by a given amount.",
|
"summary": "Extrudes by a given amount.",
|
||||||
@ -11173,22 +11201,13 @@
|
|||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"description": "The to point.",
|
"description": "The to point.",
|
||||||
"anyOf": [
|
"type": "array",
|
||||||
{
|
"items": {
|
||||||
"description": "A point.",
|
"type": "number",
|
||||||
"type": "array",
|
"format": "double"
|
||||||
"items": {
|
},
|
||||||
"type": "number",
|
"maxItems": 2,
|
||||||
"format": "double"
|
"minItems": 2
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -11201,10 +11220,6 @@
|
|||||||
},
|
},
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -13031,6 +13046,24 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pi",
|
||||||
|
"summary": "Return the value of `pi`.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "segAng",
|
"name": "segAng",
|
||||||
"summary": "Returns the angle of the segment.",
|
"summary": "Returns the angle of the segment.",
|
||||||
@ -15315,6 +15348,34 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sin",
|
||||||
|
"summary": "Computes the sine of a number (in radians).",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "startSketchAt",
|
"name": "startSketchAt",
|
||||||
"summary": "Start a sketch at a given point.",
|
"summary": "Start a sketch at a given point.",
|
||||||
@ -15341,22 +15402,13 @@
|
|||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"description": "The to point.",
|
"description": "The to point.",
|
||||||
"anyOf": [
|
"type": "array",
|
||||||
{
|
"items": {
|
||||||
"description": "A point.",
|
"type": "number",
|
||||||
"type": "array",
|
"format": "double"
|
||||||
"items": {
|
},
|
||||||
"type": "number",
|
"maxItems": 2,
|
||||||
"format": "double"
|
"minItems": 2
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -15369,10 +15421,6 @@
|
|||||||
},
|
},
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A string like `default`.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -15815,6 +15863,34 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tan",
|
||||||
|
"summary": "Computes the tangent of a number (in radians).",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "xLine",
|
"name": "xLine",
|
||||||
"summary": "Draw a line on the x-axis.",
|
"summary": "Draw a line on the x-axis.",
|
@ -16,6 +16,7 @@
|
|||||||
* [`arc`](#arc)
|
* [`arc`](#arc)
|
||||||
* [`bezierCurve`](#bezierCurve)
|
* [`bezierCurve`](#bezierCurve)
|
||||||
* [`close`](#close)
|
* [`close`](#close)
|
||||||
|
* [`cos`](#cos)
|
||||||
* [`extrude`](#extrude)
|
* [`extrude`](#extrude)
|
||||||
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
|
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
|
||||||
* [`lastSegX`](#lastSegX)
|
* [`lastSegX`](#lastSegX)
|
||||||
@ -26,12 +27,15 @@
|
|||||||
* [`line`](#line)
|
* [`line`](#line)
|
||||||
* [`lineTo`](#lineTo)
|
* [`lineTo`](#lineTo)
|
||||||
* [`min`](#min)
|
* [`min`](#min)
|
||||||
|
* [`pi`](#pi)
|
||||||
* [`segAng`](#segAng)
|
* [`segAng`](#segAng)
|
||||||
* [`segEndX`](#segEndX)
|
* [`segEndX`](#segEndX)
|
||||||
* [`segEndY`](#segEndY)
|
* [`segEndY`](#segEndY)
|
||||||
* [`segLen`](#segLen)
|
* [`segLen`](#segLen)
|
||||||
* [`show`](#show)
|
* [`show`](#show)
|
||||||
|
* [`sin`](#sin)
|
||||||
* [`startSketchAt`](#startSketchAt)
|
* [`startSketchAt`](#startSketchAt)
|
||||||
|
* [`tan`](#tan)
|
||||||
* [`xLine`](#xLine)
|
* [`xLine`](#xLine)
|
||||||
* [`xLineTo`](#xLineTo)
|
* [`xLineTo`](#xLineTo)
|
||||||
* [`yLine`](#yLine)
|
* [`yLine`](#yLine)
|
||||||
@ -1637,6 +1641,26 @@ close(sketch_group: SketchGroup) -> SketchGroup
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### cos
|
||||||
|
|
||||||
|
Computes the sine of a number (in radians).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
cos(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### extrude
|
### extrude
|
||||||
|
|
||||||
Extrudes by a given amount.
|
Extrudes by a given amount.
|
||||||
@ -2044,11 +2068,9 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
// The tag.
|
// The tag.
|
||||||
tag: string,
|
tag: string,
|
||||||
// The to point.
|
// The to point.
|
||||||
to: [number] |
|
to: [number],
|
||||||
string,
|
|
||||||
} |
|
} |
|
||||||
[number] |
|
[number]
|
||||||
string
|
|
||||||
```
|
```
|
||||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
||||||
```
|
```
|
||||||
@ -2356,6 +2378,25 @@ min(args: [number]) -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### pi
|
||||||
|
|
||||||
|
Return the value of `pi`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
pi() -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### segAng
|
### segAng
|
||||||
|
|
||||||
Returns the angle of the segment.
|
Returns the angle of the segment.
|
||||||
@ -2766,6 +2807,26 @@ show(sketch: SketchGroup)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### sin
|
||||||
|
|
||||||
|
Computes the sine of a number (in radians).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
sin(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### startSketchAt
|
### startSketchAt
|
||||||
|
|
||||||
Start a sketch at a given point.
|
Start a sketch at a given point.
|
||||||
@ -2784,11 +2845,9 @@ startSketchAt(data: LineData) -> SketchGroup
|
|||||||
// The tag.
|
// The tag.
|
||||||
tag: string,
|
tag: string,
|
||||||
// The to point.
|
// The to point.
|
||||||
to: [number] |
|
to: [number],
|
||||||
string,
|
|
||||||
} |
|
} |
|
||||||
[number] |
|
[number]
|
||||||
string
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
@ -2859,6 +2918,26 @@ string
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### tan
|
||||||
|
|
||||||
|
Computes the tangent of a number (in radians).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
tan(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### xLine
|
### xLine
|
||||||
|
|
||||||
Draw a line on the x-axis.
|
Draw a line on the x-axis.
|
75
docs/kcl/types.md
Normal file
75
docs/kcl/types.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Types
|
||||||
|
|
||||||
|
`KCL` defines the following types and keywords the language.
|
||||||
|
|
||||||
|
All these types can be nested in various forms where nesting applies. Like
|
||||||
|
arrays can hold objects and vice versa.
|
||||||
|
|
||||||
|
## Boolean
|
||||||
|
|
||||||
|
`true` or `false` work when defining values.
|
||||||
|
|
||||||
|
## Variable declaration
|
||||||
|
|
||||||
|
Variables are defined with the `let` keyword like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myBool = false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Array
|
||||||
|
|
||||||
|
An array is defined with `[]` braces. What is inside the brackets can
|
||||||
|
be of any type. For example, the following is completely valid:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myArray = ["thing", 2, false]
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to get a value from an array you can use the index like so:
|
||||||
|
`myArray[0]`.
|
||||||
|
|
||||||
|
|
||||||
|
## Object
|
||||||
|
|
||||||
|
An object is defined with `{}` braces. Here is an example object:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myObj = {a: 0, b: "thing"}
|
||||||
|
```
|
||||||
|
|
||||||
|
We support two different ways of getting properties from objects, you can call
|
||||||
|
`myObj.a` or `myObj["a"]` both work.
|
||||||
|
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
We also have support for defining your own functions. Functions can take in any
|
||||||
|
type of argument. Below is an example of the syntax:
|
||||||
|
|
||||||
|
```
|
||||||
|
fn myFn = (x) => {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see above `myFn` just returns whatever it is given.
|
||||||
|
|
||||||
|
|
||||||
|
## Binary expressions
|
||||||
|
|
||||||
|
You can also do math! Let's show an example below:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myMathExpression = 3 + 1 * 2 / 3 - 7
|
||||||
|
```
|
||||||
|
|
||||||
|
You can nest expressions in parenthesis as well:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myMathExpression = 3 + (1 * 2 / (3 - 7))
|
||||||
|
```
|
||||||
|
|
||||||
|
Please if you find any issues using any of the above expressions or syntax
|
||||||
|
please file an issue with the `ast` label on the [modeling-app
|
||||||
|
repo](https://github.com/KittyCAD/modeling-app/issues/new).
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.5.0",
|
"version": "0.7.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.9.0",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
@ -14,6 +14,7 @@
|
|||||||
"@lezer/javascript": "^1.4.7",
|
"@lezer/javascript": "^1.4.7",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
|
"@replit/codemirror-interact": "^6.3.0",
|
||||||
"@sentry/react": "^7.65.0",
|
"@sentry/react": "^7.65.0",
|
||||||
"@tauri-apps/api": "^1.3.0",
|
"@tauri-apps/api": "^1.3.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
|
BIN
public/kcma-logomark.png
Normal file
BIN
public/kcma-logomark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
26
public/roadmap.md
Normal file
26
public/roadmap.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
## KittyCAD Modeling App Roadmap
|
||||||
|
|
||||||
|
This document ties into our [GH Discussions Feature List](https://github.com/KittyCAD/modeling-app/discussions). Please upvote any features that you want to see next, or add ones that are not listed and we will review.
|
||||||
|
|
||||||
|
### Current Priority List
|
||||||
|
|
||||||
|
1. [Sketch on Face](https://github.com/KittyCAD/modeling-app/discussions/477)
|
||||||
|
2. [Revolve](https://github.com/KittyCAD/modeling-app/discussions/496)
|
||||||
|
3. [Fillet](https://github.com/KittyCAD/modeling-app/discussions/501)
|
||||||
|
4. [Linear Pattern](https://github.com/KittyCAD/modeling-app/discussions/256)
|
||||||
|
5. [Circular Pattern](https://github.com/KittyCAD/modeling-app/discussions/257)
|
||||||
|
6. [Mirror-Sketch](https://github.com/KittyCAD/modeling-app/discussions/507)
|
||||||
|
7. [Chamfer](https://github.com/KittyCAD/modeling-app/discussions/502)
|
||||||
|
8. [Sweep](https://github.com/KittyCAD/modeling-app/discussions/498)
|
||||||
|
9. [Draft](https://github.com/KittyCAD/modeling-app/discussions/495)
|
||||||
|
10. [Shell](https://github.com/KittyCAD/modeling-app/discussions/503)
|
||||||
|
11. [Union](https://github.com/KittyCAD/modeling-app/discussions/509)
|
||||||
|
12. [Mirror-Model](https://github.com/KittyCAD/modeling-app/discussions/508)
|
||||||
|
13. [Subtract](https://github.com/KittyCAD/modeling-app/discussions/510)
|
||||||
|
14. [Intersect](https://github.com/KittyCAD/modeling-app/discussions/511)
|
||||||
|
15. [Offset](https://github.com/KittyCAD/modeling-app/discussions/512)
|
||||||
|
16. [Thicken](https://github.com/KittyCAD/modeling-app/discussions/499)
|
||||||
|
17. [Import](https://github.com/KittyCAD/modeling-app/discussions/478)
|
||||||
|
18. [Assemblies](https://github.com/KittyCAD/modeling-app/discussions/494)
|
||||||
|
19. [External Thread](https://github.com/KittyCAD/modeling-app/discussions/505)
|
||||||
|
|
247
src-tauri/Cargo.lock
generated
247
src-tauri/Cargo.lock
generated
@ -67,9 +67,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.71"
|
version = "1.0.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
@ -83,7 +83,7 @@ dependencies = [
|
|||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-fs-extra",
|
"tauri-plugin-fs-extra",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.6.0",
|
"toml 0.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -110,22 +110,6 @@ dependencies = [
|
|||||||
"system-deps 6.1.0",
|
"system-deps 6.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "attohttpc"
|
|
||||||
version = "0.22.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7"
|
|
||||||
dependencies = [
|
|
||||||
"flate2",
|
|
||||||
"http",
|
|
||||||
"log",
|
|
||||||
"native-tls",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_urlencoded",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -234,6 +218,9 @@ name = "bytes"
|
|||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairo-rs"
|
name = "cairo-rs"
|
||||||
@ -266,7 +253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838"
|
checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml 0.7.3",
|
"toml 0.7.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -325,8 +312,10 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -495,7 +484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -535,7 +524,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -546,7 +535,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -628,7 +617,7 @@ checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml 0.7.3",
|
"toml 0.7.8",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg 0.11.0",
|
"winreg 0.11.0",
|
||||||
]
|
]
|
||||||
@ -805,7 +794,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -829,6 +818,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@ -1282,7 +1272,7 @@ dependencies = [
|
|||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.4.9",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -1303,6 +1293,19 @@ dependencies = [
|
|||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.56"
|
version = "0.1.56"
|
||||||
@ -1745,15 +1748,6 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom8"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@ -1836,9 +1830,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oauth2"
|
name = "oauth2"
|
||||||
version = "4.4.1"
|
version = "4.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09a6e2a2b13a56ebeabba9142f911745be6456163fd6c3d361274ebcd891a80c"
|
checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1941,7 +1935,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2128,9 +2122,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.9"
|
version = "0.2.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-utils"
|
name = "pin-utils"
|
||||||
@ -2190,7 +2184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"toml_edit 0.19.8",
|
"toml_edit 0.19.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2225,9 +2219,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.59"
|
version = "1.0.67"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
|
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@ -2417,10 +2411,12 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
|
"hyper-tls",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
|
"native-tls",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@ -2430,11 +2426,14 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
|
"tokio-util",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-streams",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
"winreg 0.10.1",
|
"winreg 0.10.1",
|
||||||
@ -2531,9 +2530,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.1"
|
version = "0.101.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
|
checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
@ -2651,29 +2650,29 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.163"
|
version = "1.0.188"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
|
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.163"
|
version = "1.0.188"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
|
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.96"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"ryu",
|
"ryu",
|
||||||
@ -2698,14 +2697,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
|
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -2748,7 +2747,7 @@ dependencies = [
|
|||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2840,6 +2839,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "soup2"
|
name = "soup2"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -2934,9 +2943,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.18"
|
version = "2.0.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -2965,7 +2974,7 @@ dependencies = [
|
|||||||
"cfg-expr 0.15.2",
|
"cfg-expr 0.15.2",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"toml 0.7.3",
|
"toml 0.7.8",
|
||||||
"version-compare 0.1.1",
|
"version-compare 0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3046,13 +3055,13 @@ checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.3.0"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d42ba3a2e8556722f31336a0750c10dbb6a81396a1c452977f515da83f69f842"
|
checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"attohttpc",
|
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
|
"bytes",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
@ -3073,6 +3082,7 @@ dependencies = [
|
|||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"regex",
|
"regex",
|
||||||
|
"reqwest",
|
||||||
"rfd",
|
"rfd",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3116,9 +3126,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5a2105f807c6f50b2fa2ce5abd62ef207bc6f14c9fcc6b8caec437f6fb13bde"
|
checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"brotli",
|
"brotli",
|
||||||
@ -3142,9 +3152,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8784cfe6f5444097e93c69107d1ac5e8f13d02850efa8d8f2a40fe79674cef46"
|
checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -3157,7 +3167,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs-extra"
|
name = "tauri-plugin-fs-extra"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#7e58dc8502f654b99d51c087421f84ccc0e03119"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#5b814f56e6368fdec46c4ddb04a07e0923ff995a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3168,9 +3178,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3b80ea3fcd5fefb60739a3b577b277e8fc30434538a2f5bba82ad7d4368c422"
|
checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
@ -3189,9 +3199,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d1c396950b1ba06aee1b4ffe6c7cd305ff433ca0e30acbc5fa1a2f92a4ce70f1"
|
checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"gtk",
|
"gtk",
|
||||||
@ -3243,7 +3253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb"
|
checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
"toml 0.7.3",
|
"toml 0.7.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3293,7 +3303,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3335,21 +3345,30 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.29.1"
|
version = "1.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
|
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.5.4",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
@ -3385,69 +3404,60 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.6.0"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
|
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime 0.5.1",
|
"toml_datetime",
|
||||||
"toml_edit 0.18.1",
|
"toml_edit 0.19.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.3"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime 0.6.2",
|
"toml_datetime",
|
||||||
"toml_edit 0.19.8",
|
"toml_edit 0.20.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.5.1"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
|
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.18.1"
|
version = "0.19.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
|
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 1.9.3",
|
"indexmap 2.0.0",
|
||||||
"nom8",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime 0.5.1",
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.8"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 1.9.3",
|
"indexmap 2.0.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime 0.6.2",
|
"toml_datetime",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3477,7 +3487,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3702,7 +3712,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3736,7 +3746,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.33",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@ -3747,6 +3757,19 @@ version = "0.2.86"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
|
checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-streams"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.63"
|
version = "0.3.63"
|
||||||
@ -3806,9 +3829,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki"
|
name = "webpki"
|
||||||
version = "0.22.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
@ -4169,9 +4192,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.4.1"
|
version = "0.5.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -16,12 +16,12 @@ tauri-build = { version = "1.4.0", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
oauth2 = "4.4.1"
|
oauth2 = "4.4.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "1.3.0", features = [ "updater", "path-all", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
|
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater"] }
|
||||||
tokio = { version = "1.29.1", features = ["time"] }
|
tokio = { version = "1.32.0", features = ["time"] }
|
||||||
toml = "0.6.0"
|
toml = "0.8.0"
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "kittycad-modeling",
|
"productName": "kittycad-modeling",
|
||||||
"version": "0.5.0"
|
"version": "0.7.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
234
src/App.tsx
234
src/App.tsx
@ -1,13 +1,6 @@
|
|||||||
import {
|
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
||||||
useRef,
|
|
||||||
useEffect,
|
|
||||||
useLayoutEffect,
|
|
||||||
useCallback,
|
|
||||||
MouseEventHandler,
|
|
||||||
} from 'react'
|
|
||||||
import { DebugPanel } from './components/DebugPanel'
|
import { DebugPanel } from './components/DebugPanel'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { asyncParser } from './lang/abstractSyntaxTree'
|
|
||||||
import { _executor } from './lang/executor'
|
import { _executor } from './lang/executor'
|
||||||
import { PaneType, useStore } from './useStore'
|
import { PaneType, useStore } from './useStore'
|
||||||
import { Logs, KCLErrors } from './components/Logs'
|
import { Logs, KCLErrors } from './components/Logs'
|
||||||
@ -16,13 +9,9 @@ import { MemoryPanel } from './components/MemoryPanel'
|
|||||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||||
import { Stream } from './components/Stream'
|
import { Stream } from './components/Stream'
|
||||||
import ModalContainer from 'react-modal-promise'
|
import ModalContainer from 'react-modal-promise'
|
||||||
import {
|
import { EngineCommand } from './lang/std/engineConnection'
|
||||||
EngineCommand,
|
|
||||||
EngineCommandManager,
|
|
||||||
} from './lang/std/engineConnection'
|
|
||||||
import { throttle } from './lib/utils'
|
import { throttle } from './lib/utils'
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
import { KCLError } from './lang/errors'
|
|
||||||
import { Resizable } from 're-resizable'
|
import { Resizable } from 're-resizable'
|
||||||
import {
|
import {
|
||||||
faCode,
|
faCode,
|
||||||
@ -41,6 +30,8 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
|
|||||||
import { CodeMenu } from 'components/CodeMenu'
|
import { CodeMenu } from 'components/CodeMenu'
|
||||||
import { TextEditor } from 'components/TextEditor'
|
import { TextEditor } from 'components/TextEditor'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
|
import { useCodeEval } from 'hooks/useCodeEval'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||||
@ -48,57 +39,23 @@ export function App() {
|
|||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const {
|
const {
|
||||||
addLog,
|
|
||||||
addKCLError,
|
|
||||||
setCode,
|
setCode,
|
||||||
setAst,
|
|
||||||
setError,
|
|
||||||
setProgramMemory,
|
|
||||||
resetLogs,
|
|
||||||
resetKCLErrors,
|
|
||||||
setArtifactMap,
|
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
setEngineCommandManager,
|
|
||||||
highlightRange,
|
|
||||||
setHighlightRange,
|
|
||||||
setCursor2,
|
|
||||||
setMediaStream,
|
|
||||||
setIsStreamReady,
|
|
||||||
isStreamReady,
|
|
||||||
buttonDownInStream,
|
buttonDownInStream,
|
||||||
openPanes,
|
openPanes,
|
||||||
setOpenPanes,
|
setOpenPanes,
|
||||||
didDragInStream,
|
didDragInStream,
|
||||||
setStreamDimensions,
|
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
setIsExecuting,
|
guiMode,
|
||||||
defferedCode,
|
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
addLog: s.addLog,
|
guiMode: s.guiMode,
|
||||||
defferedCode: s.defferedCode,
|
|
||||||
setCode: s.setCode,
|
setCode: s.setCode,
|
||||||
setAst: s.setAst,
|
|
||||||
setError: s.setError,
|
|
||||||
setProgramMemory: s.setProgramMemory,
|
|
||||||
resetLogs: s.resetLogs,
|
|
||||||
resetKCLErrors: s.resetKCLErrors,
|
|
||||||
setArtifactMap: s.setArtifactNSourceRangeMaps,
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
setEngineCommandManager: s.setEngineCommandManager,
|
|
||||||
highlightRange: s.highlightRange,
|
|
||||||
setHighlightRange: s.setHighlightRange,
|
|
||||||
setCursor2: s.setCursor2,
|
|
||||||
setMediaStream: s.setMediaStream,
|
|
||||||
isStreamReady: s.isStreamReady,
|
|
||||||
setIsStreamReady: s.setIsStreamReady,
|
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
addKCLError: s.addKCLError,
|
|
||||||
openPanes: s.openPanes,
|
openPanes: s.openPanes,
|
||||||
setOpenPanes: s.setOpenPanes,
|
setOpenPanes: s.setOpenPanes,
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
setStreamDimensions: s.setStreamDimensions,
|
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
setIsExecuting: s.setIsExecuting,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -147,131 +104,8 @@ export function App() {
|
|||||||
}
|
}
|
||||||
}, [loadedCode, setCode])
|
}, [loadedCode, setCode])
|
||||||
|
|
||||||
const streamWidth = streamRef?.current?.offsetWidth
|
useSetupEngineManager(streamRef, token)
|
||||||
const streamHeight = streamRef?.current?.offsetHeight
|
useCodeEval()
|
||||||
|
|
||||||
const width = streamWidth ? streamWidth : 0
|
|
||||||
const quadWidth = Math.round(width / 4) * 4
|
|
||||||
const height = streamHeight ? streamHeight : 0
|
|
||||||
const quadHeight = Math.round(height / 4) * 4
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
setStreamDimensions({
|
|
||||||
streamWidth: quadWidth,
|
|
||||||
streamHeight: quadHeight,
|
|
||||||
})
|
|
||||||
if (!width || !height) return
|
|
||||||
const eng = new EngineCommandManager({
|
|
||||||
setMediaStream,
|
|
||||||
setIsStreamReady,
|
|
||||||
width: quadWidth,
|
|
||||||
height: quadHeight,
|
|
||||||
token,
|
|
||||||
})
|
|
||||||
setEngineCommandManager(eng)
|
|
||||||
return () => {
|
|
||||||
eng?.tearDown()
|
|
||||||
}
|
|
||||||
}, [quadWidth, quadHeight])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isStreamReady) return
|
|
||||||
if (!engineCommandManager) return
|
|
||||||
let unsubFn: any[] = []
|
|
||||||
const asyncWrap = async () => {
|
|
||||||
try {
|
|
||||||
if (!defferedCode) {
|
|
||||||
setAst(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const _ast = await asyncParser(defferedCode)
|
|
||||||
setAst(_ast)
|
|
||||||
resetLogs()
|
|
||||||
resetKCLErrors()
|
|
||||||
engineCommandManager.endSession()
|
|
||||||
engineCommandManager.startNewSession()
|
|
||||||
setIsExecuting(true)
|
|
||||||
const programMemory = await _executor(
|
|
||||||
_ast,
|
|
||||||
{
|
|
||||||
root: {
|
|
||||||
_0: {
|
|
||||||
type: 'userVal',
|
|
||||||
value: 0,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
_90: {
|
|
||||||
type: 'userVal',
|
|
||||||
value: 90,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
_180: {
|
|
||||||
type: 'userVal',
|
|
||||||
value: 180,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
_270: {
|
|
||||||
type: 'userVal',
|
|
||||||
value: 270,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
engineCommandManager
|
|
||||||
)
|
|
||||||
|
|
||||||
const { artifactMap, sourceRangeMap } =
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
|
||||||
setIsExecuting(false)
|
|
||||||
|
|
||||||
setArtifactMap({ artifactMap, sourceRangeMap })
|
|
||||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
|
||||||
event: 'highlight_set_entity',
|
|
||||||
callback: ({ data }) => {
|
|
||||||
if (data?.entity_id) {
|
|
||||||
const sourceRange = sourceRangeMap[data.entity_id]
|
|
||||||
setHighlightRange(sourceRange)
|
|
||||||
} else if (
|
|
||||||
!highlightRange ||
|
|
||||||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
|
||||||
) {
|
|
||||||
setHighlightRange([0, 0])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const unSubClick = engineCommandManager.subscribeTo({
|
|
||||||
event: 'select_with_point',
|
|
||||||
callback: ({ data }) => {
|
|
||||||
if (!data?.entity_id) {
|
|
||||||
setCursor2()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const sourceRange = sourceRangeMap[data.entity_id]
|
|
||||||
setCursor2({ range: sourceRange, type: 'default' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
unsubFn.push(unSubHover, unSubClick)
|
|
||||||
if (programMemory !== undefined) {
|
|
||||||
setProgramMemory(programMemory)
|
|
||||||
}
|
|
||||||
|
|
||||||
setError()
|
|
||||||
} catch (e: any) {
|
|
||||||
setIsExecuting(false)
|
|
||||||
if (e instanceof KCLError) {
|
|
||||||
addKCLError(e)
|
|
||||||
} else {
|
|
||||||
setError('problem')
|
|
||||||
console.log(e)
|
|
||||||
addLog(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
asyncWrap()
|
|
||||||
return () => {
|
|
||||||
unsubFn.forEach((fn) => fn())
|
|
||||||
}
|
|
||||||
}, [defferedCode, isStreamReady, engineCommandManager])
|
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
engineCommandManager?.sendSceneCommand(message)
|
engineCommandManager?.sendSceneCommand(message)
|
||||||
@ -287,8 +121,41 @@ export function App() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
|
if (buttonDownInStream === undefined) {
|
||||||
if (buttonDownInStream) {
|
if (
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === ('sketch_line' as any)
|
||||||
|
) {
|
||||||
|
debounceSocketSend({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: newCmdId,
|
||||||
|
cmd: {
|
||||||
|
type: 'mouse_move',
|
||||||
|
window: { x, y },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
debounceSocketSend({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'highlight_set_entity',
|
||||||
|
selected_at_window: { x, y },
|
||||||
|
},
|
||||||
|
cmd_id: newCmdId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
||||||
|
debounceSocketSend({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: newCmdId,
|
||||||
|
cmd: {
|
||||||
|
type: 'handle_mouse_drag_move',
|
||||||
|
window: { x, y },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
||||||
let interaction: CameraDragInteractionType_type
|
let interaction: CameraDragInteractionType_type
|
||||||
|
|
||||||
@ -301,8 +168,10 @@ export function App() {
|
|||||||
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
} else {
|
} else {
|
||||||
|
console.log('none')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
debounceSocketSend({
|
debounceSocketSend({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -312,15 +181,6 @@ export function App() {
|
|||||||
},
|
},
|
||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'highlight_set_entity',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,11 +209,11 @@ export function App() {
|
|||||||
paneOpacity
|
paneOpacity
|
||||||
}
|
}
|
||||||
defaultSize={{
|
defaultSize={{
|
||||||
width: '400px',
|
width: '550px',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
}}
|
}}
|
||||||
minWidth={200}
|
minWidth={200}
|
||||||
maxWidth={600}
|
maxWidth={800}
|
||||||
minHeight={'auto'}
|
minHeight={'auto'}
|
||||||
maxHeight={'auto'}
|
maxHeight={'auto'}
|
||||||
handleClasses={{
|
handleClasses={{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useStore, toolTips } from './useStore'
|
import { useStore, toolTips, Selections } from './useStore'
|
||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/queryAst'
|
import { getNodePathFromSourceRange } from './lang/queryAst'
|
||||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
import { HorzVert } from './components/Toolbar/HorzVert'
|
||||||
@ -15,6 +15,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|||||||
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import styles from './Toolbar.module.css'
|
import styles from './Toolbar.module.css'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { useAppMode } from 'hooks/useAppMode'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -24,6 +26,7 @@ export const Toolbar = () => {
|
|||||||
ast,
|
ast,
|
||||||
updateAst,
|
updateAst,
|
||||||
programMemory,
|
programMemory,
|
||||||
|
engineCommandManager,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
@ -31,7 +34,9 @@ export const Toolbar = () => {
|
|||||||
ast: s.ast,
|
ast: s.ast,
|
||||||
updateAst: s.updateAst,
|
updateAst: s.updateAst,
|
||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
|
engineCommandManager: s.engineCommandManager,
|
||||||
}))
|
}))
|
||||||
|
useAppMode()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('guiMode', guiMode)
|
console.log('guiMode', guiMode)
|
||||||
@ -39,7 +44,7 @@ export const Toolbar = () => {
|
|||||||
|
|
||||||
function ToolbarButtons() {
|
function ToolbarButtons() {
|
||||||
return (
|
return (
|
||||||
<>
|
<span className="overflow-x-auto">
|
||||||
{guiMode.mode === 'default' && (
|
{guiMode.mode === 'default' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -71,9 +76,18 @@ export const Toolbar = () => {
|
|||||||
SketchOnFace
|
SketchOnFace
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{(guiMode.mode === 'canEditSketch' || false) && (
|
{guiMode.mode === 'canEditSketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
console.log('guiMode.pathId', guiMode.pathId)
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: guiMode.pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
@ -125,14 +139,23 @@ export const Toolbar = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{guiMode.mode === 'sketch' && (
|
{guiMode.mode === 'sketch' && (
|
||||||
<button onClick={() => setGuiMode({ mode: 'default' })}>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
})
|
||||||
|
setGuiMode({ mode: 'default' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
Exit sketch
|
Exit sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{toolTips
|
{toolTips
|
||||||
.filter(
|
.filter(
|
||||||
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
||||||
(sketchFnName) => ['line'].includes(sketchFnName)
|
(sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
|
||||||
)
|
)
|
||||||
.map((sketchFnName) => {
|
.map((sketchFnName) => {
|
||||||
if (
|
if (
|
||||||
@ -143,7 +166,18 @@ export const Toolbar = () => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={sketchFnName}
|
key={sketchFnName}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'set_tool',
|
||||||
|
tool:
|
||||||
|
guiMode.sketchMode === sketchFnName
|
||||||
|
? 'select'
|
||||||
|
: (sketchFnName as any),
|
||||||
|
},
|
||||||
|
})
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
...guiMode,
|
...guiMode,
|
||||||
...(guiMode.sketchMode === sketchFnName
|
...(guiMode.sketchMode === sketchFnName
|
||||||
@ -153,10 +187,11 @@ export const Toolbar = () => {
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
sketchMode: sketchFnName,
|
sketchMode: sketchFnName,
|
||||||
|
waitingFirstClick: true,
|
||||||
isTooltip: true,
|
isTooltip: true,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
{sketchFnName}
|
{sketchFnName}
|
||||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
{guiMode.sketchMode === sketchFnName && '✅'}
|
||||||
@ -180,7 +215,7 @@ export const Toolbar = () => {
|
|||||||
<Intersect />
|
<Intersect />
|
||||||
<RemoveConstrainingValues />
|
<RemoveConstrainingValues />
|
||||||
<SetAngleBetween />
|
<SetAngleBetween />
|
||||||
</>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { ProjectWithEntryPointMetadata } from '../Router'
|
|||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
|
import { NetworkHealthIndicator } from './NetworkHealthIndicator'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -43,7 +44,8 @@ export const AppHeader = ({
|
|||||||
)}
|
)}
|
||||||
{/* If there are children, show them, otherwise show User menu */}
|
{/* If there are children, show them, otherwise show User menu */}
|
||||||
{children || (
|
{children || (
|
||||||
<div className="ml-auto">
|
<div className="ml-auto flex items-center gap-1">
|
||||||
|
<NetworkHealthIndicator />
|
||||||
<UserSidebarMenu user={user} />
|
<UserSidebarMenu user={user} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
182
src/components/AstExplorer.tsx
Normal file
182
src/components/AstExplorer.tsx
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
|
||||||
|
export function AstExplorer() {
|
||||||
|
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
|
||||||
|
ast: s.ast,
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
selectionRanges: s.selectionRanges,
|
||||||
|
}))
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
selectionRanges.codeBasedSelections?.[0]?.range
|
||||||
|
)
|
||||||
|
const node = getNodeFromPath(ast, pathToNode).node
|
||||||
|
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" style={{ width: '300px' }}>
|
||||||
|
<div className="">
|
||||||
|
filter out keys:<div className="w-2 inline-block"></div>
|
||||||
|
{['start', 'end', 'type'].map((key) => {
|
||||||
|
return (
|
||||||
|
<label key={key} className="inline-flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox"
|
||||||
|
checked={filterKeys.includes(key)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (filterKeys.includes(key)) {
|
||||||
|
setFilterKeys(filterKeys.filter((k) => k !== key))
|
||||||
|
} else {
|
||||||
|
setFilterKeys([...filterKeys, key])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="mr-2">{key}</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="h-full relative"
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
setHighlightRange([0, 0])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
|
||||||
|
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayBody({
|
||||||
|
body,
|
||||||
|
filterKeys,
|
||||||
|
node,
|
||||||
|
}: {
|
||||||
|
body: { start: number; end: number; [key: string]: any }[]
|
||||||
|
filterKeys: string[]
|
||||||
|
node: any
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{body.map((b, index) => {
|
||||||
|
return (
|
||||||
|
<div className="my-2" key={index}>
|
||||||
|
<DisplayObj obj={b} filterKeys={filterKeys} node={node} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayObj({
|
||||||
|
obj,
|
||||||
|
filterKeys,
|
||||||
|
node,
|
||||||
|
}: {
|
||||||
|
obj: { start: number; end: number; [key: string]: any }
|
||||||
|
filterKeys: string[]
|
||||||
|
node: any
|
||||||
|
}) {
|
||||||
|
const { setHighlightRange, setCursor2 } = useStore((s) => ({
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
setCursor2: s.setCursor2,
|
||||||
|
}))
|
||||||
|
const ref = useRef<HTMLPreElement>(null)
|
||||||
|
const [hasCursor, setHasCursor] = useState(false)
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
node?.start === obj?.start &&
|
||||||
|
node?.end === obj?.end &&
|
||||||
|
node.type === obj?.type
|
||||||
|
) {
|
||||||
|
ref?.current?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
|
||||||
|
setHasCursor(true)
|
||||||
|
} else {
|
||||||
|
setHasCursor(false)
|
||||||
|
}
|
||||||
|
}, [node.start, node.end, node.type])
|
||||||
|
return (
|
||||||
|
<pre
|
||||||
|
ref={ref}
|
||||||
|
className={`ml-2 border-l border-violet-600 pl-1 ${
|
||||||
|
hasCursor ? 'bg-violet-100/25' : ''
|
||||||
|
}`}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
onMouseMove={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<button
|
||||||
|
className="m-0 p-0 border-0"
|
||||||
|
onClick={() => setIsCollapsed(false)}
|
||||||
|
>
|
||||||
|
{'>'}type: {obj.type}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="flex">
|
||||||
|
{/* <button className="m-0 p-0 border-0 mb-auto" onClick={() => setIsCollapsed(true)}>{'⬇️'}</button> */}
|
||||||
|
<ul className="inline-block">
|
||||||
|
{Object.entries(obj).map(([key, value]) => {
|
||||||
|
if (filterKeys.includes(key)) {
|
||||||
|
return null
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{`${key}: [`}
|
||||||
|
<DisplayBody
|
||||||
|
body={value}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
{']'}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
value?.end
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{key}:
|
||||||
|
<DisplayObj
|
||||||
|
obj={value}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
typeof value === 'string' ||
|
||||||
|
typeof value === 'number'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{key}: {value}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
}
|
@ -144,7 +144,7 @@ export function useCalc({
|
|||||||
try {
|
try {
|
||||||
const code = `const __result__ = ${value}\nshow(__result__)`
|
const code = `const __result__ = ${value}\nshow(__result__)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
const _programMem: any = { root: {} }
|
const _programMem: any = { root: {}, return: null }
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,7 @@ import { useState } from 'react'
|
|||||||
import { ActionButton } from '../components/ActionButton'
|
import { ActionButton } from '../components/ActionButton'
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { isReducedMotion } from 'lang/util'
|
import { isReducedMotion } from 'lang/util'
|
||||||
|
import { AstExplorer } from './AstExplorer'
|
||||||
|
|
||||||
type SketchModeCmd = Extract<
|
type SketchModeCmd = Extract<
|
||||||
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
||||||
@ -94,6 +95,9 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
>
|
>
|
||||||
Send sketch mode command
|
Send sketch mode command
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
<div style={{ height: '400px' }} className="overflow-y-auto">
|
||||||
|
<AstExplorer />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</CollapsiblePanel>
|
</CollapsiblePanel>
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ describe('processMemory', () => {
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const myVar = 5
|
const myVar = 5
|
||||||
const myFn = (a) => {
|
fn myFn = (a) => {
|
||||||
return a - 2
|
return a - 2
|
||||||
}
|
}
|
||||||
const otherVar = myFn(5)
|
const otherVar = myFn(5)
|
||||||
@ -29,6 +29,7 @@ describe('processMemory', () => {
|
|||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
const programMemory = await enginelessExecutor(ast, {
|
const programMemory = await enginelessExecutor(ast, {
|
||||||
root: {},
|
root: {},
|
||||||
|
return: null,
|
||||||
})
|
})
|
||||||
const output = processMemory(programMemory)
|
const output = processMemory(programMemory)
|
||||||
expect(output.myVar).toEqual(5)
|
expect(output.myVar).toEqual(5)
|
||||||
|
@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
|
|||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ProgramMemory } from '../lang/executor'
|
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
|
|
||||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||||
@ -49,8 +49,12 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
|||||||
Object.keys(programMemory.root).forEach((key) => {
|
Object.keys(programMemory.root).forEach((key) => {
|
||||||
const val = programMemory.root[key]
|
const val = programMemory.root[key]
|
||||||
if (typeof val.value !== 'function') {
|
if (typeof val.value !== 'function') {
|
||||||
if (val.type === 'sketchGroup' || val.type === 'extrudeGroup') {
|
if (val.type === 'SketchGroup') {
|
||||||
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }) => {
|
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||||
|
return rest
|
||||||
|
})
|
||||||
|
} else if (val.type === 'ExtrudeGroup') {
|
||||||
|
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||||
return rest
|
return rest
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
51
src/components/NetworkHealthIndicator.test.tsx
Normal file
51
src/components/NetworkHealthIndicator.test.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
|
import CommandBarProvider from './CommandBar'
|
||||||
|
import {
|
||||||
|
NETWORK_CONTENT,
|
||||||
|
NetworkHealthIndicator,
|
||||||
|
} from './NetworkHealthIndicator'
|
||||||
|
|
||||||
|
function TestWrap({ children }: { children: React.ReactNode }) {
|
||||||
|
// wrap in router and xState context
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('NetworkHealthIndicator tests', () => {
|
||||||
|
test('Renders the network indicator', () => {
|
||||||
|
render(
|
||||||
|
<TestWrap>
|
||||||
|
<NetworkHealthIndicator />
|
||||||
|
</TestWrap>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('network-toggle'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('network-good')).toHaveTextContent(
|
||||||
|
NETWORK_CONTENT.good
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Responds to network changes', () => {
|
||||||
|
render(
|
||||||
|
<TestWrap>
|
||||||
|
<NetworkHealthIndicator />
|
||||||
|
</TestWrap>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.offline(window)
|
||||||
|
fireEvent.click(screen.getByTestId('network-toggle'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('network-bad')).toHaveTextContent(
|
||||||
|
NETWORK_CONTENT.bad
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
112
src/components/NetworkHealthIndicator.tsx
Normal file
112
src/components/NetworkHealthIndicator.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
faCheck,
|
||||||
|
faExclamation,
|
||||||
|
faWifi,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { Popover } from '@headlessui/react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { ActionIcon } from './ActionIcon'
|
||||||
|
|
||||||
|
export const NETWORK_CONTENT = {
|
||||||
|
good: 'Network health is good',
|
||||||
|
bad: 'Network issue',
|
||||||
|
}
|
||||||
|
|
||||||
|
const NETWORK_MESSAGES = {
|
||||||
|
offline: 'You are offline',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NetworkHealthIndicator = () => {
|
||||||
|
const [networkIssues, setNetworkIssues] = useState<string[]>([])
|
||||||
|
const hasIssues = [...networkIssues.values()].length > 0
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const offlineListener = () =>
|
||||||
|
setNetworkIssues((issues) => {
|
||||||
|
return [
|
||||||
|
...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline),
|
||||||
|
NETWORK_MESSAGES.offline,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
window.addEventListener('offline', offlineListener)
|
||||||
|
|
||||||
|
const onlineListener = () =>
|
||||||
|
setNetworkIssues((issues) => {
|
||||||
|
return [...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline)]
|
||||||
|
})
|
||||||
|
window.addEventListener('online', onlineListener)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('offline', offlineListener)
|
||||||
|
window.removeEventListener('online', onlineListener)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover className="relative">
|
||||||
|
<Popover.Button
|
||||||
|
className={
|
||||||
|
'p-0 border-none relative ' +
|
||||||
|
(hasIssues
|
||||||
|
? 'focus-visible:outline-destroy-80'
|
||||||
|
: 'focus-visible:outline-succeed-80')
|
||||||
|
}
|
||||||
|
data-testid="network-toggle"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Network Health</span>
|
||||||
|
<ActionIcon
|
||||||
|
icon={faWifi}
|
||||||
|
iconClassName={
|
||||||
|
hasIssues
|
||||||
|
? 'text-destroy-80 dark:text-destroy-30'
|
||||||
|
: 'text-succeed-80 dark:text-succeed-30'
|
||||||
|
}
|
||||||
|
bgClassName={
|
||||||
|
hasIssues
|
||||||
|
? 'hover:bg-destroy-10/50 hover:dark:bg-destroy-80/50 rounded'
|
||||||
|
: 'hover:bg-succeed-10/50 hover:dark:bg-succeed-80/50 rounded'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Popover.Button>
|
||||||
|
<Popover.Panel className="absolute right-0 left-auto top-full mt-1 w-56 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch py-2 bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm">
|
||||||
|
{!hasIssues ? (
|
||||||
|
<span
|
||||||
|
className="flex items-center justify-center gap-1 px-4"
|
||||||
|
data-testid="network-good"
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon={faCheck}
|
||||||
|
bgClassName={'bg-succeed-10/50 dark:bg-succeed-80/50 rounded'}
|
||||||
|
iconClassName={'text-succeed-80 dark:text-succeed-30'}
|
||||||
|
/>
|
||||||
|
{NETWORK_CONTENT.good}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||||
|
<span
|
||||||
|
className="font-bold text-xs uppercase text-destroy-60 dark:text-destroy-50 px-4"
|
||||||
|
data-testid="network-bad"
|
||||||
|
>
|
||||||
|
{NETWORK_CONTENT.bad}
|
||||||
|
{networkIssues.length > 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
{networkIssues.map((issue) => (
|
||||||
|
<li
|
||||||
|
key={issue}
|
||||||
|
className="flex items-center gap-1 py-2 my-2 last:mb-0"
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon={faExclamation}
|
||||||
|
bgClassName={'bg-destroy-10/50 dark:bg-destroy-80/50 rounded'}
|
||||||
|
iconClassName={'text-destroy-80 dark:text-destroy-30'}
|
||||||
|
className="ml-4"
|
||||||
|
/>
|
||||||
|
<p className="flex-1 mr-4">{issue}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
@ -7,11 +7,14 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { getNormalisedCoordinates } from '../lib/utils'
|
import { getNormalisedCoordinates, roundOff } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { addStartSketch } from 'lang/modifyAst'
|
||||||
|
import { addNewSketchLn } from 'lang/std/sketch'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }) => {
|
export const Stream = ({ className = '' }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -25,6 +28,11 @@ export const Stream = ({ className = '' }) => {
|
|||||||
setDidDragInStream,
|
setDidDragInStream,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
|
guiMode,
|
||||||
|
ast,
|
||||||
|
updateAst,
|
||||||
|
setGuiMode,
|
||||||
|
programMemory,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
mediaStream: s.mediaStream,
|
mediaStream: s.mediaStream,
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
@ -34,6 +42,11 @@ export const Stream = ({ className = '' }) => {
|
|||||||
setDidDragInStream: s.setDidDragInStream,
|
setDidDragInStream: s.setDidDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
isExecuting: s.isExecuting,
|
isExecuting: s.isExecuting,
|
||||||
|
guiMode: s.guiMode,
|
||||||
|
ast: s.ast,
|
||||||
|
updateAst: s.updateAst,
|
||||||
|
setGuiMode: s.setGuiMode,
|
||||||
|
programMemory: s.programMemory,
|
||||||
}))
|
}))
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
@ -64,27 +77,50 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const newId = uuidv4()
|
const newId = uuidv4()
|
||||||
|
|
||||||
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
||||||
let interaction: CameraDragInteractionType_type
|
let interaction: CameraDragInteractionType_type = 'rotate'
|
||||||
|
|
||||||
if (interactionGuards.pan.callback(e)) {
|
if (
|
||||||
|
interactionGuards.pan.callback(e) ||
|
||||||
|
interactionGuards.pan.lenientDragStartButton === e.button
|
||||||
|
) {
|
||||||
interaction = 'pan'
|
interaction = 'pan'
|
||||||
} else if (interactionGuards.rotate.callback(e)) {
|
} else if (
|
||||||
|
interactionGuards.rotate.callback(e) ||
|
||||||
|
interactionGuards.rotate.lenientDragStartButton === e.button
|
||||||
|
) {
|
||||||
interaction = 'rotate'
|
interaction = 'rotate'
|
||||||
} else if (interactionGuards.zoom.dragCallback(e)) {
|
} else if (
|
||||||
|
interactionGuards.zoom.dragCallback(e) ||
|
||||||
|
interactionGuards.zoom.lenientDragStartButton === e.button
|
||||||
|
) {
|
||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
||||||
type: 'modeling_cmd_req',
|
engineCommandManager?.sendSceneCommand({
|
||||||
cmd: {
|
type: 'modeling_cmd_req',
|
||||||
type: 'camera_drag_start',
|
cmd: {
|
||||||
interaction,
|
type: 'handle_mouse_drag_start',
|
||||||
window: { x, y },
|
window: { x, y },
|
||||||
},
|
},
|
||||||
cmd_id: newId,
|
cmd_id: newId,
|
||||||
})
|
})
|
||||||
|
} else if (
|
||||||
|
!(
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === ('sketch_line' as any)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'camera_drag_start',
|
||||||
|
interaction,
|
||||||
|
window: { x, y },
|
||||||
|
},
|
||||||
|
cmd_id: newId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setButtonDownInStream(e.button)
|
setButtonDownInStream(e.button)
|
||||||
setClickCoords({ x, y })
|
setClickCoords({ x, y })
|
||||||
@ -93,7 +129,6 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
||||||
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -110,6 +145,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
ctrlKey,
|
ctrlKey,
|
||||||
}) => {
|
}) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
|
setButtonDownInStream(undefined)
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
@ -120,7 +156,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
const interaction = ctrlKey ? 'pan' : 'rotate'
|
const interaction = ctrlKey ? 'pan' : 'rotate'
|
||||||
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
const command: Models['WebSocketRequest_type'] = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'camera_drag_end',
|
type: 'camera_drag_end',
|
||||||
@ -128,9 +164,8 @@ export const Stream = ({ className = '' }) => {
|
|||||||
window: { x, y },
|
window: { x, y },
|
||||||
},
|
},
|
||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
})
|
}
|
||||||
|
|
||||||
setButtonDownInStream(0)
|
|
||||||
if (!didDragInStream) {
|
if (!didDragInStream) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
@ -142,6 +177,95 @@ export const Stream = ({ className = '' }) => {
|
|||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!didDragInStream && guiMode.mode === 'default') {
|
||||||
|
command.cmd = {
|
||||||
|
type: 'select_with_point',
|
||||||
|
selection_type: 'add',
|
||||||
|
selected_at_window: { x, y },
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(!didDragInStream &&
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
['move', 'select'].includes(guiMode.sketchMode)) ||
|
||||||
|
(guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === ('sketch_line' as any))
|
||||||
|
) {
|
||||||
|
command.cmd = {
|
||||||
|
type: 'mouse_click',
|
||||||
|
window: { x, y },
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === ('move' as any)
|
||||||
|
) {
|
||||||
|
command.cmd = {
|
||||||
|
type: 'handle_mouse_drag_end',
|
||||||
|
window: { x, y },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
|
||||||
|
if (command.cmd.type !== 'mouse_click' || !ast) return
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === ('sketch_line' as any as 'line')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
|
||||||
|
const curve = await engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'curve_get_control_points',
|
||||||
|
curve_id: data?.data?.entities_modified[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const coords: { x: number; y: number }[] =
|
||||||
|
curve.data.data.control_points
|
||||||
|
const _addStartSketch = addStartSketch(
|
||||||
|
ast,
|
||||||
|
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
||||||
|
[
|
||||||
|
roundOff(coords[1].x - coords[0].x),
|
||||||
|
roundOff(coords[1].y - coords[0].y),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
const _modifiedAst = _addStartSketch.modifiedAst
|
||||||
|
const _pathToNode = _addStartSketch.pathToNode
|
||||||
|
|
||||||
|
setGuiMode({
|
||||||
|
...guiMode,
|
||||||
|
pathToNode: _pathToNode,
|
||||||
|
waitingFirstClick: false,
|
||||||
|
})
|
||||||
|
updateAst(_modifiedAst)
|
||||||
|
} else if (
|
||||||
|
data?.data?.entities_modified?.length &&
|
||||||
|
!guiMode.waitingFirstClick
|
||||||
|
) {
|
||||||
|
const curve = await engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'curve_get_control_points',
|
||||||
|
curve_id: data?.data?.entities_modified[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const coords: { x: number; y: number }[] =
|
||||||
|
curve.data.data.control_points
|
||||||
|
const _modifiedAst = addNewSketchLn({
|
||||||
|
node: ast,
|
||||||
|
programMemory,
|
||||||
|
to: [coords[1].x, coords[1].y],
|
||||||
|
fnName: 'line',
|
||||||
|
pathToNode: guiMode.pathToNode,
|
||||||
|
}).modifiedAst
|
||||||
|
updateAst(_modifiedAst)
|
||||||
|
}
|
||||||
|
})
|
||||||
setDidDragInStream(false)
|
setDidDragInStream(false)
|
||||||
setClickCoords(undefined)
|
setClickCoords(undefined)
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,10 @@ import {
|
|||||||
addLineHighlight,
|
addLineHighlight,
|
||||||
lineHighlightField,
|
lineHighlightField,
|
||||||
} from 'editor/highlightextension'
|
} from 'editor/highlightextension'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap, roundOff } from 'lib/utils'
|
||||||
import { kclErrToDiagnostic } from 'lang/errors'
|
import { kclErrToDiagnostic } from 'lang/errors'
|
||||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||||
|
import interact from '@replit/codemirror-interact'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -62,7 +63,6 @@ export const TextEditor = ({
|
|||||||
sourceRangeMap,
|
sourceRangeMap,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
code: s.code,
|
code: s.code,
|
||||||
defferedCode: s.defferedCode,
|
|
||||||
defferedSetCode: s.defferedSetCode,
|
defferedSetCode: s.defferedSetCode,
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
@ -70,7 +70,6 @@ export const TextEditor = ({
|
|||||||
isLSPServerReady: s.isLSPServerReady,
|
isLSPServerReady: s.isLSPServerReady,
|
||||||
selectionRanges: s.selectionRanges,
|
selectionRanges: s.selectionRanges,
|
||||||
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
||||||
setCode: s.setCode,
|
|
||||||
setEditorView: s.setEditorView,
|
setEditorView: s.setEditorView,
|
||||||
setIsLSPServerReady: s.setIsLSPServerReady,
|
setIsLSPServerReady: s.setIsLSPServerReady,
|
||||||
setSelectionRanges: s.setSelectionRanges,
|
setSelectionRanges: s.setSelectionRanges,
|
||||||
@ -239,6 +238,38 @@ export const TextEditor = ({
|
|||||||
lintGutter(),
|
lintGutter(),
|
||||||
linter((_view) => {
|
linter((_view) => {
|
||||||
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
||||||
|
}),
|
||||||
|
interact({
|
||||||
|
rules: [
|
||||||
|
// a rule for a number dragger
|
||||||
|
{
|
||||||
|
// the regexp matching the value
|
||||||
|
regexp: /-?\b\d+\.?\d*\b/g,
|
||||||
|
// set cursor to "ew-resize" on hover
|
||||||
|
cursor: 'ew-resize',
|
||||||
|
// change number value based on mouse X movement on drag
|
||||||
|
onDrag: (text, setText, e) => {
|
||||||
|
const multiplier =
|
||||||
|
e.shiftKey && e.metaKey
|
||||||
|
? 0.01
|
||||||
|
: e.metaKey
|
||||||
|
? 0.1
|
||||||
|
: e.shiftKey
|
||||||
|
? 10
|
||||||
|
: 1
|
||||||
|
|
||||||
|
const delta = e.movementX * multiplier
|
||||||
|
|
||||||
|
const newVal = roundOff(
|
||||||
|
Number(text) + delta,
|
||||||
|
multiplier === 0.01 ? 2 : multiplier === 0.1 ? 1 : 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isNaN(newVal)) return
|
||||||
|
setText(newVal.toString())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
if (textWrapping === 'On') extensions.push(EditorView.lineWrapping)
|
if (textWrapping === 'On') extensions.push(EditorView.lineWrapping)
|
||||||
|
243
src/hooks/useAppMode.ts
Normal file
243
src/hooks/useAppMode.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
// needed somewhere to dump this logic,
|
||||||
|
// Once we have xState this should be removed
|
||||||
|
|
||||||
|
import { useStore, Selections } from 'useStore'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
|
import { isReducedMotion } from 'lang/util'
|
||||||
|
import { isOverlap } from 'lib/utils'
|
||||||
|
|
||||||
|
interface DefaultPlanes {
|
||||||
|
xy: string
|
||||||
|
yz: string
|
||||||
|
xz: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAppMode() {
|
||||||
|
const {
|
||||||
|
guiMode,
|
||||||
|
setGuiMode,
|
||||||
|
selectionRanges,
|
||||||
|
engineCommandManager,
|
||||||
|
selectionRangeTypeMap,
|
||||||
|
} = useStore((s) => ({
|
||||||
|
guiMode: s.guiMode,
|
||||||
|
setGuiMode: s.setGuiMode,
|
||||||
|
selectionRanges: s.selectionRanges,
|
||||||
|
engineCommandManager: s.engineCommandManager,
|
||||||
|
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
||||||
|
}))
|
||||||
|
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === 'selectFace' &&
|
||||||
|
engineCommandManager
|
||||||
|
) {
|
||||||
|
if (!defaultPlanes) {
|
||||||
|
const xy = createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||||
|
})
|
||||||
|
const yz = createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||||
|
})
|
||||||
|
const xz = createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||||
|
})
|
||||||
|
setDefaultPlanes({ xy, yz, xz })
|
||||||
|
} else {
|
||||||
|
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
||||||
|
Object.values(defaultPlanes).forEach((planeId) => {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'object_visible',
|
||||||
|
object_id: planeId,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (guiMode.mode === 'default') {
|
||||||
|
const pathId =
|
||||||
|
engineCommandManager &&
|
||||||
|
isCursorInSketchCommandRange(
|
||||||
|
engineCommandManager.artifactMap,
|
||||||
|
selectionRanges
|
||||||
|
)
|
||||||
|
if (pathId) {
|
||||||
|
setGuiMode({
|
||||||
|
mode: 'canEditSketch',
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
position: [0, 0, 0],
|
||||||
|
pathToNode: [],
|
||||||
|
pathId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (guiMode.mode === 'canEditSketch') {
|
||||||
|
if (
|
||||||
|
!engineCommandManager ||
|
||||||
|
!isCursorInSketchCommandRange(
|
||||||
|
engineCommandManager.artifactMap,
|
||||||
|
selectionRanges
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setGuiMode({
|
||||||
|
mode: 'default',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
guiMode,
|
||||||
|
guiMode.mode,
|
||||||
|
engineCommandManager,
|
||||||
|
selectionRanges,
|
||||||
|
selectionRangeTypeMap,
|
||||||
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unSub = engineCommandManager?.subscribeTo({
|
||||||
|
event: 'select_with_point',
|
||||||
|
callback: async ({ data }) => {
|
||||||
|
if (!data.entity_id) return
|
||||||
|
if (!defaultPlanes) return
|
||||||
|
if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
|
||||||
|
// user clicked something else in the scene
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
|
||||||
|
{
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'sketch_mode_enable',
|
||||||
|
plane_id: data.entity_id,
|
||||||
|
ortho: true,
|
||||||
|
animated: !isReducedMotion(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
||||||
|
const sketchUuid = uuidv4()
|
||||||
|
const proms: any[] = []
|
||||||
|
proms.push(
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: sketchUuid,
|
||||||
|
cmd: {
|
||||||
|
type: 'start_path',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
proms.push(
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: sketchUuid,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const res = await Promise.all(proms)
|
||||||
|
console.log('res', res)
|
||||||
|
setGuiMode({
|
||||||
|
mode: 'sketch',
|
||||||
|
sketchMode: 'sketchEdit',
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
position: [0, 0, 0],
|
||||||
|
pathToNode: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('sketchModeResponse', sketchModeResponse)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return unSub
|
||||||
|
}, [engineCommandManager, defaultPlanes])
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPlane(
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
{
|
||||||
|
x_axis,
|
||||||
|
y_axis,
|
||||||
|
color,
|
||||||
|
}: {
|
||||||
|
x_axis: Models['Point3d_type']
|
||||||
|
y_axis: Models['Point3d_type']
|
||||||
|
color: Models['Color_type']
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const planeId = uuidv4()
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'make_plane',
|
||||||
|
size: 60,
|
||||||
|
origin: { x: 0, y: 0, z: 0 },
|
||||||
|
x_axis,
|
||||||
|
y_axis,
|
||||||
|
clobber: false,
|
||||||
|
},
|
||||||
|
cmd_id: planeId,
|
||||||
|
})
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'plane_set_color',
|
||||||
|
plane_id: planeId,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
return planeId
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDefaultPlanes(
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
defaultPlanes: DefaultPlanes
|
||||||
|
) {
|
||||||
|
Object.values(defaultPlanes).forEach((planeId) => {
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'object_visible',
|
||||||
|
object_id: planeId,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCursorInSketchCommandRange(
|
||||||
|
artifactMap: ArtifactMap,
|
||||||
|
selectionRanges: Selections
|
||||||
|
): string | false {
|
||||||
|
const overlapingEntries = Object.entries(artifactMap || {}).filter(
|
||||||
|
([id, artifact]) =>
|
||||||
|
selectionRanges.codeBasedSelections.some(
|
||||||
|
(selection) =>
|
||||||
|
Array.isArray(selection.range) &&
|
||||||
|
Array.isArray(artifact.range) &&
|
||||||
|
isOverlap(selection.range, artifact.range) &&
|
||||||
|
(artifact.commandType === 'start_path' ||
|
||||||
|
artifact.commandType === 'extend_path' ||
|
||||||
|
'close_path')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
|
||||||
|
? overlapingEntries[0][1].parentId
|
||||||
|
: false
|
||||||
|
}
|
156
src/hooks/useCodeEval.ts
Normal file
156
src/hooks/useCodeEval.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { asyncParser } from '../lang/abstractSyntaxTree'
|
||||||
|
import { _executor } from '../lang/executor'
|
||||||
|
import { useStore } from '../useStore'
|
||||||
|
import { KCLError } from '../lang/errors'
|
||||||
|
|
||||||
|
// This recently moved out of app.tsx
|
||||||
|
// and is our old way of thinking that whenever the code changes we need to re-execute, instead of
|
||||||
|
// being more decisive about when and where we execute, its likey this custom hook will be
|
||||||
|
// refactored away entirely at some point
|
||||||
|
|
||||||
|
export function useCodeEval() {
|
||||||
|
const {
|
||||||
|
addLog,
|
||||||
|
addKCLError,
|
||||||
|
setAst,
|
||||||
|
setError,
|
||||||
|
setProgramMemory,
|
||||||
|
resetLogs,
|
||||||
|
resetKCLErrors,
|
||||||
|
setArtifactMap,
|
||||||
|
engineCommandManager,
|
||||||
|
highlightRange,
|
||||||
|
setHighlightRange,
|
||||||
|
setCursor2,
|
||||||
|
isStreamReady,
|
||||||
|
setIsExecuting,
|
||||||
|
defferedCode,
|
||||||
|
} = useStore((s) => ({
|
||||||
|
addLog: s.addLog,
|
||||||
|
defferedCode: s.defferedCode,
|
||||||
|
setAst: s.setAst,
|
||||||
|
setError: s.setError,
|
||||||
|
setProgramMemory: s.setProgramMemory,
|
||||||
|
resetLogs: s.resetLogs,
|
||||||
|
resetKCLErrors: s.resetKCLErrors,
|
||||||
|
setArtifactMap: s.setArtifactNSourceRangeMaps,
|
||||||
|
engineCommandManager: s.engineCommandManager,
|
||||||
|
highlightRange: s.highlightRange,
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
setCursor2: s.setCursor2,
|
||||||
|
isStreamReady: s.isStreamReady,
|
||||||
|
addKCLError: s.addKCLError,
|
||||||
|
setIsExecuting: s.setIsExecuting,
|
||||||
|
}))
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isStreamReady) return
|
||||||
|
if (!engineCommandManager) return
|
||||||
|
let unsubFn: any[] = []
|
||||||
|
const asyncWrap = async () => {
|
||||||
|
try {
|
||||||
|
if (!defferedCode) {
|
||||||
|
setAst({
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: [],
|
||||||
|
nonCodeMeta: {
|
||||||
|
noneCodeNodes: {},
|
||||||
|
start: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setProgramMemory({ root: {}, return: null })
|
||||||
|
engineCommandManager.endSession()
|
||||||
|
engineCommandManager.startNewSession()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const _ast = await asyncParser(defferedCode)
|
||||||
|
setAst(_ast)
|
||||||
|
resetLogs()
|
||||||
|
resetKCLErrors()
|
||||||
|
engineCommandManager.endSession()
|
||||||
|
engineCommandManager.startNewSession()
|
||||||
|
setIsExecuting(true)
|
||||||
|
const programMemory = await _executor(
|
||||||
|
_ast,
|
||||||
|
{
|
||||||
|
root: {
|
||||||
|
_0: {
|
||||||
|
type: 'UserVal',
|
||||||
|
value: 0,
|
||||||
|
__meta: [],
|
||||||
|
},
|
||||||
|
_90: {
|
||||||
|
type: 'UserVal',
|
||||||
|
value: 90,
|
||||||
|
__meta: [],
|
||||||
|
},
|
||||||
|
_180: {
|
||||||
|
type: 'UserVal',
|
||||||
|
value: 180,
|
||||||
|
__meta: [],
|
||||||
|
},
|
||||||
|
_270: {
|
||||||
|
type: 'UserVal',
|
||||||
|
value: 270,
|
||||||
|
__meta: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
return: null,
|
||||||
|
},
|
||||||
|
engineCommandManager
|
||||||
|
)
|
||||||
|
|
||||||
|
const { artifactMap, sourceRangeMap } =
|
||||||
|
await engineCommandManager.waitForAllCommands(_ast, programMemory)
|
||||||
|
setIsExecuting(false)
|
||||||
|
if (programMemory !== undefined) {
|
||||||
|
setProgramMemory(programMemory)
|
||||||
|
}
|
||||||
|
|
||||||
|
setArtifactMap({ artifactMap, sourceRangeMap })
|
||||||
|
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||||
|
event: 'highlight_set_entity',
|
||||||
|
callback: ({ data }) => {
|
||||||
|
if (data?.entity_id) {
|
||||||
|
const sourceRange = sourceRangeMap[data.entity_id]
|
||||||
|
setHighlightRange(sourceRange)
|
||||||
|
} else if (
|
||||||
|
!highlightRange ||
|
||||||
|
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
||||||
|
) {
|
||||||
|
setHighlightRange([0, 0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const unSubClick = engineCommandManager.subscribeTo({
|
||||||
|
event: 'select_with_point',
|
||||||
|
callback: ({ data }) => {
|
||||||
|
if (!data?.entity_id) {
|
||||||
|
setCursor2()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sourceRange = sourceRangeMap[data.entity_id]
|
||||||
|
setCursor2({ range: sourceRange, type: 'default' })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
unsubFn.push(unSubHover, unSubClick)
|
||||||
|
|
||||||
|
setError()
|
||||||
|
} catch (e: any) {
|
||||||
|
setIsExecuting(false)
|
||||||
|
if (e instanceof KCLError) {
|
||||||
|
addKCLError(e)
|
||||||
|
} else {
|
||||||
|
setError('problem')
|
||||||
|
console.log(e)
|
||||||
|
addLog(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asyncWrap()
|
||||||
|
return () => {
|
||||||
|
unsubFn.forEach((fn) => fn())
|
||||||
|
}
|
||||||
|
}, [defferedCode, isStreamReady, engineCommandManager])
|
||||||
|
}
|
48
src/hooks/useSetupEngineManager.ts
Normal file
48
src/hooks/useSetupEngineManager.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useLayoutEffect } from 'react'
|
||||||
|
import { _executor } from '../lang/executor'
|
||||||
|
import { useStore } from '../useStore'
|
||||||
|
import { EngineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
|
export function useSetupEngineManager(
|
||||||
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
|
token?: string
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
setEngineCommandManager,
|
||||||
|
setMediaStream,
|
||||||
|
setIsStreamReady,
|
||||||
|
setStreamDimensions,
|
||||||
|
} = useStore((s) => ({
|
||||||
|
setEngineCommandManager: s.setEngineCommandManager,
|
||||||
|
setMediaStream: s.setMediaStream,
|
||||||
|
setIsStreamReady: s.setIsStreamReady,
|
||||||
|
setStreamDimensions: s.setStreamDimensions,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const streamWidth = streamRef?.current?.offsetWidth
|
||||||
|
const streamHeight = streamRef?.current?.offsetHeight
|
||||||
|
|
||||||
|
const width = streamWidth ? streamWidth : 0
|
||||||
|
const quadWidth = Math.round(width / 4) * 4
|
||||||
|
const height = streamHeight ? streamHeight : 0
|
||||||
|
const quadHeight = Math.round(height / 4) * 4
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setStreamDimensions({
|
||||||
|
streamWidth: quadWidth,
|
||||||
|
streamHeight: quadHeight,
|
||||||
|
})
|
||||||
|
if (!width || !height) return
|
||||||
|
const eng = new EngineCommandManager({
|
||||||
|
setMediaStream,
|
||||||
|
setIsStreamReady,
|
||||||
|
width: quadWidth,
|
||||||
|
height: quadHeight,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
setEngineCommandManager(eng)
|
||||||
|
return () => {
|
||||||
|
eng?.tearDown()
|
||||||
|
}
|
||||||
|
}, [quadWidth, quadHeight])
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parser_wasm } from './abstractSyntaxTree'
|
||||||
import { KCLUnexpectedError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { initPromise } from './rust'
|
import { initPromise } from './rust'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
@ -1744,6 +1744,12 @@ describe('parsing errors', () => {
|
|||||||
_theError = e
|
_theError = e
|
||||||
}
|
}
|
||||||
const theError = _theError as any
|
const theError = _theError as any
|
||||||
expect(theError).toEqual(new KCLUnexpectedError('Brace', [[29, 30]]))
|
expect(theError).toEqual(
|
||||||
|
new KCLError(
|
||||||
|
'unexpected',
|
||||||
|
'Unexpected token Token { token_type: Brace, start: 29, end: 30, value: "}" }',
|
||||||
|
[[29, 30]]
|
||||||
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ show(mySketch001)`
|
|||||||
)
|
)
|
||||||
expect(shown).toEqual([
|
expect(shown).toEqual([
|
||||||
{
|
{
|
||||||
type: 'sketchGroup',
|
type: 'SketchGroup',
|
||||||
start: {
|
start: {
|
||||||
to: [0, 0],
|
to: [0, 0],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -77,7 +77,7 @@ show(mySketch001)`
|
|||||||
)
|
)
|
||||||
expect(shown).toEqual([
|
expect(shown).toEqual([
|
||||||
{
|
{
|
||||||
type: 'extrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
height: 2,
|
height: 2,
|
||||||
@ -117,7 +117,7 @@ show(theExtrude, sk2)`
|
|||||||
)
|
)
|
||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
{
|
{
|
||||||
type: 'extrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
height: 2,
|
height: 2,
|
||||||
@ -126,7 +126,7 @@ show(theExtrude, sk2)`
|
|||||||
__meta: [{ sourceRange: [13, 34] }],
|
__meta: [{ sourceRange: [13, 34] }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'extrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
height: 2,
|
height: 2,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
|
|
||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parser_wasm } from './abstractSyntaxTree'
|
||||||
import { ProgramMemory } from './executor'
|
import { ProgramMemory, SketchGroup } 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'
|
||||||
@ -117,10 +117,10 @@ show(mySketch)
|
|||||||
// ].join('\n')
|
// ].join('\n')
|
||||||
// const { root } = await exe(code)
|
// const { root } = await exe(code)
|
||||||
// expect(root.mySk1.value).toHaveLength(3)
|
// expect(root.mySk1.value).toHaveLength(3)
|
||||||
// expect(root?.rotated?.type).toBe('sketchGroup')
|
// expect(root?.rotated?.type).toBe('SketchGroup')
|
||||||
// if (
|
// if (
|
||||||
// root?.mySk1?.type !== 'sketchGroup' ||
|
// root?.mySk1?.type !== 'SketchGroup' ||
|
||||||
// root?.rotated?.type !== 'sketchGroup'
|
// root?.rotated?.type !== 'SketchGroup'
|
||||||
// )
|
// )
|
||||||
// throw new Error('not a sketch group')
|
// throw new Error('not a sketch group')
|
||||||
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
|
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
|
||||||
@ -143,7 +143,7 @@ show(mySketch)
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
expect(root.mySk1).toEqual({
|
expect(root.mySk1).toEqual({
|
||||||
type: 'sketchGroup',
|
type: 'SketchGroup',
|
||||||
start: {
|
start: {
|
||||||
to: [0, 0],
|
to: [0, 0],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -199,7 +199,7 @@ show(mySketch)
|
|||||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||||
expect(root).toEqual({
|
expect(root).toEqual({
|
||||||
three: {
|
three: {
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: 3,
|
value: 3,
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -208,7 +208,7 @@ show(mySketch)
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
yo: {
|
yo: {
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: [1, '2', 3, 9],
|
value: [1, '2', 3, 9],
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -225,7 +225,7 @@ show(mySketch)
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
expect(root.yo).toEqual({
|
expect(root.yo).toEqual({
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -240,7 +240,7 @@ show(mySketch)
|
|||||||
)
|
)
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
expect(root.myVar).toEqual({
|
expect(root.myVar).toEqual({
|
||||||
type: 'userVal',
|
type: 'UserVal',
|
||||||
value: '123',
|
value: '123',
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -338,7 +338,7 @@ describe('testing math operators', () => {
|
|||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = root.part001
|
||||||
// result of `-legLen(5, min(3, 999))` should be -4
|
// result of `-legLen(5, min(3, 999))` should be -4
|
||||||
const yVal = sketch.value?.[0]?.to?.[1]
|
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
|
||||||
expect(yVal).toBe(-4)
|
expect(yVal).toBe(-4)
|
||||||
})
|
})
|
||||||
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
||||||
@ -356,8 +356,8 @@ describe('testing math operators', () => {
|
|||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = root.part001
|
||||||
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
||||||
expect(sketch.value?.[1]?.from).toEqual([3, 4])
|
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
||||||
expect(sketch.value?.[1]?.to).toEqual([6, 0])
|
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
||||||
const removedUnaryExp = code.replace(
|
const removedUnaryExp = code.replace(
|
||||||
`-legLen(segLen('seg01', %), myVar)`,
|
`-legLen(segLen('seg01', %), myVar)`,
|
||||||
`legLen(segLen('seg01', %), myVar)`
|
`legLen(segLen('seg01', %), myVar)`
|
||||||
@ -366,7 +366,9 @@ describe('testing math operators', () => {
|
|||||||
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
||||||
|
|
||||||
// without the minus sign, the y value should be 8
|
// without the minus sign, the y value should be 8
|
||||||
expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8])
|
expect((removedUnaryExpRootSketch as SketchGroup).value?.[1]?.to).toEqual([
|
||||||
|
6, 8,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
it('with nested callExpression and binaryExpression', async () => {
|
it('with nested callExpression and binaryExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||||
@ -397,7 +399,10 @@ show(theExtrude)`
|
|||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
async function exe(code: string, programMemory: ProgramMemory = { root: {} }) {
|
async function exe(
|
||||||
|
code: string,
|
||||||
|
programMemory: ProgramMemory = { root: {}, return: null }
|
||||||
|
) {
|
||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
|
|
||||||
const result = await enginelessExecutor(ast, programMemory)
|
const result = await enginelessExecutor(ast, programMemory)
|
||||||
|
@ -5,96 +5,21 @@ import {
|
|||||||
SourceRangeMap,
|
SourceRangeMap,
|
||||||
} from './std/engineConnection'
|
} from './std/engineConnection'
|
||||||
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
|
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
|
||||||
|
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||||
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
|
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
import { rangeTypeFix } from './abstractSyntaxTree'
|
import { rangeTypeFix } from './abstractSyntaxTree'
|
||||||
|
|
||||||
export type SourceRange = [number, number]
|
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
|
||||||
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
|
export type { Position } from '../wasm-lib/kcl/bindings/Position'
|
||||||
export type Metadata = {
|
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
||||||
sourceRange: SourceRange
|
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
||||||
}
|
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
||||||
export type Position = [number, number, number]
|
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||||
export type Rotation = [number, number, number, number]
|
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||||
|
|
||||||
interface BasePath {
|
export type PathToNode = [string | number, string][]
|
||||||
from: [number, number]
|
|
||||||
to: [number, number]
|
|
||||||
name?: string
|
|
||||||
__geoMeta: {
|
|
||||||
id: string
|
|
||||||
sourceRange: SourceRange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToPoint extends BasePath {
|
|
||||||
type: 'toPoint'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Base extends BasePath {
|
|
||||||
type: 'base'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HorizontalLineTo extends BasePath {
|
|
||||||
type: 'horizontalLineTo'
|
|
||||||
x: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AngledLineTo extends BasePath {
|
|
||||||
type: 'angledLineTo'
|
|
||||||
angle: number
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeoMeta {
|
|
||||||
__geoMeta: {
|
|
||||||
id: string
|
|
||||||
sourceRange: SourceRange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Path = ToPoint | HorizontalLineTo | AngledLineTo | Base
|
|
||||||
|
|
||||||
export interface SketchGroup {
|
|
||||||
type: 'sketchGroup'
|
|
||||||
id: string
|
|
||||||
value: Path[]
|
|
||||||
start?: Base
|
|
||||||
position: Position
|
|
||||||
rotation: Rotation
|
|
||||||
__meta: Metadata[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExtrudePlane {
|
|
||||||
type: 'extrudePlane'
|
|
||||||
position: Position
|
|
||||||
rotation: Rotation
|
|
||||||
name?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExtrudeSurface = GeoMeta &
|
|
||||||
ExtrudePlane /* | ExtrudeRadius | ExtrudeSpline */
|
|
||||||
|
|
||||||
export interface ExtrudeGroup {
|
|
||||||
type: 'extrudeGroup'
|
|
||||||
id: string
|
|
||||||
value: ExtrudeSurface[]
|
|
||||||
height: number
|
|
||||||
position: Position
|
|
||||||
rotation: Rotation
|
|
||||||
__meta: Metadata[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** UserVal not produced by one of our internal functions */
|
|
||||||
export interface UserVal {
|
|
||||||
type: 'userVal'
|
|
||||||
value: any
|
|
||||||
__meta: Metadata[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
|
|
||||||
|
|
||||||
interface Memory {
|
interface Memory {
|
||||||
[key: string]: MemoryItem
|
[key: string]: MemoryItem
|
||||||
@ -102,12 +27,12 @@ interface Memory {
|
|||||||
|
|
||||||
export interface ProgramMemory {
|
export interface ProgramMemory {
|
||||||
root: Memory
|
root: Memory
|
||||||
return?: ProgramReturn
|
return: ProgramReturn | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executor = async (
|
export const executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {} },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
// 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.
|
||||||
@ -123,7 +48,7 @@ export const executor = async (
|
|||||||
engineCommandManager
|
engineCommandManager
|
||||||
)
|
)
|
||||||
const { artifactMap, sourceRangeMap } =
|
const { artifactMap, sourceRangeMap } =
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands(node, _programMemory)
|
||||||
tempMapCallback({ artifactMap, sourceRangeMap })
|
tempMapCallback({ artifactMap, sourceRangeMap })
|
||||||
|
|
||||||
engineCommandManager.endSession()
|
engineCommandManager.endSession()
|
||||||
@ -132,7 +57,7 @@ export const executor = async (
|
|||||||
|
|
||||||
export const _executor = async (
|
export const _executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {} },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
try {
|
try {
|
||||||
|
@ -114,7 +114,8 @@ describe('Testing addSketchTo', () => {
|
|||||||
expect(str).toBe(`const part001 = startSketchAt('default')
|
expect(str).toBe(`const part001 = startSketchAt('default')
|
||||||
|> ry(90, %)
|
|> ry(90, %)
|
||||||
|> line('default', %)
|
|> line('default', %)
|
||||||
show(part001)`)
|
show(part001)
|
||||||
|
`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -175,11 +176,14 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Testing moveValueIntoNewVariable', () => {
|
describe('Testing moveValueIntoNewVariable', () => {
|
||||||
const fn = (fnName: string) => `const ${fnName} = (x) => {
|
const fn = (fnName: string) => `fn ${fnName} = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const code = `${fn('def')}${fn('ghi')}${fn('jkl')}${fn('hmm')}
|
const code = `${fn('def')}${fn('jkl')}${fn('hmm')}
|
||||||
|
fn ghi = (x) => {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
const abc = 3
|
const abc = 3
|
||||||
const identifierGuy = 5
|
const identifierGuy = 5
|
||||||
const yo = 5 + 6
|
const yo = 5 + 6
|
||||||
|
@ -27,6 +27,48 @@ import {
|
|||||||
getFirstArg,
|
getFirstArg,
|
||||||
createFirstArg,
|
createFirstArg,
|
||||||
} from './std/sketch'
|
} from './std/sketch'
|
||||||
|
import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
||||||
|
|
||||||
|
export function addStartSketch(
|
||||||
|
node: Program,
|
||||||
|
start: [number, number],
|
||||||
|
end: [number, number]
|
||||||
|
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
||||||
|
const _node = { ...node }
|
||||||
|
const _name = findUniqueName(node, 'part')
|
||||||
|
|
||||||
|
const startSketchAt = createCallExpression('startSketchAt', [
|
||||||
|
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
|
||||||
|
])
|
||||||
|
const initialLineTo = createCallExpression('line', [
|
||||||
|
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
|
||||||
|
const pipeBody = [startSketchAt, initialLineTo]
|
||||||
|
|
||||||
|
const variableDeclaration = createVariableDeclaration(
|
||||||
|
_name,
|
||||||
|
createPipeExpression(pipeBody)
|
||||||
|
)
|
||||||
|
|
||||||
|
const newIndex = node.body.length
|
||||||
|
_node.body = [...node.body, variableDeclaration]
|
||||||
|
|
||||||
|
let pathToNode: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[newIndex.toString(10), 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
['0', 'index'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
id: _name,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function addSketchTo(
|
export function addSketchTo(
|
||||||
node: Program,
|
node: Program,
|
||||||
@ -151,7 +193,7 @@ export function mutateArrExp(
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (node.type === 'ArrayExpression') {
|
if (node.type === 'ArrayExpression') {
|
||||||
node.elements.forEach((element, i) => {
|
node.elements.forEach((element, i) => {
|
||||||
if (element.type === 'Literal') {
|
if (isLiteralArrayOrStatic(element)) {
|
||||||
node.elements[i] = updateWith.elements[i]
|
node.elements[i] = updateWith.elements[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -169,8 +211,8 @@ export function mutateObjExpProp(
|
|||||||
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
||||||
if (keyIndex !== -1) {
|
if (keyIndex !== -1) {
|
||||||
if (
|
if (
|
||||||
updateWith.type === 'Literal' &&
|
isLiteralArrayOrStatic(updateWith) &&
|
||||||
node.properties[keyIndex].value.type === 'Literal'
|
isLiteralArrayOrStatic(node.properties[keyIndex].value)
|
||||||
) {
|
) {
|
||||||
node.properties[keyIndex].value = updateWith
|
node.properties[keyIndex].value = updateWith
|
||||||
return true
|
return true
|
||||||
@ -180,7 +222,7 @@ export function mutateObjExpProp(
|
|||||||
) {
|
) {
|
||||||
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
||||||
arrExp.elements.forEach((element, i) => {
|
arrExp.elements.forEach((element, i) => {
|
||||||
if (element.type === 'Literal') {
|
if (isLiteralArrayOrStatic(element)) {
|
||||||
arrExp.elements[i] = updateWith.elements[i]
|
arrExp.elements[i] = updateWith.elements[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -11,26 +11,27 @@ describe('recast', () => {
|
|||||||
const code = '1 + 2'
|
const code = '1 + 2'
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('variable declaration', () => {
|
it('variable declaration', () => {
|
||||||
const code = 'const myVar = 5'
|
const code = 'const myVar = 5'
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it("variable declaration that's binary with string", () => {
|
it("variable declaration that's binary with string", () => {
|
||||||
const code = "const myVar = 5 + 'yo'"
|
const code = "const myVar = 5 + 'yo'"
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
|
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
|
||||||
const { ast: ast2 } = code2ast(codeWithOtherQuotes)
|
const { ast: ast2 } = code2ast(codeWithOtherQuotes)
|
||||||
expect(recast(ast2)).toBe(codeWithOtherQuotes)
|
expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
|
||||||
})
|
})
|
||||||
it('test assigning two variables, the second summing with the first', () => {
|
it('test assigning two variables, the second summing with the first', () => {
|
||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const newVar = myVar + 1`
|
const newVar = myVar + 1
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
@ -42,11 +43,12 @@ const newVar = myVar + 1`
|
|||||||
)
|
)
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('test with function call', () => {
|
it('test with function call', () => {
|
||||||
const code = `const myVar = "hello"
|
const code = `const myVar = "hello"
|
||||||
log(5, myVar)`
|
log(5, myVar)
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
@ -61,7 +63,7 @@ log(5, myVar)`
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast sketch declaration', () => {
|
it('recast sketch declaration', () => {
|
||||||
let code = `const mySketch = startSketchAt([0, 0])
|
let code = `const mySketch = startSketchAt([0, 0])
|
||||||
@ -70,10 +72,11 @@ log(5, myVar)`
|
|||||||
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
|
||||||
show(mySketch)`
|
show(mySketch)
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('sketch piped into callExpression', () => {
|
it('sketch piped into callExpression', () => {
|
||||||
const code = [
|
const code = [
|
||||||
@ -85,7 +88,7 @@ show(mySketch)`
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast BinaryExpression piped into CallExpression', () => {
|
it('recast BinaryExpression piped into CallExpression', () => {
|
||||||
const code = [
|
const code = [
|
||||||
@ -97,37 +100,37 @@ show(mySketch)`
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast nested binary expression', () => {
|
it('recast nested binary expression', () => {
|
||||||
const code = ['const myVar = 1 + 2 * 5'].join('\n')
|
const code = ['const myVar = 1 + 2 * 5'].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast nested binary expression with parans', () => {
|
it('recast nested binary expression with parans', () => {
|
||||||
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
|
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('unnecessary paran wrap will be remove', () => {
|
it('unnecessary paran wrap will be remove', () => {
|
||||||
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
|
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.replace('(', '').replace(')', ''))
|
expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
|
||||||
})
|
})
|
||||||
it('complex nested binary expression', () => {
|
it('complex nested binary expression', () => {
|
||||||
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
|
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('multiplied paren expressions', () => {
|
it('multiplied paren expressions', () => {
|
||||||
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
|
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast array declaration', () => {
|
it('recast array declaration', () => {
|
||||||
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||||
@ -135,7 +138,7 @@ show(mySketch)`
|
|||||||
)
|
)
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast long array declaration', () => {
|
it('recast long array declaration', () => {
|
||||||
const code = [
|
const code = [
|
||||||
@ -150,7 +153,7 @@ show(mySketch)`
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast long object exectution', () => {
|
it('recast long object exectution', () => {
|
||||||
const code = `const three = 3
|
const code = `const three = 3
|
||||||
@ -159,26 +162,29 @@ const yo = {
|
|||||||
anum: 2,
|
anum: 2,
|
||||||
identifier: three,
|
identifier: three,
|
||||||
binExp: 4 + 5
|
binExp: 4 + 5
|
||||||
}`
|
}
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast short object exectution', () => {
|
it('recast short object exectution', () => {
|
||||||
const code = `const yo = { key: 'val' }`
|
const code = `const yo = { key: 'val' }
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast object execution with member expression', () => {
|
it('recast object execution with member expression', () => {
|
||||||
const code = `const yo = { a: { b: { c: '123' } } }
|
const code = `const yo = { a: { b: { c: '123' } } }
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
const myVar = yo.a['b'][key]
|
const myVar = yo.a['b'][key]
|
||||||
const key2 = 'b'
|
const key2 = 'b'
|
||||||
const myVar2 = yo['a'][key2].c`
|
const myVar2 = yo['a'][key2].c
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -186,7 +192,8 @@ describe('testing recasting with comments and whitespace', () => {
|
|||||||
it('code with comments', () => {
|
it('code with comments', () => {
|
||||||
const code = `const yo = { a: { b: { c: '123' } } }
|
const code = `const yo = { a: { b: { c: '123' } } }
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const key = 'c'`
|
const key = 'c'
|
||||||
|
`
|
||||||
|
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
@ -199,7 +206,8 @@ const key = 'c'`
|
|||||||
/* this is
|
/* this is
|
||||||
a
|
a
|
||||||
comment */
|
comment */
|
||||||
const yo = 'bing'`
|
const yo = 'bing'
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
@ -209,13 +217,14 @@ const yo = 'bing'`
|
|||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
|
|
||||||
// this is also a comment`
|
// this is also a comment
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments in a fn block', () => {
|
it('comments in a fn block', () => {
|
||||||
const code = `const myFn = () => {
|
const code = `fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
|
|
||||||
@ -223,7 +232,8 @@ const key = 'c'
|
|||||||
comment */
|
comment */
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
// this is also a comment
|
// this is also a comment
|
||||||
}`
|
}
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
@ -239,7 +249,7 @@ const key = 'c'
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments sprinkled in all over the place', () => {
|
it('comments sprinkled in all over the place', () => {
|
||||||
const code = `
|
const code = `
|
||||||
@ -261,7 +271,8 @@ const mySk1 = startSketchAt([0, 0])
|
|||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
/*
|
/*
|
||||||
one more for good measure
|
one more for good measure
|
||||||
*/`
|
*/
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(`// comment at start
|
expect(recasted).toBe(`// comment at start
|
||||||
@ -278,7 +289,8 @@ a comment between pipe expression statements */
|
|||||||
// and another with just white space between others below
|
// and another with just white space between others below
|
||||||
|> ry(45, %)
|
|> ry(45, %)
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
// one more for good measure`)
|
// one more for good measure
|
||||||
|
`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -287,19 +299,19 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
|
|||||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('nested callExpression in unaryExpression', () => {
|
it('nested callExpression in unaryExpression', () => {
|
||||||
const code = 'const myVar = -min(100, legLen(5, 3))'
|
const code = 'const myVar = -min(100, legLen(5, 3))'
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in callExpression', () => {
|
it('with unaryExpression in callExpression', () => {
|
||||||
const code = 'const myVar = min(5, -legLen(5, 4))'
|
const code = 'const myVar = min(5, -legLen(5, 4))'
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in sketch situation', () => {
|
it('with unaryExpression in sketch situation', () => {
|
||||||
const code = [
|
const code = [
|
||||||
@ -308,7 +320,7 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -323,7 +335,8 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
|
|||||||
intersectTag: 'seg01'
|
intersectTag: 'seg01'
|
||||||
}, %)
|
}, %)
|
||||||
|> line([-0.42, -1.72], %)
|
|> line([-0.42, -1.72], %)
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
@ -333,7 +346,8 @@ show(part001)`
|
|||||||
angle: 201,
|
angle: 201,
|
||||||
offset: -1.35,
|
offset: -1.35,
|
||||||
intersectTag: 'seg01'
|
intersectTag: 'seg01'
|
||||||
}, %)`
|
}, %)
|
||||||
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
@ -342,7 +356,8 @@ show(part001)`
|
|||||||
|
|
||||||
describe('it recasts binary expression using brackets where needed', () => {
|
describe('it recasts binary expression using brackets where needed', () => {
|
||||||
it('when there are two minus in a row', () => {
|
it('when there are two minus in a row', () => {
|
||||||
const code = `const part001 = 1 - (def - abc)`
|
const code = `const part001 = 1 - (def - abc)
|
||||||
|
`
|
||||||
const recasted = recast(code2ast(code).ast)
|
const recasted = recast(code2ast(code).ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import { SourceRange } from 'lang/executor'
|
import { ProgramMemory, SourceRange } from 'lang/executor'
|
||||||
import { Selections } from 'useStore'
|
import { Selections } from 'useStore'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } 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'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
||||||
|
|
||||||
|
let lastMessage = ''
|
||||||
|
|
||||||
interface CommandInfo {
|
interface CommandInfo {
|
||||||
commandType: CommandTypes
|
commandType: CommandTypes
|
||||||
@ -754,6 +758,13 @@ export class EngineCommandManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
sendSceneCommand(command: EngineCommand): Promise<any> {
|
||||||
|
if (
|
||||||
|
command.type === 'modeling_cmd_req' &&
|
||||||
|
command.cmd.type !== lastMessage
|
||||||
|
) {
|
||||||
|
console.log('sending command', command.cmd.type)
|
||||||
|
lastMessage = command.cmd.type
|
||||||
|
}
|
||||||
if (!this.engineConnection?.isReady()) {
|
if (!this.engineConnection?.isReady()) {
|
||||||
console.log('socket not ready')
|
console.log('socket not ready')
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@ -761,7 +772,8 @@ export class EngineCommandManager {
|
|||||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||||
const cmd = command.cmd
|
const cmd = command.cmd
|
||||||
if (
|
if (
|
||||||
cmd.type === 'camera_drag_move' &&
|
(cmd.type === 'camera_drag_move' ||
|
||||||
|
cmd.type === 'handle_mouse_drag_move') &&
|
||||||
this.engineConnection?.unreliableDataChannel
|
this.engineConnection?.unreliableDataChannel
|
||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
@ -873,7 +885,10 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
return command.promise
|
return command.promise
|
||||||
}
|
}
|
||||||
async waitForAllCommands(): Promise<{
|
async waitForAllCommands(
|
||||||
|
ast?: Program,
|
||||||
|
programMemory?: ProgramMemory
|
||||||
|
): Promise<{
|
||||||
artifactMap: ArtifactMap
|
artifactMap: ArtifactMap
|
||||||
sourceRangeMap: SourceRangeMap
|
sourceRangeMap: SourceRangeMap
|
||||||
}> {
|
}> {
|
||||||
@ -882,9 +897,94 @@ export class EngineCommandManager {
|
|||||||
) as PendingCommand[]
|
) as PendingCommand[]
|
||||||
const proms = pendingCommands.map(({ promise }) => promise)
|
const proms = pendingCommands.map(({ promise }) => promise)
|
||||||
await Promise.all(proms)
|
await Promise.all(proms)
|
||||||
|
if (ast && programMemory) {
|
||||||
|
await this.fixIdMappings(ast, programMemory)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
artifactMap: this.artifactMap,
|
artifactMap: this.artifactMap,
|
||||||
sourceRangeMap: this.sourceRangeMap,
|
sourceRangeMap: this.sourceRangeMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
|
||||||
|
/* This is a temporary solution since the cmd_ids that are sent through when
|
||||||
|
sending 'extend_path' ids are not used as the segment ids.
|
||||||
|
|
||||||
|
We have a way to back fill them with 'path_get_info', however this relies on one
|
||||||
|
the sketchGroup array and the segements array returned from the server to be in
|
||||||
|
the same length and order. plus it's super hacky, we first use the path_id to get
|
||||||
|
the source range of the pipe expression then use the name of the variable to get
|
||||||
|
the sketchGroup from programMemory.
|
||||||
|
|
||||||
|
I feel queezy about relying on all these steps to always line up.
|
||||||
|
We have also had to pollute this EngineCommandManager class with knowledge of both the ast and programMemory
|
||||||
|
We should get the cmd_ids to match with the segment ids and delete this method.
|
||||||
|
*/
|
||||||
|
const pathInfoProms = []
|
||||||
|
for (const [id, artifact] of Object.entries(this.artifactMap)) {
|
||||||
|
if (artifact.commandType === 'start_path') {
|
||||||
|
pathInfoProms.push(
|
||||||
|
this.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'path_get_info',
|
||||||
|
path_id: id,
|
||||||
|
},
|
||||||
|
}).then(({ data }) => ({
|
||||||
|
originalId: id,
|
||||||
|
segments: data?.data?.segments,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathInfos = await Promise.all(pathInfoProms)
|
||||||
|
pathInfos.forEach(({ originalId, segments }) => {
|
||||||
|
const originalArtifact = this.artifactMap[originalId]
|
||||||
|
if (!originalArtifact || originalArtifact.type === 'pending') {
|
||||||
|
console.log('problem')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pipeExpPath = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
originalArtifact.range
|
||||||
|
)
|
||||||
|
const pipeExp = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
pipeExpPath,
|
||||||
|
'VariableDeclarator'
|
||||||
|
).node
|
||||||
|
if (pipeExp.type !== 'VariableDeclarator') {
|
||||||
|
console.log('problem', pipeExp, pipeExpPath, ast)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const variableName = pipeExp.id.name
|
||||||
|
const memoryItem = programMemory.root[variableName]
|
||||||
|
if (!memoryItem) {
|
||||||
|
console.log('problem', variableName, programMemory)
|
||||||
|
return
|
||||||
|
} else if (memoryItem.type !== 'SketchGroup') {
|
||||||
|
console.log('problem', memoryItem, programMemory)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const relevantSegments = segments.filter(
|
||||||
|
({ command_id }: { command_id: string | null }) => command_id
|
||||||
|
)
|
||||||
|
if (memoryItem.value.length !== relevantSegments.length) {
|
||||||
|
console.log('problem', memoryItem.value, relevantSegments)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let i = 0; i < relevantSegments.length; i++) {
|
||||||
|
const engineSegment = relevantSegments[i]
|
||||||
|
const memorySegment = memoryItem.value[i]
|
||||||
|
const oldId = memorySegment.__geoMeta.id
|
||||||
|
const artifact = this.artifactMap[oldId]
|
||||||
|
delete this.artifactMap[oldId]
|
||||||
|
delete this.sourceRangeMap[oldId]
|
||||||
|
this.artifactMap[engineSegment.command_id] = artifact
|
||||||
|
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,8 @@ describe('testing changeSketchArguments', () => {
|
|||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
show(mySketch001)`
|
show(mySketch001)
|
||||||
|
`
|
||||||
const code = genCode(lineToChange)
|
const code = genCode(lineToChange)
|
||||||
const expectedCode = genCode(lineAfterChange)
|
const expectedCode = genCode(lineAfterChange)
|
||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
@ -164,7 +165,8 @@ show(mySketch001)`
|
|||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
|> lineTo([2, 3], %)
|
|> lineTo([2, 3], %)
|
||||||
show(mySketch001)`
|
show(mySketch001)
|
||||||
|
`
|
||||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -177,7 +179,8 @@ describe('testing addTagForSketchOnFace', () => {
|
|||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
show(mySketch001)`
|
show(mySketch001)
|
||||||
|
`
|
||||||
const code = genCode(originalLine)
|
const code = genCode(originalLine)
|
||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
SketchGroup,
|
SketchGroup,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
|
MemoryItem,
|
||||||
} from '../executor'
|
} from '../executor'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
@ -19,8 +20,9 @@ import {
|
|||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
} from '../queryAst'
|
} from '../queryAst'
|
||||||
|
import { isLiteralArrayOrStatic } from './sketchcombos'
|
||||||
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
||||||
import { splitPathAtPipeExpression } from '../modifyAst'
|
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||||
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
||||||
|
|
||||||
import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes'
|
import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes'
|
||||||
@ -185,7 +187,7 @@ export const line: SketchLineHelper = {
|
|||||||
createCallback,
|
createCallback,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
@ -197,12 +199,12 @@ export const line: SketchLineHelper = {
|
|||||||
)
|
)
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
const sketch = previousProgramMemory?.root?.[variableName]
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
|
||||||
|
|
||||||
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
|
|
||||||
if (replaceExisting && createCallback) {
|
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
|
||||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
const { callExp, valueUsedInTransform } = createCallback(
|
const { callExp, valueUsedInTransform } = createCallback(
|
||||||
[newXVal, newYVal],
|
[newXVal, newYVal],
|
||||||
@ -220,7 +222,11 @@ export const line: SketchLineHelper = {
|
|||||||
createArrayExpression([newXVal, newYVal]),
|
createArrayExpression([newXVal, newYVal]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
pipe.body = [...pipe.body, callExp]
|
if (pipe.type === 'PipeExpression') {
|
||||||
|
pipe.body = [...pipe.body, callExp]
|
||||||
|
} else {
|
||||||
|
varDec.init = createPipeExpression([varDec.init, callExp])
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -238,22 +244,10 @@ export const line: SketchLineHelper = {
|
|||||||
createLiteral(roundOff(to[1] - from[1], 2)),
|
createLiteral(roundOff(to[1] - from[1], 2)),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (
|
if (callExpression.arguments?.[0].type === 'ObjectExpression') {
|
||||||
callExpression.arguments?.[0].type === 'Literal' &&
|
|
||||||
callExpression.arguments?.[0].value === 'default'
|
|
||||||
) {
|
|
||||||
callExpression.arguments[0] = toArrExp
|
|
||||||
} else if (callExpression.arguments?.[0].type === 'ObjectExpression') {
|
|
||||||
const toProp = callExpression.arguments?.[0].properties?.find(
|
const toProp = callExpression.arguments?.[0].properties?.find(
|
||||||
({ key }) => key.name === 'to'
|
({ key }) => key.name === 'to'
|
||||||
)
|
)
|
||||||
if (
|
|
||||||
toProp &&
|
|
||||||
toProp.value.type === 'Literal' &&
|
|
||||||
toProp.value.value === 'default'
|
|
||||||
) {
|
|
||||||
toProp.value = toArrExp
|
|
||||||
}
|
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
|
||||||
} else {
|
} else {
|
||||||
mutateArrExp(callExpression.arguments?.[0], toArrExp)
|
mutateArrExp(callExpression.arguments?.[0], toArrExp)
|
||||||
@ -301,7 +295,7 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newX = createLiteral(roundOff(to[0], 2))
|
const newX = createLiteral(roundOff(to[0], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newX
|
callExpression.arguments[0] = newX
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
||||||
@ -349,7 +343,7 @@ export const yLineTo: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newY = createLiteral(roundOff(to[1], 2))
|
const newY = createLiteral(roundOff(to[1], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newY
|
callExpression.arguments[0] = newY
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
||||||
@ -399,7 +393,7 @@ export const xLine: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newX
|
callExpression.arguments[0] = newX
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
||||||
@ -443,7 +437,7 @@ export const yLine: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newY
|
callExpression.arguments[0] = newY
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
||||||
@ -546,7 +540,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
)
|
)
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
const sketch = previousProgramMemory?.root?.[variableName]
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
|
||||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
||||||
const newLine = createCallback
|
const newLine = createCallback
|
||||||
@ -619,7 +613,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
)
|
)
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
const sketch = previousProgramMemory?.root?.[variableName]
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
|
||||||
|
|
||||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
||||||
@ -876,7 +870,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
const varName = varDec.declarations[0].id.name
|
const varName = varDec.declarations[0].id.name
|
||||||
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
||||||
const intersectPath = sketchGroup.value.find(
|
const intersectPath = sketchGroup.value.find(
|
||||||
({ name }) => name === intersectTagName
|
({ name }: Path) => name === intersectTagName
|
||||||
)
|
)
|
||||||
let offset = 0
|
let offset = 0
|
||||||
if (intersectPath) {
|
if (intersectPath) {
|
||||||
@ -968,60 +962,14 @@ export function addNewSketchLn({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
const { node: pipeExp, shallowPath: pipePath } =
|
const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath<
|
||||||
getNodeFromPath<PipeExpression>(node, pathToNode, 'PipeExpression')
|
PipeExpression | CallExpression
|
||||||
const maybeStartSketchAt = pipeExp.body.find(
|
>(node, pathToNode, 'PipeExpression')
|
||||||
(exp) =>
|
|
||||||
exp.type === 'CallExpression' &&
|
|
||||||
exp.callee.name === 'startSketchAt' &&
|
|
||||||
exp.arguments[0].type === 'Literal' &&
|
|
||||||
exp.arguments[0].value === 'default'
|
|
||||||
)
|
|
||||||
const maybeDefaultLine = pipeExp.body.findIndex(
|
|
||||||
(exp) =>
|
|
||||||
exp.type === 'CallExpression' &&
|
|
||||||
exp.callee.name === 'line' &&
|
|
||||||
exp.arguments[0].type === 'Literal' &&
|
|
||||||
exp.arguments[0].value === 'default'
|
|
||||||
)
|
|
||||||
const defaultLinePath: PathToNode = [
|
|
||||||
...pipePath,
|
|
||||||
['body', ''],
|
|
||||||
[maybeDefaultLine, ''],
|
|
||||||
]
|
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
const sketch = previousProgramMemory?.root?.[variableName]
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
|
||||||
|
|
||||||
if (maybeStartSketchAt) {
|
const last = sketch.value[sketch.value.length - 1] || sketch.start
|
||||||
const startSketchAt = maybeStartSketchAt as any
|
|
||||||
startSketchAt.arguments[0] = createArrayExpression([
|
|
||||||
createLiteral(to[0]),
|
|
||||||
createLiteral(to[1]),
|
|
||||||
])
|
|
||||||
return {
|
|
||||||
modifiedAst: node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maybeDefaultLine !== -1) {
|
|
||||||
const defaultLine = getNodeFromPath<CallExpression>(
|
|
||||||
node,
|
|
||||||
defaultLinePath
|
|
||||||
).node
|
|
||||||
const { from } = getSketchSegmentFromSourceRange(sketch, [
|
|
||||||
defaultLine.start,
|
|
||||||
defaultLine.end,
|
|
||||||
]).segment
|
|
||||||
return updateArgs({
|
|
||||||
node,
|
|
||||||
previousProgramMemory,
|
|
||||||
pathToNode: defaultLinePath,
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const from = last.to
|
const from = last.to
|
||||||
|
|
||||||
return add({
|
return add({
|
||||||
@ -1089,10 +1037,11 @@ export function addTagForSketchOnFace(
|
|||||||
|
|
||||||
function isAngleLiteral(lineArugement: Value): boolean {
|
function isAngleLiteral(lineArugement: Value): boolean {
|
||||||
return lineArugement?.type === 'ArrayExpression'
|
return lineArugement?.type === 'ArrayExpression'
|
||||||
? lineArugement.elements[0].type === 'Literal'
|
? isLiteralArrayOrStatic(lineArugement.elements[0])
|
||||||
: lineArugement?.type === 'ObjectExpression'
|
: lineArugement?.type === 'ObjectExpression'
|
||||||
? lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
? isLiteralArrayOrStatic(
|
||||||
.type === 'Literal'
|
lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
||||||
|
)
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1198,14 +1147,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
|||||||
} {
|
} {
|
||||||
// used for lineTo, line
|
// used for lineTo, line
|
||||||
const firstArg = callExpression.arguments[0]
|
const firstArg = callExpression.arguments[0]
|
||||||
if (firstArg.type === 'Literal' && firstArg.value === 'default') {
|
|
||||||
return {
|
|
||||||
val:
|
|
||||||
callExpression.callee.name === 'startSketchAt'
|
|
||||||
? [createLiteral(0), createLiteral(0)]
|
|
||||||
: [createLiteral(1), createLiteral(1)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (firstArg.type === 'ArrayExpression') {
|
if (firstArg.type === 'ArrayExpression') {
|
||||||
return { val: [firstArg.elements[0], firstArg.elements[1]] }
|
return { val: [firstArg.elements[0], firstArg.elements[1]] }
|
||||||
}
|
}
|
||||||
@ -1215,8 +1156,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
|||||||
if (to?.type === 'ArrayExpression') {
|
if (to?.type === 'ArrayExpression') {
|
||||||
const [x, y] = to.elements
|
const [x, y] = to.elements
|
||||||
return { val: [x, y], tag }
|
return { val: [x, y], tag }
|
||||||
} else if (to?.type === 'Literal' && to.value === 'default') {
|
|
||||||
return { val: [createLiteral(0), createLiteral(0)], tag }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('expected ArrayExpression or ObjectExpression')
|
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||||
|
@ -401,6 +401,11 @@ show(part001)`
|
|||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.root['part001'] as SketchGroup,
|
||||||
[index, index]
|
[index, index]
|
||||||
).segment
|
).segment
|
||||||
expect(segment).toEqual({ to: [0, 0.04], from: [0, 0.04], name: '' })
|
expect(segment).toEqual({
|
||||||
|
to: [0, 0.04],
|
||||||
|
from: [0, 0.04],
|
||||||
|
name: '',
|
||||||
|
type: 'base',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
} from '../abstractSyntaxTreeTypes'
|
} from '../abstractSyntaxTreeTypes'
|
||||||
import { SketchGroup, SourceRange } from '../executor'
|
import { SketchGroup, SourceRange, Path } from '../executor'
|
||||||
|
|
||||||
export function getSketchSegmentFromSourceRange(
|
export function getSketchSegmentFromSourceRange(
|
||||||
sketchGroup: SketchGroup,
|
sketchGroup: SketchGroup,
|
||||||
@ -20,10 +20,10 @@ export function getSketchSegmentFromSourceRange(
|
|||||||
startSourceRange[1] >= rangeEnd &&
|
startSourceRange[1] >= rangeEnd &&
|
||||||
sketchGroup.start
|
sketchGroup.start
|
||||||
)
|
)
|
||||||
return { segment: sketchGroup.start, index: -1 }
|
return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
|
||||||
|
|
||||||
const lineIndex = sketchGroup.value.findIndex(
|
const lineIndex = sketchGroup.value.findIndex(
|
||||||
({ __geoMeta: { sourceRange } }) =>
|
({ __geoMeta: { sourceRange } }: Path) =>
|
||||||
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
||||||
)
|
)
|
||||||
const line = sketchGroup.value[lineIndex]
|
const line = sketchGroup.value[lineIndex]
|
||||||
|
@ -124,7 +124,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> yLine(1.04, %) // ln-yLine-free should sub in segLen
|
|> yLine(1.04, %) // ln-yLine-free should sub in segLen
|
||||||
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|
||||||
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
|
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
const expectModifiedScript = `const myVar = 3
|
const expectModifiedScript = `const myVar = 3
|
||||||
const myVar2 = 5
|
const myVar2 = 5
|
||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
@ -195,7 +196,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|
||||||
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|
||||||
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
it('should transform the ast', async () => {
|
it('should transform the ast', async () => {
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parser_wasm(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
@ -254,7 +256,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
||||||
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
it('should transform horizontal lines the ast', async () => {
|
it('should transform horizontal lines the ast', async () => {
|
||||||
const expectModifiedScript = `const myVar = 2
|
const expectModifiedScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
@ -281,7 +284,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
||||||
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
||||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parser_wasm(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
@ -338,7 +342,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> yLineTo(7.68, %) // select for vertical constraint 9
|
|> yLineTo(7.68, %) // select for vertical constraint 9
|
||||||
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|> yLineTo(myVar, %) // select for vertical constraint 10
|
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parser_wasm(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
@ -380,7 +385,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> line([0.45, 1.46], %) // free
|
|> line([0.45, 1.46], %) // free
|
||||||
|> line([myVar, 0.01], %) // xRelative
|
|> line([myVar, 0.01], %) // xRelative
|
||||||
|> line([0.7, myVar], %) // yRelative
|
|> line([0.7, myVar], %) // yRelative
|
||||||
show(part001)`
|
show(part001)
|
||||||
|
`
|
||||||
it('testing for free to horizontal and vertical distance', async () => {
|
it('testing for free to horizontal and vertical distance', async () => {
|
||||||
const expectedHorizontalCode = await helperThing(
|
const expectedHorizontalCode = await helperThing(
|
||||||
inputScript,
|
inputScript,
|
||||||
|
@ -28,6 +28,7 @@ import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
|
|||||||
import { PathToNode, ProgramMemory } from '../executor'
|
import { PathToNode, ProgramMemory } from '../executor'
|
||||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||||
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
||||||
|
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
|
||||||
|
|
||||||
type LineInputsType =
|
type LineInputsType =
|
||||||
| 'xAbsolute'
|
| 'xAbsolute'
|
||||||
@ -1136,27 +1137,18 @@ export function getRemoveConstraintsTransform(
|
|||||||
|
|
||||||
// check if the function is locked down and so can't be transformed
|
// check if the function is locked down and so can't be transformed
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return transformInfo
|
||||||
if (a?.type !== 'Literal' || b?.type !== 'Literal') {
|
|
||||||
return transformInfo
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') {
|
|
||||||
return transformInfo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
Array.isArray(firstArg.val) &&
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
if (isTwoValFree) {
|
if (isTwoValFree) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
if (isOneValFree) {
|
if (isOneValFree) {
|
||||||
return transformInfo
|
return transformInfo
|
||||||
}
|
}
|
||||||
@ -1187,25 +1179,12 @@ function getTransformMapPath(
|
|||||||
|
|
||||||
// check if the function is locked down and so can't be transformed
|
// check if the function is locked down and so can't be transformed
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return false
|
||||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
if (isLiteralArrayOrStatic(firstArg.val)) {
|
||||||
Array.isArray(firstArg.val) &&
|
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
const isOneValFree =
|
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
|
||||||
if (isTwoValFree || isOneValFree) {
|
|
||||||
const info = transformMap?.[name]?.free?.[constraintType]
|
const info = transformMap?.[name]?.free?.[constraintType]
|
||||||
if (info)
|
if (info)
|
||||||
return {
|
return {
|
||||||
@ -1259,7 +1238,7 @@ export function getConstraintType(
|
|||||||
if (fnName === 'xLineTo') return 'yAbsolute'
|
if (fnName === 'xLineTo') return 'yAbsolute'
|
||||||
if (fnName === 'yLineTo') return 'xAbsolute'
|
if (fnName === 'yLineTo') return 'xAbsolute'
|
||||||
} else {
|
} else {
|
||||||
const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
|
const isFirstArgLockedDown = isNotLiteralArrayOrStatic(val[0])
|
||||||
if (fnName === 'line')
|
if (fnName === 'line')
|
||||||
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
||||||
if (fnName === 'lineTo')
|
if (fnName === 'lineTo')
|
||||||
@ -1452,7 +1431,7 @@ export function transformAstSketchLines({
|
|||||||
|
|
||||||
const varName = varDec.id.name
|
const varName = varDec.id.name
|
||||||
const sketchGroup = programMemory.root?.[varName]
|
const sketchGroup = programMemory.root?.[varName]
|
||||||
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
|
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
||||||
throw new Error('not a sketch group')
|
throw new Error('not a sketch group')
|
||||||
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
|
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
|
||||||
const referencedSegment = referencedSegmentRange
|
const referencedSegment = referencedSegmentRange
|
||||||
@ -1538,23 +1517,46 @@ export function getConstraintLevelFromSourceRange(
|
|||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
|
|
||||||
// check if the function is fully constrained
|
// check if the function is fully constrained
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return 'full'
|
||||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full'
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') return 'full'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
Array.isArray(firstArg.val) &&
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
|
|
||||||
if (isTwoValFree) return 'free'
|
if (isTwoValFree) return 'free'
|
||||||
if (isOneValFree) return 'partial'
|
if (isOneValFree) return 'partial'
|
||||||
|
|
||||||
return 'partial'
|
return 'partial'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isLiteralArrayOrStatic(
|
||||||
|
val: Value | [Value, Value] | [Value, Value, Value] | undefined
|
||||||
|
): boolean {
|
||||||
|
if (!val) return false
|
||||||
|
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
const [a, b] = val
|
||||||
|
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
val.type === 'Literal' ||
|
||||||
|
(val.type === 'UnaryExpression' && val.argument.type === 'Literal')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotLiteralArrayOrStatic(
|
||||||
|
val: Value | [Value, Value] | [Value, Value, Value]
|
||||||
|
): boolean {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
const [a, b] = val
|
||||||
|
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(val.type !== 'Literal' && val.type !== 'UnaryExpression') ||
|
||||||
|
(val.type === 'UnaryExpression' && val.argument.type !== 'Literal')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -131,10 +131,12 @@ const yi=45`
|
|||||||
})
|
})
|
||||||
it('test negative and decimal numbers', () => {
|
it('test negative and decimal numbers', () => {
|
||||||
expect(stringSummaryLexer('-1')).toEqual([
|
expect(stringSummaryLexer('-1')).toEqual([
|
||||||
"number '-1' from 0 to 2",
|
"operator '-' from 0 to 1",
|
||||||
|
"number '1' from 1 to 2",
|
||||||
])
|
])
|
||||||
expect(stringSummaryLexer('-1.5')).toEqual([
|
expect(stringSummaryLexer('-1.5')).toEqual([
|
||||||
"number '-1.5' from 0 to 4",
|
"operator '-' from 0 to 1",
|
||||||
|
"number '1.5' from 1 to 4",
|
||||||
])
|
])
|
||||||
expect(stringSummaryLexer('1.5')).toEqual([
|
expect(stringSummaryLexer('1.5')).toEqual([
|
||||||
"number '1.5' from 0 to 3",
|
"number '1.5' from 0 to 3",
|
||||||
@ -158,10 +160,12 @@ const yi=45`
|
|||||||
"whitespace ' ' from 3 to 4",
|
"whitespace ' ' from 3 to 4",
|
||||||
"operator '+' from 4 to 5",
|
"operator '+' from 4 to 5",
|
||||||
"whitespace ' ' from 5 to 6",
|
"whitespace ' ' from 5 to 6",
|
||||||
"number '-2.5' from 6 to 10",
|
"operator '-' from 6 to 7",
|
||||||
|
"number '2.5' from 7 to 10",
|
||||||
])
|
])
|
||||||
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
|
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
|
||||||
"number '-1.5' from 0 to 4",
|
"operator '-' from 0 to 1",
|
||||||
|
"number '1.5' from 1 to 4",
|
||||||
"whitespace ' ' from 4 to 5",
|
"whitespace ' ' from 4 to 5",
|
||||||
"operator '+' from 5 to 6",
|
"operator '+' from 5 to 6",
|
||||||
"whitespace ' ' from 6 to 7",
|
"whitespace ' ' from 6 to 7",
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
const noModifiersPressed = (e: React.MouseEvent) =>
|
const noModifiersPressed = (e: React.MouseEvent) =>
|
||||||
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
||||||
|
|
||||||
export type CADProgram =
|
export type CameraSystem =
|
||||||
| 'KittyCAD'
|
| 'KittyCAD'
|
||||||
| 'OnShape'
|
| 'OnShape'
|
||||||
|
| 'Trackpad Friendly'
|
||||||
| 'Solidworks'
|
| 'Solidworks'
|
||||||
| 'NX'
|
| 'NX'
|
||||||
| 'Creo'
|
| 'Creo'
|
||||||
| 'AutoCAD'
|
| 'AutoCAD'
|
||||||
|
|
||||||
export const cadPrograms: CADProgram[] = [
|
export const cameraSystems: CameraSystem[] = [
|
||||||
'KittyCAD',
|
'KittyCAD',
|
||||||
'OnShape',
|
'OnShape',
|
||||||
|
'Trackpad Friendly',
|
||||||
'Solidworks',
|
'Solidworks',
|
||||||
'NX',
|
'NX',
|
||||||
'Creo',
|
'Creo',
|
||||||
@ -21,12 +23,14 @@ export const cadPrograms: CADProgram[] = [
|
|||||||
interface MouseGuardHandler {
|
interface MouseGuardHandler {
|
||||||
description: string
|
description: string
|
||||||
callback: (e: React.MouseEvent) => boolean
|
callback: (e: React.MouseEvent) => boolean
|
||||||
|
lenientDragStartButton?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MouseGuardZoomHandler {
|
interface MouseGuardZoomHandler {
|
||||||
description: string
|
description: string
|
||||||
dragCallback: (e: React.MouseEvent) => boolean
|
dragCallback: (e: React.MouseEvent) => boolean
|
||||||
scrollCallback: (e: React.MouseEvent) => boolean
|
scrollCallback: (e: React.MouseEvent) => boolean
|
||||||
|
lenientDragStartButton?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MouseGuard {
|
interface MouseGuard {
|
||||||
@ -35,12 +39,12 @@ interface MouseGuard {
|
|||||||
rotate: MouseGuardHandler
|
rotate: MouseGuardHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
KittyCAD: {
|
KittyCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Shift + drag or middle click + drag',
|
description: 'Right click + Shift + drag or middle click + drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(e.button === 3 && noModifiersPressed(e)) ||
|
(e.button === 1 && noModifiersPressed(e)) ||
|
||||||
(e.button === 2 && e.shiftKey),
|
(e.button === 2 && e.shiftKey),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
@ -58,7 +62,7 @@ export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
|
|||||||
description: 'Right click + Ctrl + drag or middle click + drag',
|
description: 'Right click + Ctrl + drag or middle click + drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(e.button === 2 && e.ctrlKey) ||
|
(e.button === 2 && e.ctrlKey) ||
|
||||||
(e.button === 3 && noModifiersPressed(e)),
|
(e.button === 1 && noModifiersPressed(e)),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel',
|
description: 'Scroll wheel',
|
||||||
@ -70,55 +74,74 @@ export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
|
|||||||
callback: (e) => e.button === 2 && noModifiersPressed(e),
|
callback: (e) => e.button === 2 && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'Trackpad Friendly': {
|
||||||
|
pan: {
|
||||||
|
description: 'Left click + Alt + Shift + drag or middle click + drag',
|
||||||
|
callback: (e) =>
|
||||||
|
(e.button === 0 && e.altKey && e.shiftKey && !e.metaKey) ||
|
||||||
|
(e.button === 1 && noModifiersPressed(e)),
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
description: 'Scroll wheel or Left click + Alt + OS + drag',
|
||||||
|
dragCallback: (e) => e.button === 0 && e.altKey && e.metaKey,
|
||||||
|
scrollCallback: () => true,
|
||||||
|
},
|
||||||
|
rotate: {
|
||||||
|
description: 'Left click + Alt + drag',
|
||||||
|
callback: (e) => e.button === 0 && e.altKey && !e.shiftKey && !e.metaKey,
|
||||||
|
lenientDragStartButton: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
Solidworks: {
|
Solidworks: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Ctrl + drag',
|
description: 'Right click + Ctrl + drag',
|
||||||
callback: (e) => e.button === 2 && e.ctrlKey,
|
callback: (e) => e.button === 2 && e.ctrlKey,
|
||||||
|
lenientDragStartButton: 2,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Shift + drag',
|
description: 'Scroll wheel or Middle click + Shift + drag',
|
||||||
dragCallback: (e) => e.button === 3 && e.shiftKey,
|
dragCallback: (e) => e.button === 1 && e.shiftKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 3 && noModifiersPressed(e),
|
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NX: {
|
NX: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => e.button === 3 && e.shiftKey,
|
callback: (e) => e.button === 1 && e.shiftKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||||
dragCallback: (e) => e.button === 3 && e.ctrlKey,
|
dragCallback: (e) => e.button === 1 && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 3 && noModifiersPressed(e),
|
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Creo: {
|
Creo: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => e.button === 3 && e.shiftKey,
|
callback: (e) => e.button === 1 && e.shiftKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||||
dragCallback: (e) => e.button === 3 && e.ctrlKey,
|
dragCallback: (e) => e.button === 1 && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 3 && noModifiersPressed(e),
|
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AutoCAD: {
|
AutoCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 3 && noModifiersPressed(e),
|
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel',
|
description: 'Scroll wheel',
|
||||||
@ -127,7 +150,7 @@ export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
|
|||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => e.button === 3 && e.shiftKey,
|
callback: (e) => e.button === 1 && e.shiftKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
readDir,
|
readDir,
|
||||||
writeTextFile,
|
writeTextFile,
|
||||||
} from '@tauri-apps/api/fs'
|
} from '@tauri-apps/api/fs'
|
||||||
import { documentDir } from '@tauri-apps/api/path'
|
import { documentDir, homeDir } from '@tauri-apps/api/path'
|
||||||
import { isTauri } from './isTauri'
|
import { isTauri } from './isTauri'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { ProjectWithEntryPointMetadata } from '../Router'
|
||||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
@ -32,7 +32,13 @@ export async function initializeProjectDirectory(directory: string) {
|
|||||||
return directory
|
return directory
|
||||||
}
|
}
|
||||||
|
|
||||||
const docDirectory = await documentDir()
|
let docDirectory: string
|
||||||
|
try {
|
||||||
|
docDirectory = await documentDir()
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
docDirectory = await homeDir() // seems to work better on Linux
|
||||||
|
}
|
||||||
|
|
||||||
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER
|
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class MockEngineCommandManager {
|
|||||||
|
|
||||||
export async function enginelessExecutor(
|
export async function enginelessExecutor(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
pm: ProgramMemory = { root: {} }
|
pm: ProgramMemory = { root: {}, return: null }
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
@ -64,7 +64,7 @@ export async function enginelessExecutor(
|
|||||||
|
|
||||||
export async function executor(
|
export async function executor(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
pm: ProgramMemory = { root: {} }
|
pm: ProgramMemory = { root: {}, return: null }
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
const engineCommandManager = new EngineCommandManager({
|
const engineCommandManager = new EngineCommandManager({
|
||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
@ -75,6 +75,6 @@ export async function executor(
|
|||||||
await engineCommandManager.waitForReady
|
await engineCommandManager.waitForReady
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(ast, pm, engineCommandManager)
|
const programMemory = await _executor(ast, pm, engineCommandManager)
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
||||||
return programMemory
|
return programMemory
|
||||||
}
|
}
|
||||||
|
@ -118,16 +118,14 @@ async function getUser(context: UserContext) {
|
|||||||
if (!context.token && '__TAURI__' in window) throw 'not log in'
|
if (!context.token && '__TAURI__' in window) throw 'not log in'
|
||||||
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
|
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
|
||||||
if (SKIP_AUTH) return LOCAL_USER
|
if (SKIP_AUTH) return LOCAL_USER
|
||||||
try {
|
const response = await fetch(url, {
|
||||||
const response = await fetch(url, {
|
method: 'GET',
|
||||||
method: 'GET',
|
credentials: 'include',
|
||||||
credentials: 'include',
|
headers,
|
||||||
headers,
|
})
|
||||||
})
|
|
||||||
const user = await response.json()
|
const user = await response.json()
|
||||||
if ('error_code' in user) throw new Error(user.message)
|
if ('error_code' in user) throw new Error(user.message)
|
||||||
return user
|
|
||||||
} catch (e) {
|
return user
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import { CommandBarMeta } from '../lib/commands'
|
import { CommandBarMeta } from '../lib/commands'
|
||||||
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
|
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
|
||||||
import { CADProgram, cadPrograms } from 'lib/cameraControls'
|
import { CameraSystem, cameraSystems } from 'lib/cameraControls'
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export const settingsCommandBarMeta: CommandBarMeta = {
|
|||||||
name: 'cameraControls',
|
name: 'cameraControls',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
defaultValue: 'cameraControls',
|
defaultValue: 'cameraControls',
|
||||||
options: Object.values(cadPrograms).map((v) => ({ name: v })),
|
options: Object.values(cameraSystems).map((v) => ({ name: v })),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -109,7 +109,7 @@ export const settingsMachine = createMachine(
|
|||||||
predictableActionArguments: true,
|
predictableActionArguments: true,
|
||||||
context: {
|
context: {
|
||||||
baseUnit: 'in' as BaseUnit,
|
baseUnit: 'in' as BaseUnit,
|
||||||
cameraControls: 'KittyCAD' as CADProgram,
|
cameraControls: 'KittyCAD' as CameraSystem,
|
||||||
defaultDirectory: '',
|
defaultDirectory: '',
|
||||||
defaultProjectName: DEFAULT_PROJECT_NAME,
|
defaultProjectName: DEFAULT_PROJECT_NAME,
|
||||||
onboardingStatus: '',
|
onboardingStatus: '',
|
||||||
@ -232,7 +232,10 @@ export const settingsMachine = createMachine(
|
|||||||
schema: {
|
schema: {
|
||||||
events: {} as
|
events: {} as
|
||||||
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
||||||
| { type: 'Set Camera Controls'; data: { cameraControls: CADProgram } }
|
| {
|
||||||
|
type: 'Set Camera Controls'
|
||||||
|
data: { cameraControls: CameraSystem }
|
||||||
|
}
|
||||||
| { type: 'Set Default Directory'; data: { defaultDirectory: string } }
|
| { type: 'Set Default Directory'; data: { defaultDirectory: string } }
|
||||||
| {
|
| {
|
||||||
type: 'Set Default Project Name'
|
type: 'Set Default Project Name'
|
||||||
|
@ -18,8 +18,8 @@ import { IndexLoaderData, paths } from '../Router'
|
|||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import {
|
import {
|
||||||
CADProgram,
|
CameraSystem,
|
||||||
cadPrograms,
|
cameraSystems,
|
||||||
cameraMouseDragGuards,
|
cameraMouseDragGuards,
|
||||||
} from 'lib/cameraControls'
|
} from 'lib/cameraControls'
|
||||||
import { UnitSystem } from 'machines/settingsMachine'
|
import { UnitSystem } from 'machines/settingsMachine'
|
||||||
@ -103,11 +103,11 @@ export const Settings = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
send({
|
send({
|
||||||
type: 'Set Camera Controls',
|
type: 'Set Camera Controls',
|
||||||
data: { cameraControls: e.target.value as CADProgram },
|
data: { cameraControls: e.target.value as CameraSystem },
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cadPrograms.map((program) => (
|
{cameraSystems.map((program) => (
|
||||||
<option key={program} value={program}>
|
<option key={program} value={program}>
|
||||||
{program}
|
{program}
|
||||||
</option>
|
</option>
|
||||||
|
@ -43,9 +43,12 @@ export type TooTip =
|
|||||||
| 'yLineTo'
|
| 'yLineTo'
|
||||||
| 'angledLineThatIntersects'
|
| 'angledLineThatIntersects'
|
||||||
|
|
||||||
export const toolTips: TooTip[] = [
|
export const toolTips = [
|
||||||
'lineTo',
|
'sketch_line',
|
||||||
|
'move',
|
||||||
|
// original tooltips
|
||||||
'line',
|
'line',
|
||||||
|
'lineTo',
|
||||||
'angledLine',
|
'angledLine',
|
||||||
'angledLineOfXLength',
|
'angledLineOfXLength',
|
||||||
'angledLineOfYLength',
|
'angledLineOfYLength',
|
||||||
@ -56,7 +59,7 @@ export const toolTips: TooTip[] = [
|
|||||||
'xLineTo',
|
'xLineTo',
|
||||||
'yLineTo',
|
'yLineTo',
|
||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
]
|
] as any as TooTip[]
|
||||||
|
|
||||||
export type GuiModes =
|
export type GuiModes =
|
||||||
| {
|
| {
|
||||||
@ -66,6 +69,7 @@ export type GuiModes =
|
|||||||
mode: 'sketch'
|
mode: 'sketch'
|
||||||
sketchMode: TooTip
|
sketchMode: TooTip
|
||||||
isTooltip: true
|
isTooltip: true
|
||||||
|
waitingFirstClick: boolean
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
id?: string
|
id?: string
|
||||||
@ -84,6 +88,7 @@ export type GuiModes =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
mode: 'canEditSketch'
|
mode: 'canEditSketch'
|
||||||
|
pathId: string
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
@ -122,8 +127,8 @@ export interface StoreState {
|
|||||||
kclErrors: KCLError[]
|
kclErrors: KCLError[]
|
||||||
addKCLError: (err: KCLError) => void
|
addKCLError: (err: KCLError) => void
|
||||||
resetKCLErrors: () => void
|
resetKCLErrors: () => void
|
||||||
ast: Program | null
|
ast: Program
|
||||||
setAst: (ast: Program | null) => void
|
setAst: (ast: Program) => void
|
||||||
updateAst: (
|
updateAst: (
|
||||||
ast: Program,
|
ast: Program,
|
||||||
optionalParams?: {
|
optionalParams?: {
|
||||||
@ -160,8 +165,8 @@ export interface StoreState {
|
|||||||
setIsStreamReady: (isStreamReady: boolean) => void
|
setIsStreamReady: (isStreamReady: boolean) => void
|
||||||
isLSPServerReady: boolean
|
isLSPServerReady: boolean
|
||||||
setIsLSPServerReady: (isLSPServerReady: boolean) => void
|
setIsLSPServerReady: (isLSPServerReady: boolean) => void
|
||||||
buttonDownInStream: number
|
buttonDownInStream: number | undefined
|
||||||
setButtonDownInStream: (buttonDownInStream: number) => void
|
setButtonDownInStream: (buttonDownInStream: number | undefined) => void
|
||||||
didDragInStream: boolean
|
didDragInStream: boolean
|
||||||
setDidDragInStream: (didDragInStream: boolean) => void
|
setDidDragInStream: (didDragInStream: boolean) => void
|
||||||
fileId: string
|
fileId: string
|
||||||
@ -222,12 +227,13 @@ export const useStore = create<StoreState>()(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editorView.dispatch({
|
ranges.length &&
|
||||||
selection: EditorSelection.create(
|
editorView.dispatch({
|
||||||
ranges,
|
selection: EditorSelection.create(
|
||||||
selections.codeBasedSelections.length - 1
|
ranges,
|
||||||
),
|
selections.codeBasedSelections.length - 1
|
||||||
})
|
),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setCursor2: (codeSelections) => {
|
setCursor2: (codeSelections) => {
|
||||||
@ -281,7 +287,15 @@ export const useStore = create<StoreState>()(
|
|||||||
resetKCLErrors: () => {
|
resetKCLErrors: () => {
|
||||||
set({ kclErrors: [] })
|
set({ kclErrors: [] })
|
||||||
},
|
},
|
||||||
ast: null,
|
ast: {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: [],
|
||||||
|
nonCodeMeta: {
|
||||||
|
noneCodeNodes: {},
|
||||||
|
start: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
setAst: (ast) => {
|
setAst: (ast) => {
|
||||||
set({ ast })
|
set({ ast })
|
||||||
},
|
},
|
||||||
@ -290,7 +304,11 @@ export const useStore = create<StoreState>()(
|
|||||||
const astWithUpdatedSource = parser_wasm(newCode)
|
const astWithUpdatedSource = parser_wasm(newCode)
|
||||||
callBack(astWithUpdatedSource)
|
callBack(astWithUpdatedSource)
|
||||||
|
|
||||||
set({ ast: astWithUpdatedSource, code: newCode })
|
set({
|
||||||
|
ast: astWithUpdatedSource,
|
||||||
|
code: newCode,
|
||||||
|
defferedCode: newCode,
|
||||||
|
})
|
||||||
if (focusPath) {
|
if (focusPath) {
|
||||||
const { node } = getNodeFromPath<any>(
|
const { node } = getNodeFromPath<any>(
|
||||||
astWithUpdatedSource,
|
astWithUpdatedSource,
|
||||||
@ -342,7 +360,7 @@ export const useStore = create<StoreState>()(
|
|||||||
setError: (error = '') => {
|
setError: (error = '') => {
|
||||||
set({ errorState: { isError: !!error, error } })
|
set({ errorState: { isError: !!error, error } })
|
||||||
},
|
},
|
||||||
programMemory: { root: {}, pendingMemory: {} },
|
programMemory: { root: {}, return: null },
|
||||||
setProgramMemory: (programMemory) => set({ programMemory }),
|
setProgramMemory: (programMemory) => set({ programMemory }),
|
||||||
isShiftDown: false,
|
isShiftDown: false,
|
||||||
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
||||||
@ -356,7 +374,7 @@ export const useStore = create<StoreState>()(
|
|||||||
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
|
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
|
||||||
isLSPServerReady: false,
|
isLSPServerReady: false,
|
||||||
setIsLSPServerReady: (isLSPServerReady) => set({ isLSPServerReady }),
|
setIsLSPServerReady: (isLSPServerReady) => set({ isLSPServerReady }),
|
||||||
buttonDownInStream: 0,
|
buttonDownInStream: undefined,
|
||||||
setButtonDownInStream: (buttonDownInStream) => {
|
setButtonDownInStream: (buttonDownInStream) => {
|
||||||
set({ buttonDownInStream })
|
set({ buttonDownInStream })
|
||||||
},
|
},
|
||||||
|
724
src/wasm-lib/Cargo.lock
generated
724
src/wasm-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,10 +12,19 @@ bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
|||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
||||||
serde_json = "1.0.93"
|
serde_json = "1.0.106"
|
||||||
wasm-bindgen = "0.2.87"
|
wasm-bindgen = "0.2.87"
|
||||||
wasm-bindgen-futures = "0.4.37"
|
wasm-bindgen-futures = "0.4.37"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
image = "0.24.7"
|
||||||
|
kittycad = "0.2.25"
|
||||||
|
reqwest = { version = "0.11.20", default-features = false }
|
||||||
|
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
|
twenty-twenty = "0.6.1"
|
||||||
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
js-sys = "0.3.64"
|
js-sys = "0.3.64"
|
||||||
|
@ -14,9 +14,9 @@ proc-macro = true
|
|||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
serde = { version = "1.0.186", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.29", features = ["full"] }
|
syn = { version = "2.0.33", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expectorate = "1.0.7"
|
expectorate = "1.0.7"
|
||||||
|
@ -9,7 +9,7 @@ license = "MIT"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||||
clap = { version = "4.4.2", features = ["cargo", "derive", "env", "unicode"] }
|
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"] }
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
derive-docs = { version = "0.1.3" }
|
derive-docs = { version = "0.1.3" }
|
||||||
#derive-docs = { path = "../derive-docs" }
|
#derive-docs = { path = "../derive-docs" }
|
||||||
@ -18,9 +18,9 @@ lazy_static = "1.4.0"
|
|||||||
parse-display = "0.8.2"
|
parse-display = "0.8.2"
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
||||||
serde = {version = "1.0.152", features = ["derive"] }
|
serde = {version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.93"
|
serde_json = "1.0.106"
|
||||||
thiserror = "1.0.47"
|
thiserror = "1.0.48"
|
||||||
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
|
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
|
||||||
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, R
|
|||||||
use crate::{
|
use crate::{
|
||||||
engine::EngineConnection,
|
engine::EngineConnection,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange},
|
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
||||||
parser::PIPE_OPERATOR,
|
parser::PIPE_OPERATOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -449,6 +449,7 @@ pub enum BinaryPart {
|
|||||||
BinaryExpression(Box<BinaryExpression>),
|
BinaryExpression(Box<BinaryExpression>),
|
||||||
CallExpression(Box<CallExpression>),
|
CallExpression(Box<CallExpression>),
|
||||||
UnaryExpression(Box<UnaryExpression>),
|
UnaryExpression(Box<UnaryExpression>),
|
||||||
|
MemberExpression(Box<MemberExpression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BinaryPart> for crate::executor::SourceRange {
|
impl From<BinaryPart> for crate::executor::SourceRange {
|
||||||
@ -471,6 +472,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
|
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
||||||
|
BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +483,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.start(),
|
BinaryPart::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||||
|
BinaryPart::MemberExpression(member_expression) => member_expression.start(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,6 +494,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.end(),
|
BinaryPart::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||||
|
BinaryPart::MemberExpression(member_expression) => member_expression.end(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,15 +504,22 @@ impl BinaryPart {
|
|||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &mut EngineConnection,
|
engine: &mut EngineConnection,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
pipe_info.is_in_pipe = false;
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
|
// stop the execution of the pipe.
|
||||||
|
// THIS IS IMPORTANT.
|
||||||
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
|
new_pipe_info.is_in_pipe = false;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
BinaryPart::Literal(literal) => Ok(literal.into()),
|
BinaryPart::Literal(literal) => Ok(literal.into()),
|
||||||
BinaryPart::Identifier(identifier) => {
|
BinaryPart::Identifier(identifier) => {
|
||||||
let value = memory.get(&identifier.name, identifier.into())?;
|
let value = memory.get(&identifier.name, identifier.into())?;
|
||||||
Ok(value.clone())
|
Ok(value.clone())
|
||||||
}
|
}
|
||||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(memory, pipe_info, engine),
|
BinaryPart::BinaryExpression(binary_expression) => {
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, engine),
|
binary_expression.get_result(memory, &mut new_pipe_info, engine)
|
||||||
|
}
|
||||||
|
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, &mut new_pipe_info, engine),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => {
|
BinaryPart::UnaryExpression(unary_expression) => {
|
||||||
// Return an error this should not happen.
|
// Return an error this should not happen.
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
@ -516,6 +527,7 @@ impl BinaryPart {
|
|||||||
source_ranges: vec![unary_expression.into()],
|
source_ranges: vec![unary_expression.into()],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,6 +541,9 @@ impl BinaryPart {
|
|||||||
}
|
}
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||||
|
BinaryPart::MemberExpression(member_expression) => {
|
||||||
|
member_expression.get_hover_value_for_position(pos, code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,6 +561,9 @@ impl BinaryPart {
|
|||||||
BinaryPart::UnaryExpression(ref mut unary_expression) => {
|
BinaryPart::UnaryExpression(ref mut unary_expression) => {
|
||||||
unary_expression.rename_identifiers(old_name, new_name)
|
unary_expression.rename_identifiers(old_name, new_name)
|
||||||
}
|
}
|
||||||
|
BinaryPart::MemberExpression(ref mut member_expression) => {
|
||||||
|
member_expression.rename_identifiers(old_name, new_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -718,8 +736,12 @@ impl CallExpression {
|
|||||||
binary_expression.get_result(memory, pipe_info, engine)?
|
binary_expression.get_result(memory, pipe_info, engine)?
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
pipe_info.is_in_pipe = false;
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
call_expression.execute(memory, pipe_info, engine)?
|
// stop the execution of the pipe.
|
||||||
|
// THIS IS IMPORTANT.
|
||||||
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
|
new_pipe_info.is_in_pipe = false;
|
||||||
|
call_expression.execute(memory, &mut new_pipe_info, engine)?
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.get_result(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::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?,
|
||||||
@ -740,12 +762,7 @@ impl CallExpression {
|
|||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.clone(),
|
.clone(),
|
||||||
Value::MemberExpression(member_expression) => {
|
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
|
||||||
source_ranges: vec![member_expression.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Value::FunctionExpression(function_expression) => {
|
Value::FunctionExpression(function_expression) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||||
@ -773,7 +790,7 @@ impl CallExpression {
|
|||||||
Function::InMemory => {
|
Function::InMemory => {
|
||||||
let mem = memory.clone();
|
let mem = memory.clone();
|
||||||
let func = mem.get(&fn_name, self.into())?;
|
let func = mem.get(&fn_name, self.into())?;
|
||||||
let result = func.call_fn(&fn_args, memory, engine)?.ok_or_else(|| {
|
let result = func.call_fn(&fn_args, &mem, engine)?.ok_or_else(|| {
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("Result of function {} is undefined", fn_name),
|
message: format!("Result of function {} is undefined", fn_name),
|
||||||
source_ranges: vec![self.into()],
|
source_ranges: vec![self.into()],
|
||||||
@ -1071,23 +1088,23 @@ impl Literal {
|
|||||||
|
|
||||||
impl From<Literal> for MemoryItem {
|
impl From<Literal> for MemoryItem {
|
||||||
fn from(literal: Literal) -> Self {
|
fn from(literal: Literal) -> Self {
|
||||||
MemoryItem::UserVal {
|
MemoryItem::UserVal(UserVal {
|
||||||
value: literal.value.clone(),
|
value: literal.value.clone(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: literal.into(),
|
source_range: literal.into(),
|
||||||
}],
|
}],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Box<Literal>> for MemoryItem {
|
impl From<&Box<Literal>> for MemoryItem {
|
||||||
fn from(literal: &Box<Literal>) -> Self {
|
fn from(literal: &Box<Literal>) -> Self {
|
||||||
MemoryItem::UserVal {
|
MemoryItem::UserVal(UserVal {
|
||||||
value: literal.value.clone(),
|
value: literal.value.clone(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: literal.into(),
|
source_range: literal.into(),
|
||||||
}],
|
}],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1199,8 +1216,12 @@ impl ArrayExpression {
|
|||||||
binary_expression.get_result(memory, pipe_info, engine)?
|
binary_expression.get_result(memory, pipe_info, engine)?
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
pipe_info.is_in_pipe = false;
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
call_expression.execute(memory, pipe_info, engine)?
|
// stop the execution of the pipe.
|
||||||
|
// THIS IS IMPORTANT.
|
||||||
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
|
new_pipe_info.is_in_pipe = false;
|
||||||
|
call_expression.execute(memory, &mut new_pipe_info, engine)?
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.get_result(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::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?,
|
||||||
@ -1212,12 +1233,7 @@ impl ArrayExpression {
|
|||||||
source_ranges: vec![pipe_substitution.into()],
|
source_ranges: vec![pipe_substitution.into()],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Value::MemberExpression(member_expression) => {
|
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
|
||||||
source_ranges: vec![member_expression.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Value::FunctionExpression(function_expression) => {
|
Value::FunctionExpression(function_expression) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||||
@ -1230,12 +1246,12 @@ impl ArrayExpression {
|
|||||||
results.push(result);
|
results.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: results.into(),
|
value: results.into(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
@ -1321,8 +1337,12 @@ impl ObjectExpression {
|
|||||||
binary_expression.get_result(memory, pipe_info, engine)?
|
binary_expression.get_result(memory, pipe_info, engine)?
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
pipe_info.is_in_pipe = false;
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
call_expression.execute(memory, pipe_info, engine)?
|
// stop the execution of the pipe.
|
||||||
|
// THIS IS IMPORTANT.
|
||||||
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
|
new_pipe_info.is_in_pipe = false;
|
||||||
|
call_expression.execute(memory, &mut new_pipe_info, engine)?
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.get_result(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::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?,
|
||||||
@ -1351,12 +1371,12 @@ impl ObjectExpression {
|
|||||||
object.insert(property.key.name.clone(), result.get_json_value()?);
|
object.insert(property.key.name.clone(), result.get_json_value()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: object.into(),
|
value: object.into(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
@ -1535,6 +1555,38 @@ impl MemberExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_result_array(&self, memory: &mut ProgramMemory, index: usize) -> Result<MemoryItem, KclError> {
|
||||||
|
let array = 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::Array(array) = array {
|
||||||
|
if let Some(value) = array.get(index) {
|
||||||
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: value.clone(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("index {} not found in array", index),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("MemberExpression array is not an array: {:?}", array),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_result(&self, memory: &mut ProgramMemory) -> Result<MemoryItem, KclError> {
|
pub fn get_result(&self, memory: &mut ProgramMemory) -> Result<MemoryItem, KclError> {
|
||||||
let property_name = match &self.property {
|
let property_name = match &self.property {
|
||||||
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
|
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
|
||||||
@ -1543,9 +1595,12 @@ impl MemberExpression {
|
|||||||
// Parse this as a string.
|
// Parse this as a string.
|
||||||
if let serde_json::Value::String(string) = value {
|
if let serde_json::Value::String(string) = value {
|
||||||
string
|
string
|
||||||
|
} else if let serde_json::Value::Number(_) = &value {
|
||||||
|
// It can also be a number if we are getting a member of an array.
|
||||||
|
return self.get_result_array(memory, parse_json_number_as_usize(&value, self.into())?);
|
||||||
} else {
|
} else {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("Expected string literal for property name, found {:?}", value),
|
message: format!("Expected string literal or number for property name, found {:?}", value),
|
||||||
source_ranges: vec![literal.into()],
|
source_ranges: vec![literal.into()],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -1563,12 +1618,12 @@ impl MemberExpression {
|
|||||||
|
|
||||||
if let serde_json::Value::Object(map) = object {
|
if let serde_json::Value::Object(map) = object {
|
||||||
if let Some(value) = map.get(&property_name) {
|
if let Some(value) = map.get(&property_name) {
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: value.clone(),
|
value: value.clone(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("Property {} not found in object", property_name),
|
message: format!("Property {} not found in object", property_name),
|
||||||
@ -1674,10 +1729,20 @@ impl BinaryExpression {
|
|||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &mut EngineConnection,
|
engine: &mut EngineConnection,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
pipe_info.is_in_pipe = false;
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
|
// stop the execution of the pipe.
|
||||||
|
// THIS IS IMPORTANT.
|
||||||
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
|
new_pipe_info.is_in_pipe = false;
|
||||||
|
|
||||||
let left_json_value = self.left.get_result(memory, pipe_info, engine)?.get_json_value()?;
|
let left_json_value = self
|
||||||
let right_json_value = self.right.get_result(memory, pipe_info, engine)?.get_json_value()?;
|
.left
|
||||||
|
.get_result(memory, &mut new_pipe_info, engine)?
|
||||||
|
.get_json_value()?;
|
||||||
|
let right_json_value = self
|
||||||
|
.right
|
||||||
|
.get_result(memory, &mut new_pipe_info, engine)?
|
||||||
|
.get_json_value()?;
|
||||||
|
|
||||||
// First check if we are doing string concatenation.
|
// First check if we are doing string concatenation.
|
||||||
if self.operator == BinaryOperator::Add {
|
if self.operator == BinaryOperator::Add {
|
||||||
@ -1686,12 +1751,12 @@ impl BinaryExpression {
|
|||||||
parse_json_value_as_string(&right_json_value),
|
parse_json_value_as_string(&right_json_value),
|
||||||
) {
|
) {
|
||||||
let value = serde_json::Value::String(format!("{}{}", left, right));
|
let value = serde_json::Value::String(format!("{}{}", left, right));
|
||||||
return Ok(MemoryItem::UserVal {
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
value,
|
value,
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1706,12 +1771,12 @@ impl BinaryExpression {
|
|||||||
BinaryOperator::Mod => (left % right).into(),
|
BinaryOperator::Mod => (left % right).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value,
|
value,
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
@ -1737,6 +1802,22 @@ pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_json_number_as_usize(j: &serde_json::Value, source_range: SourceRange) -> Result<usize, KclError> {
|
||||||
|
if let serde_json::Value::Number(n) = &j {
|
||||||
|
Ok(n.as_i64().ok_or_else(|| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
message: format!("Invalid index: {}", j),
|
||||||
|
})
|
||||||
|
})? as usize)
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
message: format!("Invalid index: {}", j),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
||||||
if let serde_json::Value::String(n) = &j {
|
if let serde_json::Value::String(n) = &j {
|
||||||
Some(n.clone())
|
Some(n.clone())
|
||||||
@ -1803,18 +1884,25 @@ impl UnaryExpression {
|
|||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &mut EngineConnection,
|
engine: &mut EngineConnection,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
pipe_info.is_in_pipe = false;
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
|
// stop the execution of the pipe.
|
||||||
|
// THIS IS IMPORTANT.
|
||||||
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
|
new_pipe_info.is_in_pipe = false;
|
||||||
|
|
||||||
let num = parse_json_number_as_f64(
|
let num = parse_json_number_as_f64(
|
||||||
&self.argument.get_result(memory, pipe_info, engine)?.get_json_value()?,
|
&self
|
||||||
|
.argument
|
||||||
|
.get_result(memory, &mut new_pipe_info, engine)?
|
||||||
|
.get_json_value()?,
|
||||||
self.into(),
|
self.into(),
|
||||||
)?;
|
)?;
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
value: (-(num)).into(),
|
value: (-(num)).into(),
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.into(),
|
source_range: self.into(),
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a hover value that includes the given character position.
|
/// Returns a hover value that includes the given character position.
|
||||||
@ -1980,6 +2068,9 @@ impl_value_meta!(FunctionExpression);
|
|||||||
|
|
||||||
impl FunctionExpression {
|
impl FunctionExpression {
|
||||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
|
// We don't want to end with a new line inside nested functions.
|
||||||
|
let mut new_options = options.clone();
|
||||||
|
new_options.insert_final_newline = false;
|
||||||
format!(
|
format!(
|
||||||
"({}) => {{\n{}{}\n}}",
|
"({}) => {{\n{}{}\n}}",
|
||||||
self.params
|
self.params
|
||||||
@ -1988,7 +2079,7 @@ impl FunctionExpression {
|
|||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
options.get_indentation(indentation_level + 1),
|
options.get_indentation(indentation_level + 1),
|
||||||
self.body.recast(options, indentation_level + 1)
|
self.body.recast(&new_options, indentation_level + 1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2056,7 +2147,7 @@ impl FormatOptions {
|
|||||||
Self {
|
Self {
|
||||||
tab_size: 2,
|
tab_size: 2,
|
||||||
use_tabs: false,
|
use_tabs: false,
|
||||||
insert_final_newline: false,
|
insert_final_newline: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2127,7 +2218,8 @@ show(part001)"#;
|
|||||||
r#"const part001 = startSketchAt('default')
|
r#"const part001 = startSketchAt('default')
|
||||||
|> ry(90, %)
|
|> ry(90, %)
|
||||||
|> line('default', %)
|
|> line('default', %)
|
||||||
show(part001)"#
|
show(part001)
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2145,7 +2237,8 @@ show(part001)"#
|
|||||||
recasted,
|
recasted,
|
||||||
r#"const part001 = startSketchAt([0.0, 5.0])
|
r#"const part001 = startSketchAt([0.0, 5.0])
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|> line([0.6804562304, 0.9087880491], %)"#
|
|> line([0.6804562304, 0.9087880491], %)
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2163,7 +2256,8 @@ show(part001)"#
|
|||||||
recasted,
|
recasted,
|
||||||
r#"const part001 = startSketchAt([0.0, 5.0])
|
r#"const part001 = startSketchAt([0.0, 5.0])
|
||||||
|> line([0.4900857016, -0.0240763666], %) // hello world
|
|> line([0.4900857016, -0.0240763666], %) // hello world
|
||||||
|> line([0.6804562304, 0.9087880491], %)"#
|
|> line([0.6804562304, 0.9087880491], %)
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
@ -2182,13 +2276,14 @@ show(part001)"#
|
|||||||
r#"const part001 = startSketchAt([0.0, 5.0])
|
r#"const part001 = startSketchAt([0.0, 5.0])
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
// hello world
|
// hello world
|
||||||
|> line([0.6804562304, 0.9087880491], %)"#
|
|> line([0.6804562304, 0.9087880491], %)
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_comment_in_a_fn_block() {
|
fn test_recast_comment_in_a_fn_block() {
|
||||||
let some_program_string = r#"const myFn = () => {
|
let some_program_string = r#"fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } } /* block
|
const yo = { a: { b: { c: '123' } } } /* block
|
||||||
comment */
|
comment */
|
||||||
@ -2204,7 +2299,7 @@ show(part001)"#
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"const myFn = () => {
|
r#"fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
/* block
|
/* block
|
||||||
@ -2212,7 +2307,8 @@ show(part001)"#
|
|||||||
const key = 'c'
|
const key = 'c'
|
||||||
// this is also a comment
|
// this is also a comment
|
||||||
return things
|
return things
|
||||||
}"#
|
}
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2254,7 +2350,8 @@ a comment between pipe expression statements */
|
|||||||
// and another with just white space between others below
|
// and another with just white space between others below
|
||||||
|> ry(45, %)
|
|> ry(45, %)
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
// one more for good measure"#
|
// one more for good measure
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2276,7 +2373,7 @@ show(part001)"#;
|
|||||||
let program = parser.ast().unwrap();
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(recasted, some_program_string);
|
assert_eq!(recasted.trim(), some_program_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2295,7 +2392,8 @@ const yo = [
|
|||||||
"three",
|
"three",
|
||||||
4 + 5,
|
4 + 5,
|
||||||
" hey oooooo really long long long"
|
" hey oooooo really long long long"
|
||||||
]"#;
|
]
|
||||||
|
"#;
|
||||||
let tokens = crate::tokeniser::lexer(some_program_string);
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
let program = parser.ast().unwrap();
|
let program = parser.ast().unwrap();
|
||||||
@ -2319,7 +2417,7 @@ const things = "things"
|
|||||||
let program = parser.ast().unwrap();
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(recasted, some_program_string.trim());
|
assert_eq!(recasted.trim(), some_program_string.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2337,7 +2435,7 @@ const things = "things"
|
|||||||
let program = parser.ast().unwrap();
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(recasted, some_program_string.trim());
|
assert_eq!(recasted.trim(), some_program_string.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2362,7 +2460,7 @@ const part001 = startSketchAt([0, 0])
|
|||||||
let program = parser.ast().unwrap();
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(recasted, some_program_string);
|
assert_eq!(recasted.trim(), some_program_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2435,7 +2533,8 @@ fn ghi = (part001) => {
|
|||||||
return part001
|
return part001
|
||||||
}
|
}
|
||||||
|
|
||||||
show(mySuperCoolPart)"#
|
show(mySuperCoolPart)
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2454,7 +2553,56 @@ show(mySuperCoolPart)"#
|
|||||||
recasted,
|
recasted,
|
||||||
r#"fn ghi = (newName, y, z) => {
|
r#"fn ghi = (newName, y, z) => {
|
||||||
return newName
|
return newName
|
||||||
}"#
|
}
|
||||||
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_negative_var() {
|
||||||
|
let some_program_string = r#"const w = 20
|
||||||
|
const l = 8
|
||||||
|
const h = 10
|
||||||
|
|
||||||
|
const firstExtrude = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, -l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
show(firstExtrude)"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
recasted,
|
||||||
|
r#"const w = 20
|
||||||
|
const l = 8
|
||||||
|
const h = 10
|
||||||
|
|
||||||
|
const firstExtrude = startSketchAt([0, 0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, -l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
show(firstExtrude)
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_recast_math_start_negative() {
|
||||||
|
let some_program_string = r#"const myVar = -5 + 6"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(recasted.trim(), some_program_string);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,16 +98,14 @@ impl ProgramReturn {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type")]
|
||||||
pub enum MemoryItem {
|
pub enum MemoryItem {
|
||||||
UserVal {
|
UserVal(UserVal),
|
||||||
value: serde_json::Value,
|
|
||||||
#[serde(rename = "__meta")]
|
|
||||||
meta: Vec<Metadata>,
|
|
||||||
},
|
|
||||||
SketchGroup(SketchGroup),
|
SketchGroup(SketchGroup),
|
||||||
ExtrudeGroup(ExtrudeGroup),
|
ExtrudeGroup(ExtrudeGroup),
|
||||||
|
#[ts(skip)]
|
||||||
ExtrudeTransform(ExtrudeTransform),
|
ExtrudeTransform(ExtrudeTransform),
|
||||||
|
#[ts(skip)]
|
||||||
Function {
|
Function {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
func: Option<MemoryFunction>,
|
func: Option<MemoryFunction>,
|
||||||
@ -119,7 +117,16 @@ pub enum MemoryItem {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
|
pub struct UserVal {
|
||||||
|
pub value: serde_json::Value,
|
||||||
|
#[serde(rename = "__meta")]
|
||||||
|
pub meta: Vec<Metadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub struct ExtrudeTransform {
|
pub struct ExtrudeTransform {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub rotation: Rotation,
|
pub rotation: Rotation,
|
||||||
@ -138,7 +145,7 @@ pub type MemoryFunction = fn(
|
|||||||
impl From<MemoryItem> for Vec<SourceRange> {
|
impl From<MemoryItem> for Vec<SourceRange> {
|
||||||
fn from(item: MemoryItem) -> Self {
|
fn from(item: MemoryItem) -> Self {
|
||||||
match item {
|
match item {
|
||||||
MemoryItem::UserVal { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::SketchGroup(s) => s.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::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
@ -149,8 +156,8 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
|||||||
|
|
||||||
impl MemoryItem {
|
impl MemoryItem {
|
||||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||||
if let MemoryItem::UserVal { value, .. } = self {
|
if let MemoryItem::UserVal(user_val) = self {
|
||||||
Ok(value.clone())
|
Ok(user_val.value.clone())
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("Not a user value: {:?}", self),
|
message: format!("Not a user value: {:?}", self),
|
||||||
@ -186,7 +193,7 @@ impl MemoryItem {
|
|||||||
/// A sketch group is a collection of paths.
|
/// A sketch group is a collection of paths.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub struct SketchGroup {
|
pub struct SketchGroup {
|
||||||
/// The id of the sketch group.
|
/// The id of the sketch group.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
@ -238,7 +245,7 @@ impl SketchGroup {
|
|||||||
/// An extrude group is a collection of extrude surfaces.
|
/// An extrude group is a collection of extrude surfaces.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub struct ExtrudeGroup {
|
pub struct ExtrudeGroup {
|
||||||
/// The id of the extrude group.
|
/// The id of the extrude group.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
@ -276,15 +283,15 @@ pub enum BodyType {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Position(pub [f64; 3]);
|
pub struct Position(#[ts(type = "[number, number, number]")] pub [f64; 3]);
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Rotation(pub [f64; 4]);
|
pub struct Rotation(#[ts(type = "[number, number, number, number]")] pub [f64; 4]);
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
|
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct SourceRange(pub [usize; 2]);
|
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
|
||||||
|
|
||||||
impl SourceRange {
|
impl SourceRange {
|
||||||
/// Create a new source range.
|
/// Create a new source range.
|
||||||
@ -401,8 +408,10 @@ impl From<SourceRange> for Metadata {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BasePath {
|
pub struct BasePath {
|
||||||
/// The from point.
|
/// The from point.
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
pub from: [f64; 2],
|
pub from: [f64; 2],
|
||||||
/// The to point.
|
/// The to point.
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
pub to: [f64; 2],
|
pub to: [f64; 2],
|
||||||
/// The name of the path.
|
/// The name of the path.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -595,7 +604,9 @@ pub fn execute(
|
|||||||
|
|
||||||
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||||
} else if let Some(func) = memory.clone().root.get(&fn_name) {
|
} else if let Some(func) = memory.clone().root.get(&fn_name) {
|
||||||
func.call_fn(&args, memory, engine)?;
|
let result = func.call_fn(&args, memory, engine)?;
|
||||||
|
|
||||||
|
memory.return_ = result;
|
||||||
} else {
|
} else {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("No such name {} defined", fn_name),
|
message: format!("No such name {} defined", fn_name),
|
||||||
@ -696,11 +707,39 @@ pub fn execute(
|
|||||||
let result = bin_expr.get_result(memory, &mut pipe_info, engine)?;
|
let result = bin_expr.get_result(memory, &mut pipe_info, engine)?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
|
Value::UnaryExpression(unary_expr) => {
|
||||||
|
let result = unary_expr.get_result(memory, &mut pipe_info, engine)?;
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
|
}
|
||||||
Value::Identifier(identifier) => {
|
Value::Identifier(identifier) => {
|
||||||
let value = memory.get(&identifier.name, identifier.into())?.clone();
|
let value = memory.get(&identifier.name, identifier.into())?.clone();
|
||||||
memory.return_ = Some(ProgramReturn::Value(value));
|
memory.return_ = Some(ProgramReturn::Value(value));
|
||||||
}
|
}
|
||||||
_ => (),
|
Value::Literal(literal) => {
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(literal.into()));
|
||||||
|
}
|
||||||
|
Value::ArrayExpression(array_expr) => {
|
||||||
|
let result = array_expr.execute(memory, &mut pipe_info, engine)?;
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
|
}
|
||||||
|
Value::ObjectExpression(obj_expr) => {
|
||||||
|
let result = obj_expr.execute(memory, &mut pipe_info, engine)?;
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
|
}
|
||||||
|
Value::CallExpression(call_expr) => {
|
||||||
|
let result = call_expr.execute(memory, &mut pipe_info, engine)?;
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
|
}
|
||||||
|
Value::MemberExpression(member_expr) => {
|
||||||
|
let result = member_expr.get_result(memory)?;
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
|
}
|
||||||
|
Value::PipeExpression(pipe_expr) => {
|
||||||
|
let result = pipe_expr.get_result(memory, &mut pipe_info, engine)?;
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
|
}
|
||||||
|
Value::PipeSubstitution(_) => {}
|
||||||
|
Value::FunctionExpression(_) => {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -774,16 +813,16 @@ show(part001)"#,
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_fn_definitions() {
|
async fn test_execute_fn_definitions() {
|
||||||
let ast = r#"const def = (x) => {
|
let ast = r#"fn def = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
const ghi = (x) => {
|
fn ghi = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
const jkl = (x) => {
|
fn jkl = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
const hmm = (x) => {
|
fn hmm = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -856,4 +895,281 @@ show(part001)"#;
|
|||||||
|
|
||||||
parse_execute(ast).await.unwrap();
|
parse_execute(ast).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_function_literal_in_pipe() {
|
||||||
|
let ast = r#"const w = 20
|
||||||
|
const l = 8
|
||||||
|
const h = 10
|
||||||
|
|
||||||
|
fn thing = () => {
|
||||||
|
return -8
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstExtrude = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, thing()], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
show(firstExtrude)"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_function_unary_in_pipe() {
|
||||||
|
let ast = r#"const w = 20
|
||||||
|
const l = 8
|
||||||
|
const h = 10
|
||||||
|
|
||||||
|
fn thing = (x) => {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstExtrude = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, thing(8)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
show(firstExtrude)"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_function_array_in_pipe() {
|
||||||
|
let ast = r#"const w = 20
|
||||||
|
const l = 8
|
||||||
|
const h = 10
|
||||||
|
|
||||||
|
fn thing = (x) => {
|
||||||
|
return [0, -x]
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstExtrude = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line(thing(8), %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
show(firstExtrude)"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_function_call_in_pipe() {
|
||||||
|
let ast = r#"const w = 20
|
||||||
|
const l = 8
|
||||||
|
const h = 10
|
||||||
|
|
||||||
|
fn other_thing = (y) => {
|
||||||
|
return -y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thing = (x) => {
|
||||||
|
return other_thing(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstExtrude = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, thing(8)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
show(firstExtrude)"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_function_sketch() {
|
||||||
|
let ast = r#"fn box = (h, l, w) => {
|
||||||
|
const myBox = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, -l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
const fnBox = box(3, 6, 10)
|
||||||
|
|
||||||
|
show(fnBox)"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_get_member_of_object_with_function_period() {
|
||||||
|
let ast = r#"fn box = (obj) => {
|
||||||
|
let myBox = startSketchAt(obj.start)
|
||||||
|
|> line([0, obj.l], %)
|
||||||
|
|> line([obj.w, 0], %)
|
||||||
|
|> line([0, -obj.l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(obj.h, %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
||||||
|
|
||||||
|
show(thisBox)
|
||||||
|
"#;
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_get_member_of_object_with_function_brace() {
|
||||||
|
let ast = r#"fn box = (obj) => {
|
||||||
|
let myBox = startSketchAt(obj["start"])
|
||||||
|
|> line([0, obj["l"]], %)
|
||||||
|
|> line([obj["w"], 0], %)
|
||||||
|
|> line([0, -obj["l"]], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(obj["h"], %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
||||||
|
|
||||||
|
show(thisBox)
|
||||||
|
"#;
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_get_member_of_object_with_function_mix_period_brace() {
|
||||||
|
let ast = r#"fn box = (obj) => {
|
||||||
|
let myBox = startSketchAt(obj["start"])
|
||||||
|
|> line([0, obj["l"]], %)
|
||||||
|
|> line([obj["w"], 0], %)
|
||||||
|
|> line([10 - obj["w"], -obj.l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(obj["h"], %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
||||||
|
|
||||||
|
show(thisBox)
|
||||||
|
"#;
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
#[ignore] // ignore til we get loops
|
||||||
|
async fn test_execute_with_function_sketch_loop_objects() {
|
||||||
|
let ast = r#"fn box = (obj) => {
|
||||||
|
let myBox = startSketchAt(obj.start)
|
||||||
|
|> line([0, obj.l], %)
|
||||||
|
|> line([obj.w, 0], %)
|
||||||
|
|> line([0, -obj.l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(obj.h, %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
|
||||||
|
const thisBox = box(var)
|
||||||
|
show(thisBox)
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
#[ignore] // ignore til we get loops
|
||||||
|
async fn test_execute_with_function_sketch_loop_array() {
|
||||||
|
let ast = r#"fn box = (h, l, w, start) => {
|
||||||
|
const myBox = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, -l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
|
||||||
|
const thisBox = box(var[0], var[1], var[2], var[3])
|
||||||
|
show(thisBox)
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_get_member_of_array_with_function() {
|
||||||
|
let ast = r#"fn box = (array) => {
|
||||||
|
let myBox = startSketchAt(array[0])
|
||||||
|
|> line([0, array[1]], %)
|
||||||
|
|> line([array[2], 0], %)
|
||||||
|
|> line([0, -array[1]], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(array[3], %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisBox = box([[0,0], 6, 10, 3])
|
||||||
|
|
||||||
|
show(thisBox)
|
||||||
|
"#;
|
||||||
|
parse_execute(ast).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_math_execute_with_functions() {
|
||||||
|
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(5.0),
|
||||||
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_math_execute() {
|
||||||
|
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(7.4),
|
||||||
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_math_execute_start_negative() {
|
||||||
|
let ast = r#"const myVar = -5 + 6"#;
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(1.0),
|
||||||
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_math_define_decimal_without_leading_zero() {
|
||||||
|
let ast = r#"let thing = .4 + 7"#;
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(7.4),
|
||||||
|
memory.root.get("thing").unwrap().get_json_value().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_syntax_tree_types::{
|
abstract_syntax_tree_types::{
|
||||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, ValueMeta,
|
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression, ValueMeta,
|
||||||
},
|
},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::SourceRange,
|
executor::SourceRange,
|
||||||
@ -81,6 +81,7 @@ pub enum MathExpression {
|
|||||||
BinaryExpression(Box<BinaryExpression>),
|
BinaryExpression(Box<BinaryExpression>),
|
||||||
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
|
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
|
||||||
ParenthesisToken(Box<ParenthesisToken>),
|
ParenthesisToken(Box<ParenthesisToken>),
|
||||||
|
MemberExpression(Box<MemberExpression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathExpression {
|
impl MathExpression {
|
||||||
@ -92,6 +93,7 @@ impl MathExpression {
|
|||||||
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
|
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
|
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
|
||||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(),
|
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(),
|
||||||
|
MathExpression::MemberExpression(member_expression) => member_expression.start(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +105,7 @@ impl MathExpression {
|
|||||||
MathExpression::BinaryExpression(binary_expression) => binary_expression.end(),
|
MathExpression::BinaryExpression(binary_expression) => binary_expression.end(),
|
||||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
|
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
|
||||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(),
|
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(),
|
||||||
|
MathExpression::MemberExpression(member_expression) => member_expression.end(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,7 +136,7 @@ impl ReversePolishNotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let current_token = self.parser.get_token(0)?;
|
let current_token = self.parser.get_token(0)?;
|
||||||
if current_token.token_type == TokenType::Word || current_token.token_type == TokenType::Keyword {
|
if current_token.token_type == TokenType::Word {
|
||||||
if let Ok(next) = self.parser.get_token(1) {
|
if let Ok(next) = self.parser.get_token(1) {
|
||||||
if next.token_type == TokenType::Brace && next.value == "(" {
|
if next.token_type == TokenType::Brace && next.value == "(" {
|
||||||
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
|
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
|
||||||
@ -149,6 +152,24 @@ impl ReversePolishNotation {
|
|||||||
);
|
);
|
||||||
return rpn.parse();
|
return rpn.parse();
|
||||||
}
|
}
|
||||||
|
if (current_token.token_type == TokenType::Word)
|
||||||
|
&& (next.token_type == TokenType::Period
|
||||||
|
|| (next.token_type == TokenType::Brace && next.value == "["))
|
||||||
|
{
|
||||||
|
// Find the end of the binary expression, ie the member expression.
|
||||||
|
let end = self.parser.make_member_expression(0)?.last_index;
|
||||||
|
let rpn = ReversePolishNotation::new(
|
||||||
|
&self.parser.tokens[end + 1..],
|
||||||
|
&self
|
||||||
|
.previous_postfix
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(self.parser.tokens[0..end + 1].iter().cloned())
|
||||||
|
.collect::<Vec<Token>>(),
|
||||||
|
&self.operators,
|
||||||
|
);
|
||||||
|
return rpn.parse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rpn = ReversePolishNotation::new(
|
let rpn = ReversePolishNotation::new(
|
||||||
@ -164,7 +185,6 @@ impl ReversePolishNotation {
|
|||||||
return rpn.parse();
|
return rpn.parse();
|
||||||
} else if current_token.token_type == TokenType::Number
|
} else if current_token.token_type == TokenType::Number
|
||||||
|| current_token.token_type == TokenType::Word
|
|| current_token.token_type == TokenType::Word
|
||||||
|| current_token.token_type == TokenType::Keyword
|
|
||||||
|| current_token.token_type == TokenType::String
|
|| current_token.token_type == TokenType::String
|
||||||
{
|
{
|
||||||
let rpn = ReversePolishNotation::new(
|
let rpn = ReversePolishNotation::new(
|
||||||
@ -180,6 +200,35 @@ impl ReversePolishNotation {
|
|||||||
return rpn.parse();
|
return rpn.parse();
|
||||||
} else if let Ok(binop) = BinaryOperator::from_str(current_token.value.as_str()) {
|
} else if let Ok(binop) = BinaryOperator::from_str(current_token.value.as_str()) {
|
||||||
if !self.operators.is_empty() {
|
if !self.operators.is_empty() {
|
||||||
|
if binop == BinaryOperator::Sub {
|
||||||
|
// We need to check if we have a "sub" and if the previous token is a word or
|
||||||
|
// number or string, then we need to treat it as a negative number.
|
||||||
|
// This oddity only applies to the "-" operator.
|
||||||
|
if let Some(prevtoken) = self.previous_postfix.last() {
|
||||||
|
if prevtoken.token_type == TokenType::Operator {
|
||||||
|
// Get the next token and see if it is a number.
|
||||||
|
if let Ok(nexttoken) = self.parser.get_token(1) {
|
||||||
|
if nexttoken.token_type == TokenType::Number {
|
||||||
|
// We have a negative number/ word or string.
|
||||||
|
// Change the value of the token to be the negative number/ word or string.
|
||||||
|
let mut new_token = nexttoken.clone();
|
||||||
|
new_token.value = format!("-{}", nexttoken.value);
|
||||||
|
let rpn = ReversePolishNotation::new(
|
||||||
|
&self.parser.tokens[2..],
|
||||||
|
&self
|
||||||
|
.previous_postfix
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(vec![new_token.clone()])
|
||||||
|
.collect::<Vec<Token>>(),
|
||||||
|
&self.operators,
|
||||||
|
);
|
||||||
|
return rpn.parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Ok(prevbinop) = BinaryOperator::from_str(self.operators[self.operators.len() - 1].value.as_str())
|
if let Ok(prevbinop) = BinaryOperator::from_str(self.operators[self.operators.len() - 1].value.as_str())
|
||||||
{
|
{
|
||||||
if prevbinop.precedence() >= binop.precedence() {
|
if prevbinop.precedence() >= binop.precedence() {
|
||||||
@ -196,6 +245,29 @@ impl ReversePolishNotation {
|
|||||||
return rpn.parse();
|
return rpn.parse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if self.previous_postfix.is_empty()
|
||||||
|
&& current_token.token_type == TokenType::Operator
|
||||||
|
&& current_token.value == "-"
|
||||||
|
{
|
||||||
|
if let Ok(nexttoken) = self.parser.get_token(1) {
|
||||||
|
if nexttoken.token_type == TokenType::Number {
|
||||||
|
// We have a negative number/ word or string.
|
||||||
|
// Change the value of the token to be the negative number/ word or string.
|
||||||
|
let mut new_token = nexttoken.clone();
|
||||||
|
new_token.value = format!("-{}", nexttoken.value);
|
||||||
|
let rpn = ReversePolishNotation::new(
|
||||||
|
&self.parser.tokens[2..],
|
||||||
|
&self
|
||||||
|
.previous_postfix
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(vec![new_token.clone()])
|
||||||
|
.collect::<Vec<Token>>(),
|
||||||
|
&self.operators,
|
||||||
|
);
|
||||||
|
return rpn.parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rpn = ReversePolishNotation::new(
|
let rpn = ReversePolishNotation::new(
|
||||||
@ -299,7 +371,7 @@ impl ReversePolishNotation {
|
|||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![SourceRange([a.start(), a.end()])],
|
source_ranges: vec![SourceRange([a.start(), a.end()])],
|
||||||
message: format!("{:?}", a),
|
message: format!("{:?}", a),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -338,7 +410,7 @@ impl ReversePolishNotation {
|
|||||||
start_extended: None,
|
start_extended: None,
|
||||||
})));
|
})));
|
||||||
return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||||
} else if current_token.token_type == TokenType::Word || current_token.token_type == TokenType::Keyword {
|
} else if current_token.token_type == TokenType::Word {
|
||||||
if reverse_polish_notation_tokens.len() > 1 {
|
if reverse_polish_notation_tokens.len() > 1 {
|
||||||
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
||||||
&& reverse_polish_notation_tokens[1].value == "("
|
&& reverse_polish_notation_tokens[1].value == "("
|
||||||
@ -350,6 +422,18 @@ impl ReversePolishNotation {
|
|||||||
)));
|
)));
|
||||||
return self.build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
|
return self.build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
|
||||||
}
|
}
|
||||||
|
if reverse_polish_notation_tokens[1].token_type == TokenType::Period
|
||||||
|
|| (reverse_polish_notation_tokens[1].token_type == TokenType::Brace
|
||||||
|
&& reverse_polish_notation_tokens[1].value == "[")
|
||||||
|
{
|
||||||
|
let mut new_stack = stack;
|
||||||
|
let member_expression = self.parser.make_member_expression(0)?;
|
||||||
|
new_stack.push(MathExpression::MemberExpression(Box::new(member_expression.expression)));
|
||||||
|
return self.build_tree(
|
||||||
|
&reverse_polish_notation_tokens[member_expression.last_index + 1..],
|
||||||
|
new_stack,
|
||||||
|
);
|
||||||
|
}
|
||||||
let mut new_stack = stack;
|
let mut new_stack = stack;
|
||||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||||
name: current_token.value.clone(),
|
name: current_token.value.clone(),
|
||||||
@ -396,7 +480,7 @@ impl ReversePolishNotation {
|
|||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
message: format!("{:?}", a),
|
message: format!("{:?}", a),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let paran = match &stack[stack.len() - 2] {
|
let paran = match &stack[stack.len() - 2] {
|
||||||
@ -445,7 +529,7 @@ impl ReversePolishNotation {
|
|||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
message: format!("{:?}", a),
|
message: format!("{:?}", a),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut new_stack = stack[0..stack.len() - 2].to_vec();
|
let mut new_stack = stack[0..stack.len() - 2].to_vec();
|
||||||
@ -483,6 +567,10 @@ impl ReversePolishNotation {
|
|||||||
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.start),
|
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.start),
|
||||||
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.start),
|
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.start),
|
||||||
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.start),
|
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.start),
|
||||||
|
MathExpression::MemberExpression(member_expression) => (
|
||||||
|
BinaryPart::MemberExpression(member_expression.clone()),
|
||||||
|
member_expression.start,
|
||||||
|
),
|
||||||
a => {
|
a => {
|
||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
@ -513,6 +601,10 @@ impl ReversePolishNotation {
|
|||||||
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.end),
|
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.end),
|
||||||
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.end),
|
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.end),
|
||||||
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.end),
|
MathExpression::BinaryExpression(bin_exp) => (BinaryPart::BinaryExpression(bin_exp.clone()), bin_exp.end),
|
||||||
|
MathExpression::MemberExpression(member_expression) => (
|
||||||
|
BinaryPart::MemberExpression(member_expression.clone()),
|
||||||
|
member_expression.end,
|
||||||
|
),
|
||||||
a => {
|
a => {
|
||||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
@ -521,13 +613,7 @@ impl ReversePolishNotation {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let right_end = match right.0.clone() {
|
let right_end = right.0.clone().end();
|
||||||
BinaryPart::BinaryExpression(_bin_exp) => right.1,
|
|
||||||
BinaryPart::Literal(lit) => lit.end,
|
|
||||||
BinaryPart::Identifier(ident) => ident.end,
|
|
||||||
BinaryPart::CallExpression(call) => call.end,
|
|
||||||
BinaryPart::UnaryExpression(unary_exp) => unary_exp.end,
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree = BinaryExpression {
|
let tree = BinaryExpression {
|
||||||
operator: BinaryOperator::from_str(¤t_token.value.clone()).map_err(|err| {
|
operator: BinaryOperator::from_str(¤t_token.value.clone()).map_err(|err| {
|
||||||
@ -562,25 +648,13 @@ impl MathParser {
|
|||||||
pub fn parse(&mut self) -> Result<BinaryExpression, KclError> {
|
pub fn parse(&mut self) -> Result<BinaryExpression, KclError> {
|
||||||
let rpn = self.rpn.parse()?;
|
let rpn = self.rpn.parse()?;
|
||||||
let tree_with_maybe_bad_top_level_start_end = self.rpn.build_tree(&rpn, vec![])?;
|
let tree_with_maybe_bad_top_level_start_end = self.rpn.build_tree(&rpn, vec![])?;
|
||||||
let left_start = match tree_with_maybe_bad_top_level_start_end.clone().left {
|
let left_start = tree_with_maybe_bad_top_level_start_end.clone().left.start();
|
||||||
BinaryPart::BinaryExpression(bin_exp) => bin_exp.start,
|
|
||||||
BinaryPart::Literal(lit) => lit.start,
|
|
||||||
BinaryPart::Identifier(ident) => ident.start,
|
|
||||||
BinaryPart::CallExpression(call) => call.start,
|
|
||||||
BinaryPart::UnaryExpression(unary_exp) => unary_exp.start,
|
|
||||||
};
|
|
||||||
let min_start = if left_start < tree_with_maybe_bad_top_level_start_end.start {
|
let min_start = if left_start < tree_with_maybe_bad_top_level_start_end.start {
|
||||||
left_start
|
left_start
|
||||||
} else {
|
} else {
|
||||||
tree_with_maybe_bad_top_level_start_end.start
|
tree_with_maybe_bad_top_level_start_end.start
|
||||||
};
|
};
|
||||||
let right_end = match tree_with_maybe_bad_top_level_start_end.clone().right {
|
let right_end = tree_with_maybe_bad_top_level_start_end.clone().right.end();
|
||||||
BinaryPart::BinaryExpression(bin_exp) => bin_exp.end,
|
|
||||||
BinaryPart::Literal(lit) => lit.end,
|
|
||||||
BinaryPart::Identifier(ident) => ident.end,
|
|
||||||
BinaryPart::CallExpression(call) => call.end,
|
|
||||||
BinaryPart::UnaryExpression(unary_exp) => unary_exp.end,
|
|
||||||
};
|
|
||||||
let max_end = if right_end > tree_with_maybe_bad_top_level_start_end.end {
|
let max_end = if right_end > tree_with_maybe_bad_top_level_start_end.end {
|
||||||
right_end
|
right_end
|
||||||
} else {
|
} else {
|
||||||
@ -629,6 +703,60 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_expression_add_no_spaces() {
|
||||||
|
let tokens = crate::tokeniser::lexer("1+2");
|
||||||
|
let mut parser = MathParser::new(&tokens);
|
||||||
|
let result = parser.parse().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
BinaryExpression {
|
||||||
|
operator: BinaryOperator::Add,
|
||||||
|
start: 0,
|
||||||
|
end: 3,
|
||||||
|
left: BinaryPart::Literal(Box::new(Literal {
|
||||||
|
value: serde_json::Value::Number(serde_json::Number::from(1)),
|
||||||
|
raw: "1".to_string(),
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
})),
|
||||||
|
right: BinaryPart::Literal(Box::new(Literal {
|
||||||
|
value: serde_json::Value::Number(serde_json::Number::from(2)),
|
||||||
|
raw: "2".to_string(),
|
||||||
|
start: 2,
|
||||||
|
end: 3,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_expression_sub_no_spaces() {
|
||||||
|
let tokens = crate::tokeniser::lexer("1 -2");
|
||||||
|
let mut parser = MathParser::new(&tokens);
|
||||||
|
let result = parser.parse().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
BinaryExpression {
|
||||||
|
operator: BinaryOperator::Sub,
|
||||||
|
start: 0,
|
||||||
|
end: 4,
|
||||||
|
left: BinaryPart::Literal(Box::new(Literal {
|
||||||
|
value: serde_json::Value::Number(serde_json::Number::from(1)),
|
||||||
|
raw: "1".to_string(),
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
})),
|
||||||
|
right: BinaryPart::Literal(Box::new(Literal {
|
||||||
|
value: serde_json::Value::Number(serde_json::Number::from(2)),
|
||||||
|
raw: "2".to_string(),
|
||||||
|
start: 3,
|
||||||
|
end: 4,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_expression_plus_followed_by_star() {
|
fn test_parse_expression_plus_followed_by_star() {
|
||||||
let tokens = crate::tokeniser::lexer("1 + 2 * 3");
|
let tokens = crate::tokeniser::lexer("1 + 2 * 3");
|
||||||
|
@ -427,7 +427,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
let very_next_token = self.get_token(index + 1)?;
|
let very_next_token = self.get_token(index + 1)?;
|
||||||
if (current_token.token_type == TokenType::Word || current_token.token_type == TokenType::Keyword)
|
if (current_token.token_type == TokenType::Word)
|
||||||
&& very_next_token.token_type == TokenType::Brace
|
&& very_next_token.token_type == TokenType::Brace
|
||||||
&& very_next_token.value == "("
|
&& very_next_token.value == "("
|
||||||
{
|
{
|
||||||
@ -550,22 +550,41 @@ impl Parser {
|
|||||||
&self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
||||||
|
has_opening_brace: bool,
|
||||||
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
||||||
let previous_keys = _previous_keys.unwrap_or(vec![]);
|
let previous_keys = _previous_keys.unwrap_or(vec![]);
|
||||||
let next_token = self.next_meaningful_token(index, None)?;
|
let next_token = self.next_meaningful_token(index, None)?;
|
||||||
let _next_token = next_token.clone();
|
if next_token.index == self.tokens.len() - 1 {
|
||||||
if _next_token.index == self.tokens.len() - 1 {
|
|
||||||
return Ok(previous_keys);
|
return Ok(previous_keys);
|
||||||
}
|
}
|
||||||
let period_or_opening_bracket = match next_token.token {
|
let mut has_opening_brace = match &next_token.token {
|
||||||
Some(next_token_val) => {
|
Some(next_token_val) => {
|
||||||
if next_token_val.token_type == TokenType::Brace && next_token_val.value == "]" {
|
if next_token_val.token_type == TokenType::Brace && next_token_val.value == "[" {
|
||||||
self.next_meaningful_token(next_token.index, None)?
|
true
|
||||||
} else {
|
} else {
|
||||||
_next_token
|
has_opening_brace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => _next_token,
|
None => has_opening_brace,
|
||||||
|
};
|
||||||
|
let period_or_opening_bracket = match &next_token.token {
|
||||||
|
Some(next_token_val) => {
|
||||||
|
if has_opening_brace && next_token_val.token_type == TokenType::Brace && next_token_val.value == "]" {
|
||||||
|
// We need to reset our has_opening_brace flag, since we've closed it.
|
||||||
|
has_opening_brace = false;
|
||||||
|
let next_next_token = self.next_meaningful_token(next_token.index, None)?;
|
||||||
|
if let Some(next_next_token_val) = &next_next_token.token {
|
||||||
|
if next_next_token_val.token_type == TokenType::Brace && next_next_token_val.value == "[" {
|
||||||
|
// Set the opening brace flag again, since we've opened it again.
|
||||||
|
has_opening_brace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next_next_token.clone()
|
||||||
|
} else {
|
||||||
|
next_token.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => next_token.clone(),
|
||||||
};
|
};
|
||||||
if let Some(period_or_opening_bracket_token) = period_or_opening_bracket.token {
|
if let Some(period_or_opening_bracket_token) = period_or_opening_bracket.token {
|
||||||
if period_or_opening_bracket_token.token_type != TokenType::Period
|
if period_or_opening_bracket_token.token_type != TokenType::Period
|
||||||
@ -573,11 +592,26 @@ impl Parser {
|
|||||||
{
|
{
|
||||||
return Ok(previous_keys);
|
return Ok(previous_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't care if we never opened the brace.
|
||||||
|
if !has_opening_brace && period_or_opening_bracket_token.token_type == TokenType::Brace {
|
||||||
|
return Ok(previous_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure its the right kind of brace, we don't care about ().
|
||||||
|
if period_or_opening_bracket_token.token_type == TokenType::Brace
|
||||||
|
&& period_or_opening_bracket_token.value != "["
|
||||||
|
&& period_or_opening_bracket_token.value != "]"
|
||||||
|
{
|
||||||
|
return Ok(previous_keys);
|
||||||
|
}
|
||||||
|
|
||||||
let key_token = self.next_meaningful_token(period_or_opening_bracket.index, None)?;
|
let key_token = self.next_meaningful_token(period_or_opening_bracket.index, None)?;
|
||||||
let next_period_or_opening_bracket = self.next_meaningful_token(key_token.index, None)?;
|
let next_period_or_opening_bracket = self.next_meaningful_token(key_token.index, None)?;
|
||||||
let is_braced = match next_period_or_opening_bracket.token {
|
let is_braced = match next_period_or_opening_bracket.token {
|
||||||
Some(next_period_or_opening_bracket_val) => {
|
Some(next_period_or_opening_bracket_val) => {
|
||||||
next_period_or_opening_bracket_val.token_type == TokenType::Brace
|
has_opening_brace
|
||||||
|
&& next_period_or_opening_bracket_val.token_type == TokenType::Brace
|
||||||
&& next_period_or_opening_bracket_val.value == "]"
|
&& next_period_or_opening_bracket_val.value == "]"
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
@ -588,23 +622,19 @@ impl Parser {
|
|||||||
key_token.index
|
key_token.index
|
||||||
};
|
};
|
||||||
if let Some(key_token_token) = key_token.token {
|
if let Some(key_token_token) = key_token.token {
|
||||||
let key = if key_token_token.token_type == TokenType::Word
|
let key = if key_token_token.token_type == TokenType::Word {
|
||||||
|| key_token_token.token_type == TokenType::Keyword
|
|
||||||
{
|
|
||||||
LiteralIdentifier::Identifier(Box::new(self.make_identifier(key_token.index)?))
|
LiteralIdentifier::Identifier(Box::new(self.make_identifier(key_token.index)?))
|
||||||
} else {
|
} else {
|
||||||
LiteralIdentifier::Literal(Box::new(self.make_literal(key_token.index)?))
|
LiteralIdentifier::Literal(Box::new(self.make_literal(key_token.index)?))
|
||||||
};
|
};
|
||||||
let computed = is_braced
|
let computed = is_braced && key_token_token.token_type == TokenType::Word;
|
||||||
&& (key_token_token.token_type == TokenType::Word
|
|
||||||
|| key_token_token.token_type == TokenType::Keyword);
|
|
||||||
let mut new_previous_keys = previous_keys;
|
let mut new_previous_keys = previous_keys;
|
||||||
new_previous_keys.push(ObjectKeyInfo {
|
new_previous_keys.push(ObjectKeyInfo {
|
||||||
key,
|
key,
|
||||||
index: end_index,
|
index: end_index,
|
||||||
computed,
|
computed,
|
||||||
});
|
});
|
||||||
self.collect_object_keys(key_token.index, Some(new_previous_keys))
|
self.collect_object_keys(key_token.index, Some(new_previous_keys), has_opening_brace)
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::Unimplemented(KclErrorDetails {
|
Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
source_ranges: vec![period_or_opening_bracket_token.clone().into()],
|
source_ranges: vec![period_or_opening_bracket_token.clone().into()],
|
||||||
@ -616,9 +646,9 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_member_expression(&self, index: usize) -> Result<MemberExpressionReturn, KclError> {
|
pub fn make_member_expression(&self, index: usize) -> Result<MemberExpressionReturn, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
let mut keys_info = self.collect_object_keys(index, None)?;
|
let mut keys_info = self.collect_object_keys(index, None, false)?;
|
||||||
if keys_info.is_empty() {
|
if keys_info.is_empty() {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
@ -653,6 +683,7 @@ impl Parser {
|
|||||||
|
|
||||||
fn find_end_of_binary_expression(&self, index: usize) -> Result<usize, KclError> {
|
fn find_end_of_binary_expression(&self, index: usize) -> Result<usize, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
|
|
||||||
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||||
let closing_parenthesis = self.find_closing_brace(index, 0, "")?;
|
let closing_parenthesis = self.find_closing_brace(index, 0, "")?;
|
||||||
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
||||||
@ -669,28 +700,42 @@ impl Parser {
|
|||||||
Ok(closing_parenthesis)
|
Ok(closing_parenthesis)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (current_token.token_type == TokenType::Keyword || current_token.token_type == TokenType::Word)
|
|
||||||
&& self.get_token(index + 1)?.token_type == TokenType::Brace
|
if current_token.token_type == TokenType::Word {
|
||||||
&& self.get_token(index + 1)?.value == "("
|
if let Ok(next_token) = self.get_token(index + 1) {
|
||||||
{
|
if next_token.token_type == TokenType::Period
|
||||||
let closing_parenthesis = self.find_closing_brace(index + 1, 0, "")?;
|
|| (next_token.token_type == TokenType::Brace && next_token.value == "[")
|
||||||
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
|
||||||
return if let Some(maybe_another_operator_token) = maybe_another_operator.token {
|
|
||||||
if maybe_another_operator_token.token_type != TokenType::Operator
|
|
||||||
|| maybe_another_operator_token.value == PIPE_OPERATOR
|
|
||||||
{
|
{
|
||||||
Ok(closing_parenthesis)
|
let member_expression = self.make_member_expression(index)?;
|
||||||
} else {
|
return self.find_end_of_binary_expression(member_expression.last_index);
|
||||||
let next_right = self.next_meaningful_token(maybe_another_operator.index, None)?;
|
|
||||||
self.find_end_of_binary_expression(next_right.index)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(closing_parenthesis)
|
if next_token.token_type == TokenType::Brace && next_token.value == "(" {
|
||||||
};
|
let closing_parenthesis = self.find_closing_brace(index + 1, 0, "")?;
|
||||||
|
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
||||||
|
return if let Some(maybe_another_operator_token) = maybe_another_operator.token {
|
||||||
|
if maybe_another_operator_token.token_type != TokenType::Operator
|
||||||
|
|| maybe_another_operator_token.value == PIPE_OPERATOR
|
||||||
|
{
|
||||||
|
Ok(closing_parenthesis)
|
||||||
|
} else {
|
||||||
|
let next_right = self.next_meaningful_token(maybe_another_operator.index, None)?;
|
||||||
|
self.find_end_of_binary_expression(next_right.index)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(closing_parenthesis)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let maybe_operator = self.next_meaningful_token(index, None)?;
|
let maybe_operator = self.next_meaningful_token(index, None)?;
|
||||||
if let Some(maybe_operator_token) = maybe_operator.token {
|
if let Some(maybe_operator_token) = maybe_operator.token {
|
||||||
if maybe_operator_token.token_type != TokenType::Operator || maybe_operator_token.value == PIPE_OPERATOR {
|
if maybe_operator_token.token_type == TokenType::Number {
|
||||||
|
return self.find_end_of_binary_expression(maybe_operator.index);
|
||||||
|
} else if maybe_operator_token.token_type != TokenType::Operator
|
||||||
|
|| maybe_operator_token.value == PIPE_OPERATOR
|
||||||
|
{
|
||||||
return Ok(index);
|
return Ok(index);
|
||||||
}
|
}
|
||||||
let next_right = self.next_meaningful_token(maybe_operator.index, None)?;
|
let next_right = self.next_meaningful_token(maybe_operator.index, None)?;
|
||||||
@ -731,7 +776,6 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if current_token.token_type == TokenType::Word
|
if current_token.token_type == TokenType::Word
|
||||||
|| current_token.token_type == TokenType::Keyword
|
|
||||||
|| current_token.token_type == TokenType::Number
|
|| current_token.token_type == TokenType::Number
|
||||||
|| current_token.token_type == TokenType::String
|
|| current_token.token_type == TokenType::String
|
||||||
{
|
{
|
||||||
@ -745,6 +789,29 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Account for negative numbers.
|
||||||
|
if current_token.token_type == TokenType::Operator || current_token.value == "-" {
|
||||||
|
if let Some(next_token) = &next.token {
|
||||||
|
if next_token.token_type == TokenType::Word
|
||||||
|
|| next_token.token_type == TokenType::Number
|
||||||
|
|| next_token.token_type == TokenType::String
|
||||||
|
{
|
||||||
|
// See if the next token is an operator.
|
||||||
|
let next_right = self.next_meaningful_token(next.index, None)?;
|
||||||
|
if let Some(next_right_token) = next_right.token {
|
||||||
|
if next_right_token.token_type == TokenType::Operator {
|
||||||
|
let binary_expression = self.make_binary_expression(index)?;
|
||||||
|
return Ok(ValueReturn {
|
||||||
|
value: Value::BinaryExpression(Box::new(binary_expression.expression)),
|
||||||
|
last_index: binary_expression.last_index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if current_token.token_type == TokenType::Brace && current_token.value == "{" {
|
if current_token.token_type == TokenType::Brace && current_token.value == "{" {
|
||||||
let object_expression = self.make_object_expression(index)?;
|
let object_expression = self.make_object_expression(index)?;
|
||||||
return Ok(ValueReturn {
|
return Ok(ValueReturn {
|
||||||
@ -761,11 +828,25 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_token) = next.token {
|
if let Some(next_token) = next.token {
|
||||||
if (current_token.token_type == TokenType::Keyword || current_token.token_type == TokenType::Word)
|
if (current_token.token_type == TokenType::Word)
|
||||||
&& (next_token.token_type == TokenType::Period
|
&& (next_token.token_type == TokenType::Period
|
||||||
|| (next_token.token_type == TokenType::Brace && next_token.value == "["))
|
|| (next_token.token_type == TokenType::Brace && next_token.value == "["))
|
||||||
{
|
{
|
||||||
let member_expression = self.make_member_expression(index)?;
|
let member_expression = self.make_member_expression(index)?;
|
||||||
|
// If the next token is an operator, we need to make a binary expression.
|
||||||
|
let next_right = self.next_meaningful_token(member_expression.last_index, None)?;
|
||||||
|
if let Some(next_right_token) = next_right.token {
|
||||||
|
if next_right_token.token_type == TokenType::Operator
|
||||||
|
|| next_right_token.token_type == TokenType::Number
|
||||||
|
{
|
||||||
|
let binary_expression = self.make_binary_expression(index)?;
|
||||||
|
return Ok(ValueReturn {
|
||||||
|
value: Value::BinaryExpression(Box::new(binary_expression.expression)),
|
||||||
|
last_index: binary_expression.last_index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(ValueReturn {
|
return Ok(ValueReturn {
|
||||||
value: Value::MemberExpression(Box::new(member_expression.expression)),
|
value: Value::MemberExpression(Box::new(member_expression.expression)),
|
||||||
last_index: member_expression.last_index,
|
last_index: member_expression.last_index,
|
||||||
@ -820,7 +901,7 @@ impl Parser {
|
|||||||
|
|
||||||
Err(KclError::Unexpected(KclErrorDetails {
|
Err(KclError::Unexpected(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
message: format!("{:?}", current_token.token_type),
|
message: format!("Unexpected token {:?}", current_token),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -841,10 +922,12 @@ impl Parser {
|
|||||||
if let Some(next_token_token) = next_token.token {
|
if let Some(next_token_token) = next_token.token {
|
||||||
let is_closing_brace = next_token_token.token_type == TokenType::Brace && next_token_token.value == "]";
|
let is_closing_brace = next_token_token.token_type == TokenType::Brace && next_token_token.value == "]";
|
||||||
let is_comma = next_token_token.token_type == TokenType::Comma;
|
let is_comma = next_token_token.token_type == TokenType::Comma;
|
||||||
if !is_closing_brace && !is_comma {
|
// Check if we have a double period, which would act as an expansion operator.
|
||||||
|
let is_double_period = next_token_token.token_type == TokenType::DoublePeriod;
|
||||||
|
if !is_closing_brace && !is_comma && !is_double_period {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![next_token_token.clone().into()],
|
source_ranges: vec![next_token_token.clone().into()],
|
||||||
message: format!("Expected a comma or closing brace, found {:?}", next_token_token.value),
|
message: format!("Expected a `,`, `]`, or `..`, found {:?}", next_token_token.value),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
let next_call_index = if is_closing_brace {
|
let next_call_index = if is_closing_brace {
|
||||||
@ -852,9 +935,60 @@ impl Parser {
|
|||||||
} else {
|
} else {
|
||||||
self.next_meaningful_token(next_token.index, None)?.index
|
self.next_meaningful_token(next_token.index, None)?.index
|
||||||
};
|
};
|
||||||
let mut _previous_elements = previous_elements;
|
|
||||||
_previous_elements.push(current_element.value);
|
if is_double_period {
|
||||||
self.make_array_elements(next_call_index, _previous_elements)
|
// We want to expand the array.
|
||||||
|
// Make sure the previous element is a number literal.
|
||||||
|
if first_element_token.token_type != TokenType::Number {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![first_element_token.into()],
|
||||||
|
message: "`..` expansion operator requires a number literal on both sides".to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the next element is a number literal.
|
||||||
|
let last_element_token = self.get_token(next_call_index)?;
|
||||||
|
if last_element_token.token_type != TokenType::Number {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![last_element_token.into()],
|
||||||
|
message: "`..` expansion operator requires a number literal on both sides".to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the array.
|
||||||
|
let mut previous_elements = previous_elements.clone();
|
||||||
|
let first_element = first_element_token.value.parse::<i64>().map_err(|_| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![first_element_token.into()],
|
||||||
|
message: "expected a number literal".to_string(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let last_element = last_element_token.value.parse::<i64>().map_err(|_| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![last_element_token.into()],
|
||||||
|
message: "expected a number literal".to_string(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
if first_element > last_element {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![first_element_token.into(), last_element_token.into()],
|
||||||
|
message: "first element must be less than or equal to the last element".to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
for i in first_element..=last_element {
|
||||||
|
previous_elements.push(Value::Literal(Box::new(Literal {
|
||||||
|
start: first_element_token.start,
|
||||||
|
end: first_element_token.end,
|
||||||
|
value: i.into(),
|
||||||
|
raw: i.to_string(),
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
return self.make_array_elements(next_call_index + 1, previous_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut previous_elements = previous_elements.clone();
|
||||||
|
previous_elements.push(current_element.value);
|
||||||
|
self.make_array_elements(next_call_index, previous_elements)
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::Unimplemented(KclErrorDetails {
|
Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
source_ranges: vec![first_element_token.into()],
|
source_ranges: vec![first_element_token.into()],
|
||||||
@ -867,12 +1001,18 @@ impl Parser {
|
|||||||
let opening_brace_token = self.get_token(index)?;
|
let opening_brace_token = self.get_token(index)?;
|
||||||
let first_element_token = self.next_meaningful_token(index, None)?;
|
let first_element_token = self.next_meaningful_token(index, None)?;
|
||||||
// Make sure there is a closing brace.
|
// Make sure there is a closing brace.
|
||||||
let _closing_brace = self.find_closing_brace(index, 0, "")?;
|
let closing_brace_index = self.find_closing_brace(index, 0, "").map_err(|_| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![opening_brace_token.into()],
|
||||||
|
message: "missing a closing brace for the array".to_string(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let closing_brace_token = self.get_token(closing_brace_index)?;
|
||||||
let array_elements = self.make_array_elements(first_element_token.index, Vec::new())?;
|
let array_elements = self.make_array_elements(first_element_token.index, Vec::new())?;
|
||||||
Ok(ArrayReturn {
|
Ok(ArrayReturn {
|
||||||
expression: ArrayExpression {
|
expression: ArrayExpression {
|
||||||
start: opening_brace_token.start,
|
start: opening_brace_token.start,
|
||||||
end: self.get_token(array_elements.last_index)?.end,
|
end: closing_brace_token.end,
|
||||||
elements: array_elements.elements,
|
elements: array_elements.elements,
|
||||||
},
|
},
|
||||||
last_index: array_elements.last_index,
|
last_index: array_elements.last_index,
|
||||||
@ -949,9 +1089,23 @@ impl Parser {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let argument_token = self.next_meaningful_token(index, None)?;
|
let argument_token = self.next_meaningful_token(index, None)?;
|
||||||
|
|
||||||
if let Some(argument_token_token) = argument_token.token {
|
if let Some(argument_token_token) = argument_token.token {
|
||||||
let next_brace_or_comma = self.next_meaningful_token(argument_token.index, None)?;
|
let next_brace_or_comma = self.next_meaningful_token(argument_token.index, None)?;
|
||||||
if let Some(next_brace_or_comma_token) = next_brace_or_comma.token {
|
if let Some(next_brace_or_comma_token) = next_brace_or_comma.token {
|
||||||
|
if (argument_token_token.token_type == TokenType::Word)
|
||||||
|
&& (next_brace_or_comma_token.token_type == TokenType::Period
|
||||||
|
|| (next_brace_or_comma_token.token_type == TokenType::Brace
|
||||||
|
&& next_brace_or_comma_token.value == "["))
|
||||||
|
{
|
||||||
|
let member_expression = self.make_member_expression(argument_token.index)?;
|
||||||
|
let mut _previous_args = previous_args;
|
||||||
|
_previous_args.push(Value::MemberExpression(Box::new(member_expression.expression)));
|
||||||
|
let next_comma_or_brace_token_index =
|
||||||
|
self.next_meaningful_token(member_expression.last_index, None)?.index;
|
||||||
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
|
}
|
||||||
|
|
||||||
let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma
|
let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma
|
||||||
|| next_brace_or_comma_token.token_type == TokenType::Brace;
|
|| next_brace_or_comma_token.token_type == TokenType::Brace;
|
||||||
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" {
|
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" {
|
||||||
@ -962,12 +1116,12 @@ impl Parser {
|
|||||||
_previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression)));
|
_previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression)));
|
||||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" {
|
if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" {
|
||||||
let unary_expression = self.make_unary_expression(argument_token.index)?;
|
let value = self.make_value(argument_token.index)?;
|
||||||
let next_comma_or_brace_token_index =
|
let next_comma_or_brace_token_index = self.next_meaningful_token(value.last_index, None)?.index;
|
||||||
self.next_meaningful_token(unary_expression.last_index, None)?.index;
|
|
||||||
let mut _previous_args = previous_args;
|
let mut _previous_args = previous_args;
|
||||||
_previous_args.push(Value::UnaryExpression(Box::new(unary_expression.expression)));
|
_previous_args.push(value.value);
|
||||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
}
|
}
|
||||||
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "{" {
|
if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "{" {
|
||||||
@ -978,8 +1132,7 @@ impl Parser {
|
|||||||
_previous_args.push(Value::ObjectExpression(Box::new(object_expression.expression)));
|
_previous_args.push(Value::ObjectExpression(Box::new(object_expression.expression)));
|
||||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||||
}
|
}
|
||||||
if (argument_token_token.token_type == TokenType::Keyword
|
if (argument_token_token.token_type == TokenType::Word
|
||||||
|| argument_token_token.token_type == TokenType::Word
|
|
||||||
|| argument_token_token.token_type == TokenType::Number
|
|| argument_token_token.token_type == TokenType::Number
|
||||||
|| argument_token_token.token_type == TokenType::String)
|
|| argument_token_token.token_type == TokenType::String)
|
||||||
&& next_brace_or_comma_token.token_type == TokenType::Operator
|
&& next_brace_or_comma_token.token_type == TokenType::Operator
|
||||||
@ -998,6 +1151,7 @@ impl Parser {
|
|||||||
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
|
_previous_args.push(Value::BinaryExpression(Box::new(binary_expression.expression)));
|
||||||
return self.make_arguments(binary_expression.last_index, _previous_args);
|
return self.make_arguments(binary_expression.last_index, _previous_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if argument_token_token.token_type == TokenType::Operator
|
if argument_token_token.token_type == TokenType::Operator
|
||||||
&& argument_token_token.value == PIPE_SUBSTITUTION_OPERATOR
|
&& argument_token_token.value == PIPE_SUBSTITUTION_OPERATOR
|
||||||
{
|
{
|
||||||
@ -1010,8 +1164,7 @@ impl Parser {
|
|||||||
_previous_args.push(value);
|
_previous_args.push(value);
|
||||||
return self.make_arguments(next_brace_or_comma.index, _previous_args);
|
return self.make_arguments(next_brace_or_comma.index, _previous_args);
|
||||||
}
|
}
|
||||||
if (argument_token_token.token_type == TokenType::Keyword
|
if argument_token_token.token_type == TokenType::Word
|
||||||
|| argument_token_token.token_type == TokenType::Word)
|
|
||||||
&& next_brace_or_comma_token.token_type == TokenType::Brace
|
&& next_brace_or_comma_token.token_type == TokenType::Brace
|
||||||
&& next_brace_or_comma_token.value == "("
|
&& next_brace_or_comma_token.value == "("
|
||||||
{
|
{
|
||||||
@ -1085,9 +1238,14 @@ impl Parser {
|
|||||||
let brace_token = self.next_meaningful_token(index, None)?;
|
let brace_token = self.next_meaningful_token(index, None)?;
|
||||||
let callee = self.make_identifier(index)?;
|
let callee = self.make_identifier(index)?;
|
||||||
// Make sure there is a closing brace.
|
// Make sure there is a closing brace.
|
||||||
let _closing_brace_token = self.find_closing_brace(brace_token.index, 0, "")?;
|
let closing_brace_index = self.find_closing_brace(brace_token.index, 0, "").map_err(|_| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![current_token.into()],
|
||||||
|
message: "missing a closing brace for the function call".to_string(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let closing_brace_token = self.get_token(closing_brace_index)?;
|
||||||
let args = self.make_arguments(brace_token.index, vec![])?;
|
let args = self.make_arguments(brace_token.index, vec![])?;
|
||||||
let closing_brace_token = self.get_token(args.last_index)?;
|
|
||||||
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
||||||
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
||||||
} else {
|
} else {
|
||||||
@ -1127,6 +1285,19 @@ impl Parser {
|
|||||||
previous_declarators: Vec<VariableDeclarator>,
|
previous_declarators: Vec<VariableDeclarator>,
|
||||||
) -> Result<VariableDeclaratorsReturn, KclError> {
|
) -> Result<VariableDeclaratorsReturn, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
|
|
||||||
|
// Make sure they are not assigning a variable to a reserved keyword.
|
||||||
|
// Or a stdlib function.
|
||||||
|
if current_token.token_type == TokenType::Keyword || self.stdlib.fns.contains_key(¤t_token.value) {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![current_token.into()],
|
||||||
|
message: format!(
|
||||||
|
"Cannot assign a variable to a reserved keyword: {}",
|
||||||
|
current_token.value
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let assignment = self.next_meaningful_token(index, None)?;
|
let assignment = self.next_meaningful_token(index, None)?;
|
||||||
let Some(assignment_token) = assignment.token else {
|
let Some(assignment_token) = assignment.token else {
|
||||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
@ -1169,17 +1340,40 @@ impl Parser {
|
|||||||
fn make_variable_declaration(&self, index: usize) -> Result<VariableDeclarationResult, KclError> {
|
fn make_variable_declaration(&self, index: usize) -> Result<VariableDeclarationResult, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
let declaration_start_token = self.next_meaningful_token(index, None)?;
|
let declaration_start_token = self.next_meaningful_token(index, None)?;
|
||||||
|
let kind = VariableKind::from_str(¤t_token.value).map_err(|_| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![current_token.into()],
|
||||||
|
message: format!("Unexpected token: {}", current_token.value),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
let variable_declarators_result = self.make_variable_declarators(declaration_start_token.index, vec![])?;
|
let variable_declarators_result = self.make_variable_declarators(declaration_start_token.index, vec![])?;
|
||||||
|
|
||||||
|
// Check if we have a fn variable kind but are not assigning a function.
|
||||||
|
if !variable_declarators_result.declarations.is_empty() {
|
||||||
|
if let Some(declarator) = variable_declarators_result.declarations.get(0) {
|
||||||
|
if let Value::FunctionExpression(_) = declarator.init {
|
||||||
|
if kind != VariableKind::Fn {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![current_token.into()],
|
||||||
|
message: format!("Expected a `fn` variable kind, found: `{}`", current_token.value),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we have anything other than a function, make sure we are not using the `fn` variable kind.
|
||||||
|
if kind == VariableKind::Fn {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![current_token.into()],
|
||||||
|
message: format!("Expected a `let` variable kind, found: `{}`", current_token.value),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(VariableDeclarationResult {
|
Ok(VariableDeclarationResult {
|
||||||
declaration: VariableDeclaration {
|
declaration: VariableDeclaration {
|
||||||
start: current_token.start,
|
start: current_token.start,
|
||||||
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
|
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
|
||||||
kind: VariableKind::from_str(¤t_token.value).map_err(|_| {
|
kind,
|
||||||
KclError::Syntax(KclErrorDetails {
|
|
||||||
source_ranges: vec![current_token.into()],
|
|
||||||
message: "Unexpected token".to_string(),
|
|
||||||
})
|
|
||||||
})?,
|
|
||||||
declarations: variable_declarators_result.declarations,
|
declarations: variable_declarators_result.declarations,
|
||||||
},
|
},
|
||||||
last_index: variable_declarators_result.last_index,
|
last_index: variable_declarators_result.last_index,
|
||||||
@ -1189,27 +1383,39 @@ impl Parser {
|
|||||||
fn make_params(&self, index: usize, previous_params: Vec<Identifier>) -> Result<ParamsResult, KclError> {
|
fn make_params(&self, index: usize, previous_params: Vec<Identifier>) -> Result<ParamsResult, KclError> {
|
||||||
let brace_or_comma_token = self.get_token(index)?;
|
let brace_or_comma_token = self.get_token(index)?;
|
||||||
let argument = self.next_meaningful_token(index, None)?;
|
let argument = self.next_meaningful_token(index, None)?;
|
||||||
if let Some(argument_token) = argument.token {
|
let Some(argument_token) = argument.token else {
|
||||||
let should_finish_recursion = (argument_token.token_type == TokenType::Brace
|
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||||
&& argument_token.value == ")")
|
|
||||||
|| (brace_or_comma_token.token_type == TokenType::Brace && brace_or_comma_token.value == ")");
|
|
||||||
if should_finish_recursion {
|
|
||||||
return Ok(ParamsResult {
|
|
||||||
params: previous_params,
|
|
||||||
last_index: index,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let next_brace_or_comma_token = self.next_meaningful_token(argument.index, None)?;
|
|
||||||
let identifier = self.make_identifier(argument.index)?;
|
|
||||||
let mut _previous_params = previous_params;
|
|
||||||
_previous_params.push(identifier);
|
|
||||||
self.make_params(next_brace_or_comma_token.index, _previous_params)
|
|
||||||
} else {
|
|
||||||
Err(KclError::Unimplemented(KclErrorDetails {
|
|
||||||
source_ranges: vec![brace_or_comma_token.into()],
|
source_ranges: vec![brace_or_comma_token.into()],
|
||||||
message: format!("Unexpected token {}", brace_or_comma_token.value),
|
message: format!("expected a function parameter, found: {}", brace_or_comma_token.value),
|
||||||
}))
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let should_finish_recursion = (argument_token.token_type == TokenType::Brace && argument_token.value == ")")
|
||||||
|
|| (brace_or_comma_token.token_type == TokenType::Brace && brace_or_comma_token.value == ")");
|
||||||
|
if should_finish_recursion {
|
||||||
|
return Ok(ParamsResult {
|
||||||
|
params: previous_params,
|
||||||
|
last_index: index,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure they are not assigning a variable to a reserved keyword.
|
||||||
|
// Or a stdlib function.
|
||||||
|
if argument_token.token_type == TokenType::Keyword || self.stdlib.fns.contains_key(&argument_token.value) {
|
||||||
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![argument_token.clone().into()],
|
||||||
|
message: format!(
|
||||||
|
"Cannot assign a variable to a reserved keyword: {}",
|
||||||
|
argument_token.value
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_brace_or_comma_token = self.next_meaningful_token(argument.index, None)?;
|
||||||
|
let identifier = self.make_identifier(argument.index)?;
|
||||||
|
let mut _previous_params = previous_params;
|
||||||
|
_previous_params.push(identifier);
|
||||||
|
self.make_params(next_brace_or_comma_token.index, _previous_params)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||||
@ -1239,10 +1445,11 @@ impl Parser {
|
|||||||
Value::Literal(literal) => BinaryPart::Literal(literal),
|
Value::Literal(literal) => BinaryPart::Literal(literal),
|
||||||
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
|
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
|
||||||
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
|
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
|
||||||
|
Value::MemberExpression(member_expression) => BinaryPart::MemberExpression(member_expression),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![current_token.into()],
|
source_ranges: vec![current_token.into()],
|
||||||
message: "Invalid argument for unary expression".to_string(),
|
message: format!("Invalid argument for unary expression: {:?}", argument.value),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1462,7 +1669,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_token) = next.token {
|
if let Some(next_token) = next.token {
|
||||||
if (token.token_type == TokenType::Keyword || token.token_type == TokenType::Word)
|
if token.token_type == TokenType::Word
|
||||||
&& next_token.token_type == TokenType::Brace
|
&& next_token.token_type == TokenType::Brace
|
||||||
&& next_token.value == "("
|
&& next_token.value == "("
|
||||||
{
|
{
|
||||||
@ -1488,9 +1695,7 @@ impl Parser {
|
|||||||
|
|
||||||
let next_thing = self.next_meaningful_token(token_index, None)?;
|
let next_thing = self.next_meaningful_token(token_index, None)?;
|
||||||
if let Some(next_thing_token) = next_thing.token {
|
if let Some(next_thing_token) = next_thing.token {
|
||||||
if (token.token_type == TokenType::Number
|
if (token.token_type == TokenType::Number || token.token_type == TokenType::Word)
|
||||||
|| token.token_type == TokenType::Word
|
|
||||||
|| token.token_type == TokenType::Keyword)
|
|
||||||
&& next_thing_token.token_type == TokenType::Operator
|
&& next_thing_token.token_type == TokenType::Operator
|
||||||
{
|
{
|
||||||
if let Some(node) = &next_thing.non_code_node {
|
if let Some(node) = &next_thing.non_code_node {
|
||||||
@ -1513,7 +1718,7 @@ impl Parser {
|
|||||||
|
|
||||||
Err(KclError::Syntax(KclErrorDetails {
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: vec![token.into()],
|
source_ranges: vec![token.into()],
|
||||||
message: "unexpected token".to_string(),
|
message: format!("unexpected token {}", token.value),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1730,7 +1935,7 @@ const key = 'c'"#,
|
|||||||
fn test_collect_object_keys() {
|
fn test_collect_object_keys() {
|
||||||
let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]");
|
let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]");
|
||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let keys_info = parser.collect_object_keys(6, None).unwrap();
|
let keys_info = parser.collect_object_keys(6, None, false).unwrap();
|
||||||
assert_eq!(keys_info.len(), 2);
|
assert_eq!(keys_info.len(), 2);
|
||||||
let first_key = match keys_info[0].key.clone() {
|
let first_key = match keys_info[0].key.clone() {
|
||||||
LiteralIdentifier::Identifier(identifier) => format!("identifier-{}", identifier.name),
|
LiteralIdentifier::Identifier(identifier) => format!("identifier-{}", identifier.name),
|
||||||
@ -2759,6 +2964,73 @@ show(mySk1)"#;
|
|||||||
assert!(result.err().unwrap().to_string().contains("Unexpected token"));
|
assert!(result.err().unwrap().to_string().contains("Unexpected token"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_double_nested_braces() {
|
||||||
|
let tokens = crate::tokeniser::lexer(r#"const prop = yo["one"][two]"#);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_binary_expression_period_number_first() {
|
||||||
|
let tokens = crate::tokeniser::lexer(
|
||||||
|
r#"const obj = { a: 1, b: 2 }
|
||||||
|
const height = 1 - obj.a"#,
|
||||||
|
);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_binary_expression_brace_number_first() {
|
||||||
|
let tokens = crate::tokeniser::lexer(
|
||||||
|
r#"const obj = { a: 1, b: 2 }
|
||||||
|
const height = 1 - obj["a"]"#,
|
||||||
|
);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_binary_expression_brace_number_second() {
|
||||||
|
let tokens = crate::tokeniser::lexer(
|
||||||
|
r#"const obj = { a: 1, b: 2 }
|
||||||
|
const height = obj["a"] - 1"#,
|
||||||
|
);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_binary_expression_in_array_number_first() {
|
||||||
|
let tokens = crate::tokeniser::lexer(
|
||||||
|
r#"const obj = { a: 1, b: 2 }
|
||||||
|
const height = [1 - obj["a"], 0]"#,
|
||||||
|
);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_binary_expression_in_array_number_second() {
|
||||||
|
let tokens = crate::tokeniser::lexer(
|
||||||
|
r#"const obj = { a: 1, b: 2 }
|
||||||
|
const height = [obj["a"] - 1, 0]"#,
|
||||||
|
);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
|
||||||
|
let tokens = crate::tokeniser::lexer(
|
||||||
|
r#"const obj = { a: 1, b: 2 }
|
||||||
|
const height = [obj["a"] -1, 0]"#,
|
||||||
|
);
|
||||||
|
let parser = Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_half_pipe() {
|
fn test_parse_half_pipe() {
|
||||||
let tokens = crate::tokeniser::lexer(
|
let tokens = crate::tokeniser::lexer(
|
||||||
@ -2816,7 +3088,10 @@ z(-[["#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.err().unwrap().to_string().contains("unexpected end"));
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([1, 2])], message: "missing a closing brace for the function call" }"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2828,7 +3103,10 @@ z(-[["#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.err().unwrap().to_string().contains("unexpected end"));
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 1])], message: "missing a closing brace for the function call" }"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2881,4 +3159,229 @@ e
|
|||||||
.to_string()
|
.to_string()
|
||||||
.contains("unexpected end of expression"));
|
.contains("unexpected end of expression"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_expand_array() {
|
||||||
|
let code = "const myArray = [0..10]";
|
||||||
|
let parser = Parser::new(crate::tokeniser::lexer(code));
|
||||||
|
let result = parser.ast().unwrap();
|
||||||
|
let expected_result = Program {
|
||||||
|
start: 0,
|
||||||
|
end: 23,
|
||||||
|
body: vec![BodyItem::VariableDeclaration(VariableDeclaration {
|
||||||
|
start: 0,
|
||||||
|
end: 23,
|
||||||
|
declarations: vec![VariableDeclarator {
|
||||||
|
start: 6,
|
||||||
|
end: 23,
|
||||||
|
id: Identifier {
|
||||||
|
start: 6,
|
||||||
|
end: 13,
|
||||||
|
name: "myArray".to_string(),
|
||||||
|
},
|
||||||
|
init: Value::ArrayExpression(Box::new(ArrayExpression {
|
||||||
|
start: 16,
|
||||||
|
end: 23,
|
||||||
|
elements: vec![
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 0.into(),
|
||||||
|
raw: "0".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 1.into(),
|
||||||
|
raw: "1".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 2.into(),
|
||||||
|
raw: "2".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 3.into(),
|
||||||
|
raw: "3".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 4.into(),
|
||||||
|
raw: "4".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 5.into(),
|
||||||
|
raw: "5".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 6.into(),
|
||||||
|
raw: "6".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 7.into(),
|
||||||
|
raw: "7".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 8.into(),
|
||||||
|
raw: "8".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 9.into(),
|
||||||
|
raw: "9".to_string(),
|
||||||
|
})),
|
||||||
|
Value::Literal(Box::new(Literal {
|
||||||
|
start: 17,
|
||||||
|
end: 18,
|
||||||
|
value: 10.into(),
|
||||||
|
raw: "10".to_string(),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
}],
|
||||||
|
kind: VariableKind::Const,
|
||||||
|
})],
|
||||||
|
non_code_meta: NoneCodeMeta {
|
||||||
|
none_code_nodes: Default::default(),
|
||||||
|
start: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_keyword_in_variable() {
|
||||||
|
let some_program_string = r#"const let = "thing""#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([6, 9])], message: "Cannot assign a variable to a reserved keyword: let" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_keyword_in_fn_name() {
|
||||||
|
let some_program_string = r#"fn let = () {}"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([3, 6])], message: "Cannot assign a variable to a reserved keyword: let" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_stdlib_in_fn_name() {
|
||||||
|
let some_program_string = r#"fn cos = () {}"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([3, 6])], message: "Cannot assign a variable to a reserved keyword: cos" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_keyword_in_fn_args() {
|
||||||
|
let some_program_string = r#"fn thing = (let) => {
|
||||||
|
return 1
|
||||||
|
}"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([12, 15])], message: "Cannot assign a variable to a reserved keyword: let" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_stdlib_in_fn_args() {
|
||||||
|
let some_program_string = r#"fn thing = (cos) => {
|
||||||
|
return 1
|
||||||
|
}"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([12, 15])], message: "Cannot assign a variable to a reserved keyword: cos" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyword_ok_in_fn_args_return() {
|
||||||
|
let some_program_string = r#"fn thing = (param) => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
thing(false)
|
||||||
|
"#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
parser.ast().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_define_function_as_var() {
|
||||||
|
for name in ["var", "let", "const"] {
|
||||||
|
let some_program_string = format!(
|
||||||
|
r#"{} thing = (param) => {{
|
||||||
|
return true
|
||||||
|
}}
|
||||||
|
|
||||||
|
thing(false)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
let tokens = crate::tokeniser::lexer(&some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
format!(
|
||||||
|
r#"syntax: KclErrorDetails {{ source_ranges: [SourceRange([0, {}])], message: "Expected a `fn` variable kind, found: `{}`" }}"#,
|
||||||
|
name.len(),
|
||||||
|
name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_define_var_as_function() {
|
||||||
|
let some_program_string = r#"fn thing = "thing""#;
|
||||||
|
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let result = parser.ast();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 2])], message: "Expected a `let` variable kind, found: `fn`" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
70
src/wasm-lib/kcl/src/std/math.rs
Normal file
70
src/wasm-lib/kcl/src/std/math.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
//! Functions related to mathematics.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use derive_docs::stdlib;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
|
||||||
|
use crate::{errors::KclError, executor::MemoryItem, std::Args};
|
||||||
|
|
||||||
|
/// Computes the cosine of a number (in radians).
|
||||||
|
pub fn cos(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let num = args.get_number()?;
|
||||||
|
let result = inner_cos(num)?;
|
||||||
|
|
||||||
|
args.make_user_val_from_f64(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the sine of a number (in radians).
|
||||||
|
#[stdlib {
|
||||||
|
name = "cos",
|
||||||
|
}]
|
||||||
|
fn inner_cos(num: f64) -> Result<f64, KclError> {
|
||||||
|
Ok(num.cos())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the sine of a number (in radians).
|
||||||
|
pub fn sin(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let num = args.get_number()?;
|
||||||
|
let result = inner_sin(num)?;
|
||||||
|
|
||||||
|
args.make_user_val_from_f64(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the sine of a number (in radians).
|
||||||
|
#[stdlib {
|
||||||
|
name = "sin",
|
||||||
|
}]
|
||||||
|
fn inner_sin(num: f64) -> Result<f64, KclError> {
|
||||||
|
Ok(num.sin())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the tangent of a number (in radians).
|
||||||
|
pub fn tan(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let num = args.get_number()?;
|
||||||
|
let result = inner_tan(num)?;
|
||||||
|
|
||||||
|
args.make_user_val_from_f64(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the tangent of a number (in radians).
|
||||||
|
#[stdlib {
|
||||||
|
name = "tan",
|
||||||
|
}]
|
||||||
|
fn inner_tan(num: f64) -> Result<f64, KclError> {
|
||||||
|
Ok(num.tan())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of `pi`.
|
||||||
|
pub fn pi(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let result = inner_pi()?;
|
||||||
|
|
||||||
|
args.make_user_val_from_f64(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of `pi`.
|
||||||
|
#[stdlib {
|
||||||
|
name = "pi",
|
||||||
|
}]
|
||||||
|
fn inner_pi() -> Result<f64, KclError> {
|
||||||
|
Ok(std::f64::consts::PI)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
//! Functions implemented for language execution.
|
//! Functions implemented for language execution.
|
||||||
|
|
||||||
pub mod extrude;
|
pub mod extrude;
|
||||||
|
pub mod math;
|
||||||
pub mod segment;
|
pub mod segment;
|
||||||
pub mod sketch;
|
pub mod sketch;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
@ -61,6 +62,10 @@ impl StdLib {
|
|||||||
Box::new(crate::std::sketch::Close),
|
Box::new(crate::std::sketch::Close),
|
||||||
Box::new(crate::std::sketch::Arc),
|
Box::new(crate::std::sketch::Arc),
|
||||||
Box::new(crate::std::sketch::BezierCurve),
|
Box::new(crate::std::sketch::BezierCurve),
|
||||||
|
Box::new(crate::std::math::Cos),
|
||||||
|
Box::new(crate::std::math::Sin),
|
||||||
|
Box::new(crate::std::math::Tan),
|
||||||
|
Box::new(crate::std::math::Pi),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut fns = HashMap::new();
|
let mut fns = HashMap::new();
|
||||||
@ -103,12 +108,12 @@ impl<'a> Args<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
||||||
Ok(MemoryItem::UserVal {
|
Ok(MemoryItem::UserVal(crate::executor::UserVal {
|
||||||
value: j,
|
value: j,
|
||||||
meta: vec![Metadata {
|
meta: vec![Metadata {
|
||||||
source_range: self.source_range,
|
source_range: self.source_range,
|
||||||
}],
|
}],
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {
|
fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {
|
||||||
@ -122,6 +127,21 @@ impl<'a> Args<'a> {
|
|||||||
)?))
|
)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_number(&self) -> Result<f64, KclError> {
|
||||||
|
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()?;
|
||||||
|
|
||||||
|
parse_json_number_as_f64(&first_value, self.source_range)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_number_array(&self) -> Result<Vec<f64>, KclError> {
|
fn get_number_array(&self) -> Result<Vec<f64>, KclError> {
|
||||||
let mut numbers: Vec<f64> = Vec::new();
|
let mut numbers: Vec<f64> = Vec::new();
|
||||||
for arg in &self.args {
|
for arg in &self.args {
|
||||||
@ -471,7 +491,7 @@ pub fn leg_angle_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
name = "legAngX",
|
name = "legAngX",
|
||||||
}]
|
}]
|
||||||
fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
|
fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
|
||||||
(leg.min(hypotenuse) / hypotenuse).acos() * 180.0 / std::f64::consts::PI
|
(leg.min(hypotenuse) / hypotenuse).acos().to_degrees()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the angle of the given leg for y.
|
/// Returns the angle of the given leg for y.
|
||||||
@ -486,7 +506,7 @@ pub fn leg_angle_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
name = "legAngY",
|
name = "legAngY",
|
||||||
}]
|
}]
|
||||||
fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 {
|
fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 {
|
||||||
(leg.min(hypotenuse) / hypotenuse).asin() * 180.0 / std::f64::consts::PI
|
(leg.min(hypotenuse) / hypotenuse).asin().to_degrees()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The primitive types that can be used in a KCL file.
|
/// The primitive types that can be used in a KCL file.
|
||||||
@ -591,7 +611,7 @@ mod tests {
|
|||||||
buf.push_str(&fn_docs);
|
buf.push_str(&fn_docs);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectorate::assert_contents("../../../docs/kcl.md", &buf);
|
expectorate::assert_contents("../../../docs/kcl/std.md", &buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -606,7 +626,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectorate::assert_contents(
|
expectorate::assert_contents(
|
||||||
"../../../docs/kcl.json",
|
"../../../docs/kcl/std.json",
|
||||||
&serde_json::to_string_pretty(&json_data).unwrap(),
|
&serde_json::to_string_pretty(&json_data).unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ fn inner_angle_to_match_length_x(
|
|||||||
if diff > length {
|
if diff > length {
|
||||||
Ok(0.0)
|
Ok(0.0)
|
||||||
} else {
|
} else {
|
||||||
Ok(angle_r * 180.0 / std::f64::consts::PI)
|
Ok(angle_r.to_degrees())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +285,6 @@ fn inner_angle_to_match_length_y(
|
|||||||
if diff > length {
|
if diff > length {
|
||||||
Ok(0.0)
|
Ok(0.0)
|
||||||
} else {
|
} else {
|
||||||
Ok(angle_r * 180.0 / std::f64::consts::PI)
|
Ok(angle_r.to_degrees())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,34 +161,12 @@ pub enum LineData {
|
|||||||
/// A point with a tag.
|
/// A point with a tag.
|
||||||
PointWithTag {
|
PointWithTag {
|
||||||
/// The to point.
|
/// The to point.
|
||||||
to: PointOrDefault,
|
to: [f64; 2],
|
||||||
/// The tag.
|
/// The tag.
|
||||||
tag: String,
|
tag: String,
|
||||||
},
|
},
|
||||||
/// A point.
|
/// A point.
|
||||||
Point([f64; 2]),
|
Point([f64; 2]),
|
||||||
/// A string like `default`.
|
|
||||||
Default(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A point or a default value.
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "camelCase", untagged)]
|
|
||||||
pub enum PointOrDefault {
|
|
||||||
/// A point.
|
|
||||||
Point([f64; 2]),
|
|
||||||
/// A string like `default`.
|
|
||||||
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.
|
/// Draw a line.
|
||||||
@ -205,12 +183,9 @@ pub fn line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
}]
|
}]
|
||||||
fn inner_line(data: LineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_line(data: LineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.get_coords_from_paths()?;
|
||||||
|
|
||||||
let default = [0.2, 1.0];
|
|
||||||
let inner_args = match &data {
|
let inner_args = match &data {
|
||||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
LineData::PointWithTag { to, .. } => *to,
|
||||||
LineData::Point(to) => *to,
|
LineData::Point(to) => *to,
|
||||||
LineData::Default(_) => default,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let to = [from.x + inner_args[0], from.y + inner_args[1]];
|
let to = [from.x + inner_args[0], from.y + inner_args[1]];
|
||||||
@ -283,10 +258,7 @@ pub fn x_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
}]
|
}]
|
||||||
fn inner_x_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_x_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let line_data = match data {
|
let line_data = match data {
|
||||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [length, 0.0], tag },
|
||||||
to: PointOrDefault::Point([length, 0.0]),
|
|
||||||
tag,
|
|
||||||
},
|
|
||||||
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
|
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -308,10 +280,7 @@ pub fn y_line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
}]
|
}]
|
||||||
fn inner_y_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_y_line(data: AxisLineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let line_data = match data {
|
let line_data = match data {
|
||||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [0.0, length], tag },
|
||||||
to: PointOrDefault::Point([0.0, length]),
|
|
||||||
tag,
|
|
||||||
},
|
|
||||||
AxisLineData::Length(length) => LineData::Point([0.0, length]),
|
AxisLineData::Length(length) => LineData::Point([0.0, length]),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -360,8 +329,8 @@ fn inner_angled_line(
|
|||||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||||
};
|
};
|
||||||
let to: [f64; 2] = [
|
let to: [f64; 2] = [
|
||||||
from.x + length * f64::cos(angle * std::f64::consts::PI / 180.0),
|
from.x + length * f64::cos(angle.to_radians()),
|
||||||
from.y + length * f64::sin(angle * std::f64::consts::PI / 180.0),
|
from.y + length * f64::sin(angle.to_radians()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
@ -382,6 +351,20 @@ fn inner_angled_line(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 mut new_sketch_group = sketch_group.clone();
|
let mut new_sketch_group = sketch_group.clone();
|
||||||
new_sketch_group.value.push(current_path);
|
new_sketch_group.value.push(current_path);
|
||||||
Ok(new_sketch_group)
|
Ok(new_sketch_group)
|
||||||
@ -413,10 +396,7 @@ fn inner_angled_line_of_x_length(
|
|||||||
|
|
||||||
let new_sketch_group = inner_line(
|
let new_sketch_group = inner_line(
|
||||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||||
LineData::PointWithTag {
|
LineData::PointWithTag { to, tag }
|
||||||
to: PointOrDefault::Point(to),
|
|
||||||
tag,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LineData::Point(to)
|
LineData::Point(to)
|
||||||
},
|
},
|
||||||
@ -469,7 +449,7 @@ fn inner_angled_line_to_x(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let x_component = x_to - from.x;
|
let x_component = x_to - from.x;
|
||||||
let y_component = x_component * f64::tan(angle * std::f64::consts::PI / 180.0);
|
let y_component = x_component * f64::tan(angle.to_radians());
|
||||||
let y_to = from.y + y_component;
|
let y_to = from.y + y_component;
|
||||||
|
|
||||||
let new_sketch_group = inner_line_to(
|
let new_sketch_group = inner_line_to(
|
||||||
@ -511,10 +491,7 @@ fn inner_angled_line_of_y_length(
|
|||||||
|
|
||||||
let new_sketch_group = inner_line(
|
let new_sketch_group = inner_line(
|
||||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||||
LineData::PointWithTag {
|
LineData::PointWithTag { to, tag }
|
||||||
to: PointOrDefault::Point(to),
|
|
||||||
tag,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LineData::Point(to)
|
LineData::Point(to)
|
||||||
},
|
},
|
||||||
@ -549,7 +526,7 @@ fn inner_angled_line_to_y(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let y_component = y_to - from.y;
|
let y_component = y_to - from.y;
|
||||||
let x_component = y_component / f64::tan(angle * std::f64::consts::PI / 180.0);
|
let x_component = y_component / f64::tan(angle.to_radians());
|
||||||
let x_to = from.x + x_component;
|
let x_to = from.x + x_component;
|
||||||
|
|
||||||
let new_sketch_group = inner_line_to(
|
let new_sketch_group = inner_line_to(
|
||||||
@ -640,11 +617,9 @@ pub fn start_sketch_at(args: &mut Args) -> Result<MemoryItem, KclError> {
|
|||||||
name = "startSketchAt",
|
name = "startSketchAt",
|
||||||
}]
|
}]
|
||||||
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result<SketchGroup, KclError> {
|
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||||
let default = [0.0, 0.0];
|
|
||||||
let to = match &data {
|
let to = match &data {
|
||||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
LineData::PointWithTag { to, .. } => *to,
|
||||||
LineData::Point(to) => *to,
|
LineData::Point(to) => *to,
|
||||||
LineData::Default(_) => default,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
@ -978,16 +953,12 @@ mod tests {
|
|||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::std::sketch::{LineData, PointOrDefault};
|
use crate::std::sketch::LineData;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_line_data() {
|
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]);
|
let data = LineData::Point([0.0, 1.0]);
|
||||||
str_json = serde_json::to_string(&data).unwrap();
|
let mut str_json = serde_json::to_string(&data).unwrap();
|
||||||
assert_eq!(str_json, "[0.0,1.0]");
|
assert_eq!(str_json, "[0.0,1.0]");
|
||||||
|
|
||||||
str_json = "[0, 1]".to_string();
|
str_json = "[0, 1]".to_string();
|
||||||
@ -999,7 +970,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
data,
|
data,
|
||||||
LineData::PointWithTag {
|
LineData::PointWithTag {
|
||||||
to: PointOrDefault::Point([0.0, 1.0]),
|
to: [0.0, 1.0],
|
||||||
tag: "thing".to_string()
|
tag: "thing".to_string()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
|
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
|
||||||
let x = b[0] - a[0];
|
let x = b[0] - a[0];
|
||||||
let y = b[1] - a[1];
|
let y = b[1] - a[1];
|
||||||
normalise_angle(y.atan2(x) * 180.0 / std::f64::consts::PI)
|
normalise_angle(y.atan2(x).to_degrees())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normalise_angle(angle: f64) -> f64 {
|
pub fn normalise_angle(angle: f64) -> f64 {
|
||||||
@ -98,8 +98,8 @@ pub fn distance_between_points(point_a: &[f64; 2], point_b: &[f64; 2]) -> f64 {
|
|||||||
|
|
||||||
pub fn calculate_intersection_of_two_lines(line1: &[[f64; 2]; 2], line2_angle: f64, line2_point: [f64; 2]) -> [f64; 2] {
|
pub fn calculate_intersection_of_two_lines(line1: &[[f64; 2]; 2], line2_angle: f64, line2_point: [f64; 2]) -> [f64; 2] {
|
||||||
let line2_point_b = [
|
let line2_point_b = [
|
||||||
line2_point[0] + f64::cos(line2_angle * std::f64::consts::PI / 180.0) * 10.0,
|
line2_point[0] + f64::cos(line2_angle.to_radians()) * 10.0,
|
||||||
line2_point[1] + f64::sin(line2_angle * std::f64::consts::PI / 180.0) * 10.0,
|
line2_point[1] + f64::sin(line2_angle.to_radians()) * 10.0,
|
||||||
];
|
];
|
||||||
intersect(line1[0], line1[1], line2_point, line2_point_b)
|
intersect(line1[0], line1[1], line2_point, line2_point_b)
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ fn offset_line(offset: f64, p1: [f64; 2], p2: [f64; 2]) -> [[f64; 2]; 2] {
|
|||||||
|
|
||||||
pub fn get_y_component(angle_degree: f64, x_component: f64) -> [f64; 2] {
|
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 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 y_component = x_component * f64::tan(normalised_angle.to_radians());
|
||||||
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
|
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
|
||||||
-1.0
|
-1.0
|
||||||
} else {
|
} else {
|
||||||
@ -156,7 +156,7 @@ pub fn get_y_component(angle_degree: f64, x_component: f64) -> [f64; 2] {
|
|||||||
|
|
||||||
pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
|
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 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 x_component = y_component / f64::tan(normalised_angle.to_radians());
|
||||||
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
|
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
|
||||||
-1.0
|
-1.0
|
||||||
} else {
|
} else {
|
||||||
@ -166,8 +166,8 @@ pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f64, radius: f64) -> (Point2d, Point2d) {
|
pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f64, radius: f64) -> (Point2d, Point2d) {
|
||||||
let start_angle = start_angle_deg * (std::f64::consts::PI / 180.0);
|
let start_angle = start_angle_deg.to_radians();
|
||||||
let end_angle = end_angle_deg * (std::f64::consts::PI / 180.0);
|
let end_angle = end_angle_deg.to_radians();
|
||||||
|
|
||||||
let center = Point2d {
|
let center = Point2d {
|
||||||
x: -1.0 * (radius * start_angle.cos() - from.x),
|
x: -1.0 * (radius * start_angle.cos() - from.x),
|
||||||
@ -214,8 +214,8 @@ pub fn arc_angles(
|
|||||||
let start_angle = (from.y - center.y).atan2(from.x - center.x);
|
let start_angle = (from.y - center.y).atan2(from.x - center.x);
|
||||||
let end_angle = (to.y - center.y).atan2(to.x - center.x);
|
let end_angle = (to.y - center.y).atan2(to.x - center.x);
|
||||||
|
|
||||||
let start_angle_deg = start_angle * (180.0 / std::f64::consts::PI);
|
let start_angle_deg = start_angle.to_degrees();
|
||||||
let end_angle_deg = end_angle * (180.0 / std::f64::consts::PI);
|
let end_angle_deg = end_angle.to_degrees();
|
||||||
|
|
||||||
Ok((start_angle_deg, end_angle_deg))
|
Ok((start_angle_deg, end_angle_deg))
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ pub enum TokenType {
|
|||||||
Colon,
|
Colon,
|
||||||
/// A period.
|
/// A period.
|
||||||
Period,
|
Period,
|
||||||
|
/// A double period: `..`.
|
||||||
|
DoublePeriod,
|
||||||
/// A line comment.
|
/// A line comment.
|
||||||
LineComment,
|
LineComment,
|
||||||
/// A block comment.
|
/// A block comment.
|
||||||
@ -54,7 +56,12 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
|||||||
TokenType::LineComment => Self::COMMENT,
|
TokenType::LineComment => Self::COMMENT,
|
||||||
TokenType::BlockComment => Self::COMMENT,
|
TokenType::BlockComment => Self::COMMENT,
|
||||||
TokenType::Function => Self::FUNCTION,
|
TokenType::Function => Self::FUNCTION,
|
||||||
TokenType::Whitespace | TokenType::Brace | TokenType::Comma | TokenType::Colon | TokenType::Period => {
|
TokenType::Whitespace
|
||||||
|
| TokenType::Brace
|
||||||
|
| TokenType::Comma
|
||||||
|
| TokenType::Colon
|
||||||
|
| TokenType::Period
|
||||||
|
| TokenType::DoublePeriod => {
|
||||||
anyhow::bail!("unsupported token type: {:?}", token_type)
|
anyhow::bail!("unsupported token type: {:?}", token_type)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -130,12 +137,12 @@ impl From<&Token> for crate::executor::SourceRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref NUMBER: Regex = Regex::new(r"^-?\d+(\.\d+)?").unwrap();
|
static ref NUMBER: Regex = Regex::new(r"^(\d+(\.\d*)?|\.\d+)\b").unwrap();
|
||||||
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
|
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
|
||||||
static ref WORD: Regex = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*").unwrap();
|
static ref WORD: Regex = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*").unwrap();
|
||||||
// TODO: these should be generated using our struct types for these.
|
// TODO: these should be generated using our struct types for these.
|
||||||
static ref KEYWORD: Regex =
|
static ref KEYWORD: Regex =
|
||||||
Regex::new(r"^(if|else|for|while|return|break|continue|fn|let|true|false|nil|and|or|not|var|const)\b").unwrap();
|
Regex::new(r"^(if|else|for|while|return|break|continue|fn|let|mut|loop|true|false|nil|and|or|not|var|const)\b").unwrap();
|
||||||
static ref OPERATOR: Regex = Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
|
static ref OPERATOR: Regex = Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
|
||||||
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
|
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
|
||||||
static ref BLOCK_START: Regex = Regex::new(r"^\{").unwrap();
|
static ref BLOCK_START: Regex = Regex::new(r"^\{").unwrap();
|
||||||
@ -147,6 +154,7 @@ lazy_static! {
|
|||||||
static ref COMMA: Regex = Regex::new(r"^,").unwrap();
|
static ref COMMA: Regex = Regex::new(r"^,").unwrap();
|
||||||
static ref COLON: Regex = Regex::new(r"^:").unwrap();
|
static ref COLON: Regex = Regex::new(r"^:").unwrap();
|
||||||
static ref PERIOD: Regex = Regex::new(r"^\.").unwrap();
|
static ref PERIOD: Regex = Regex::new(r"^\.").unwrap();
|
||||||
|
static ref DOUBLE_PERIOD: Regex = Regex::new(r"^\.\.").unwrap();
|
||||||
static ref LINECOMMENT: Regex = Regex::new(r"^//.*").unwrap();
|
static ref LINECOMMENT: Regex = Regex::new(r"^//.*").unwrap();
|
||||||
static ref BLOCKCOMMENT: Regex = Regex::new(r"^/\*[\s\S]*?\*/").unwrap();
|
static ref BLOCKCOMMENT: Regex = Regex::new(r"^/\*[\s\S]*?\*/").unwrap();
|
||||||
}
|
}
|
||||||
@ -196,6 +204,9 @@ fn is_comma(character: &str) -> bool {
|
|||||||
fn is_colon(character: &str) -> bool {
|
fn is_colon(character: &str) -> bool {
|
||||||
COLON.is_match(character)
|
COLON.is_match(character)
|
||||||
}
|
}
|
||||||
|
fn is_double_period(character: &str) -> bool {
|
||||||
|
DOUBLE_PERIOD.is_match(character)
|
||||||
|
}
|
||||||
fn is_period(character: &str) -> bool {
|
fn is_period(character: &str) -> bool {
|
||||||
PERIOD.is_match(character)
|
PERIOD.is_match(character)
|
||||||
}
|
}
|
||||||
@ -296,13 +307,6 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
|||||||
start_index,
|
start_index,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if is_number(str_from_index) {
|
|
||||||
return Some(make_token(
|
|
||||||
TokenType::Number,
|
|
||||||
&match_first(str_from_index, &NUMBER)?,
|
|
||||||
start_index,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if is_operator(str_from_index) {
|
if is_operator(str_from_index) {
|
||||||
return Some(make_token(
|
return Some(make_token(
|
||||||
TokenType::Operator,
|
TokenType::Operator,
|
||||||
@ -310,6 +314,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
|||||||
start_index,
|
start_index,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if is_number(str_from_index) {
|
||||||
|
return Some(make_token(
|
||||||
|
TokenType::Number,
|
||||||
|
&match_first(str_from_index, &NUMBER)?,
|
||||||
|
start_index,
|
||||||
|
));
|
||||||
|
}
|
||||||
if is_keyword(str_from_index) {
|
if is_keyword(str_from_index) {
|
||||||
return Some(make_token(
|
return Some(make_token(
|
||||||
TokenType::Keyword,
|
TokenType::Keyword,
|
||||||
@ -331,6 +342,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
|||||||
start_index,
|
start_index,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if is_double_period(str_from_index) {
|
||||||
|
return Some(make_token(
|
||||||
|
TokenType::DoublePeriod,
|
||||||
|
&match_first(str_from_index, &DOUBLE_PERIOD)?,
|
||||||
|
start_index,
|
||||||
|
));
|
||||||
|
}
|
||||||
if is_period(str_from_index) {
|
if is_period(str_from_index) {
|
||||||
return Some(make_token(
|
return Some(make_token(
|
||||||
TokenType::Period,
|
TokenType::Period,
|
||||||
@ -376,19 +394,18 @@ mod tests {
|
|||||||
fn is_number_test() {
|
fn is_number_test() {
|
||||||
assert!(is_number("1"));
|
assert!(is_number("1"));
|
||||||
assert!(is_number("1 abc"));
|
assert!(is_number("1 abc"));
|
||||||
assert!(is_number("1abc"));
|
|
||||||
assert!(is_number("1.1"));
|
assert!(is_number("1.1"));
|
||||||
assert!(is_number("1.1 abc"));
|
assert!(is_number("1.1 abc"));
|
||||||
assert!(!is_number("a"));
|
assert!(!is_number("a"));
|
||||||
|
|
||||||
assert!(is_number("1"));
|
assert!(is_number("1"));
|
||||||
|
assert!(is_number(".1"));
|
||||||
assert!(is_number("5?"));
|
assert!(is_number("5?"));
|
||||||
assert!(is_number("5 + 6"));
|
assert!(is_number("5 + 6"));
|
||||||
assert!(is_number("5 + a"));
|
assert!(is_number("5 + a"));
|
||||||
assert!(is_number("-5"));
|
|
||||||
assert!(is_number("5.5"));
|
assert!(is_number("5.5"));
|
||||||
assert!(is_number("-5.5"));
|
|
||||||
|
|
||||||
|
assert!(!is_number("1abc"));
|
||||||
assert!(!is_number("a"));
|
assert!(!is_number("a"));
|
||||||
assert!(!is_number("?"));
|
assert!(!is_number("?"));
|
||||||
assert!(!is_number("?5"));
|
assert!(!is_number("?5"));
|
||||||
|
98
src/wasm-lib/tests/executor/main.rs
Normal file
98
src/wasm-lib/tests/executor/main.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
/// Executes a kcl program and takes a snapshot of the result.
|
||||||
|
/// This returns the bytes of the snapshot.
|
||||||
|
async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
||||||
|
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
||||||
|
let http_client = reqwest::Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
// For file conversions we need this to be long.
|
||||||
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(60));
|
||||||
|
let ws_client = reqwest::Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
// For file conversions we need this to be long.
|
||||||
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(60))
|
||||||
|
.tcp_keepalive(std::time::Duration::from_secs(600))
|
||||||
|
.http1_only();
|
||||||
|
|
||||||
|
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
||||||
|
|
||||||
|
// Create the client.
|
||||||
|
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
||||||
|
|
||||||
|
let ws = client
|
||||||
|
.modeling()
|
||||||
|
.commands_ws(None, None, None, None, Some(false))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create a temporary file to write the output to.
|
||||||
|
let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
||||||
|
|
||||||
|
let tokens = kcl_lib::tokeniser::lexer(code);
|
||||||
|
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast()?;
|
||||||
|
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
||||||
|
let mut engine = kcl_lib::engine::EngineConnection::new(
|
||||||
|
ws,
|
||||||
|
std::env::temp_dir().display().to_string().as_str(),
|
||||||
|
output_file.display().to_string().as_str(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)?;
|
||||||
|
|
||||||
|
// Send a snapshot request to the engine.
|
||||||
|
engine.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
kcl_lib::executor::SourceRange::default(),
|
||||||
|
kittycad::types::ModelingCmd::TakeSnapshot {
|
||||||
|
format: kittycad::types::ImageFormat::Png,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Wait for the snapshot to be taken.
|
||||||
|
engine.wait_for_snapshot().await;
|
||||||
|
|
||||||
|
// Read the output file.
|
||||||
|
let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
|
||||||
|
Ok(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_function_sketch() {
|
||||||
|
let code = r#"fn box = (h, l, w) => {
|
||||||
|
const myBox = startSketchAt([0,0])
|
||||||
|
|> line([0, l], %)
|
||||||
|
|> line([w, 0], %)
|
||||||
|
|> line([0, -l], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(h, %)
|
||||||
|
|
||||||
|
return myBox
|
||||||
|
}
|
||||||
|
|
||||||
|
const fnBox = box(3, 6, 10)
|
||||||
|
|
||||||
|
show(fnBox)"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_with_angled_line() {
|
||||||
|
let code = r#"const part001 = startSketchAt([4.83, 12.56])
|
||||||
|
|> line([15.1, 2.48], %)
|
||||||
|
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|
||||||
|
|> line([-15.17, -4.1], %)
|
||||||
|
|> angledLine([segAng('seg01', %), 12.35], %)
|
||||||
|
|> line([-13.02, 10.03], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(4, %)
|
||||||
|
|
||||||
|
show(part001)"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 1.0);
|
||||||
|
}
|
BIN
src/wasm-lib/tests/executor/outputs/angled_line.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/angled_line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
src/wasm-lib/tests/executor/outputs/function_sketch.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/function_sketch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
@ -1207,7 +1207,7 @@
|
|||||||
"@codemirror/view" "^6.0.0"
|
"@codemirror/view" "^6.0.0"
|
||||||
crelt "^1.0.5"
|
crelt "^1.0.5"
|
||||||
|
|
||||||
"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.1.4", "@codemirror/state@^6.2.0":
|
"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.1.4", "@codemirror/state@^6.2.0", "@codemirror/state@^6.2.1":
|
||||||
version "6.2.1"
|
version "6.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.1.tgz#6dc8d8e5abb26b875e3164191872d69a5e85bd73"
|
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.1.tgz#6dc8d8e5abb26b875e3164191872d69a5e85bd73"
|
||||||
integrity sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==
|
integrity sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==
|
||||||
@ -1629,6 +1629,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8"
|
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8"
|
||||||
integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==
|
integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==
|
||||||
|
|
||||||
|
"@replit/codemirror-interact@^6.3.0":
|
||||||
|
version "6.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@replit/codemirror-interact/-/codemirror-interact-6.3.0.tgz#977432c0b8f1a2995b93b1d5acaac27dbbb30c37"
|
||||||
|
integrity sha512-kB7ukZaZZkeKEiN5KLFOq9snxnFZRBjICLkKu5bqfPrPJXYDBmirzzpZE1dPX6qtNH5jTE3m3I1lJ+ltxk08fA==
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state" "^6.2.1"
|
||||||
|
|
||||||
"@rollup/pluginutils@^4.2.1":
|
"@rollup/pluginutils@^4.2.1":
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||||
|
Reference in New Issue
Block a user