Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
caddac5059 | |||
54751aa7bb | |||
7b7d5e5f5e | |||
f7971bddef | |||
e4f2e66029 | |||
663c396128 | |||
8db86a6783 | |||
d7ad7c749e | |||
6e3c642d22 | |||
4d7433ff3a | |||
4e93146559 | |||
731a9bfbdb | |||
cdb4c36cf5 | |||
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 |
@ -1,6 +1,6 @@
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
||||
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
||||
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||
VITE_KC_SENTRY_DSN=
|
||||
|
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/*/*' }}
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
runs-on: ubuntu-20.04
|
||||
sign-windows-msi:
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'release'
|
||||
needs: [build-test-web, build-apps]
|
||||
env:
|
||||
@ -157,23 +157,80 @@ jobs:
|
||||
|
||||
- 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
|
||||
run: |
|
||||
ls -l artifact/*/*itty*
|
||||
DARWIN_SIG=`cat artifact/macos/*.app.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}
|
||||
jq --null-input \
|
||||
--arg version "v${VERSION_NO_V}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_sig "$DARWIN_SIG" \
|
||||
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
||||
--arg linux_sig "$LINUX_SIG" \
|
||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
||||
--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,
|
||||
"pub_date": $pub_date,
|
||||
"notes": $notes,
|
||||
"platforms": {
|
||||
"darwin-x86_64": {
|
||||
"signature": $darwin_sig,
|
||||
@ -195,6 +252,34 @@ jobs:
|
||||
}' > 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
|
||||
uses: 'google-github-actions/auth@v1.1.1'
|
||||
with:
|
||||
@ -219,6 +304,12 @@ jobs:
|
||||
path: last_update.json
|
||||
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
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
100
README.md
100
README.md
@ -1,48 +1,72 @@
|
||||
## Kurt demo project
|
||||

|
||||
|
||||
## KittyCAD Modeling App
|
||||
|
||||
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
|
||||
```
|
||||
then
|
||||
|
||||
followed by:
|
||||
|
||||
```
|
||||
yarn build:wasm
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
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 😉
|
||||

|
||||
|
||||
|
||||
<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">
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
VERSION=x.y.z yarn run bump-jsons
|
||||
```
|
||||
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
||||
|
||||
2. Merge the PR
|
||||
|
@ -9322,6 +9322,34 @@
|
||||
"unpublished": 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",
|
||||
"summary": "Extrudes by a given amount.",
|
||||
@ -11173,9 +11201,6 @@
|
||||
},
|
||||
"to": {
|
||||
"description": "The to point.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A point.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
@ -11183,12 +11208,6 @@
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
{
|
||||
"description": "A string like `default`.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -11201,10 +11220,6 @@
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
{
|
||||
"description": "A string like `default`.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -13031,6 +13046,24 @@
|
||||
"unpublished": 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",
|
||||
"summary": "Returns the angle of the segment.",
|
||||
@ -15315,6 +15348,34 @@
|
||||
"unpublished": 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",
|
||||
"summary": "Start a sketch at a given point.",
|
||||
@ -15341,9 +15402,6 @@
|
||||
},
|
||||
"to": {
|
||||
"description": "The to point.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A point.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
@ -15351,12 +15409,6 @@
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
{
|
||||
"description": "A string like `default`.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -15369,10 +15421,6 @@
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
{
|
||||
"description": "A string like `default`.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -15815,6 +15863,34 @@
|
||||
"unpublished": 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",
|
||||
"summary": "Draw a line on the x-axis.",
|
@ -16,6 +16,7 @@
|
||||
* [`arc`](#arc)
|
||||
* [`bezierCurve`](#bezierCurve)
|
||||
* [`close`](#close)
|
||||
* [`cos`](#cos)
|
||||
* [`extrude`](#extrude)
|
||||
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
|
||||
* [`lastSegX`](#lastSegX)
|
||||
@ -26,12 +27,15 @@
|
||||
* [`line`](#line)
|
||||
* [`lineTo`](#lineTo)
|
||||
* [`min`](#min)
|
||||
* [`pi`](#pi)
|
||||
* [`segAng`](#segAng)
|
||||
* [`segEndX`](#segEndX)
|
||||
* [`segEndY`](#segEndY)
|
||||
* [`segLen`](#segLen)
|
||||
* [`show`](#show)
|
||||
* [`sin`](#sin)
|
||||
* [`startSketchAt`](#startSketchAt)
|
||||
* [`tan`](#tan)
|
||||
* [`xLine`](#xLine)
|
||||
* [`xLineTo`](#xLineTo)
|
||||
* [`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
|
||||
|
||||
Extrudes by a given amount.
|
||||
@ -2044,11 +2068,9 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
|
||||
// The tag.
|
||||
tag: string,
|
||||
// The to point.
|
||||
to: [number] |
|
||||
string,
|
||||
to: [number],
|
||||
} |
|
||||
[number] |
|
||||
string
|
||||
[number]
|
||||
```
|
||||
* `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
|
||||
|
||||
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
|
||||
|
||||
Start a sketch at a given point.
|
||||
@ -2784,11 +2845,9 @@ startSketchAt(data: LineData) -> SketchGroup
|
||||
// The tag.
|
||||
tag: string,
|
||||
// The to point.
|
||||
to: [number] |
|
||||
string,
|
||||
to: [number],
|
||||
} |
|
||||
[number] |
|
||||
string
|
||||
[number]
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
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",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.9.0",
|
||||
@ -14,6 +14,7 @@
|
||||
"@lezer/javascript": "^1.4.7",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@replit/codemirror-interact": "^6.3.0",
|
||||
"@sentry/react": "^7.65.0",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
|
42
public/expectations.md
Normal file
42
public/expectations.md
Normal file
@ -0,0 +1,42 @@
|
||||
## Alpha Users Expectations
|
||||
|
||||
### Welcome
|
||||
|
||||
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
|
||||
|
||||
### KittyCAD Modeling App (KCMA)
|
||||
|
||||
What we are introducing to you is our KittyCAD Modeling App (KCMA). KCMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. KCMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
|
||||
|
||||
### Why Code?
|
||||
|
||||
Plenty of you have professional CAD experience, and may not understand why coding your model would be helpful. The "code-CAD" paradigm isn’t as popular as traditional CAD programs (SolidWorks, NX, CREO, OnShape, etc.), but it certainly has its benefits. Some benefits include:
|
||||
|
||||
- Automation and parametric design
|
||||
- Customization and flexibility
|
||||
- Algorithmic and generative design
|
||||
- Reproducibility
|
||||
- Easier integration with other tools
|
||||
|
||||
### Before You Use KCMA
|
||||
|
||||
Before you dive straight into the app, we wanted to lay some expectations out for you.
|
||||
|
||||
- KCMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. KCMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
|
||||
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
|
||||
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure it’s not a duplicate or it’s feasible for the current state of the app.
|
||||
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
|
||||
- **High:** The bug is blocking you from continuing.
|
||||
- Example: Every time I click the extrude button with two faces selected, the app crashes.
|
||||
- **Medium:** You can find a workaround to the problem, but it increases your time spent working or makes it unenjoyable.
|
||||
- Example: When the app is full screen on Mac, the settings are not showing properly. It works if I have the app windowed.
|
||||
- **Low:** The bug is annoying but doesn’t affect workflow or block you from continuing (usually you can say “It would be nice if ___, but it’s not needed”)
|
||||
- Example: It would be nice if the camera would orient normal to the sketching surface when I select a face/plane and click “sketch”.
|
||||
- We want you all to be aware that we may reach out to you in regard to issues, bugs, problems, and satisfaction. This will typically be for further clarification so we can really nail things down.
|
||||
|
||||
### Discord
|
||||
We will be using Discord a lot more now that the Alpha has been released to people outside of the company. Please feel free to discuss and talk with us in the **alpha users** section of the server. We highly encourage you to engage with us on Discord!
|
||||
|
||||
### Thank You!
|
||||
|
||||
Once again, from all of us to you, thank you for being a part of the closed Alpha. We are happy to chat with you all, hear your feedback, and see some of your projects!
|
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)
|
||||
|
945
src-tauri/Cargo.lock
generated
945
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -16,13 +16,14 @@ tauri-build = { version = "1.4.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
oauth2 = "4.4.1"
|
||||
kittycad = "0.2.25"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "1.3.0", features = [ "updater", "path-all", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
|
||||
tokio = { version = "1.29.1", features = ["time"] }
|
||||
toml = "0.6.0"
|
||||
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tokio = { version = "1.32.0", features = ["time"] }
|
||||
toml = "0.8.0"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||
|
@ -85,6 +85,24 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
///This command returns the KittyCAD user info given a token.
|
||||
/// The string returned from this method is the user info as a json string.
|
||||
#[tauri::command]
|
||||
async fn get_user(token: Option<String>) -> Result<kittycad::types::User, InvokeError> {
|
||||
println!("Getting user info...");
|
||||
|
||||
// use kittycad library to fetch the user info from /user/me
|
||||
let client = kittycad::Client::new(token.unwrap());
|
||||
|
||||
let user_info: kittycad::types::User = client
|
||||
.users()
|
||||
.get_self()
|
||||
.await
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
|
||||
Ok(user_info)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
@ -97,7 +115,12 @@ fn main() {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![login, read_toml, read_txt_file])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_user,
|
||||
login,
|
||||
read_toml,
|
||||
read_txt_file
|
||||
])
|
||||
.plugin(tauri_plugin_fs_extra::init())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "kittycad-modeling",
|
||||
"version": "0.6.0"
|
||||
"version": "0.7.1"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
248
src/App.tsx
248
src/App.tsx
@ -1,13 +1,6 @@
|
||||
import {
|
||||
useRef,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useCallback,
|
||||
MouseEventHandler,
|
||||
} from 'react'
|
||||
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
||||
import { DebugPanel } from './components/DebugPanel'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { asyncParser } from './lang/abstractSyntaxTree'
|
||||
import { _executor } from './lang/executor'
|
||||
import { PaneType, useStore } from './useStore'
|
||||
import { Logs, KCLErrors } from './components/Logs'
|
||||
@ -16,13 +9,9 @@ import { MemoryPanel } from './components/MemoryPanel'
|
||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||
import { Stream } from './components/Stream'
|
||||
import ModalContainer from 'react-modal-promise'
|
||||
import {
|
||||
EngineCommand,
|
||||
EngineCommandManager,
|
||||
} from './lang/std/engineConnection'
|
||||
import { EngineCommand } from './lang/std/engineConnection'
|
||||
import { throttle } from './lib/utils'
|
||||
import { AppHeader } from './components/AppHeader'
|
||||
import { KCLError } from './lang/errors'
|
||||
import { Resizable } from 're-resizable'
|
||||
import {
|
||||
faCode,
|
||||
@ -41,6 +30,8 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
|
||||
import { CodeMenu } from 'components/CodeMenu'
|
||||
import { TextEditor } from 'components/TextEditor'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||
|
||||
export function App() {
|
||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||
@ -48,59 +39,25 @@ export function App() {
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useHotKeyListener()
|
||||
const {
|
||||
addLog,
|
||||
addKCLError,
|
||||
setCode,
|
||||
setAst,
|
||||
setError,
|
||||
setProgramMemory,
|
||||
resetLogs,
|
||||
resetKCLErrors,
|
||||
setArtifactMap,
|
||||
engineCommandManager,
|
||||
setEngineCommandManager,
|
||||
highlightRange,
|
||||
setHighlightRange,
|
||||
setCursor2,
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
isStreamReady,
|
||||
buttonDownInStream,
|
||||
openPanes,
|
||||
setOpenPanes,
|
||||
didDragInStream,
|
||||
setStreamDimensions,
|
||||
streamDimensions,
|
||||
setIsExecuting,
|
||||
defferedCode,
|
||||
guiMode,
|
||||
setGuiMode,
|
||||
} = useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
addLog: s.addLog,
|
||||
defferedCode: s.defferedCode,
|
||||
setGuiMode: s.setGuiMode,
|
||||
setCode: s.setCode,
|
||||
setAst: s.setAst,
|
||||
setError: s.setError,
|
||||
setProgramMemory: s.setProgramMemory,
|
||||
resetLogs: s.resetLogs,
|
||||
resetKCLErrors: s.resetKCLErrors,
|
||||
setArtifactMap: s.setArtifactNSourceRangeMaps,
|
||||
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,
|
||||
addKCLError: s.addKCLError,
|
||||
openPanes: s.openPanes,
|
||||
setOpenPanes: s.setOpenPanes,
|
||||
didDragInStream: s.didDragInStream,
|
||||
setStreamDimensions: s.setStreamDimensions,
|
||||
streamDimensions: s.streamDimensions,
|
||||
setIsExecuting: s.setIsExecuting,
|
||||
}))
|
||||
|
||||
const {
|
||||
@ -127,6 +84,38 @@ export function App() {
|
||||
useHotkeys('shift + l', () => togglePane('logs'))
|
||||
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
||||
useHotkeys('shift + d', () => togglePane('debug'))
|
||||
useHotkeys('esc', () => {
|
||||
if (guiMode.mode === 'sketch') {
|
||||
if (guiMode.sketchMode === 'selectFace') return
|
||||
if (guiMode.sketchMode === 'sketchEdit') {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'edit_mode_exit' },
|
||||
})
|
||||
setGuiMode({ mode: 'default' })
|
||||
} else {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_tool',
|
||||
tool: 'select',
|
||||
},
|
||||
})
|
||||
setGuiMode({
|
||||
mode: 'sketch',
|
||||
sketchMode: 'sketchEdit',
|
||||
rotation: guiMode.rotation,
|
||||
position: guiMode.position,
|
||||
pathToNode: guiMode.pathToNode,
|
||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setGuiMode({ mode: 'default' })
|
||||
}
|
||||
})
|
||||
|
||||
const paneOpacity =
|
||||
onboardingStatus === onboardingPaths.CAMERA
|
||||
@ -149,142 +138,8 @@ export function App() {
|
||||
}
|
||||
}, [loadedCode, setCode])
|
||||
|
||||
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])
|
||||
|
||||
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: {} })
|
||||
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: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
engineCommandManager
|
||||
)
|
||||
|
||||
const { artifactMap, sourceRangeMap } =
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
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])
|
||||
useSetupEngineManager(streamRef, token)
|
||||
useEngineConnectionSubscriptions()
|
||||
|
||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||
engineCommandManager?.sendSceneCommand(message)
|
||||
@ -313,18 +168,6 @@ export function App() {
|
||||
window: { x, y },
|
||||
},
|
||||
})
|
||||
} 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 },
|
||||
},
|
||||
})
|
||||
} else {
|
||||
debounceSocketSend({
|
||||
type: 'modeling_cmd_req',
|
||||
@ -336,6 +179,17 @@ export function App() {
|
||||
})
|
||||
}
|
||||
} 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]
|
||||
let interaction: CameraDragInteractionType_type
|
||||
|
||||
|
@ -47,6 +47,15 @@
|
||||
@apply hover:bg-cool-20;
|
||||
}
|
||||
|
||||
.smallScrollbar::-webkit-scrollbar {
|
||||
@apply h-0.5;
|
||||
}
|
||||
|
||||
.smallScrollbar {
|
||||
@apply overflow-x-auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
:global(.dark) .popoverToggle {
|
||||
@apply hover:bg-cool-90;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ export const Toolbar = () => {
|
||||
updateAst,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
executeAst,
|
||||
} = useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
setGuiMode: s.setGuiMode,
|
||||
@ -35,6 +36,7 @@ export const Toolbar = () => {
|
||||
updateAst: s.updateAst,
|
||||
programMemory: s.programMemory,
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
executeAst: s.executeAst,
|
||||
}))
|
||||
useAppMode()
|
||||
|
||||
@ -44,7 +46,7 @@ export const Toolbar = () => {
|
||||
|
||||
function ToolbarButtons() {
|
||||
return (
|
||||
<span className="overflow-x-auto">
|
||||
<span className={styles.smallScrollbar}>
|
||||
{guiMode.mode === 'default' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
@ -70,7 +72,7 @@ export const Toolbar = () => {
|
||||
pathToNode,
|
||||
programMemory
|
||||
)
|
||||
updateAst(modifiedAst)
|
||||
updateAst(modifiedAst, true)
|
||||
}}
|
||||
>
|
||||
SketchOnFace
|
||||
@ -113,7 +115,7 @@ export const Toolbar = () => {
|
||||
ast,
|
||||
pathToNode
|
||||
)
|
||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||
}}
|
||||
>
|
||||
ExtrudeSketch
|
||||
@ -130,7 +132,7 @@ export const Toolbar = () => {
|
||||
pathToNode,
|
||||
false
|
||||
)
|
||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||
}}
|
||||
>
|
||||
ExtrudeSketch (w/o pipe)
|
||||
@ -146,7 +148,14 @@ export const Toolbar = () => {
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'edit_mode_exit' },
|
||||
})
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||
})
|
||||
|
||||
setGuiMode({ mode: 'default' })
|
||||
executeAst()
|
||||
}}
|
||||
>
|
||||
Exit sketch
|
||||
|
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 {
|
||||
const code = `const __result__ = ${value}\nshow(__result__)`
|
||||
const ast = parser_wasm(code)
|
||||
const _programMem: any = { root: {} }
|
||||
const _programMem: any = { root: {}, return: null }
|
||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||
})
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { faEllipsis } from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faEllipsis,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { ActionIcon } from './ActionIcon'
|
||||
import { useStore } from 'useStore'
|
||||
import styles from './CodeMenu.module.css'
|
||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||
import { editorShortcutMeta } from './TextEditor'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
const { formatCode } = useStore((s) => ({
|
||||
@ -52,6 +56,24 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
</button>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item>
|
||||
<a
|
||||
className={styles.button}
|
||||
href="https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>Read the KCL docs</span>
|
||||
<small>
|
||||
On GitHub
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1 align-text-top"
|
||||
width={12}
|
||||
/>
|
||||
</small>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
</Menu>
|
||||
|
@ -6,6 +6,7 @@ import { useState } from 'react'
|
||||
import { ActionButton } from '../components/ActionButton'
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import { isReducedMotion } from 'lang/util'
|
||||
import { AstExplorer } from './AstExplorer'
|
||||
|
||||
type SketchModeCmd = Extract<
|
||||
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
||||
@ -94,6 +95,9 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
||||
>
|
||||
Send sketch mode command
|
||||
</ActionButton>
|
||||
<div style={{ height: '400px' }} className="overflow-y-auto">
|
||||
<AstExplorer />
|
||||
</div>
|
||||
</section>
|
||||
</CollapsiblePanel>
|
||||
)
|
||||
|
@ -24,6 +24,9 @@ import {
|
||||
StateFrom,
|
||||
} from 'xstate'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -108,6 +111,7 @@ export const GlobalStateProvider = ({
|
||||
actions: {
|
||||
goToSignInPage: () => {
|
||||
navigate(paths.SIGN_IN)
|
||||
|
||||
logout()
|
||||
},
|
||||
goToIndexPage: () => {
|
||||
@ -149,10 +153,12 @@ export const GlobalStateProvider = ({
|
||||
export default GlobalStateProvider
|
||||
|
||||
export function logout() {
|
||||
const url = withBaseUrl('/logout')
|
||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
||||
return fetch(url, {
|
||||
return (
|
||||
!isTauri() &&
|
||||
fetch(withBaseUrl('/logout'), {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ describe('processMemory', () => {
|
||||
// Enable rotations #152
|
||||
const code = `
|
||||
const myVar = 5
|
||||
const myFn = (a) => {
|
||||
fn myFn = (a) => {
|
||||
return a - 2
|
||||
}
|
||||
const otherVar = myFn(5)
|
||||
@ -29,6 +29,7 @@ describe('processMemory', () => {
|
||||
const ast = parser_wasm(code)
|
||||
const programMemory = await enginelessExecutor(ast, {
|
||||
root: {},
|
||||
return: null,
|
||||
})
|
||||
const output = processMemory(programMemory)
|
||||
expect(output.myVar).toEqual(5)
|
||||
|
@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
|
||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||
import { useStore } from '../useStore'
|
||||
import { useMemo } from 'react'
|
||||
import { ProgramMemory } from '../lang/executor'
|
||||
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
|
||||
import { Themes } from '../lib/theme'
|
||||
|
||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||
@ -49,8 +49,12 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
||||
Object.keys(programMemory.root).forEach((key) => {
|
||||
const val = programMemory.root[key]
|
||||
if (typeof val.value !== 'function') {
|
||||
if (val.type === 'sketchGroup' || val.type === 'extrudeGroup') {
|
||||
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }) => {
|
||||
if (val.type === 'SketchGroup') {
|
||||
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||
return rest
|
||||
})
|
||||
} else if (val.type === 'ExtrudeGroup') {
|
||||
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||
return rest
|
||||
})
|
||||
} else {
|
||||
|
@ -14,7 +14,13 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
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'
|
||||
import {
|
||||
addCloseToPipe,
|
||||
addNewSketchLn,
|
||||
compareVec2Epsilon,
|
||||
} from 'lang/std/sketch'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
||||
|
||||
export const Stream = ({ className = '' }) => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@ -204,8 +210,8 @@ export const Stream = ({ className = '' }) => {
|
||||
window: { x, y },
|
||||
}
|
||||
}
|
||||
engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
|
||||
if (command.cmd.type !== 'mouse_click' || !ast) return
|
||||
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
|
||||
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
||||
if (
|
||||
!(
|
||||
guiMode.mode === 'sketch' &&
|
||||
@ -214,13 +220,28 @@ export const Stream = ({ className = '' }) => {
|
||||
)
|
||||
return
|
||||
|
||||
if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
|
||||
// Check if the sketch group already exists.
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
guiMode.pathToNode,
|
||||
'VariableDeclarator'
|
||||
).node
|
||||
const variableName = varDec?.id?.name
|
||||
const sketchGroup = programMemory.root[variableName]
|
||||
const isEditingExistingSketch =
|
||||
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
||||
|
||||
if (
|
||||
resp?.data?.data?.entities_modified?.length &&
|
||||
guiMode.waitingFirstClick &&
|
||||
!isEditingExistingSketch
|
||||
) {
|
||||
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],
|
||||
curve_id: resp?.data?.data?.entities_modified[0],
|
||||
},
|
||||
})
|
||||
const coords: { x: number; y: number }[] =
|
||||
@ -241,29 +262,65 @@ export const Stream = ({ className = '' }) => {
|
||||
pathToNode: _pathToNode,
|
||||
waitingFirstClick: false,
|
||||
})
|
||||
updateAst(_modifiedAst)
|
||||
updateAst(_modifiedAst, false)
|
||||
} else if (
|
||||
data?.data?.entities_modified?.length &&
|
||||
!guiMode.waitingFirstClick
|
||||
resp?.data?.data?.entities_modified?.length &&
|
||||
(!guiMode.waitingFirstClick || isEditingExistingSketch)
|
||||
) {
|
||||
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],
|
||||
curve_id: resp?.data?.data?.entities_modified[0],
|
||||
},
|
||||
})
|
||||
const coords: { x: number; y: number }[] =
|
||||
curve.data.data.control_points
|
||||
const _modifiedAst = addNewSketchLn({
|
||||
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
guiMode.pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
const variableName = varDec.id.name
|
||||
const sketchGroup = programMemory.root[variableName]
|
||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
|
||||
const initialCoords = sketchGroup.value[0].from
|
||||
|
||||
const isClose = compareVec2Epsilon(initialCoords, [
|
||||
coords[1].x,
|
||||
coords[1].y,
|
||||
])
|
||||
|
||||
let _modifiedAst: Program
|
||||
if (!isClose) {
|
||||
_modifiedAst = addNewSketchLn({
|
||||
node: ast,
|
||||
programMemory,
|
||||
to: [coords[1].x, coords[1].y],
|
||||
fnName: 'line',
|
||||
pathToNode: guiMode.pathToNode,
|
||||
}).modifiedAst
|
||||
updateAst(_modifiedAst)
|
||||
updateAst(_modifiedAst, false)
|
||||
} else {
|
||||
_modifiedAst = addCloseToPipe({
|
||||
node: ast,
|
||||
programMemory,
|
||||
pathToNode: guiMode.pathToNode,
|
||||
})
|
||||
setGuiMode({
|
||||
mode: 'default',
|
||||
})
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'sketch_mode_disable',
|
||||
},
|
||||
})
|
||||
updateAst(_modifiedAst, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
setDidDragInStream(false)
|
||||
|
@ -26,9 +26,10 @@ import {
|
||||
addLineHighlight,
|
||||
lineHighlightField,
|
||||
} from 'editor/highlightextension'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
import { isOverlap, roundOff } from 'lib/utils'
|
||||
import { kclErrToDiagnostic } from 'lang/errors'
|
||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||
import interact from '@replit/codemirror-interact'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
formatCode: {
|
||||
@ -49,7 +50,7 @@ export const TextEditor = ({
|
||||
const pathParams = useParams()
|
||||
const {
|
||||
code,
|
||||
defferedSetCode,
|
||||
deferredSetCode,
|
||||
editorView,
|
||||
engineCommandManager,
|
||||
formatCode,
|
||||
@ -59,10 +60,9 @@ export const TextEditor = ({
|
||||
setEditorView,
|
||||
setIsLSPServerReady,
|
||||
setSelectionRanges,
|
||||
sourceRangeMap,
|
||||
} = useStore((s) => ({
|
||||
code: s.code,
|
||||
defferedSetCode: s.defferedSetCode,
|
||||
deferredSetCode: s.deferredSetCode,
|
||||
editorView: s.editorView,
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
formatCode: s.formatCode,
|
||||
@ -72,7 +72,6 @@ export const TextEditor = ({
|
||||
setEditorView: s.setEditorView,
|
||||
setIsLSPServerReady: s.setIsLSPServerReady,
|
||||
setSelectionRanges: s.setSelectionRanges,
|
||||
sourceRangeMap: s.sourceRangeMap,
|
||||
}))
|
||||
|
||||
const {
|
||||
@ -125,7 +124,7 @@ export const TextEditor = ({
|
||||
|
||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
||||
defferedSetCode(value)
|
||||
deferredSetCode(value)
|
||||
if (isTauri() && pathParams.id) {
|
||||
// Save the file to disk
|
||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||
@ -173,11 +172,11 @@ export const TextEditor = ({
|
||||
)
|
||||
const idBasedSelections = codeBasedSelections
|
||||
.map(({ type, range }) => {
|
||||
const hasOverlap = Object.entries(sourceRangeMap).filter(
|
||||
([_, sourceRange]) => {
|
||||
const hasOverlap = Object.entries(
|
||||
engineCommandManager?.sourceRangeMap || {}
|
||||
).filter(([_, sourceRange]) => {
|
||||
return isOverlap(sourceRange, range)
|
||||
}
|
||||
)
|
||||
})
|
||||
if (hasOverlap.length) {
|
||||
return {
|
||||
type,
|
||||
@ -237,6 +236,38 @@ export const TextEditor = ({
|
||||
lintGutter(),
|
||||
linter((_view) => {
|
||||
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)
|
||||
|
@ -82,7 +82,7 @@ export const EqualAngle = () => {
|
||||
transformInfos,
|
||||
programMemory,
|
||||
})
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -82,7 +82,7 @@ export const EqualLength = () => {
|
||||
transformInfos,
|
||||
programMemory,
|
||||
})
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -61,7 +61,7 @@ export const HorzVert = ({
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -154,7 +154,7 @@ export const Intersect = () => {
|
||||
initialVariableName: 'offset',
|
||||
} as any)
|
||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} else {
|
||||
@ -182,7 +182,7 @@ export const Intersect = () => {
|
||||
)
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
updateAst(_modifiedAst, {
|
||||
updateAst(_modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export const RemoveConstrainingValues = () => {
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -124,7 +124,7 @@ export const SetAbsDistance = ({
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
|
||||
updateAst(_modifiedAst, {
|
||||
updateAst(_modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -113,7 +113,7 @@ export const SetAngleBetween = () => {
|
||||
initialVariableName: 'angle',
|
||||
} as any)
|
||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} else {
|
||||
@ -141,7 +141,7 @@ export const SetAngleBetween = () => {
|
||||
)
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
updateAst(_modifiedAst, {
|
||||
updateAst(_modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ export const SetHorzVertDistance = ({
|
||||
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||
} as any))
|
||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||
updateAst(modifiedAst, {
|
||||
updateAst(modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} else {
|
||||
@ -163,7 +163,7 @@ export const SetHorzVertDistance = ({
|
||||
)
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
updateAst(_modifiedAst, {
|
||||
updateAst(_modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ export const SetAngleLength = ({
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
|
||||
updateAst(_modifiedAst, {
|
||||
updateAst(_modifiedAst, true, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -11,8 +11,9 @@ import { isOverlap } from 'lib/utils'
|
||||
|
||||
interface DefaultPlanes {
|
||||
xy: string
|
||||
yz: string
|
||||
xz: string
|
||||
// TODO re-enable
|
||||
// yz: string
|
||||
// xz: string
|
||||
}
|
||||
|
||||
export function useAppMode() {
|
||||
@ -42,34 +43,26 @@ export function useAppMode() {
|
||||
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 })
|
||||
// TODO re-enable
|
||||
// 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 })
|
||||
} else {
|
||||
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false)
|
||||
}
|
||||
}
|
||||
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') {
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||
}
|
||||
if (guiMode.mode === 'default') {
|
||||
const pathId =
|
||||
engineCommandManager &&
|
||||
isCursorInSketchCommandRange(
|
||||
@ -128,7 +121,7 @@ export function useAppMode() {
|
||||
},
|
||||
}
|
||||
)
|
||||
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||
const sketchUuid = uuidv4()
|
||||
const proms: any[] = []
|
||||
proms.push(
|
||||
@ -204,9 +197,10 @@ function createPlane(
|
||||
return planeId
|
||||
}
|
||||
|
||||
function hideDefaultPlanes(
|
||||
engineCommandManager: EngineCommandManager,
|
||||
defaultPlanes: DefaultPlanes
|
||||
function setDefaultPlanesHidden(
|
||||
engineCommandManager: EngineCommandManager | undefined,
|
||||
defaultPlanes: DefaultPlanes,
|
||||
hidden: boolean
|
||||
) {
|
||||
Object.values(defaultPlanes).forEach((planeId) => {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
@ -215,7 +209,7 @@ function hideDefaultPlanes(
|
||||
cmd: {
|
||||
type: 'object_visible',
|
||||
object_id: planeId,
|
||||
hidden: true,
|
||||
hidden: hidden,
|
||||
},
|
||||
})
|
||||
})
|
||||
@ -229,15 +223,17 @@ function isCursorInSketchCommandRange(
|
||||
([id, artifact]) =>
|
||||
selectionRanges.codeBasedSelections.some(
|
||||
(selection) =>
|
||||
Array.isArray(selection.range) &&
|
||||
Array.isArray(artifact.range) &&
|
||||
Array.isArray(selection?.range) &&
|
||||
Array.isArray(artifact?.range) &&
|
||||
isOverlap(selection.range, artifact.range) &&
|
||||
(artifact.commandType === 'start_path' ||
|
||||
artifact.commandType === 'extend_path' ||
|
||||
'close_path')
|
||||
artifact.commandType === 'close_path')
|
||||
)
|
||||
)
|
||||
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
|
||||
return overlapingEntries.length && overlapingEntries[0][1].parentId
|
||||
? overlapingEntries[0][1].parentId
|
||||
: false
|
||||
: overlapingEntries.find(
|
||||
([, artifact]) => artifact.commandType === 'start_path'
|
||||
)?.[0] || false
|
||||
}
|
||||
|
50
src/hooks/useEngineConnectionSubscriptions.ts
Normal file
50
src/hooks/useEngineConnectionSubscriptions.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
|
||||
export function useEngineConnectionSubscriptions() {
|
||||
const {
|
||||
engineCommandManager,
|
||||
setCursor2,
|
||||
setHighlightRange,
|
||||
highlightRange,
|
||||
} = useStore((s) => ({
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
setCursor2: s.setCursor2,
|
||||
setHighlightRange: s.setHighlightRange,
|
||||
highlightRange: s.highlightRange,
|
||||
}))
|
||||
useEffect(() => {
|
||||
if (!engineCommandManager) return
|
||||
|
||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||
event: 'highlight_set_entity',
|
||||
callback: ({ data }) => {
|
||||
if (data?.entity_id) {
|
||||
const sourceRange =
|
||||
engineCommandManager.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 = engineCommandManager.sourceRangeMap[data.entity_id]
|
||||
setCursor2({ range: sourceRange, type: 'default' })
|
||||
},
|
||||
})
|
||||
return () => {
|
||||
unSubHover()
|
||||
unSubClick()
|
||||
}
|
||||
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
|
||||
}
|
53
src/hooks/useSetupEngineManager.ts
Normal file
53
src/hooks/useSetupEngineManager.ts
Normal file
@ -0,0 +1,53 @@
|
||||
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,
|
||||
executeCode,
|
||||
} = useStore((s) => ({
|
||||
setEngineCommandManager: s.setEngineCommandManager,
|
||||
setMediaStream: s.setMediaStream,
|
||||
setIsStreamReady: s.setIsStreamReady,
|
||||
setStreamDimensions: s.setStreamDimensions,
|
||||
executeCode: s.executeCode,
|
||||
}))
|
||||
|
||||
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)
|
||||
eng.waitForReady.then(() => {
|
||||
executeCode()
|
||||
})
|
||||
return () => {
|
||||
eng?.tearDown()
|
||||
}
|
||||
}, [quadWidth, quadHeight])
|
||||
}
|
@ -46,7 +46,7 @@ export function useConvertToVariable() {
|
||||
variableName
|
||||
)
|
||||
|
||||
updateAst(_modifiedAst)
|
||||
updateAst(_modifiedAst, true)
|
||||
} catch (e) {
|
||||
console.log('e', e)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { parser_wasm } from './abstractSyntaxTree'
|
||||
import { KCLUnexpectedError } from './errors'
|
||||
import { KCLError } from './errors'
|
||||
import { initPromise } from './rust'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
@ -1744,6 +1744,12 @@ describe('parsing errors', () => {
|
||||
_theError = e
|
||||
}
|
||||
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([
|
||||
{
|
||||
type: 'sketchGroup',
|
||||
type: 'SketchGroup',
|
||||
start: {
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
@ -77,7 +77,7 @@ show(mySketch001)`
|
||||
)
|
||||
expect(shown).toEqual([
|
||||
{
|
||||
type: 'extrudeGroup',
|
||||
type: 'ExtrudeGroup',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
height: 2,
|
||||
@ -117,7 +117,7 @@ show(theExtrude, sk2)`
|
||||
)
|
||||
expect(geos).toEqual([
|
||||
{
|
||||
type: 'extrudeGroup',
|
||||
type: 'ExtrudeGroup',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
height: 2,
|
||||
@ -126,7 +126,7 @@ show(theExtrude, sk2)`
|
||||
__meta: [{ sourceRange: [13, 34] }],
|
||||
},
|
||||
{
|
||||
type: 'extrudeGroup',
|
||||
type: 'ExtrudeGroup',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
height: 2,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { parser_wasm } from './abstractSyntaxTree'
|
||||
import { ProgramMemory } from './executor'
|
||||
import { ProgramMemory, SketchGroup } from './executor'
|
||||
import { initPromise } from './rust'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { vi } from 'vitest'
|
||||
@ -117,10 +117,10 @@ show(mySketch)
|
||||
// ].join('\n')
|
||||
// const { root } = await exe(code)
|
||||
// expect(root.mySk1.value).toHaveLength(3)
|
||||
// expect(root?.rotated?.type).toBe('sketchGroup')
|
||||
// expect(root?.rotated?.type).toBe('SketchGroup')
|
||||
// if (
|
||||
// root?.mySk1?.type !== 'sketchGroup' ||
|
||||
// root?.rotated?.type !== 'sketchGroup'
|
||||
// root?.mySk1?.type !== 'SketchGroup' ||
|
||||
// root?.rotated?.type !== 'SketchGroup'
|
||||
// )
|
||||
// throw new Error('not a sketch group')
|
||||
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
|
||||
@ -143,7 +143,7 @@ show(mySketch)
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.mySk1).toEqual({
|
||||
type: 'sketchGroup',
|
||||
type: 'SketchGroup',
|
||||
start: {
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
@ -199,7 +199,7 @@ show(mySketch)
|
||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||
expect(root).toEqual({
|
||||
three: {
|
||||
type: 'userVal',
|
||||
type: 'UserVal',
|
||||
value: 3,
|
||||
__meta: [
|
||||
{
|
||||
@ -208,7 +208,7 @@ show(mySketch)
|
||||
],
|
||||
},
|
||||
yo: {
|
||||
type: 'userVal',
|
||||
type: 'UserVal',
|
||||
value: [1, '2', 3, 9],
|
||||
__meta: [
|
||||
{
|
||||
@ -225,7 +225,7 @@ show(mySketch)
|
||||
].join('\n')
|
||||
const { root } = await exe(code)
|
||||
expect(root.yo).toEqual({
|
||||
type: 'userVal',
|
||||
type: 'UserVal',
|
||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
||||
__meta: [
|
||||
{
|
||||
@ -240,7 +240,7 @@ show(mySketch)
|
||||
)
|
||||
const { root } = await exe(code)
|
||||
expect(root.myVar).toEqual({
|
||||
type: 'userVal',
|
||||
type: 'UserVal',
|
||||
value: '123',
|
||||
__meta: [
|
||||
{
|
||||
@ -338,7 +338,7 @@ describe('testing math operators', () => {
|
||||
const { root } = await exe(code)
|
||||
const sketch = root.part001
|
||||
// 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)
|
||||
})
|
||||
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 sketch = root.part001
|
||||
// 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.value?.[1]?.to).toEqual([6, 0])
|
||||
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
||||
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
||||
const removedUnaryExp = code.replace(
|
||||
`-legLen(segLen('seg01', %), myVar)`,
|
||||
`legLen(segLen('seg01', %), myVar)`
|
||||
@ -366,7 +366,9 @@ describe('testing math operators', () => {
|
||||
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
|
||||
|
||||
// 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 () => {
|
||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||
@ -397,7 +399,10 @@ show(theExtrude)`
|
||||
|
||||
// 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 result = await enginelessExecutor(ast, programMemory)
|
||||
|
@ -5,96 +5,21 @@ import {
|
||||
SourceRangeMap,
|
||||
} from './std/engineConnection'
|
||||
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 { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
import { rangeTypeFix } from './abstractSyntaxTree'
|
||||
|
||||
export type SourceRange = [number, number]
|
||||
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
|
||||
export type Metadata = {
|
||||
sourceRange: SourceRange
|
||||
}
|
||||
export type Position = [number, number, number]
|
||||
export type Rotation = [number, number, number, number]
|
||||
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
|
||||
export type { Position } from '../wasm-lib/kcl/bindings/Position'
|
||||
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
||||
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
||||
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
||||
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||
|
||||
interface BasePath {
|
||||
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
|
||||
export type PathToNode = [string | number, string][]
|
||||
|
||||
interface Memory {
|
||||
[key: string]: MemoryItem
|
||||
@ -102,12 +27,12 @@ interface Memory {
|
||||
|
||||
export interface ProgramMemory {
|
||||
root: Memory
|
||||
return?: ProgramReturn
|
||||
return: ProgramReturn | null
|
||||
}
|
||||
|
||||
export const executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {} },
|
||||
programMemory: ProgramMemory = { root: {}, return: null },
|
||||
engineCommandManager: EngineCommandManager,
|
||||
// work around while the gemotry is still be stored on the frontend
|
||||
// will be removed when the stream UI is added.
|
||||
@ -123,7 +48,7 @@ export const executor = async (
|
||||
engineCommandManager
|
||||
)
|
||||
const { artifactMap, sourceRangeMap } =
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
await engineCommandManager.waitForAllCommands(node, _programMemory)
|
||||
tempMapCallback({ artifactMap, sourceRangeMap })
|
||||
|
||||
engineCommandManager.endSession()
|
||||
@ -132,7 +57,7 @@ export const executor = async (
|
||||
|
||||
export const _executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {} },
|
||||
programMemory: ProgramMemory = { root: {}, return: null },
|
||||
engineCommandManager: EngineCommandManager
|
||||
): Promise<ProgramMemory> => {
|
||||
try {
|
||||
|
@ -176,7 +176,7 @@ show(part001)`
|
||||
})
|
||||
|
||||
describe('Testing moveValueIntoNewVariable', () => {
|
||||
const fn = (fnName: string) => `const ${fnName} = (x) => {
|
||||
const fn = (fnName: string) => `fn ${fnName} = (x) => {
|
||||
return x
|
||||
}
|
||||
`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Selection, TooTip } from '../useStore'
|
||||
import { Selection, ToolTip } from '../useStore'
|
||||
import {
|
||||
Program,
|
||||
CallExpression,
|
||||
@ -27,6 +27,7 @@ import {
|
||||
getFirstArg,
|
||||
createFirstArg,
|
||||
} from './std/sketch'
|
||||
import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
||||
|
||||
export function addStartSketch(
|
||||
node: Program,
|
||||
@ -51,11 +52,12 @@ export function addStartSketch(
|
||||
createPipeExpression(pipeBody)
|
||||
)
|
||||
|
||||
const newIndex = node.body.length
|
||||
_node.body = [...node.body, variableDeclaration]
|
||||
|
||||
let pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
['0', 'index'],
|
||||
[newIndex.toString(10), 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
@ -191,7 +193,7 @@ export function mutateArrExp(
|
||||
): boolean {
|
||||
if (node.type === 'ArrayExpression') {
|
||||
node.elements.forEach((element, i) => {
|
||||
if (element.type === 'Literal') {
|
||||
if (isLiteralArrayOrStatic(element)) {
|
||||
node.elements[i] = updateWith.elements[i]
|
||||
}
|
||||
})
|
||||
@ -209,8 +211,8 @@ export function mutateObjExpProp(
|
||||
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
||||
if (keyIndex !== -1) {
|
||||
if (
|
||||
updateWith.type === 'Literal' &&
|
||||
node.properties[keyIndex].value.type === 'Literal'
|
||||
isLiteralArrayOrStatic(updateWith) &&
|
||||
isLiteralArrayOrStatic(node.properties[keyIndex].value)
|
||||
) {
|
||||
node.properties[keyIndex].value = updateWith
|
||||
return true
|
||||
@ -220,7 +222,7 @@ export function mutateObjExpProp(
|
||||
) {
|
||||
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
||||
arrExp.elements.forEach((element, i) => {
|
||||
if (element.type === 'Literal') {
|
||||
if (isLiteralArrayOrStatic(element)) {
|
||||
arrExp.elements[i] = updateWith.elements[i]
|
||||
}
|
||||
})
|
||||
@ -303,7 +305,11 @@ export function extrudeSketch(
|
||||
}
|
||||
const name = findUniqueName(node, 'part')
|
||||
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||
const showCallIndex = getShowIndex(_node)
|
||||
let showCallIndex = getShowIndex(_node)
|
||||
if (showCallIndex == -1) {
|
||||
// We didn't find a show, so let's just append everything
|
||||
showCallIndex = _node.body.length
|
||||
}
|
||||
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
['body', ''],
|
||||
@ -633,7 +639,7 @@ export function giveSketchFnCallTag(
|
||||
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
|
||||
const tagStr = String(tagValue.value)
|
||||
const newFirstArg = createFirstArg(
|
||||
primaryCallExp.callee.name as TooTip,
|
||||
primaryCallExp.callee.name as ToolTip,
|
||||
firstArg.val,
|
||||
tagValue
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
|
||||
import { Selection, TooTip } from '../useStore'
|
||||
import { Selection, ToolTip } from '../useStore'
|
||||
import {
|
||||
BinaryExpression,
|
||||
Program,
|
||||
@ -457,7 +457,7 @@ export function isLinesParallelAndConstrained(
|
||||
const secondaryFirstArg = getFirstArg(secondaryNode)
|
||||
const constraintType = getConstraintType(
|
||||
secondaryFirstArg.val,
|
||||
secondaryNode.callee.name as TooTip
|
||||
secondaryNode.callee.name as ToolTip
|
||||
)
|
||||
const constraintLevel = getConstraintLevelFromSourceRange(
|
||||
secondaryLine.range,
|
||||
|
@ -224,7 +224,7 @@ const key = 'c'
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('comments in a fn block', () => {
|
||||
const code = `const myFn = () => {
|
||||
const code = `fn myFn = () => {
|
||||
// this is a comment
|
||||
const yo = { a: { b: { c: '123' } } }
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { SourceRange } from 'lang/executor'
|
||||
import { ProgramMemory, SourceRange } from 'lang/executor'
|
||||
import { Selections } from 'useStore'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { exportSave } from 'lib/exportSave'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
||||
|
||||
let lastMessage = ''
|
||||
|
||||
@ -393,8 +395,6 @@ export class EngineConnection {
|
||||
|
||||
let videoTrack = mediaStream.getVideoTracks()[0]
|
||||
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
||||
// TODO(paultag): this needs type information from the KittyCAD typescript
|
||||
// library once it's updated
|
||||
let client_metrics: ClientMetrics = {
|
||||
rtc_frames_decoded: 0,
|
||||
rtc_frames_dropped: 0,
|
||||
@ -422,12 +422,13 @@ export class EngineConnection {
|
||||
videoTrackReport.framesReceived
|
||||
client_metrics.rtc_frames_per_second =
|
||||
videoTrackReport.framesPerSecond || 0
|
||||
client_metrics.rtc_freeze_count = videoTrackReport.freezeCount
|
||||
client_metrics.rtc_freeze_count =
|
||||
videoTrackReport.freezeCount || 0
|
||||
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
|
||||
client_metrics.rtc_keyframes_decoded =
|
||||
videoTrackReport.keyFramesDecoded
|
||||
client_metrics.rtc_total_freezes_duration_sec =
|
||||
videoTrackReport.totalFreezesDuration
|
||||
videoTrackReport.totalFreezesDuration || 0
|
||||
} else if (videoTrackReport.type === 'transport') {
|
||||
// videoTrackReport.bytesReceived,
|
||||
// videoTrackReport.bytesSent,
|
||||
@ -770,7 +771,8 @@ export class EngineCommandManager {
|
||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||
const cmd = command.cmd
|
||||
if (
|
||||
cmd.type === 'camera_drag_move' &&
|
||||
(cmd.type === 'camera_drag_move' ||
|
||||
cmd.type === 'handle_mouse_drag_move') &&
|
||||
this.engineConnection?.unreliableDataChannel
|
||||
) {
|
||||
cmd.sequence = this.outSequence
|
||||
@ -882,7 +884,10 @@ export class EngineCommandManager {
|
||||
}
|
||||
return command.promise
|
||||
}
|
||||
async waitForAllCommands(): Promise<{
|
||||
async waitForAllCommands(
|
||||
ast?: Program,
|
||||
programMemory?: ProgramMemory
|
||||
): Promise<{
|
||||
artifactMap: ArtifactMap
|
||||
sourceRangeMap: SourceRangeMap
|
||||
}> {
|
||||
@ -891,9 +896,94 @@ export class EngineCommandManager {
|
||||
) as PendingCommand[]
|
||||
const proms = pendingCommands.map(({ promise }) => promise)
|
||||
await Promise.all(proms)
|
||||
if (ast && programMemory) {
|
||||
await this.fixIdMappings(ast, programMemory)
|
||||
}
|
||||
|
||||
return {
|
||||
artifactMap: this.artifactMap,
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
addNewSketchLn,
|
||||
getYComponent,
|
||||
getXComponent,
|
||||
addCloseToPipe,
|
||||
} from './sketch'
|
||||
import { parser_wasm } from '../abstractSyntaxTree'
|
||||
import { getNodePathFromSourceRange } from '../queryAst'
|
||||
@ -146,7 +147,7 @@ show(mySketch001)`
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
expect(sourceStart).toBe(66)
|
||||
const { modifiedAst } = addNewSketchLn({
|
||||
let { modifiedAst } = addNewSketchLn({
|
||||
node: ast,
|
||||
programMemory,
|
||||
to: [2, 3],
|
||||
@ -160,12 +161,33 @@ show(mySketch001)`
|
||||
],
|
||||
})
|
||||
// Enable rotations #152
|
||||
const expectedCode = `const mySketch001 = startSketchAt([0, 0])
|
||||
let expectedCode = `const mySketch001 = startSketchAt([0, 0])
|
||||
// |> rx(45, %)
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> lineTo([2, 3], %)
|
||||
show(mySketch001)
|
||||
`
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
|
||||
modifiedAst = addCloseToPipe({
|
||||
node: ast,
|
||||
programMemory,
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
|
||||
expectedCode = `const mySketch001 = startSketchAt([0, 0])
|
||||
// |> rx(45, %)
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> close(%)
|
||||
show(mySketch001)
|
||||
`
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
})
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
SketchGroup,
|
||||
SourceRange,
|
||||
PathToNode,
|
||||
MemoryItem,
|
||||
} from '../executor'
|
||||
import {
|
||||
Program,
|
||||
@ -19,7 +20,8 @@ import {
|
||||
getNodeFromPathCurry,
|
||||
getNodePathFromSourceRange,
|
||||
} from '../queryAst'
|
||||
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
||||
import { isLiteralArrayOrStatic } from './sketchcombos'
|
||||
import { GuiModes, toolTips, ToolTip } from '../../useStore'
|
||||
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
||||
|
||||
@ -55,7 +57,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
||||
}
|
||||
|
||||
export function createFirstArg(
|
||||
sketchFn: TooTip,
|
||||
sketchFn: ToolTip,
|
||||
val: Value | [Value, Value] | [Value, Value, Value],
|
||||
tag?: Value
|
||||
): Value {
|
||||
@ -197,7 +199,7 @@ export const line: SketchLineHelper = {
|
||||
)
|
||||
const variableName = varDec.id.name
|
||||
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 newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
@ -293,7 +295,7 @@ export const xLineTo: SketchLineHelper = {
|
||||
pathToNode
|
||||
)
|
||||
const newX = createLiteral(roundOff(to[0], 2))
|
||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newX
|
||||
} else {
|
||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
||||
@ -341,7 +343,7 @@ export const yLineTo: SketchLineHelper = {
|
||||
pathToNode
|
||||
)
|
||||
const newY = createLiteral(roundOff(to[1], 2))
|
||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newY
|
||||
} else {
|
||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
||||
@ -391,7 +393,7 @@ export const xLine: SketchLineHelper = {
|
||||
pathToNode
|
||||
)
|
||||
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newX
|
||||
} else {
|
||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
||||
@ -435,7 +437,7 @@ export const yLine: SketchLineHelper = {
|
||||
pathToNode
|
||||
)
|
||||
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newY
|
||||
} else {
|
||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
||||
@ -538,7 +540,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
)
|
||||
const variableName = varDec.id.name
|
||||
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 xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
||||
const newLine = createCallback
|
||||
@ -611,7 +613,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
)
|
||||
const variableName = varDec.id.name
|
||||
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 yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
||||
@ -868,7 +870,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
||||
const intersectPath = sketchGroup.value.find(
|
||||
({ name }) => name === intersectTagName
|
||||
({ name }: Path) => name === intersectTagName
|
||||
)
|
||||
let offset = 0
|
||||
if (intersectPath) {
|
||||
@ -941,17 +943,29 @@ interface CreateLineFnCallArgs {
|
||||
programMemory: ProgramMemory
|
||||
to: [number, number]
|
||||
from: [number, number]
|
||||
fnName: TooTip
|
||||
fnName: ToolTip
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
export function compareVec2Epsilon(
|
||||
vec1: [number, number],
|
||||
vec2: [number, number]
|
||||
) {
|
||||
const compareEpsilon = 0.015625 // or 2^-6
|
||||
const xDifference = Math.abs(vec1[0] - vec2[0])
|
||||
const yDifference = Math.abs(vec1[0] - vec2[0])
|
||||
return xDifference < compareEpsilon && yDifference < compareEpsilon
|
||||
}
|
||||
|
||||
export function addNewSketchLn({
|
||||
node: _node,
|
||||
programMemory: previousProgramMemory,
|
||||
to,
|
||||
fnName,
|
||||
pathToNode,
|
||||
}: Omit<CreateLineFnCallArgs, 'from'>): { modifiedAst: Program } {
|
||||
}: Omit<CreateLineFnCallArgs, 'from'>): {
|
||||
modifiedAst: Program
|
||||
} {
|
||||
const node = JSON.parse(JSON.stringify(_node))
|
||||
const { add, updateArgs } = sketchLineHelperMap?.[fnName] || {}
|
||||
if (!add || !updateArgs) throw new Error('not a sketch line helper')
|
||||
@ -965,11 +979,10 @@ export function addNewSketchLn({
|
||||
>(node, pathToNode, 'PipeExpression')
|
||||
const variableName = varDec.id.name
|
||||
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 last = sketch.value[sketch.value.length - 1] || sketch.start
|
||||
const from = last.to
|
||||
|
||||
return add({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
@ -980,6 +993,29 @@ export function addNewSketchLn({
|
||||
})
|
||||
}
|
||||
|
||||
export function addCloseToPipe({
|
||||
node,
|
||||
pathToNode,
|
||||
}: {
|
||||
node: Program
|
||||
programMemory: ProgramMemory
|
||||
pathToNode: PathToNode
|
||||
}) {
|
||||
const _node = { ...node }
|
||||
const closeExpression = createCallExpression('close', [
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
).node
|
||||
if (pipeExpression.type !== 'PipeExpression')
|
||||
throw new Error('not a pipe expression')
|
||||
pipeExpression.body = [...pipeExpression.body, closeExpression]
|
||||
return _node
|
||||
}
|
||||
|
||||
export function replaceSketchLine({
|
||||
node,
|
||||
programMemory,
|
||||
@ -993,7 +1029,7 @@ export function replaceSketchLine({
|
||||
node: Program
|
||||
programMemory: ProgramMemory
|
||||
sourceRange: SourceRange
|
||||
fnName: TooTip
|
||||
fnName: ToolTip
|
||||
to: [number, number]
|
||||
from: [number, number]
|
||||
createCallback: TransformCallback
|
||||
@ -1035,10 +1071,11 @@ export function addTagForSketchOnFace(
|
||||
|
||||
function isAngleLiteral(lineArugement: Value): boolean {
|
||||
return lineArugement?.type === 'ArrayExpression'
|
||||
? lineArugement.elements[0].type === 'Literal'
|
||||
? isLiteralArrayOrStatic(lineArugement.elements[0])
|
||||
: lineArugement?.type === 'ObjectExpression'
|
||||
? lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
||||
.type === 'Literal'
|
||||
? isLiteralArrayOrStatic(
|
||||
lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
||||
)
|
||||
: false
|
||||
}
|
||||
|
||||
@ -1171,7 +1208,7 @@ function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
|
||||
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
|
||||
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
|
||||
callExpression?.callee?.name as TooTip
|
||||
callExpression?.callee?.name as ToolTip
|
||||
)
|
||||
? 'to'
|
||||
: 'length'
|
||||
|
@ -401,6 +401,11 @@ show(part001)`
|
||||
programMemory.root['part001'] as SketchGroup,
|
||||
[index, index]
|
||||
).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',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TooTip, toolTips } from '../../useStore'
|
||||
import { ToolTip, toolTips } from '../../useStore'
|
||||
import {
|
||||
Program,
|
||||
VariableDeclarator,
|
||||
CallExpression,
|
||||
} from '../abstractSyntaxTreeTypes'
|
||||
import { SketchGroup, SourceRange } from '../executor'
|
||||
import { SketchGroup, SourceRange, Path } from '../executor'
|
||||
|
||||
export function getSketchSegmentFromSourceRange(
|
||||
sketchGroup: SketchGroup,
|
||||
@ -20,10 +20,10 @@ export function getSketchSegmentFromSourceRange(
|
||||
startSourceRange[1] >= rangeEnd &&
|
||||
sketchGroup.start
|
||||
)
|
||||
return { segment: sketchGroup.start, index: -1 }
|
||||
return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
|
||||
|
||||
const lineIndex = sketchGroup.value.findIndex(
|
||||
({ __geoMeta: { sourceRange } }) =>
|
||||
({ __geoMeta: { sourceRange } }: Path) =>
|
||||
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
||||
)
|
||||
const line = sketchGroup.value[lineIndex]
|
||||
@ -67,7 +67,10 @@ export function isSketchVariablesLinked(
|
||||
return false
|
||||
const firstCallExp = // first in pipe expression or just the call expression
|
||||
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
|
||||
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
|
||||
if (
|
||||
!firstCallExp ||
|
||||
!toolTips.includes(firstCallExp?.callee?.name as ToolTip)
|
||||
)
|
||||
return false
|
||||
// convention for sketch fns is that the second argument is the sketch group
|
||||
const secondArg = firstCallExp?.arguments[1]
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
getConstraintLevelFromSourceRange,
|
||||
} from './sketchcombos'
|
||||
import { initPromise } from '../rust'
|
||||
import { Selections, TooTip } from '../../useStore'
|
||||
import { Selections, ToolTip } from '../../useStore'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
import { recast } from '../../lang/recast'
|
||||
|
||||
@ -68,7 +68,7 @@ function getConstraintTypeFromSourceHelper(
|
||||
Value,
|
||||
Value
|
||||
]
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||
return getConstraintType(args, fnName)
|
||||
}
|
||||
function getConstraintTypeFromSourceHelper2(
|
||||
@ -76,7 +76,7 @@ function getConstraintTypeFromSourceHelper2(
|
||||
): ReturnType<typeof getConstraintType> {
|
||||
const ast = parser_wasm(code)
|
||||
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||
return getConstraintType(arg, fnName)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TransformCallback } from './stdTypes'
|
||||
import { Selections, toolTips, TooTip, Selection } from '../../useStore'
|
||||
import { Selections, toolTips, ToolTip, Selection } from '../../useStore'
|
||||
import {
|
||||
CallExpression,
|
||||
Program,
|
||||
@ -28,6 +28,7 @@ import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
|
||||
import { PathToNode, ProgramMemory } from '../executor'
|
||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
||||
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
|
||||
|
||||
type LineInputsType =
|
||||
| 'xAbsolute'
|
||||
@ -53,7 +54,7 @@ export type ConstraintType =
|
||||
| 'setAngleBetween'
|
||||
|
||||
function createCallWrapper(
|
||||
a: TooTip,
|
||||
a: ToolTip,
|
||||
val: [Value, Value] | Value,
|
||||
tag?: Value,
|
||||
valueUsedInTransform?: number
|
||||
@ -100,7 +101,7 @@ function intersectCallWrapper({
|
||||
}
|
||||
|
||||
export type TransformInfo = {
|
||||
tooltip: TooTip
|
||||
tooltip: ToolTip
|
||||
createNode: (a: {
|
||||
varValA: Value // x / angle
|
||||
varValB: Value // y / length or x y for angledLineOfXlength etc
|
||||
@ -111,7 +112,7 @@ export type TransformInfo = {
|
||||
}
|
||||
|
||||
type TransformMap = {
|
||||
[key in TooTip]?: {
|
||||
[key in ToolTip]?: {
|
||||
[key in LineInputsType | 'free']?: {
|
||||
[key in ConstraintType]?: TransformInfo
|
||||
}
|
||||
@ -1094,12 +1095,12 @@ export function getRemoveConstraintsTransform(
|
||||
sketchFnExp: CallExpression,
|
||||
constraintType: ConstraintType
|
||||
): TransformInfo | false {
|
||||
let name = sketchFnExp.callee.name as TooTip
|
||||
let name = sketchFnExp.callee.name as ToolTip
|
||||
if (!toolTips.includes(name)) {
|
||||
return false
|
||||
}
|
||||
const xyLineMap: {
|
||||
[key in TooTip]?: TooTip
|
||||
[key in ToolTip]?: ToolTip
|
||||
} = {
|
||||
xLine: 'line',
|
||||
yLine: 'line',
|
||||
@ -1136,27 +1137,18 @@ export function getRemoveConstraintsTransform(
|
||||
|
||||
// check if the function is locked down and so can't be transformed
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
if (Array.isArray(firstArg.val)) {
|
||||
const [a, b] = firstArg.val
|
||||
if (a?.type !== 'Literal' || b?.type !== 'Literal') {
|
||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||
return transformInfo
|
||||
}
|
||||
} else {
|
||||
if (firstArg.val?.type !== 'Literal') {
|
||||
return transformInfo
|
||||
}
|
||||
}
|
||||
|
||||
// check if the function has no constraints
|
||||
const isTwoValFree =
|
||||
Array.isArray(firstArg.val) &&
|
||||
firstArg.val?.[0]?.type === 'Literal' &&
|
||||
firstArg.val?.[1]?.type === 'Literal'
|
||||
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||
if (isTwoValFree) {
|
||||
return false
|
||||
}
|
||||
const isOneValFree =
|
||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
||||
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||
if (isOneValFree) {
|
||||
return transformInfo
|
||||
}
|
||||
@ -1175,37 +1167,24 @@ function getTransformMapPath(
|
||||
constraintType: ConstraintType
|
||||
):
|
||||
| {
|
||||
toolTip: TooTip
|
||||
toolTip: ToolTip
|
||||
lineInputType: LineInputsType | 'free'
|
||||
constraintType: ConstraintType
|
||||
}
|
||||
| false {
|
||||
const name = sketchFnExp.callee.name as TooTip
|
||||
const name = sketchFnExp.callee.name as ToolTip
|
||||
if (!toolTips.includes(name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the function is locked down and so can't be transformed
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
if (Array.isArray(firstArg.val)) {
|
||||
const [a, b] = firstArg.val
|
||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') {
|
||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (firstArg.val?.type !== 'Literal') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check if the function has no constraints
|
||||
const isTwoValFree =
|
||||
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) {
|
||||
if (isLiteralArrayOrStatic(firstArg.val)) {
|
||||
const info = transformMap?.[name]?.free?.[constraintType]
|
||||
if (info)
|
||||
return {
|
||||
@ -1246,7 +1225,7 @@ export function getTransformInfo(
|
||||
|
||||
export function getConstraintType(
|
||||
val: Value | [Value, Value] | [Value, Value, Value],
|
||||
fnName: TooTip
|
||||
fnName: ToolTip
|
||||
): LineInputsType | null {
|
||||
// this function assumes that for two val sketch functions that one arg is locked down not both
|
||||
// and for one val sketch functions that the arg is NOT locked down
|
||||
@ -1259,7 +1238,7 @@ export function getConstraintType(
|
||||
if (fnName === 'xLineTo') return 'yAbsolute'
|
||||
if (fnName === 'yLineTo') return 'xAbsolute'
|
||||
} else {
|
||||
const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
|
||||
const isFirstArgLockedDown = isNotLiteralArrayOrStatic(val[0])
|
||||
if (fnName === 'line')
|
||||
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
||||
if (fnName === 'lineTo')
|
||||
@ -1452,7 +1431,7 @@ export function transformAstSketchLines({
|
||||
|
||||
const varName = varDec.id.name
|
||||
const sketchGroup = programMemory.root?.[varName]
|
||||
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
|
||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
||||
throw new Error('not a sketch group')
|
||||
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
|
||||
const referencedSegment = referencedSegmentRange
|
||||
@ -1466,7 +1445,7 @@ export function transformAstSketchLines({
|
||||
programMemory,
|
||||
sourceRange: range,
|
||||
referencedSegment,
|
||||
fnName: transformTo || (callExp.callee.name as TooTip),
|
||||
fnName: transformTo || (callExp.callee.name as ToolTip),
|
||||
to,
|
||||
from,
|
||||
createCallback: callBack({
|
||||
@ -1532,29 +1511,52 @@ export function getConstraintLevelFromSourceRange(
|
||||
getNodePathFromSourceRange(ast, cursorRange),
|
||||
'CallExpression'
|
||||
)
|
||||
const name = sketchFnExp?.callee?.name as TooTip
|
||||
const name = sketchFnExp?.callee?.name as ToolTip
|
||||
if (!toolTips.includes(name)) return 'free'
|
||||
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
|
||||
// check if the function is fully constrained
|
||||
if (Array.isArray(firstArg.val)) {
|
||||
const [a, b] = firstArg.val
|
||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full'
|
||||
} else {
|
||||
if (firstArg.val?.type !== 'Literal') return 'full'
|
||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||
return 'full'
|
||||
}
|
||||
|
||||
// check if the function has no constraints
|
||||
const isTwoValFree =
|
||||
Array.isArray(firstArg.val) &&
|
||||
firstArg.val?.[0]?.type === 'Literal' &&
|
||||
firstArg.val?.[1]?.type === 'Literal'
|
||||
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||
const isOneValFree =
|
||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
||||
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||
|
||||
if (isTwoValFree) return 'free'
|
||||
if (isOneValFree) 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')
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ProgramMemory, Path, SourceRange } from '../executor'
|
||||
import { Program, Value } from '../abstractSyntaxTreeTypes'
|
||||
import { TooTip } from '../../useStore'
|
||||
import { ToolTip } from '../../useStore'
|
||||
import { PathToNode } from '../executor'
|
||||
import { EngineCommandManager } from './engineConnection'
|
||||
|
||||
@ -45,7 +45,7 @@ export type TransformCallback = (
|
||||
}
|
||||
|
||||
export type SketchCallTransfromMap = {
|
||||
[key in TooTip]: TransformCallback
|
||||
[key in ToolTip]: TransformCallback
|
||||
}
|
||||
|
||||
export interface SketchLineHelper {
|
||||
|
@ -131,10 +131,12 @@ const yi=45`
|
||||
})
|
||||
it('test negative and decimal numbers', () => {
|
||||
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([
|
||||
"number '-1.5' from 0 to 4",
|
||||
"operator '-' from 0 to 1",
|
||||
"number '1.5' from 1 to 4",
|
||||
])
|
||||
expect(stringSummaryLexer('1.5')).toEqual([
|
||||
"number '1.5' from 0 to 3",
|
||||
@ -158,10 +160,12 @@ const yi=45`
|
||||
"whitespace ' ' from 3 to 4",
|
||||
"operator '+' from 4 to 5",
|
||||
"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([
|
||||
"number '-1.5' from 0 to 4",
|
||||
"operator '-' from 0 to 1",
|
||||
"number '1.5' from 1 to 4",
|
||||
"whitespace ' ' from 4 to 5",
|
||||
"operator '+' from 5 to 6",
|
||||
"whitespace ' ' from 6 to 7",
|
||||
|
@ -49,7 +49,7 @@ class MockEngineCommandManager {
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Program,
|
||||
pm: ProgramMemory = { root: {} }
|
||||
pm: ProgramMemory = { root: {}, return: null }
|
||||
): Promise<ProgramMemory> {
|
||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
@ -64,7 +64,7 @@ export async function enginelessExecutor(
|
||||
|
||||
export async function executor(
|
||||
ast: Program,
|
||||
pm: ProgramMemory = { root: {} }
|
||||
pm: ProgramMemory = { root: {}, return: null }
|
||||
): Promise<ProgramMemory> {
|
||||
const engineCommandManager = new EngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
@ -75,6 +75,6 @@ export async function executor(
|
||||
await engineCommandManager.waitForReady
|
||||
engineCommandManager.startNewSession()
|
||||
const programMemory = await _executor(ast, pm, engineCommandManager)
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
||||
return programMemory
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export function throttle<T>(
|
||||
}
|
||||
|
||||
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
|
||||
export function defferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null
|
||||
let latestArgs: T
|
||||
|
||||
@ -66,7 +66,7 @@ export function defferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
func(latestArgs)
|
||||
}
|
||||
|
||||
function deffered(args: T) {
|
||||
function deferred(args: T) {
|
||||
latestArgs = args
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
@ -74,7 +74,7 @@ export function defferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
|
||||
return deffered
|
||||
return deferred
|
||||
}
|
||||
|
||||
export function getNormalisedCoordinates({
|
||||
|
@ -2,6 +2,8 @@ import { createMachine, assign } from 'xstate'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import withBaseURL from '../lib/withBaseURL'
|
||||
import { CommandBarMeta } from '../lib/commands'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
|
||||
const SKIP_AUTH =
|
||||
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
|
||||
@ -115,16 +117,25 @@ async function getUser(context: UserContext) {
|
||||
const headers: { [key: string]: string } = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
if (!context.token && '__TAURI__' in window) throw 'not log in'
|
||||
|
||||
if (!context.token && isTauri()) throw new Error('No token found')
|
||||
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
if (SKIP_AUTH) return LOCAL_USER
|
||||
const response = await fetch(url, {
|
||||
|
||||
const userPromise = !isTauri()
|
||||
? fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((err) => console.error('error from Browser getUser', err))
|
||||
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
|
||||
token: context.token,
|
||||
}).catch((err) => console.error('error from Tauri getUser', err))
|
||||
|
||||
const user = await userPromise
|
||||
|
||||
const user = await response.json()
|
||||
if ('error_code' in user) throw new Error(user.message)
|
||||
|
||||
return user
|
||||
|
316
src/useStore.ts
316
src/useStore.ts
@ -4,6 +4,7 @@ import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||
import { parser_wasm } from './lang/abstractSyntaxTree'
|
||||
import { Program } from './lang/abstractSyntaxTreeTypes'
|
||||
import { getNodeFromPath } from './lang/queryAst'
|
||||
import { enginelessExecutor } from './lib/testHelpers'
|
||||
import {
|
||||
ProgramMemory,
|
||||
Position,
|
||||
@ -13,13 +14,10 @@ import {
|
||||
} from './lang/executor'
|
||||
import { recast } from './lang/recast'
|
||||
import { EditorSelection } from '@codemirror/state'
|
||||
import {
|
||||
ArtifactMap,
|
||||
SourceRangeMap,
|
||||
EngineCommandManager,
|
||||
} from './lang/std/engineConnection'
|
||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||
import { KCLError } from './lang/errors'
|
||||
import { defferExecution } from 'lib/utils'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { _executor } from './lang/executor'
|
||||
|
||||
export type Selection = {
|
||||
type: 'default' | 'line-end' | 'line-mid'
|
||||
@ -29,7 +27,7 @@ export type Selections = {
|
||||
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[]
|
||||
codeBasedSelections: Selection[]
|
||||
}
|
||||
export type TooTip =
|
||||
export type ToolTip =
|
||||
| 'lineTo'
|
||||
| 'line'
|
||||
| 'angledLine'
|
||||
@ -59,7 +57,7 @@ export const toolTips = [
|
||||
'xLineTo',
|
||||
'yLineTo',
|
||||
'angledLineThatIntersects',
|
||||
] as any as TooTip[]
|
||||
] as any as ToolTip[]
|
||||
|
||||
export type GuiModes =
|
||||
| {
|
||||
@ -67,7 +65,7 @@ export type GuiModes =
|
||||
}
|
||||
| {
|
||||
mode: 'sketch'
|
||||
sketchMode: TooTip
|
||||
sketchMode: ToolTip
|
||||
isTooltip: true
|
||||
waitingFirstClick: boolean
|
||||
rotation: Rotation
|
||||
@ -123,40 +121,37 @@ export interface StoreState {
|
||||
setGuiMode: (guiMode: GuiModes) => void
|
||||
logs: string[]
|
||||
addLog: (log: string) => void
|
||||
resetLogs: () => void
|
||||
setLogs: (logs: string[]) => void
|
||||
kclErrors: KCLError[]
|
||||
addKCLError: (err: KCLError) => void
|
||||
setErrors: (errors: KCLError[]) => void
|
||||
resetKCLErrors: () => void
|
||||
ast: Program
|
||||
setAst: (ast: Program) => void
|
||||
executeAst: (ast?: Program) => void
|
||||
executeAstMock: (ast?: Program) => void
|
||||
updateAst: (
|
||||
ast: Program,
|
||||
execute: boolean,
|
||||
optionalParams?: {
|
||||
focusPath?: PathToNode
|
||||
callBack?: (ast: Program) => void
|
||||
}
|
||||
) => void
|
||||
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
|
||||
updateAstAsync: (
|
||||
ast: Program,
|
||||
reexecute: boolean,
|
||||
focusPath?: PathToNode
|
||||
) => void
|
||||
code: string
|
||||
defferedCode: string
|
||||
setCode: (code: string) => void
|
||||
defferedSetCode: (code: string) => void
|
||||
deferredSetCode: (code: string) => void
|
||||
executeCode: (code?: string) => void
|
||||
formatCode: () => void
|
||||
errorState: {
|
||||
isError: boolean
|
||||
error: string
|
||||
}
|
||||
setError: (error?: string) => void
|
||||
programMemory: ProgramMemory
|
||||
setProgramMemory: (programMemory: ProgramMemory) => void
|
||||
isShiftDown: boolean
|
||||
setIsShiftDown: (isShiftDown: boolean) => void
|
||||
artifactMap: ArtifactMap
|
||||
sourceRangeMap: SourceRangeMap
|
||||
setArtifactNSourceRangeMaps: (a: {
|
||||
artifactMap: ArtifactMap
|
||||
sourceRangeMap: SourceRangeMap
|
||||
}) => void
|
||||
engineCommandManager?: EngineCommandManager
|
||||
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
|
||||
mediaStream?: MediaStream
|
||||
@ -197,10 +192,13 @@ let pendingAstUpdates: number[] = []
|
||||
export const useStore = create<StoreState>()(
|
||||
persist(
|
||||
(set, get) => {
|
||||
const setDefferedCode = defferExecution(
|
||||
(code: string) => set({ defferedCode: code }),
|
||||
600
|
||||
)
|
||||
// We defer this so that likely our ast has caught up to the code.
|
||||
// If we are making changes that are not reflected in the ast, we
|
||||
// should not be updating the ast.
|
||||
const setDeferredCode = deferExecution((code: string) => {
|
||||
set({ code })
|
||||
get().executeCode(code)
|
||||
}, 600)
|
||||
return {
|
||||
editorView: null,
|
||||
setEditorView: (editorView) => {
|
||||
@ -214,6 +212,22 @@ export const useStore = create<StoreState>()(
|
||||
editorView.dispatch({ effects: addLineHighlight.of(selection) })
|
||||
}
|
||||
},
|
||||
executeCode: async (code) => {
|
||||
const result = await executeCode({
|
||||
code: code || get().code,
|
||||
lastAst: get().ast,
|
||||
engineCommandManager: get().engineCommandManager,
|
||||
})
|
||||
if (!result.isChange) {
|
||||
return
|
||||
}
|
||||
set({
|
||||
ast: result.ast,
|
||||
logs: result.logs,
|
||||
kclErrors: result.errors,
|
||||
programMemory: result.programMemory,
|
||||
})
|
||||
},
|
||||
setCursor: (selections) => {
|
||||
const { editorView } = get()
|
||||
if (!editorView) return
|
||||
@ -243,7 +257,10 @@ export const useStore = create<StoreState>()(
|
||||
get().setCursor({
|
||||
otherSelections: currestSelections.otherSelections,
|
||||
codeBasedSelections: [
|
||||
{ range: [0, code.length - 1], type: 'default' },
|
||||
{
|
||||
range: [0, code.length ? code.length - 1 : 0],
|
||||
type: 'default',
|
||||
},
|
||||
],
|
||||
})
|
||||
return
|
||||
@ -277,8 +294,8 @@ export const useStore = create<StoreState>()(
|
||||
set((state) => ({ logs: [...state.logs, log] }))
|
||||
}
|
||||
},
|
||||
resetLogs: () => {
|
||||
set({ logs: [] })
|
||||
setLogs: (logs) => {
|
||||
set({ logs })
|
||||
},
|
||||
kclErrors: [],
|
||||
addKCLError: (e) => {
|
||||
@ -287,6 +304,9 @@ export const useStore = create<StoreState>()(
|
||||
resetKCLErrors: () => {
|
||||
set({ kclErrors: [] })
|
||||
},
|
||||
setErrors: (errors) => {
|
||||
set({ kclErrors: errors })
|
||||
},
|
||||
ast: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
@ -299,7 +319,47 @@ export const useStore = create<StoreState>()(
|
||||
setAst: (ast) => {
|
||||
set({ ast })
|
||||
},
|
||||
updateAst: async (ast, { focusPath, callBack = () => {} } = {}) => {
|
||||
executeAst: async (ast) => {
|
||||
const _ast = ast || get().ast
|
||||
if (!get().isStreamReady) return
|
||||
const engineCommandManager = get().engineCommandManager!
|
||||
if (!engineCommandManager) return
|
||||
|
||||
set({ isExecuting: true })
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast: _ast,
|
||||
engineCommandManager,
|
||||
})
|
||||
set({
|
||||
programMemory,
|
||||
logs,
|
||||
kclErrors: errors,
|
||||
isExecuting: false,
|
||||
})
|
||||
},
|
||||
executeAstMock: async (ast) => {
|
||||
const _ast = ast || get().ast
|
||||
if (!get().isStreamReady) return
|
||||
const engineCommandManager = get().engineCommandManager!
|
||||
if (!engineCommandManager) return
|
||||
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast: _ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
})
|
||||
set({
|
||||
programMemory,
|
||||
logs,
|
||||
kclErrors: errors,
|
||||
isExecuting: false,
|
||||
})
|
||||
},
|
||||
updateAst: async (
|
||||
ast,
|
||||
reexecute,
|
||||
{ focusPath, callBack = () => {} } = {}
|
||||
) => {
|
||||
const newCode = recast(ast)
|
||||
const astWithUpdatedSource = parser_wasm(newCode)
|
||||
callBack(astWithUpdatedSource)
|
||||
@ -307,7 +367,6 @@ export const useStore = create<StoreState>()(
|
||||
set({
|
||||
ast: astWithUpdatedSource,
|
||||
code: newCode,
|
||||
defferedCode: newCode,
|
||||
})
|
||||
if (focusPath) {
|
||||
const { node } = getNodeFromPath<any>(
|
||||
@ -328,24 +387,33 @@ export const useStore = create<StoreState>()(
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (reexecute) {
|
||||
// Call execute on the set ast.
|
||||
get().executeAst(astWithUpdatedSource)
|
||||
} else {
|
||||
// When we don't re-execute, we still want to update the program
|
||||
// memory with the new ast. So we will hit the mock executor
|
||||
// instead.
|
||||
get().executeAstMock(astWithUpdatedSource)
|
||||
}
|
||||
},
|
||||
updateAstAsync: async (ast, focusPath) => {
|
||||
updateAstAsync: async (ast, reexecute, focusPath) => {
|
||||
// clear any pending updates
|
||||
pendingAstUpdates.forEach((id) => clearTimeout(id))
|
||||
pendingAstUpdates = []
|
||||
// setup a new update
|
||||
pendingAstUpdates.push(
|
||||
setTimeout(() => {
|
||||
get().updateAst(ast, { focusPath })
|
||||
get().updateAst(ast, reexecute, { focusPath })
|
||||
}, 100) as unknown as number
|
||||
)
|
||||
},
|
||||
code: '',
|
||||
defferedCode: '',
|
||||
setCode: (code) => set({ code, defferedCode: code }),
|
||||
defferedSetCode: (code) => {
|
||||
setCode: (code) => set({ code }),
|
||||
deferredSetCode: (code) => {
|
||||
set({ code })
|
||||
setDefferedCode(code)
|
||||
setDeferredCode(code)
|
||||
},
|
||||
formatCode: async () => {
|
||||
const code = get().code
|
||||
@ -353,20 +421,10 @@ export const useStore = create<StoreState>()(
|
||||
const newCode = recast(ast)
|
||||
set({ code: newCode, ast })
|
||||
},
|
||||
errorState: {
|
||||
isError: false,
|
||||
error: '',
|
||||
},
|
||||
setError: (error = '') => {
|
||||
set({ errorState: { isError: !!error, error } })
|
||||
},
|
||||
programMemory: { root: {}, pendingMemory: {} },
|
||||
programMemory: { root: {}, return: null },
|
||||
setProgramMemory: (programMemory) => set({ programMemory }),
|
||||
isShiftDown: false,
|
||||
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
||||
artifactMap: {},
|
||||
sourceRangeMap: {},
|
||||
setArtifactNSourceRangeMaps: (maps) => set({ ...maps }),
|
||||
setEngineCommandManager: (engineCommandManager) =>
|
||||
set({ engineCommandManager }),
|
||||
setMediaStream: (mediaStream) => set({ mediaStream }),
|
||||
@ -409,9 +467,165 @@ export const useStore = create<StoreState>()(
|
||||
partialize: (state) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(state).filter(([key]) =>
|
||||
['code', 'defferedCode', 'openPanes'].includes(key)
|
||||
['code', 'openPanes'].includes(key)
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const defaultProgramMemory: ProgramMemory['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: [],
|
||||
},
|
||||
PI: {
|
||||
type: 'UserVal',
|
||||
value: Math.PI,
|
||||
__meta: [],
|
||||
},
|
||||
}
|
||||
|
||||
async function executeCode({
|
||||
engineCommandManager,
|
||||
code,
|
||||
lastAst,
|
||||
}: {
|
||||
code: string
|
||||
lastAst: Program
|
||||
engineCommandManager?: EngineCommandManager
|
||||
}): Promise<
|
||||
| {
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
ast: Program
|
||||
isChange: true
|
||||
}
|
||||
| { isChange: false }
|
||||
> {
|
||||
let ast: Program
|
||||
try {
|
||||
ast = parser_wasm(code)
|
||||
} catch (e) {
|
||||
let errors: KCLError[] = []
|
||||
let logs: string[] = [JSON.stringify(e)]
|
||||
if (e instanceof KCLError) {
|
||||
errors = [e]
|
||||
logs = []
|
||||
if (e.msg === 'file is empty') engineCommandManager?.endSession()
|
||||
}
|
||||
return {
|
||||
isChange: true,
|
||||
logs,
|
||||
errors,
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
ast: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
body: [],
|
||||
nonCodeMeta: {
|
||||
noneCodeNodes: {},
|
||||
start: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
// Check if the ast we have is equal to the ast in the storage.
|
||||
// If it is, we don't need to update the ast.
|
||||
if (!engineCommandManager || JSON.stringify(ast) === JSON.stringify(lastAst))
|
||||
return { isChange: false }
|
||||
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
})
|
||||
return {
|
||||
ast,
|
||||
logs,
|
||||
errors,
|
||||
programMemory,
|
||||
isChange: true,
|
||||
}
|
||||
}
|
||||
|
||||
async function executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor = false,
|
||||
}: {
|
||||
ast: Program
|
||||
engineCommandManager: EngineCommandManager
|
||||
useFakeExecutor?: boolean
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
}> {
|
||||
try {
|
||||
if (!useFakeExecutor) {
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
}
|
||||
const programMemory = await (useFakeExecutor
|
||||
? enginelessExecutor(ast, {
|
||||
root: defaultProgramMemory,
|
||||
return: null,
|
||||
})
|
||||
: _executor(
|
||||
ast,
|
||||
{
|
||||
root: defaultProgramMemory,
|
||||
return: null,
|
||||
},
|
||||
engineCommandManager
|
||||
))
|
||||
|
||||
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
programMemory,
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e instanceof KCLError) {
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
return {
|
||||
logs: [e],
|
||||
errors: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
250
src/wasm-lib/Cargo.lock
generated
250
src/wasm-lib/Cargo.lock
generated
@ -150,7 +150,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -306,7 +306,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"time 0.3.27",
|
||||
"time",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@ -363,18 +363,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.26"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
||||
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -390,30 +387,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
version = "4.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"clap_derive 3.2.25",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap 1.9.3",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
|
||||
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive 4.4.2",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -424,25 +403,13 @@ checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex 0.5.1",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"terminal_size",
|
||||
"unicase",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.2"
|
||||
@ -452,16 +419,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -645,7 +603,7 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -659,7 +617,7 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -933,7 +891,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -985,7 +943,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -1244,7 +1202,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1255,6 +1212,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1269,6 +1227,17 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.8.0"
|
||||
@ -1282,7 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"rustix",
|
||||
"rustix 0.38.9",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -1345,7 +1314,7 @@ version = "0.1.26"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bson",
|
||||
"clap 4.4.2",
|
||||
"clap",
|
||||
"dashmap",
|
||||
"derive-docs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"expectorate",
|
||||
@ -1393,7 +1362,7 @@ dependencies = [
|
||||
"rand",
|
||||
"reqwest",
|
||||
"reqwest-conditional-middleware",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"reqwest-middleware",
|
||||
"reqwest-retry",
|
||||
"reqwest-tracing",
|
||||
"schemars",
|
||||
@ -1447,6 +1416,12 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
@ -1554,7 +1529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -1675,22 +1650,22 @@ checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
|
||||
|
||||
[[package]]
|
||||
name = "openapitor"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "120168eae5b6485690af708bd1030547df62cca10a643763d416ab0e6831decb"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#3d74c1dfb41146a268a644e9fde2c19a8cd66895"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap 3.2.25",
|
||||
"clap",
|
||||
"data-encoding",
|
||||
"format_serde_error",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
"json-patch",
|
||||
"log",
|
||||
"numeral",
|
||||
"once_cell",
|
||||
"openapiv3",
|
||||
"phonenumber",
|
||||
"proc-macro2",
|
||||
@ -1698,7 +1673,7 @@ dependencies = [
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest-middleware 0.1.6",
|
||||
"reqwest-middleware",
|
||||
"rustfmt-wrapper",
|
||||
"schemars",
|
||||
"serde",
|
||||
@ -1711,18 +1686,17 @@ dependencies = [
|
||||
"slog-stdlog",
|
||||
"slog-term",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapiv3"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b1a9f106eb0a780abd17ba9fca8e0843e3461630bcbe2af0ad4d5d3ba4e9aa4"
|
||||
checksum = "75e56d5c441965b6425165b7e3223cc933ca469834f4a8b4786817a1f9dc4f13"
|
||||
dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@ -1752,12 +1726,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@ -1829,7 +1797,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.7.4",
|
||||
"structmeta",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1891,7 +1859,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1967,9 +1935,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -2183,26 +2151,10 @@ checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"reqwest",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"reqwest-middleware",
|
||||
"task-local-extensions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-middleware"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69539cea4148dce683bec9dc95be3f0397a9bb2c248a49c8296a9d21659a8cdd"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"futures",
|
||||
"http",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"task-local-extensions",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-middleware"
|
||||
version = "0.2.3"
|
||||
@ -2233,7 +2185,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"parking_lot 0.11.2",
|
||||
"reqwest",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"reqwest-middleware",
|
||||
"retry-policies",
|
||||
"task-local-extensions",
|
||||
"tokio",
|
||||
@ -2253,7 +2205,7 @@ dependencies = [
|
||||
"matchit",
|
||||
"opentelemetry",
|
||||
"reqwest",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"reqwest-middleware",
|
||||
"task-local-extensions",
|
||||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
@ -2310,6 +2262,20 @@ dependencies = [
|
||||
"toolchain_find",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.9"
|
||||
@ -2319,7 +2285,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.4.5",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2484,9 +2450,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.187"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a7fe14252655bd1e578af19f5fa00fe02fd0013b100ca6b49fde31c41bae4c"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -2502,13 +2468,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.187"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2524,9 +2490,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"itoa",
|
||||
@ -2542,7 +2508,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2554,7 +2520,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2677,7 +2643,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slog",
|
||||
"time 0.3.27",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2712,7 +2678,7 @@ dependencies = [
|
||||
"slog",
|
||||
"term",
|
||||
"thread_local",
|
||||
"time 0.3.27",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2771,7 +2737,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2782,7 +2748,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2798,9 +2764,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.29"
|
||||
version = "2.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2837,7 +2803,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix",
|
||||
"rustix 0.38.9",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2862,32 +2828,33 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
name = "terminal_size"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"rustix 0.37.23",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2911,17 +2878,6 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.27"
|
||||
@ -2994,7 +2950,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3110,7 +3066,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3139,7 +3095,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3225,7 +3181,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -3400,12 +3356,6 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -3433,7 +3383,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -3468,7 +3418,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -12,7 +12,7 @@ bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
||||
gloo-utils = "0.2.0"
|
||||
kcl-lib = { path = "kcl" }
|
||||
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-futures = "0.4.37"
|
||||
|
||||
|
@ -14,11 +14,11 @@ proc-macro = true
|
||||
convert_case = "0.6.0"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
serde = { version = "1.0.186", features = ["derive"] }
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.29", features = ["full"] }
|
||||
syn = { version = "2.0.33", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
expectorate = "1.0.7"
|
||||
openapitor = "0.0.5"
|
||||
openapitor = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "main" }
|
||||
pretty_assertions = "1.4.0"
|
||||
|
@ -9,7 +9,7 @@ license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
derive-docs = { version = "0.1.3" }
|
||||
#derive-docs = { path = "../derive-docs" }
|
||||
@ -18,9 +18,9 @@ lazy_static = "1.4.0"
|
||||
parse-display = "0.8.2"
|
||||
regex = "1.7.1"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
||||
serde = {version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
thiserror = "1.0.47"
|
||||
serde = {version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.106"
|
||||
thiserror = "1.0.48"
|
||||
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"] }
|
||||
|
||||
|
@ -12,7 +12,7 @@ use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, R
|
||||
use crate::{
|
||||
engine::EngineConnection,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange},
|
||||
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
||||
parser::PIPE_OPERATOR,
|
||||
};
|
||||
|
||||
@ -449,6 +449,7 @@ pub enum BinaryPart {
|
||||
BinaryExpression(Box<BinaryExpression>),
|
||||
CallExpression(Box<CallExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
}
|
||||
|
||||
impl From<BinaryPart> for crate::executor::SourceRange {
|
||||
@ -471,6 +472,7 @@ impl BinaryPart {
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
||||
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::CallExpression(call_expression) => call_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::CallExpression(call_expression) => call_expression.end(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.end(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,6 +527,7 @@ impl BinaryPart {
|
||||
source_ranges: vec![unary_expression.into()],
|
||||
}))
|
||||
}
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,6 +541,9 @@ impl BinaryPart {
|
||||
}
|
||||
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::MemberExpression(member_expression) => {
|
||||
member_expression.get_hover_value_for_position(pos, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -553,6 +561,9 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(ref mut unary_expression) => {
|
||||
unary_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
BinaryPart::MemberExpression(ref mut member_expression) => {
|
||||
member_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -751,12 +762,7 @@ impl CallExpression {
|
||||
})
|
||||
})?
|
||||
.clone(),
|
||||
Value::MemberExpression(member_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||
@ -1082,23 +1088,23 @@ impl Literal {
|
||||
|
||||
impl From<Literal> for MemoryItem {
|
||||
fn from(literal: Literal) -> Self {
|
||||
MemoryItem::UserVal {
|
||||
MemoryItem::UserVal(UserVal {
|
||||
value: literal.value.clone(),
|
||||
meta: vec![Metadata {
|
||||
source_range: literal.into(),
|
||||
}],
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Box<Literal>> for MemoryItem {
|
||||
fn from(literal: &Box<Literal>) -> Self {
|
||||
MemoryItem::UserVal {
|
||||
MemoryItem::UserVal(UserVal {
|
||||
value: literal.value.clone(),
|
||||
meta: vec![Metadata {
|
||||
source_range: literal.into(),
|
||||
}],
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1227,12 +1233,7 @@ impl ArrayExpression {
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
}));
|
||||
}
|
||||
Value::MemberExpression(member_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("MemberExpression not implemented here: {:?}", member_expression),
|
||||
source_ranges: vec![member_expression.into()],
|
||||
}));
|
||||
}
|
||||
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
||||
Value::FunctionExpression(function_expression) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("FunctionExpression not implemented here: {:?}", function_expression),
|
||||
@ -1245,12 +1246,12 @@ impl ArrayExpression {
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
Ok(MemoryItem::UserVal {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: results.into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
@ -1370,12 +1371,12 @@ impl ObjectExpression {
|
||||
object.insert(property.key.name.clone(), result.get_json_value()?);
|
||||
}
|
||||
|
||||
Ok(MemoryItem::UserVal {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: object.into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
@ -1554,6 +1555,38 @@ impl MemberExpression {
|
||||
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> {
|
||||
let property_name = match &self.property {
|
||||
LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(),
|
||||
@ -1562,9 +1595,12 @@ impl MemberExpression {
|
||||
// Parse this as a string.
|
||||
if let serde_json::Value::String(string) = value {
|
||||
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 {
|
||||
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()],
|
||||
}));
|
||||
}
|
||||
@ -1582,12 +1618,12 @@ impl MemberExpression {
|
||||
|
||||
if let serde_json::Value::Object(map) = object {
|
||||
if let Some(value) = map.get(&property_name) {
|
||||
Ok(MemoryItem::UserVal {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: value.clone(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Property {} not found in object", property_name),
|
||||
@ -1715,12 +1751,12 @@ impl BinaryExpression {
|
||||
parse_json_value_as_string(&right_json_value),
|
||||
) {
|
||||
let value = serde_json::Value::String(format!("{}{}", left, right));
|
||||
return Ok(MemoryItem::UserVal {
|
||||
return Ok(MemoryItem::UserVal(UserVal {
|
||||
value,
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1735,12 +1771,12 @@ impl BinaryExpression {
|
||||
BinaryOperator::Mod => (left % right).into(),
|
||||
};
|
||||
|
||||
Ok(MemoryItem::UserVal {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value,
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
@ -1766,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> {
|
||||
if let serde_json::Value::String(n) = &j {
|
||||
Some(n.clone())
|
||||
@ -1845,12 +1897,12 @@ impl UnaryExpression {
|
||||
.get_json_value()?,
|
||||
self.into(),
|
||||
)?;
|
||||
Ok(MemoryItem::UserVal {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: (-(num)).into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns a hover value that includes the given character position.
|
||||
@ -2231,7 +2283,7 @@ show(part001)
|
||||
|
||||
#[test]
|
||||
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
|
||||
const yo = { a: { b: { c: '123' } } } /* block
|
||||
comment */
|
||||
@ -2247,7 +2299,7 @@ show(part001)
|
||||
let recasted = program.recast(&Default::default(), 0);
|
||||
assert_eq!(
|
||||
recasted,
|
||||
r#"const myFn = () => {
|
||||
r#"fn myFn = () => {
|
||||
// this is a comment
|
||||
const yo = { a: { b: { c: '123' } } }
|
||||
/* block
|
||||
@ -2542,4 +2594,15 @@ 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)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum MemoryItem {
|
||||
UserVal {
|
||||
value: serde_json::Value,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
UserVal(UserVal),
|
||||
SketchGroup(SketchGroup),
|
||||
ExtrudeGroup(ExtrudeGroup),
|
||||
#[ts(skip)]
|
||||
ExtrudeTransform(ExtrudeTransform),
|
||||
#[ts(skip)]
|
||||
Function {
|
||||
#[serde(skip)]
|
||||
func: Option<MemoryFunction>,
|
||||
@ -119,7 +117,16 @@ pub enum MemoryItem {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[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 position: Position,
|
||||
pub rotation: Rotation,
|
||||
@ -138,7 +145,7 @@ pub type MemoryFunction = fn(
|
||||
impl From<MemoryItem> for Vec<SourceRange> {
|
||||
fn from(item: MemoryItem) -> Self {
|
||||
match item {
|
||||
MemoryItem::UserVal { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||
@ -149,8 +156,8 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
||||
|
||||
impl MemoryItem {
|
||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||
if let MemoryItem::UserVal { value, .. } = self {
|
||||
Ok(value.clone())
|
||||
if let MemoryItem::UserVal(user_val) = self {
|
||||
Ok(user_val.value.clone())
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Not a user value: {:?}", self),
|
||||
@ -186,7 +193,7 @@ impl MemoryItem {
|
||||
/// A sketch group is a collection of paths.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub struct SketchGroup {
|
||||
/// The id of the sketch group.
|
||||
pub id: uuid::Uuid,
|
||||
@ -238,7 +245,7 @@ impl SketchGroup {
|
||||
/// An extrude group is a collection of extrude surfaces.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub struct ExtrudeGroup {
|
||||
/// The id of the extrude group.
|
||||
pub id: uuid::Uuid,
|
||||
@ -276,15 +283,15 @@ pub enum BodyType {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
||||
#[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)]
|
||||
#[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)]
|
||||
#[ts(export)]
|
||||
pub struct SourceRange(pub [usize; 2]);
|
||||
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
|
||||
|
||||
impl SourceRange {
|
||||
/// Create a new source range.
|
||||
@ -341,7 +348,7 @@ impl SourceRange {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct Point2d {
|
||||
pub x: f64,
|
||||
@ -372,6 +379,16 @@ impl From<Point2d> for kittycad::types::Point2D {
|
||||
}
|
||||
}
|
||||
|
||||
impl Point2d {
|
||||
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
||||
pub fn scale(self, scalar: f64) -> Self {
|
||||
Self {
|
||||
x: self.x * scalar,
|
||||
y: self.y * scalar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct Point3d {
|
||||
@ -401,8 +418,10 @@ impl From<SourceRange> for Metadata {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BasePath {
|
||||
/// The from point.
|
||||
#[ts(type = "[number, number]")]
|
||||
pub from: [f64; 2],
|
||||
/// The to point.
|
||||
#[ts(type = "[number, number]")]
|
||||
pub to: [f64; 2],
|
||||
/// The name of the path.
|
||||
pub name: String,
|
||||
@ -804,16 +823,16 @@ show(part001)"#,
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_fn_definitions() {
|
||||
let ast = r#"const def = (x) => {
|
||||
let ast = r#"fn def = (x) => {
|
||||
return x
|
||||
}
|
||||
const ghi = (x) => {
|
||||
fn ghi = (x) => {
|
||||
return x
|
||||
}
|
||||
const jkl = (x) => {
|
||||
fn jkl = (x) => {
|
||||
return x
|
||||
}
|
||||
const hmm = (x) => {
|
||||
fn hmm = (x) => {
|
||||
return x
|
||||
}
|
||||
|
||||
@ -981,7 +1000,7 @@ show(firstExtrude)"#;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_with_function_sketch() {
|
||||
let ast = r#"const box = (h, l, w) => {
|
||||
let ast = r#"fn box = (h, l, w) => {
|
||||
const myBox = startSketchAt([0,0])
|
||||
|> line([0, l], %)
|
||||
|> line([w, 0], %)
|
||||
@ -998,4 +1017,169 @@ 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::{
|
||||
abstract_syntax_tree_types::{
|
||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, ValueMeta,
|
||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression, ValueMeta,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
@ -81,6 +81,7 @@ pub enum MathExpression {
|
||||
BinaryExpression(Box<BinaryExpression>),
|
||||
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
|
||||
ParenthesisToken(Box<ParenthesisToken>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
}
|
||||
|
||||
impl MathExpression {
|
||||
@ -92,6 +93,7 @@ impl MathExpression {
|
||||
MathExpression::BinaryExpression(binary_expression) => binary_expression.start(),
|
||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.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::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.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)?;
|
||||
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 next.token_type == TokenType::Brace && next.value == "(" {
|
||||
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
|
||||
@ -149,6 +152,24 @@ impl ReversePolishNotation {
|
||||
);
|
||||
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(
|
||||
@ -164,7 +185,6 @@ impl ReversePolishNotation {
|
||||
return rpn.parse();
|
||||
} else if current_token.token_type == TokenType::Number
|
||||
|| current_token.token_type == TokenType::Word
|
||||
|| current_token.token_type == TokenType::Keyword
|
||||
|| current_token.token_type == TokenType::String
|
||||
{
|
||||
let rpn = ReversePolishNotation::new(
|
||||
@ -180,6 +200,35 @@ impl ReversePolishNotation {
|
||||
return rpn.parse();
|
||||
} else if let Ok(binop) = BinaryOperator::from_str(current_token.value.as_str()) {
|
||||
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 prevbinop.precedence() >= binop.precedence() {
|
||||
@ -196,6 +245,29 @@ impl ReversePolishNotation {
|
||||
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(
|
||||
@ -299,7 +371,7 @@ impl ReversePolishNotation {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange([a.start(), a.end()])],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -338,7 +410,7 @@ impl ReversePolishNotation {
|
||||
start_extended: None,
|
||||
})));
|
||||
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[1].token_type == TokenType::Brace
|
||||
&& 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);
|
||||
}
|
||||
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;
|
||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||
name: current_token.value.clone(),
|
||||
@ -396,7 +480,7 @@ impl ReversePolishNotation {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}));
|
||||
}
|
||||
};
|
||||
let paran = match &stack[stack.len() - 2] {
|
||||
@ -445,7 +529,7 @@ impl ReversePolishNotation {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: format!("{:?}", a),
|
||||
}))
|
||||
}));
|
||||
}
|
||||
};
|
||||
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::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.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 => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
@ -513,6 +601,10 @@ impl ReversePolishNotation {
|
||||
MathExpression::Identifier(ident) => (BinaryPart::Identifier(ident.clone()), ident.end),
|
||||
MathExpression::CallExpression(call) => (BinaryPart::CallExpression(call.clone()), call.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 => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
@ -521,13 +613,7 @@ impl ReversePolishNotation {
|
||||
}
|
||||
};
|
||||
|
||||
let right_end = match right.0.clone() {
|
||||
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 right_end = right.0.clone().end();
|
||||
|
||||
let tree = BinaryExpression {
|
||||
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> {
|
||||
let rpn = self.rpn.parse()?;
|
||||
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 {
|
||||
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 left_start = tree_with_maybe_bad_top_level_start_end.clone().left.start();
|
||||
let min_start = if left_start < tree_with_maybe_bad_top_level_start_end.start {
|
||||
left_start
|
||||
} else {
|
||||
tree_with_maybe_bad_top_level_start_end.start
|
||||
};
|
||||
let right_end = match tree_with_maybe_bad_top_level_start_end.clone().right {
|
||||
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 right_end = tree_with_maybe_bad_top_level_start_end.clone().right.end();
|
||||
let max_end = if right_end > tree_with_maybe_bad_top_level_start_end.end {
|
||||
right_end
|
||||
} 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]
|
||||
fn test_parse_expression_plus_followed_by_star() {
|
||||
let tokens = crate::tokeniser::lexer("1 + 2 * 3");
|
||||
|
@ -427,7 +427,7 @@ impl Parser {
|
||||
}
|
||||
let current_token = self.get_token(index)?;
|
||||
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.value == "("
|
||||
{
|
||||
@ -550,22 +550,41 @@ impl Parser {
|
||||
&self,
|
||||
index: usize,
|
||||
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
||||
has_opening_brace: bool,
|
||||
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
||||
let previous_keys = _previous_keys.unwrap_or(vec![]);
|
||||
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);
|
||||
}
|
||||
let period_or_opening_bracket = match next_token.token {
|
||||
let mut has_opening_brace = match &next_token.token {
|
||||
Some(next_token_val) => {
|
||||
if next_token_val.token_type == TokenType::Brace && next_token_val.value == "]" {
|
||||
self.next_meaningful_token(next_token.index, None)?
|
||||
if next_token_val.token_type == TokenType::Brace && next_token_val.value == "[" {
|
||||
true
|
||||
} 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 period_or_opening_bracket_token.token_type != TokenType::Period
|
||||
@ -573,11 +592,26 @@ impl Parser {
|
||||
{
|
||||
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 next_period_or_opening_bracket = self.next_meaningful_token(key_token.index, None)?;
|
||||
let is_braced = match next_period_or_opening_bracket.token {
|
||||
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 == "]"
|
||||
}
|
||||
None => false,
|
||||
@ -588,23 +622,19 @@ impl Parser {
|
||||
key_token.index
|
||||
};
|
||||
if let Some(key_token_token) = key_token.token {
|
||||
let key = if key_token_token.token_type == TokenType::Word
|
||||
|| key_token_token.token_type == TokenType::Keyword
|
||||
{
|
||||
let key = if key_token_token.token_type == TokenType::Word {
|
||||
LiteralIdentifier::Identifier(Box::new(self.make_identifier(key_token.index)?))
|
||||
} else {
|
||||
LiteralIdentifier::Literal(Box::new(self.make_literal(key_token.index)?))
|
||||
};
|
||||
let computed = is_braced
|
||||
&& (key_token_token.token_type == TokenType::Word
|
||||
|| key_token_token.token_type == TokenType::Keyword);
|
||||
let computed = is_braced && key_token_token.token_type == TokenType::Word;
|
||||
let mut new_previous_keys = previous_keys;
|
||||
new_previous_keys.push(ObjectKeyInfo {
|
||||
key,
|
||||
index: end_index,
|
||||
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 {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
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 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() {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
@ -653,6 +683,7 @@ impl Parser {
|
||||
|
||||
fn find_end_of_binary_expression(&self, index: usize) -> Result<usize, KclError> {
|
||||
let current_token = self.get_token(index)?;
|
||||
|
||||
if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||
let closing_parenthesis = self.find_closing_brace(index, 0, "")?;
|
||||
let maybe_another_operator = self.next_meaningful_token(closing_parenthesis, None)?;
|
||||
@ -669,10 +700,17 @@ impl Parser {
|
||||
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
|
||||
&& self.get_token(index + 1)?.value == "("
|
||||
|
||||
if current_token.token_type == TokenType::Word {
|
||||
if let Ok(next_token) = self.get_token(index + 1) {
|
||||
if next_token.token_type == TokenType::Period
|
||||
|| (next_token.token_type == TokenType::Brace && next_token.value == "[")
|
||||
{
|
||||
let member_expression = self.make_member_expression(index)?;
|
||||
return self.find_end_of_binary_expression(member_expression.last_index);
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -688,9 +726,16 @@ impl Parser {
|
||||
Ok(closing_parenthesis)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let maybe_operator = self.next_meaningful_token(index, None)?;
|
||||
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);
|
||||
}
|
||||
let next_right = self.next_meaningful_token(maybe_operator.index, None)?;
|
||||
@ -731,7 +776,6 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
if current_token.token_type == TokenType::Word
|
||||
|| current_token.token_type == TokenType::Keyword
|
||||
|| current_token.token_type == TokenType::Number
|
||||
|| 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 == "{" {
|
||||
let object_expression = self.make_object_expression(index)?;
|
||||
return Ok(ValueReturn {
|
||||
@ -761,11 +828,25 @@ impl Parser {
|
||||
}
|
||||
|
||||
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::Brace && next_token.value == "["))
|
||||
{
|
||||
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 {
|
||||
value: Value::MemberExpression(Box::new(member_expression.expression)),
|
||||
last_index: member_expression.last_index,
|
||||
@ -820,7 +901,7 @@ impl Parser {
|
||||
|
||||
Err(KclError::Unexpected(KclErrorDetails {
|
||||
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 {
|
||||
let is_closing_brace = next_token_token.token_type == TokenType::Brace && next_token_token.value == "]";
|
||||
let is_comma = next_token_token.token_type == TokenType::Comma;
|
||||
if !is_closing_brace && !is_comma {
|
||||
// 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 {
|
||||
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 {
|
||||
@ -852,9 +935,60 @@ impl Parser {
|
||||
} else {
|
||||
self.next_meaningful_token(next_token.index, None)?.index
|
||||
};
|
||||
let mut _previous_elements = previous_elements;
|
||||
_previous_elements.push(current_element.value);
|
||||
self.make_array_elements(next_call_index, _previous_elements)
|
||||
|
||||
if is_double_period {
|
||||
// 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 {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![first_element_token.into()],
|
||||
@ -867,12 +1001,18 @@ impl Parser {
|
||||
let opening_brace_token = self.get_token(index)?;
|
||||
let first_element_token = self.next_meaningful_token(index, None)?;
|
||||
// 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())?;
|
||||
Ok(ArrayReturn {
|
||||
expression: ArrayExpression {
|
||||
start: opening_brace_token.start,
|
||||
end: self.get_token(array_elements.last_index)?.end,
|
||||
end: closing_brace_token.end,
|
||||
elements: array_elements.elements,
|
||||
},
|
||||
last_index: array_elements.last_index,
|
||||
@ -949,9 +1089,23 @@ impl Parser {
|
||||
});
|
||||
}
|
||||
let argument_token = self.next_meaningful_token(index, None)?;
|
||||
|
||||
if let Some(argument_token_token) = argument_token.token {
|
||||
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 (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
|
||||
|| next_brace_or_comma_token.token_type == TokenType::Brace;
|
||||
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)));
|
||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||
}
|
||||
|
||||
if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" {
|
||||
let unary_expression = self.make_unary_expression(argument_token.index)?;
|
||||
let next_comma_or_brace_token_index =
|
||||
self.next_meaningful_token(unary_expression.last_index, None)?.index;
|
||||
let value = self.make_value(argument_token.index)?;
|
||||
let next_comma_or_brace_token_index = self.next_meaningful_token(value.last_index, None)?.index;
|
||||
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);
|
||||
}
|
||||
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)));
|
||||
return self.make_arguments(next_comma_or_brace_token_index, _previous_args);
|
||||
}
|
||||
if (argument_token_token.token_type == TokenType::Keyword
|
||||
|| argument_token_token.token_type == TokenType::Word
|
||||
if (argument_token_token.token_type == TokenType::Word
|
||||
|| argument_token_token.token_type == TokenType::Number
|
||||
|| argument_token_token.token_type == TokenType::String)
|
||||
&& 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)));
|
||||
return self.make_arguments(binary_expression.last_index, _previous_args);
|
||||
}
|
||||
|
||||
if argument_token_token.token_type == TokenType::Operator
|
||||
&& argument_token_token.value == PIPE_SUBSTITUTION_OPERATOR
|
||||
{
|
||||
@ -1010,8 +1164,7 @@ impl Parser {
|
||||
_previous_args.push(value);
|
||||
return self.make_arguments(next_brace_or_comma.index, _previous_args);
|
||||
}
|
||||
if (argument_token_token.token_type == TokenType::Keyword
|
||||
|| argument_token_token.token_type == TokenType::Word)
|
||||
if argument_token_token.token_type == TokenType::Word
|
||||
&& next_brace_or_comma_token.token_type == TokenType::Brace
|
||||
&& next_brace_or_comma_token.value == "("
|
||||
{
|
||||
@ -1085,9 +1238,14 @@ impl Parser {
|
||||
let brace_token = self.next_meaningful_token(index, None)?;
|
||||
let callee = self.make_identifier(index)?;
|
||||
// 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 closing_brace_token = self.get_token(args.last_index)?;
|
||||
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
||||
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
||||
} else {
|
||||
@ -1127,6 +1285,19 @@ impl Parser {
|
||||
previous_declarators: Vec<VariableDeclarator>,
|
||||
) -> Result<VariableDeclaratorsReturn, KclError> {
|
||||
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 Some(assignment_token) = assignment.token else {
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
@ -1169,17 +1340,40 @@ impl Parser {
|
||||
fn make_variable_declaration(&self, index: usize) -> Result<VariableDeclarationResult, KclError> {
|
||||
let current_token = self.get_token(index)?;
|
||||
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![])?;
|
||||
|
||||
// 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 {
|
||||
declaration: VariableDeclaration {
|
||||
start: current_token.start,
|
||||
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
|
||||
kind: VariableKind::from_str(¤t_token.value).map_err(|_| {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "Unexpected token".to_string(),
|
||||
})
|
||||
})?,
|
||||
kind,
|
||||
declarations: variable_declarators_result.declarations,
|
||||
},
|
||||
last_index: variable_declarators_result.last_index,
|
||||
@ -1189,9 +1383,14 @@ impl Parser {
|
||||
fn make_params(&self, index: usize, previous_params: Vec<Identifier>) -> Result<ParamsResult, KclError> {
|
||||
let brace_or_comma_token = self.get_token(index)?;
|
||||
let argument = self.next_meaningful_token(index, None)?;
|
||||
if let Some(argument_token) = argument.token {
|
||||
let should_finish_recursion = (argument_token.token_type == TokenType::Brace
|
||||
&& argument_token.value == ")")
|
||||
let Some(argument_token) = argument.token else {
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![brace_or_comma_token.into()],
|
||||
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 {
|
||||
@ -1199,17 +1398,24 @@ impl Parser {
|
||||
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)
|
||||
} else {
|
||||
Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![brace_or_comma_token.into()],
|
||||
message: format!("Unexpected token {}", brace_or_comma_token.value),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||
@ -1239,10 +1445,11 @@ impl Parser {
|
||||
Value::Literal(literal) => BinaryPart::Literal(literal),
|
||||
Value::UnaryExpression(unary_expression) => BinaryPart::UnaryExpression(unary_expression),
|
||||
Value::CallExpression(call_expression) => BinaryPart::CallExpression(call_expression),
|
||||
Value::MemberExpression(member_expression) => BinaryPart::MemberExpression(member_expression),
|
||||
_ => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
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 (token.token_type == TokenType::Keyword || token.token_type == TokenType::Word)
|
||||
if token.token_type == TokenType::Word
|
||||
&& next_token.token_type == TokenType::Brace
|
||||
&& next_token.value == "("
|
||||
{
|
||||
@ -1488,9 +1695,7 @@ impl Parser {
|
||||
|
||||
let next_thing = self.next_meaningful_token(token_index, None)?;
|
||||
if let Some(next_thing_token) = next_thing.token {
|
||||
if (token.token_type == TokenType::Number
|
||||
|| token.token_type == TokenType::Word
|
||||
|| token.token_type == TokenType::Keyword)
|
||||
if (token.token_type == TokenType::Number || token.token_type == TokenType::Word)
|
||||
&& next_thing_token.token_type == TokenType::Operator
|
||||
{
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
@ -1513,7 +1718,7 @@ impl Parser {
|
||||
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
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() {
|
||||
let tokens = crate::tokeniser::lexer("const prop = yo.one[\"two\"]");
|
||||
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);
|
||||
let first_key = match keys_info[0].key.clone() {
|
||||
LiteralIdentifier::Identifier(identifier) => format!("identifier-{}", identifier.name),
|
||||
@ -2759,6 +2964,73 @@ show(mySk1)"#;
|
||||
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]
|
||||
fn test_parse_half_pipe() {
|
||||
let tokens = crate::tokeniser::lexer(
|
||||
@ -2816,7 +3088,10 @@ z(-[["#,
|
||||
let parser = Parser::new(tokens);
|
||||
let result = parser.ast();
|
||||
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]
|
||||
@ -2828,7 +3103,10 @@ z(-[["#,
|
||||
let parser = Parser::new(tokens);
|
||||
let result = parser.ast();
|
||||
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]
|
||||
@ -2881,4 +3159,229 @@ e
|
||||
.to_string()
|
||||
.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.
|
||||
|
||||
pub mod extrude;
|
||||
pub mod math;
|
||||
pub mod segment;
|
||||
pub mod sketch;
|
||||
pub mod utils;
|
||||
@ -61,6 +62,10 @@ impl StdLib {
|
||||
Box::new(crate::std::sketch::Close),
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
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();
|
||||
@ -103,12 +108,12 @@ impl<'a> Args<'a> {
|
||||
}
|
||||
|
||||
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,
|
||||
meta: vec![Metadata {
|
||||
source_range: self.source_range,
|
||||
}],
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
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> {
|
||||
let mut numbers: Vec<f64> = Vec::new();
|
||||
for arg in &self.args {
|
||||
@ -471,7 +491,7 @@ pub fn leg_angle_x(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
name = "legAngX",
|
||||
}]
|
||||
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.
|
||||
@ -486,7 +506,7 @@ pub fn leg_angle_y(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
name = "legAngY",
|
||||
}]
|
||||
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.
|
||||
@ -591,7 +611,7 @@ mod tests {
|
||||
buf.push_str(&fn_docs);
|
||||
}
|
||||
|
||||
expectorate::assert_contents("../../../docs/kcl.md", &buf);
|
||||
expectorate::assert_contents("../../../docs/kcl/std.md", &buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -606,7 +626,7 @@ mod tests {
|
||||
}
|
||||
|
||||
expectorate::assert_contents(
|
||||
"../../../docs/kcl.json",
|
||||
"../../../docs/kcl/std.json",
|
||||
&serde_json::to_string_pretty(&json_data).unwrap(),
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{MemoryItem, SketchGroup},
|
||||
std::{utils::get_angle, Args},
|
||||
std::{utils::Angle, Args},
|
||||
};
|
||||
|
||||
/// Returns the segment end of x.
|
||||
@ -174,9 +174,9 @@ fn inner_segment_angle(segment_name: &str, sketch_group: SketchGroup, args: &mut
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let result = get_angle(&line.from, &line.to);
|
||||
let result = Angle::between(line.from.into(), line.to.into());
|
||||
|
||||
Ok(result)
|
||||
Ok(result.degrees())
|
||||
}
|
||||
|
||||
/// Returns the angle to match the given length for x.
|
||||
@ -230,7 +230,7 @@ fn inner_angle_to_match_length_x(
|
||||
if diff > length {
|
||||
Ok(0.0)
|
||||
} 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 {
|
||||
Ok(0.0)
|
||||
} else {
|
||||
Ok(angle_r * 180.0 / std::f64::consts::PI)
|
||||
Ok(angle_r.to_degrees())
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::utils::Angle;
|
||||
|
||||
/// Data to draw a line to a point.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -161,34 +163,12 @@ pub enum LineData {
|
||||
/// A point with a tag.
|
||||
PointWithTag {
|
||||
/// The to point.
|
||||
to: PointOrDefault,
|
||||
to: [f64; 2],
|
||||
/// The tag.
|
||||
tag: String,
|
||||
},
|
||||
/// A point.
|
||||
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.
|
||||
@ -205,12 +185,9 @@ pub fn line(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
}]
|
||||
fn inner_line(data: LineData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let default = [0.2, 1.0];
|
||||
let inner_args = match &data {
|
||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
||||
LineData::PointWithTag { to, .. } => *to,
|
||||
LineData::Point(to) => *to,
|
||||
LineData::Default(_) => default,
|
||||
};
|
||||
|
||||
let to = [from.x + inner_args[0], from.y + inner_args[1]];
|
||||
@ -283,10 +260,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> {
|
||||
let line_data = match data {
|
||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([length, 0.0]),
|
||||
tag,
|
||||
},
|
||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [length, 0.0], tag },
|
||||
AxisLineData::Length(length) => LineData::Point([length, 0.0]),
|
||||
};
|
||||
|
||||
@ -308,10 +282,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> {
|
||||
let line_data = match data {
|
||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([0.0, length]),
|
||||
tag,
|
||||
},
|
||||
AxisLineData::LengthWithTag { length, tag } => LineData::PointWithTag { to: [0.0, length], tag },
|
||||
AxisLineData::Length(length) => LineData::Point([0.0, length]),
|
||||
};
|
||||
|
||||
@ -360,8 +331,8 @@ fn inner_angled_line(
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
let to: [f64; 2] = [
|
||||
from.x + length * f64::cos(angle * std::f64::consts::PI / 180.0),
|
||||
from.y + length * f64::sin(angle * std::f64::consts::PI / 180.0),
|
||||
from.x + length * f64::cos(angle.to_radians()),
|
||||
from.y + length * f64::sin(angle.to_radians()),
|
||||
];
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
@ -423,16 +394,13 @@ fn inner_angled_line_of_x_length(
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
|
||||
let to = get_y_component(angle, length);
|
||||
let to = get_y_component(Angle::from_degrees(angle), length);
|
||||
|
||||
let new_sketch_group = inner_line(
|
||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||
LineData::PointWithTag {
|
||||
to: PointOrDefault::Point(to),
|
||||
tag,
|
||||
}
|
||||
LineData::PointWithTag { to: to.into(), tag }
|
||||
} else {
|
||||
LineData::Point(to)
|
||||
LineData::Point(to.into())
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
@ -483,7 +451,7 @@ fn inner_angled_line_to_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 new_sketch_group = inner_line_to(
|
||||
@ -521,16 +489,13 @@ fn inner_angled_line_of_y_length(
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
|
||||
let to = get_x_component(angle, length);
|
||||
let to = get_x_component(Angle::from_degrees(angle), length);
|
||||
|
||||
let new_sketch_group = inner_line(
|
||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||
LineData::PointWithTag {
|
||||
to: PointOrDefault::Point(to),
|
||||
tag,
|
||||
}
|
||||
LineData::PointWithTag { to: to.into(), tag }
|
||||
} else {
|
||||
LineData::Point(to)
|
||||
LineData::Point(to.into())
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
@ -563,7 +528,7 @@ fn inner_angled_line_to_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 new_sketch_group = inner_line_to(
|
||||
@ -625,16 +590,16 @@ fn inner_angled_line_that_intersects(
|
||||
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let to = intersection_with_parallel_line(
|
||||
&[intersect_path.from, intersect_path.to],
|
||||
&[intersect_path.from.into(), intersect_path.to.into()],
|
||||
data.offset.unwrap_or_default(),
|
||||
data.angle,
|
||||
from.into(),
|
||||
from,
|
||||
);
|
||||
|
||||
let line_to_data = if let Some(tag) = data.tag {
|
||||
LineToData::PointWithTag { to, tag }
|
||||
LineToData::PointWithTag { to: to.into(), tag }
|
||||
} else {
|
||||
LineToData::Point(to)
|
||||
LineToData::Point(to.into())
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
|
||||
@ -654,11 +619,9 @@ pub fn start_sketch_at(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
name = "startSketchAt",
|
||||
}]
|
||||
fn inner_start_sketch_at(data: LineData, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let default = [0.0, 0.0];
|
||||
let to = match &data {
|
||||
LineData::PointWithTag { to, .. } => to.get_point_with_default(default),
|
||||
LineData::PointWithTag { to, .. } => *to,
|
||||
LineData::Point(to) => *to,
|
||||
LineData::Default(_) => default,
|
||||
};
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
@ -805,7 +768,7 @@ pub fn arc(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
name = "arc",
|
||||
}]
|
||||
fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let (center, angle_start, angle_end, radius, end) = match &data {
|
||||
ArcData::AnglesAndRadiusWithTag {
|
||||
@ -814,23 +777,27 @@ fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Resul
|
||||
radius,
|
||||
..
|
||||
} => {
|
||||
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
|
||||
(center, *angle_start, *angle_end, *radius, end)
|
||||
let a_start = Angle::from_degrees(*angle_start);
|
||||
let a_end = Angle::from_degrees(*angle_end);
|
||||
let (center, end) = arc_center_and_end(from, a_start, a_end, *radius);
|
||||
(center, a_start, a_end, *radius, end)
|
||||
}
|
||||
ArcData::AnglesAndRadius {
|
||||
angle_start,
|
||||
angle_end,
|
||||
radius,
|
||||
} => {
|
||||
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
|
||||
(center, *angle_start, *angle_end, *radius, end)
|
||||
let a_start = Angle::from_degrees(*angle_start);
|
||||
let a_end = Angle::from_degrees(*angle_end);
|
||||
let (center, end) = arc_center_and_end(from, a_start, a_end, *radius);
|
||||
(center, a_start, a_end, *radius, end)
|
||||
}
|
||||
ArcData::CenterToRadiusWithTag { center, to, radius, .. } => {
|
||||
let (angle_start, angle_end) = arc_angles(&from, ¢er.into(), &to.into(), *radius, args.source_range)?;
|
||||
let (angle_start, angle_end) = arc_angles(from, center.into(), to.into(), *radius, args.source_range)?;
|
||||
(center.into(), angle_start, angle_end, *radius, to.into())
|
||||
}
|
||||
ArcData::CenterToRadius { center, to, radius } => {
|
||||
let (angle_start, angle_end) = arc_angles(&from, ¢er.into(), &to.into(), *radius, args.source_range)?;
|
||||
let (angle_start, angle_end) = arc_angles(from, center.into(), to.into(), *radius, args.source_range)?;
|
||||
(center.into(), angle_start, angle_end, *radius, to.into())
|
||||
}
|
||||
};
|
||||
@ -842,8 +809,8 @@ fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Resul
|
||||
ModelingCmd::ExtendPath {
|
||||
path: sketch_group.id,
|
||||
segment: kittycad::types::PathSegment::Arc {
|
||||
angle_start,
|
||||
angle_end,
|
||||
angle_start: angle_start.degrees(),
|
||||
angle_end: angle_end.degrees(),
|
||||
center: center.into(),
|
||||
radius,
|
||||
},
|
||||
@ -992,16 +959,12 @@ mod tests {
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::std::sketch::{LineData, PointOrDefault};
|
||||
use crate::std::sketch::LineData;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_line_data() {
|
||||
let mut str_json = "\"default\"".to_string();
|
||||
let data: LineData = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, LineData::Default("default".to_string()));
|
||||
|
||||
let data = LineData::Point([0.0, 1.0]);
|
||||
str_json = serde_json::to_string(&data).unwrap();
|
||||
let mut str_json = serde_json::to_string(&data).unwrap();
|
||||
assert_eq!(str_json, "[0.0,1.0]");
|
||||
|
||||
str_json = "[0, 1]".to_string();
|
||||
@ -1013,7 +976,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
data,
|
||||
LineData::PointWithTag {
|
||||
to: PointOrDefault::Point([0.0, 1.0]),
|
||||
to: [0.0, 1.0],
|
||||
tag: "thing".to_string()
|
||||
}
|
||||
);
|
||||
|
@ -1,30 +1,85 @@
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{Point2d, SourceRange},
|
||||
};
|
||||
|
||||
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
|
||||
let x = b[0] - a[0];
|
||||
let y = b[1] - a[1];
|
||||
normalise_angle(y.atan2(x) * 180.0 / std::f64::consts::PI)
|
||||
#[derive(Clone, Copy, Default, PartialOrd, PartialEq, Debug)]
|
||||
pub struct Angle {
|
||||
degrees: f64,
|
||||
}
|
||||
|
||||
pub fn normalise_angle(angle: f64) -> f64 {
|
||||
impl Angle {
|
||||
const ZERO: Self = Self { degrees: 0.0 };
|
||||
/// Make an angle of the given degrees.
|
||||
pub fn from_degrees(degrees: f64) -> Self {
|
||||
Self { degrees }
|
||||
}
|
||||
/// Make an angle of the given radians.
|
||||
pub fn from_radians(radians: f64) -> Self {
|
||||
Self::from_degrees(radians.to_degrees())
|
||||
}
|
||||
/// Get the angle in degrees
|
||||
pub fn degrees(&self) -> f64 {
|
||||
self.degrees
|
||||
}
|
||||
/// Get the angle in radians
|
||||
pub fn radians(&self) -> f64 {
|
||||
self.degrees.to_radians()
|
||||
}
|
||||
/// Get the angle between these points
|
||||
pub fn between(a: Point2d, b: Point2d) -> Self {
|
||||
let x = b.x - a.x;
|
||||
let y = b.y - a.y;
|
||||
Self::from_radians(y.atan2(x)).normalize()
|
||||
}
|
||||
/// Normalize the angle
|
||||
pub fn normalize(self) -> Self {
|
||||
let angle = self.degrees();
|
||||
let result = ((angle % 360.0) + 360.0) % 360.0;
|
||||
if result > 180.0 {
|
||||
result - 360.0
|
||||
} else {
|
||||
result
|
||||
Self::from_degrees(if result > 180.0 { result - 360.0 } else { result })
|
||||
}
|
||||
/// Gives the ▲-angle between from and to angles (shortest path), use radians.
|
||||
///
|
||||
/// Sign of the returned angle denotes direction, positive means counterClockwise 🔄
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::f64::consts::PI;
|
||||
/// use kcl_lib::std::utils::Angle;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Angle::delta(Angle::from_radians(PI / 8.0), Angle::from_radians(PI / 4.0)),
|
||||
/// Angle::from_radians(PI / 8.0)
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn delta(from_angle: Self, to_angle: Self) -> Self {
|
||||
let norm_from_angle = normalize_rad(from_angle.radians());
|
||||
let norm_to_angle = normalize_rad(to_angle.radians());
|
||||
let provisional = norm_to_angle - norm_from_angle;
|
||||
|
||||
if provisional > -PI && provisional <= PI {
|
||||
return Angle::from_radians(provisional);
|
||||
}
|
||||
if provisional > PI {
|
||||
return Angle::from_radians(provisional - 2.0 * PI);
|
||||
}
|
||||
if provisional < -PI {
|
||||
return Angle::from_radians(provisional + 2.0 * PI);
|
||||
}
|
||||
Angle::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clockwise_sign(points: &[[f64; 2]]) -> i32 {
|
||||
pub fn clockwise_sign(points: &[Point2d]) -> i32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..points.len() {
|
||||
let current_point = points[i];
|
||||
let next_point = points[(i + 1) % points.len()];
|
||||
sum += (next_point[0] - current_point[0]) * (next_point[1] + current_point[1]);
|
||||
sum += (next_point.x - current_point.x) * (next_point.y + current_point.y);
|
||||
}
|
||||
if sum >= 0.0 {
|
||||
1
|
||||
@ -35,139 +90,145 @@ pub fn clockwise_sign(points: &[[f64; 2]]) -> i32 {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn normalize_rad(angle: f64) -> f64 {
|
||||
let draft = angle % (2.0 * std::f64::consts::PI);
|
||||
let draft = angle % (2.0 * PI);
|
||||
if draft < 0.0 {
|
||||
draft + 2.0 * std::f64::consts::PI
|
||||
draft + 2.0 * PI
|
||||
} else {
|
||||
draft
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives the ▲-angle between from and to angles (shortest path), use radians.
|
||||
///
|
||||
/// Sign of the returned angle denotes direction, positive means counterClockwise 🔄
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::delta_angle(std::f64::consts::PI / 8.0, std::f64::consts::PI / 4.0),
|
||||
/// std::f64::consts::PI / 8.0
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn delta_angle(from_angle: f64, to_angle: f64) -> f64 {
|
||||
let norm_from_angle = normalize_rad(from_angle);
|
||||
let norm_to_angle = normalize_rad(to_angle);
|
||||
let provisional = norm_to_angle - norm_from_angle;
|
||||
|
||||
if provisional > -std::f64::consts::PI && provisional <= std::f64::consts::PI {
|
||||
return provisional;
|
||||
}
|
||||
if provisional > std::f64::consts::PI {
|
||||
return provisional - 2.0 * std::f64::consts::PI;
|
||||
}
|
||||
if provisional < -std::f64::consts::PI {
|
||||
return provisional + 2.0 * std::f64::consts::PI;
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
/// Calculates the distance between two points.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kcl_lib::executor::Point2d;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::distance_between_points(&[0.0, 0.0], &[0.0, 5.0]),
|
||||
/// kcl_lib::std::utils::distance_between_points(Point2d::ZERO, Point2d{x: 0.0, y: 5.0}),
|
||||
/// 5.0
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::distance_between_points(&[0.0, 0.0], &[3.0, 4.0]),
|
||||
/// kcl_lib::std::utils::distance_between_points(Point2d::ZERO, Point2d{x: 3.0, y: 4.0}),
|
||||
/// 5.0
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn distance_between_points(point_a: &[f64; 2], point_b: &[f64; 2]) -> f64 {
|
||||
let x1 = point_a[0];
|
||||
let y1 = point_a[1];
|
||||
let x2 = point_b[0];
|
||||
let y2 = point_b[1];
|
||||
pub fn distance_between_points(point_a: Point2d, point_b: Point2d) -> f64 {
|
||||
let x1 = point_a.x;
|
||||
let y1 = point_a.y;
|
||||
let x2 = point_b.x;
|
||||
let y2 = point_b.y;
|
||||
|
||||
((y2 - y1).powi(2) + (x2 - x1).powi(2)).sqrt()
|
||||
}
|
||||
|
||||
pub fn calculate_intersection_of_two_lines(line1: &[[f64; 2]; 2], line2_angle: f64, line2_point: [f64; 2]) -> [f64; 2] {
|
||||
let line2_point_b = [
|
||||
line2_point[0] + f64::cos(line2_angle * std::f64::consts::PI / 180.0) * 10.0,
|
||||
line2_point[1] + f64::sin(line2_angle * std::f64::consts::PI / 180.0) * 10.0,
|
||||
];
|
||||
pub fn calculate_intersection_of_two_lines(line1: &[Point2d; 2], line2_angle: f64, line2_point: Point2d) -> Point2d {
|
||||
let line2_point_b = Point2d {
|
||||
x: line2_point.x + f64::cos(line2_angle.to_radians()) * 10.0,
|
||||
y: line2_point.y + f64::sin(line2_angle.to_radians()) * 10.0,
|
||||
};
|
||||
intersect(line1[0], line1[1], line2_point, line2_point_b)
|
||||
}
|
||||
|
||||
pub fn intersect(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2], p4: [f64; 2]) -> [f64; 2] {
|
||||
let slope = |p1: [f64; 2], p2: [f64; 2]| (p1[1] - p2[1]) / (p1[0] - p2[0]);
|
||||
let constant = |p1: [f64; 2], p2: [f64; 2]| p1[1] - slope(p1, p2) * p1[0];
|
||||
let get_y = |for_x: f64, p1: [f64; 2], p2: [f64; 2]| slope(p1, p2) * for_x + constant(p1, p2);
|
||||
pub fn intersect(p1: Point2d, p2: Point2d, p3: Point2d, p4: Point2d) -> Point2d {
|
||||
let slope = |p1: Point2d, p2: Point2d| (p1.y - p2.y) / (p1.x - p2.x);
|
||||
let constant = |p1: Point2d, p2: Point2d| p1.y - slope(p1, p2) * p1.x;
|
||||
let get_y = |for_x: f64, p1: Point2d, p2: Point2d| slope(p1, p2) * for_x + constant(p1, p2);
|
||||
|
||||
if p1[0] == p2[0] {
|
||||
return [p1[0], get_y(p1[0], p3, p4)];
|
||||
if p1.x == p2.x {
|
||||
return Point2d {
|
||||
x: p1.x,
|
||||
y: get_y(p1.x, p3, p4),
|
||||
};
|
||||
}
|
||||
if p3[0] == p4[0] {
|
||||
return [p3[0], get_y(p3[0], p1, p2)];
|
||||
if p3.x == p4.x {
|
||||
return Point2d {
|
||||
x: p3.x,
|
||||
y: get_y(p3.x, p1, p2),
|
||||
};
|
||||
}
|
||||
|
||||
let x = (constant(p3, p4) - constant(p1, p2)) / (slope(p1, p2) - slope(p3, p4));
|
||||
let y = get_y(x, p1, p2);
|
||||
[x, y]
|
||||
Point2d { x, y }
|
||||
}
|
||||
|
||||
pub fn intersection_with_parallel_line(
|
||||
line1: &[[f64; 2]; 2],
|
||||
line1: &[Point2d; 2],
|
||||
line1_offset: f64,
|
||||
line2_angle: f64,
|
||||
line2_point: [f64; 2],
|
||||
) -> [f64; 2] {
|
||||
line2_point: Point2d,
|
||||
) -> Point2d {
|
||||
calculate_intersection_of_two_lines(&offset_line(line1_offset, line1[0], line1[1]), line2_angle, line2_point)
|
||||
}
|
||||
|
||||
fn offset_line(offset: f64, p1: [f64; 2], p2: [f64; 2]) -> [[f64; 2]; 2] {
|
||||
if p1[0] == p2[0] {
|
||||
let direction = (p1[1] - p2[1]).signum();
|
||||
return [[p1[0] + offset * direction, p1[1]], [p2[0] + offset * direction, p2[1]]];
|
||||
fn offset_line(offset: f64, p1: Point2d, p2: Point2d) -> [Point2d; 2] {
|
||||
if p1.x == p2.x {
|
||||
let direction = (p1.y - p2.y).signum();
|
||||
return [
|
||||
Point2d {
|
||||
x: p1.x + offset * direction,
|
||||
y: p1.y,
|
||||
},
|
||||
Point2d {
|
||||
x: p2.x + offset * direction,
|
||||
y: p2.y,
|
||||
},
|
||||
];
|
||||
}
|
||||
if p1[1] == p2[1] {
|
||||
let direction = (p2[0] - p1[0]).signum();
|
||||
return [[p1[0], p1[1] + offset * direction], [p2[0], p2[1] + offset * direction]];
|
||||
if p1.y == p2.y {
|
||||
let direction = (p2.x - p1.x).signum();
|
||||
return [
|
||||
Point2d {
|
||||
x: p1.x,
|
||||
y: p1.y + offset * direction,
|
||||
},
|
||||
Point2d {
|
||||
x: p2.x,
|
||||
y: p2.y + offset * direction,
|
||||
},
|
||||
];
|
||||
}
|
||||
let x_offset = offset / f64::sin(f64::atan2(p1[1] - p2[1], p1[0] - p2[0]));
|
||||
[[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]]
|
||||
let x_offset = offset / f64::sin(f64::atan2(p1.y - p2.y, p1.x - p2.x));
|
||||
[
|
||||
Point2d {
|
||||
x: p1.x + x_offset,
|
||||
y: p1.y,
|
||||
},
|
||||
Point2d {
|
||||
x: p2.x + x_offset,
|
||||
y: p2.y,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_y_component(angle_degree: f64, x_component: f64) -> [f64; 2] {
|
||||
let normalised_angle = ((angle_degree % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let y_component = x_component * f64::tan(normalised_angle * std::f64::consts::PI / 180.0);
|
||||
pub fn get_y_component(angle: Angle, x: f64) -> Point2d {
|
||||
let normalised_angle = ((angle.degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let y = x * f64::tan(normalised_angle.to_radians());
|
||||
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
[sign * x_component, sign * y_component]
|
||||
Point2d { x, y }.scale(sign)
|
||||
}
|
||||
|
||||
pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
|
||||
let normalised_angle = ((angle_degree % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let x_component = y_component / f64::tan(normalised_angle * std::f64::consts::PI / 180.0);
|
||||
pub fn get_x_component(angle: Angle, y: f64) -> Point2d {
|
||||
let normalised_angle = ((angle.degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let x = y / f64::tan(normalised_angle.to_radians());
|
||||
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
[sign * x_component, sign * y_component]
|
||||
Point2d { x, y }.scale(sign)
|
||||
}
|
||||
|
||||
pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f64, radius: f64) -> (Point2d, Point2d) {
|
||||
let start_angle = start_angle_deg * (std::f64::consts::PI / 180.0);
|
||||
let end_angle = end_angle_deg * (std::f64::consts::PI / 180.0);
|
||||
pub fn arc_center_and_end(from: Point2d, start_angle: Angle, end_angle: Angle, radius: f64) -> (Point2d, Point2d) {
|
||||
let start_angle = start_angle.radians();
|
||||
let end_angle = end_angle.radians();
|
||||
|
||||
let center = Point2d {
|
||||
x: -1.0 * (radius * start_angle.cos() - from.x),
|
||||
@ -183,12 +244,12 @@ pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f
|
||||
}
|
||||
|
||||
pub fn arc_angles(
|
||||
from: &Point2d,
|
||||
to: &Point2d,
|
||||
center: &Point2d,
|
||||
from: Point2d,
|
||||
to: Point2d,
|
||||
center: Point2d,
|
||||
radius: f64,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(f64, f64), KclError> {
|
||||
) -> Result<(Angle, Angle), KclError> {
|
||||
// First make sure that the points are on the circumference of the circle.
|
||||
// If not, we'll return an error.
|
||||
if !is_on_circumference(center, from, radius) {
|
||||
@ -214,13 +275,10 @@ pub fn arc_angles(
|
||||
let start_angle = (from.y - center.y).atan2(from.x - center.x);
|
||||
let end_angle = (to.y - center.y).atan2(to.x - center.x);
|
||||
|
||||
let start_angle_deg = start_angle * (180.0 / std::f64::consts::PI);
|
||||
let end_angle_deg = end_angle * (180.0 / std::f64::consts::PI);
|
||||
|
||||
Ok((start_angle_deg, end_angle_deg))
|
||||
Ok((Angle::from_radians(start_angle), Angle::from_radians(end_angle)))
|
||||
}
|
||||
|
||||
pub fn is_on_circumference(center: &Point2d, point: &Point2d, radius: f64) -> bool {
|
||||
pub fn is_on_circumference(center: Point2d, point: Point2d, radius: f64) -> bool {
|
||||
let dx = point.x - center.x;
|
||||
let dy = point.y - center.y;
|
||||
|
||||
@ -237,7 +295,7 @@ mod tests {
|
||||
// Here you can bring your functions into scope
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::{get_x_component, get_y_component};
|
||||
use super::{get_x_component, get_y_component, Angle};
|
||||
use crate::executor::SourceRange;
|
||||
|
||||
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
|
||||
@ -261,28 +319,28 @@ mod tests {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &(angle, expected_result) in EACH_QUAD.iter() {
|
||||
let res = get_y_component(angle as f64, 1.0);
|
||||
results.push([res[0].round() as i32, res[1].round() as i32]);
|
||||
let res = get_y_component(Angle::from_degrees(angle as f64), 1.0);
|
||||
results.push([res.x.round() as i32, res.y.round() as i32]);
|
||||
expected.push(expected_result);
|
||||
}
|
||||
|
||||
assert_eq!(results, expected);
|
||||
|
||||
let result = get_y_component(0.0, 1.0);
|
||||
assert_eq!(result[0] as i32, 1);
|
||||
assert_eq!(result[1] as i32, 0);
|
||||
let result = get_y_component(Angle::ZERO, 1.0);
|
||||
assert_eq!(result.x as i32, 1);
|
||||
assert_eq!(result.y as i32, 0);
|
||||
|
||||
let result = get_y_component(90.0, 1.0);
|
||||
assert_eq!(result[0] as i32, 1);
|
||||
assert!(result[1] > 100000.0);
|
||||
let result = get_y_component(Angle::from_degrees(90.0), 1.0);
|
||||
assert_eq!(result.x as i32, 1);
|
||||
assert!(result.y > 100000.0);
|
||||
|
||||
let result = get_y_component(180.0, 1.0);
|
||||
assert_eq!(result[0] as i32, -1);
|
||||
assert!((result[1] - 0.0).abs() < f64::EPSILON);
|
||||
let result = get_y_component(Angle::from_degrees(180.0), 1.0);
|
||||
assert_eq!(result.x as i32, -1);
|
||||
assert!((result.y - 0.0).abs() < f64::EPSILON);
|
||||
|
||||
let result = get_y_component(270.0, 1.0);
|
||||
assert_eq!(result[0] as i32, -1);
|
||||
assert!(result[1] < -100000.0);
|
||||
let result = get_y_component(Angle::from_degrees(270.0), 1.0);
|
||||
assert_eq!(result.x as i32, -1);
|
||||
assert!(result.y < -100000.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -291,45 +349,60 @@ mod tests {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &(angle, expected_result) in EACH_QUAD.iter() {
|
||||
let res = get_x_component(angle as f64, 1.0);
|
||||
results.push([res[0].round() as i32, res[1].round() as i32]);
|
||||
let res = get_x_component(Angle::from_degrees(angle as f64), 1.0);
|
||||
results.push([res.x.round() as i32, res.y.round() as i32]);
|
||||
expected.push(expected_result);
|
||||
}
|
||||
|
||||
assert_eq!(results, expected);
|
||||
|
||||
let result = get_x_component(0.0, 1.0);
|
||||
assert!(result[0] > 100000.0);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
let result = get_x_component(Angle::ZERO, 1.0);
|
||||
assert!(result.x > 100000.0);
|
||||
assert_eq!(result.y as i32, 1);
|
||||
|
||||
let result = get_x_component(90.0, 1.0);
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
let result = get_x_component(Angle::from_degrees(90.0), 1.0);
|
||||
assert!((result.x - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result.y as i32, 1);
|
||||
|
||||
let result = get_x_component(180.0, 1.0);
|
||||
assert!(result[0] < -100000.0);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
let result = get_x_component(Angle::from_degrees(180.0), 1.0);
|
||||
assert!(result.x < -100000.0);
|
||||
assert_eq!(result.y as i32, 1);
|
||||
|
||||
let result = get_x_component(270.0, 1.0);
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, -1);
|
||||
let result = get_x_component(Angle::from_degrees(270.0), 1.0);
|
||||
assert!((result.x - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result.y as i32, -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_center_and_end() {
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 90.0, 1.0);
|
||||
let (center, end) = super::arc_center_and_end(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
Angle::ZERO,
|
||||
Angle::from_degrees(90.0),
|
||||
1.0,
|
||||
);
|
||||
assert_eq!(center.x.round(), -1.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -1.0);
|
||||
assert_eq!(end.y, 1.0);
|
||||
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 1.0);
|
||||
let (center, end) = super::arc_center_and_end(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
Angle::ZERO,
|
||||
Angle::from_degrees(180.0),
|
||||
1.0,
|
||||
);
|
||||
assert_eq!(center.x.round(), -1.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -2.0);
|
||||
assert_eq!(end.y.round(), 0.0);
|
||||
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 10.0);
|
||||
let (center, end) = super::arc_center_and_end(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
Angle::ZERO,
|
||||
Angle::from_degrees(180.0),
|
||||
10.0,
|
||||
);
|
||||
assert_eq!(center.x.round(), -10.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -20.0);
|
||||
@ -339,42 +412,42 @@ mod tests {
|
||||
#[test]
|
||||
fn test_arc_angles() {
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -1.0, y: 1.0 },
|
||||
&super::Point2d { x: -1.0, y: 0.0 },
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
super::Point2d { x: -1.0, y: 1.0 },
|
||||
super::Point2d { x: -1.0, y: 0.0 },
|
||||
1.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 90.0);
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 90.0);
|
||||
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -2.0, y: 0.0 },
|
||||
&super::Point2d { x: -1.0, y: 0.0 },
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
super::Point2d { x: -2.0, y: 0.0 },
|
||||
super::Point2d { x: -1.0, y: 0.0 },
|
||||
1.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 180.0);
|
||||
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -20.0, y: 0.0 },
|
||||
&super::Point2d { x: -10.0, y: 0.0 },
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
super::Point2d { x: -20.0, y: 0.0 },
|
||||
super::Point2d { x: -10.0, y: 0.0 },
|
||||
10.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 180.0);
|
||||
|
||||
let result = super::arc_angles(
|
||||
&super::Point2d { x: 0.0, y: 5.0 },
|
||||
&super::Point2d { x: 5.0, y: 5.0 },
|
||||
&super::Point2d { x: 10.0, y: -10.0 },
|
||||
super::Point2d { x: 0.0, y: 5.0 },
|
||||
super::Point2d { x: 5.0, y: 5.0 },
|
||||
super::Point2d { x: 10.0, y: -10.0 },
|
||||
10.0,
|
||||
SourceRange(Default::default()),
|
||||
);
|
||||
@ -384,7 +457,7 @@ mod tests {
|
||||
} else {
|
||||
panic!("Expected error");
|
||||
}
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 180.0);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ pub enum TokenType {
|
||||
Colon,
|
||||
/// A period.
|
||||
Period,
|
||||
/// A double period: `..`.
|
||||
DoublePeriod,
|
||||
/// A line comment.
|
||||
LineComment,
|
||||
/// A block comment.
|
||||
@ -54,7 +56,12 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
||||
TokenType::LineComment => Self::COMMENT,
|
||||
TokenType::BlockComment => Self::COMMENT,
|
||||
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)
|
||||
}
|
||||
})
|
||||
@ -130,12 +137,12 @@ impl From<&Token> for crate::executor::SourceRange {
|
||||
}
|
||||
|
||||
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 WORD: Regex = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*").unwrap();
|
||||
// TODO: these should be generated using our struct types for these.
|
||||
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 STRING: 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 COLON: 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 BLOCKCOMMENT: Regex = Regex::new(r"^/\*[\s\S]*?\*/").unwrap();
|
||||
}
|
||||
@ -196,6 +204,9 @@ fn is_comma(character: &str) -> bool {
|
||||
fn is_colon(character: &str) -> bool {
|
||||
COLON.is_match(character)
|
||||
}
|
||||
fn is_double_period(character: &str) -> bool {
|
||||
DOUBLE_PERIOD.is_match(character)
|
||||
}
|
||||
fn is_period(character: &str) -> bool {
|
||||
PERIOD.is_match(character)
|
||||
}
|
||||
@ -296,13 +307,6 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
||||
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) {
|
||||
return Some(make_token(
|
||||
TokenType::Operator,
|
||||
@ -310,6 +314,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
||||
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) {
|
||||
return Some(make_token(
|
||||
TokenType::Keyword,
|
||||
@ -331,6 +342,13 @@ fn return_token_at_index(s: &str, start_index: usize) -> Option<Token> {
|
||||
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) {
|
||||
return Some(make_token(
|
||||
TokenType::Period,
|
||||
@ -376,19 +394,18 @@ mod tests {
|
||||
fn is_number_test() {
|
||||
assert!(is_number("1"));
|
||||
assert!(is_number("1 abc"));
|
||||
assert!(is_number("1abc"));
|
||||
assert!(is_number("1.1"));
|
||||
assert!(is_number("1.1 abc"));
|
||||
assert!(!is_number("a"));
|
||||
|
||||
assert!(is_number("1"));
|
||||
assert!(is_number(".1"));
|
||||
assert!(is_number("5?"));
|
||||
assert!(is_number("5 + 6"));
|
||||
assert!(is_number("5 + a"));
|
||||
assert!(is_number("-5"));
|
||||
assert!(is_number("5.5"));
|
||||
assert!(is_number("-5.5"));
|
||||
|
||||
assert!(!is_number("1abc"));
|
||||
assert!(!is_number("a"));
|
||||
assert!(!is_number("?"));
|
||||
assert!(!is_number("?5"));
|
||||
|
@ -61,7 +61,7 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_with_function_sketch() {
|
||||
let code = r#"const box = (h, l, w) => {
|
||||
let code = r#"fn box = (h, l, w) => {
|
||||
const myBox = startSketchAt([0,0])
|
||||
|> line([0, l], %)
|
||||
|> line([w, 0], %)
|
||||
|
@ -1207,7 +1207,7 @@
|
||||
"@codemirror/view" "^6.0.0"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.1.tgz#6dc8d8e5abb26b875e3164191872d69a5e85bd73"
|
||||
integrity sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==
|
||||
@ -1629,6 +1629,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8"
|
||||
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":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||
|
Reference in New Issue
Block a user