From 03217f4dab90484ec87a8140699b9ce71c9f4686 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 28 Mar 2025 13:03:20 -0400 Subject: [PATCH] feat: rust interpreter Signed-off-by: Nick Mitchell --- .github/workflows/rust-interpreter.yml | 36 + .github/workflows/tauri-cli.yml | 2 +- pdl-live-react/package-lock.json | 72 + pdl-live-react/package.json | 4 +- pdl-live-react/src-tauri/.taurignore | 4 +- pdl-live-react/src-tauri/Cargo.lock | 1380 ++++++++++++++++- pdl-live-react/src-tauri/Cargo.toml | 9 + pdl-live-react/src-tauri/src/cli.rs | 13 + .../src-tauri/src/commands/interpreter.rs | 10 + pdl-live-react/src-tauri/src/commands/mod.rs | 1 + pdl-live-react/src-tauri/src/compile/beeai.rs | 199 ++- pdl-live-react/src-tauri/src/lib.rs | 1 + pdl-live-react/src-tauri/src/pdl/ast.rs | 668 +++++++- .../src-tauri/src/pdl/interpreter.rs | 974 ++++++++++++ .../src-tauri/src/pdl/interpreter_tests.rs | 537 +++++++ pdl-live-react/src-tauri/src/pdl/mod.rs | 2 + pdl-live-react/src-tauri/tauri.conf.json | 14 + .../src-tauri/tests/cli/call-no-args.pdl | 9 + .../src-tauri/tests/cli/call-with-args.pdl | 12 + .../src-tauri/tests/cli/code-python.pdl | 2 + pdl-live-react/src-tauri/tests/cli/data1.pdl | 11 + pdl-live-react/src-tauri/tests/cli/data2.pdl | 12 + pdl-live-react/src-tauri/tests/cli/data3.pdl | 12 + pdl-live-react/src-tauri/tests/cli/data4.pdl | 16 + pdl-live-react/src-tauri/tests/cli/if1.pdl | 2 + pdl-live-react/src-tauri/tests/cli/if2.pdl | 5 + .../src-tauri/tests/cli/include1.pdl | 1 + .../tests/cli/json-parser-lastOf.pdl | 6 + .../src-tauri/tests/cli/json-parser.pdl | 6 + .../src-tauri/tests/cli/model-input-array.pdl | 7 + .../tests/cli/model-input-string.pdl | 2 + .../src-tauri/tests/cli/object1.pdl | 9 + .../src-tauri/tests/cli/object2.pdl | 11 + .../src-tauri/tests/cli/read-file.pdl | 5 + .../src-tauri/tests/cli/read-stdin.pdl | 3 + .../src-tauri/tests/cli/repeat1.pdl | 5 + .../src-tauri/tests/cli/repeat2.pdl | 6 + .../src-tauri/tests/cli/repeat3.pdl | 9 + pdl-live-react/src-tauri/tests/data/foo.txt | 1 + .../src-tauri/tests/data/struct.yaml | 2 + pdl-live-react/src/page/Run.css | 1 + pdl-live-react/src/page/Run.tsx | 145 ++ pdl-live-react/src/page/welcome/Links.tsx | 4 + pdl-live-react/src/routes/PdlRoutes.tsx | 2 + 44 files changed, 4016 insertions(+), 216 deletions(-) create mode 100644 .github/workflows/rust-interpreter.yml create mode 100644 pdl-live-react/src-tauri/src/commands/interpreter.rs create mode 100644 pdl-live-react/src-tauri/src/pdl/interpreter.rs create mode 100644 pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs create mode 100644 pdl-live-react/src-tauri/tests/cli/call-no-args.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/call-with-args.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/code-python.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/data1.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/data2.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/data3.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/data4.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/if1.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/if2.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/include1.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/json-parser-lastOf.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/json-parser.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/model-input-array.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/model-input-string.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/object1.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/object2.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/read-file.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/read-stdin.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/repeat1.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/repeat2.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/repeat3.pdl create mode 100644 pdl-live-react/src-tauri/tests/data/foo.txt create mode 100644 pdl-live-react/src-tauri/tests/data/struct.yaml create mode 100644 pdl-live-react/src/page/Run.css create mode 100644 pdl-live-react/src/page/Run.tsx diff --git a/.github/workflows/rust-interpreter.yml b/.github/workflows/rust-interpreter.yml new file mode 100644 index 000000000..f9827e813 --- /dev/null +++ b/.github/workflows/rust-interpreter.yml @@ -0,0 +1,36 @@ +name: Rust Interpreter Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +# cancel any prior runs for this workflow and this PR (or branch) +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + rust-interpreter: + name: Test Rust interpreter + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./pdl-live-react + steps: + - uses: actions/checkout@v4 + - name: Set up node + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install dependencies + # sleep 2 to wait for ollama to be running... hack warning + run: | + npm ci & sudo apt update && sudo apt install -y libgtk-3-dev libwebkit2gtk-4.1-dev librsvg2-dev patchelf at-spi2-core & + (curl -fsSL https://ollama.com/install.sh | sudo -E sh && sleep 2) + wait + # todo: do this in rust + ollama pull granite3.2:2b + - name: Run interpreter tests + run: npm run test:interpreter diff --git a/.github/workflows/tauri-cli.yml b/.github/workflows/tauri-cli.yml index 33fdc3794..30a9c25ea 100644 --- a/.github/workflows/tauri-cli.yml +++ b/.github/workflows/tauri-cli.yml @@ -45,7 +45,7 @@ jobs: for i in ./demos/beeai/*.py do pdl compile beeai $i -g -o /tmp/z.json && jq .description /tmp/z.json done - + - name: Test pdl run against production build env: DISPLAY: :1 diff --git a/pdl-live-react/package-lock.json b/pdl-live-react/package-lock.json index d642d914f..0973de40b 100644 --- a/pdl-live-react/package-lock.json +++ b/pdl-live-react/package-lock.json @@ -8,6 +8,7 @@ "name": "PDL", "version": "0.5.1", "dependencies": { + "@patternfly/react-code-editor": "^6.1.0", "@patternfly/react-core": "^6.1.0", "@tauri-apps/api": "^2.3.0", "@tauri-apps/plugin-cli": "^2.2.0", @@ -1092,6 +1093,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1130,6 +1154,41 @@ "node": ">= 8" } }, + "node_modules/@patternfly/react-code-editor": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.1.0.tgz", + "integrity": "sha512-ae04+DdkgXFn3wEzvNCncNa78ZK3Swh5ng8p7yqFrD6lhW69NoJf+DdQlHi8gM8Qy05DNnIemSbQWpWLpInyzw==", + "license": "MIT", + "dependencies": { + "@monaco-editor/react": "^4.6.0", + "@patternfly/react-core": "^6.1.0", + "@patternfly/react-icons": "^6.1.0", + "@patternfly/react-styles": "^6.1.0", + "react-dropzone": "14.3.5", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, + "node_modules/@patternfly/react-code-editor/node_modules/react-dropzone": { + "version": "14.3.5", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.5.tgz", + "integrity": "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/@patternfly/react-core": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.1.0.tgz", @@ -4717,6 +4776,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "license": "MIT", + "peer": true + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5659,6 +5725,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/string-comparison": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string-comparison/-/string-comparison-1.3.0.tgz", diff --git a/pdl-live-react/package.json b/pdl-live-react/package.json index b92ff1339..05c71cb15 100644 --- a/pdl-live-react/package.json +++ b/pdl-live-react/package.json @@ -14,7 +14,8 @@ "tauri": "tauri", "test:quality": "concurrently -n 'lint,types,formatting' 'npm run lint' 'tsc --build --noEmit' \"prettier --check 'tests/**/*.ts' 'src/**/*.{ts,tsx,css}'\"", "test:ui": "playwright install --with-deps && playwright test", - "test:bee": "until [ -f ./src-tauri/target/debug/pdl ]; do sleep 1; done; for i in ./demos/beeai/*.py; do ./src-tauri/target/debug/pdl compile beeai $i -g --output - | jq; done", + "test:bee": "until [ -f ./src-tauri/target/debug/pdl ]; do sleep 1; done; for i in ./demos/beeai/*.py; do ./src-tauri/target/debug/pdl compile beeai $i -g --output - | jq; done", + "test:interpreter": "cd src-tauri && cargo test", "types": "(cd .. && python -m src.pdl.pdl --schema > src/pdl/pdl-schema.json) && json2ts ../src/pdl/pdl-schema.json src/pdl_ast.d.ts --unreachableDefinitions && npm run format", "test": "concurrently -n 'quality,playwright' 'npm run test:quality' 'npm run test:ui'", "pdl": "./src-tauri/target/debug/pdl", @@ -22,6 +23,7 @@ "start": "npm run tauri dev" }, "dependencies": { + "@patternfly/react-code-editor": "^6.1.0", "@patternfly/react-core": "^6.1.0", "@tauri-apps/api": "^2.3.0", "@tauri-apps/plugin-cli": "^2.2.0", diff --git a/pdl-live-react/src-tauri/.taurignore b/pdl-live-react/src-tauri/.taurignore index 38dfdae12..848200e85 100644 --- a/pdl-live-react/src-tauri/.taurignore +++ b/pdl-live-react/src-tauri/.taurignore @@ -1,4 +1,6 @@ # emacs temp files *~ \#*\# -.\#* \ No newline at end of file +.\#* +tests/**/*.txt +tests/**/*.pdl diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index d018d148a..a47ae2e8a 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -17,6 +17,19 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -118,6 +131,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -244,6 +263,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "async-task" version = "4.7.1" @@ -290,6 +331,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -405,6 +457,17 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata 0.1.10", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -499,6 +562,15 @@ dependencies = [ "toml", ] +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + [[package]] name = "cc" version = "1.2.17" @@ -561,8 +633,10 @@ checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -593,6 +667,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -634,6 +717,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -657,9 +750,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.10.0", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -670,7 +763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.10.0", "libc", ] @@ -726,6 +819,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "crypto-common" version = "0.1.6" @@ -831,6 +930,27 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -997,6 +1117,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enumflags2" version = "0.7.11" @@ -1044,6 +1170,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + [[package]] name = "event-listener" version = "5.4.0" @@ -1065,12 +1197,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.0.3", + "windows-sys 0.59.0", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1123,6 +1272,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1130,7 +1288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1144,6 +1302,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1389,6 +1553,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1407,8 +1580,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1577,12 +1752,27 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -1613,6 +1803,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -1625,6 +1830,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -1698,6 +1918,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -1908,9 +2144,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1941,6 +2177,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "is-wsl" version = "0.4.0" @@ -1957,6 +2205,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -2046,6 +2303,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "junction" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72bbdfd737a243da3dfc1f99ee8d6e166480f17ab4ac84d7c34aacd73fc7bd16" +dependencies = [ + "scopeguard", + "windows-sys 0.52.0", +] + [[package]] name = "keyboard-types" version = "0.7.0" @@ -2070,12 +2337,48 @@ dependencies = [ "selectors", ] +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + [[package]] name = "libappindicator" version = "0.9.0" @@ -2116,6 +2419,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libredox" version = "0.1.3" @@ -2160,6 +2469,15 @@ version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +dependencies = [ + "twox-hash", +] + [[package]] name = "mac" version = "0.1.1" @@ -2167,46 +2485,120 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] -name = "markup5ever" -version = "0.11.0" +name = "malachite" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "2fbdf9cb251732db30a7200ebb6ae5d22fe8e11397364416617d2c2cf0c51cb5" dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", + "malachite-base", + "malachite-nz", + "malachite-q", ] [[package]] -name = "matches" -version = "0.1.10" +name = "malachite-base" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" +dependencies = [ + "hashbrown 0.14.5", + "itertools", + "libm", + "ryu", +] [[package]] -name = "memchr" -version = "2.7.4" +name = "malachite-bigint" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "d149aaa2965d70381709d9df4c7ee1fc0de1c614a4efc2ee356f5e43d68749f8" +dependencies = [ + "derive_more 1.0.0", + "malachite", + "num-integer", + "num-traits", + "paste", +] [[package]] -name = "memoffset" -version = "0.9.1" +name = "malachite-nz" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" dependencies = [ - "autocfg", + "itertools", + "libm", + "malachite-base", ] [[package]] -name = "mime" -version = "0.3.17" +name = "malachite-q" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f235d5747b1256b47620f5640c2a17a88c7569eebdf27cd9cb130e1a619191" +dependencies = [ + "itertools", + "malachite-base", + "malachite-nz", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minijinja" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98642a6dfca91122779a307b77cd07a4aa951fbe32232aaf5bad9febc66be754" +dependencies = [ + "aho-corasick", + "serde", +] + [[package]] name = "miniz_oxide" version = "0.8.5" @@ -2249,6 +2641,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2285,6 +2694,27 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.28.0" @@ -2316,12 +2746,30 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2331,6 +2779,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + [[package]] name = "num_enum" version = "0.7.3" @@ -2573,6 +3031,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "ollama-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4b4750770584c8b4a643d0329e7bedacc4ecf68b7c7ac3e1fec2bafd6312f7" +dependencies = [ + "async-stream", + "log", + "reqwest", + "schemars", + "serde", + "serde_json", + "static_assertions", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "url", +] + [[package]] name = "once_cell" version = "1.21.1" @@ -2591,12 +3068,62 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "optional" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978aa494585d3ca4ad74929863093e87cac9790d81fe7aba2b3dc2890643a0fc" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2617,6 +3144,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "owo-colors" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" + [[package]] name = "pango" version = "0.18.3" @@ -2671,6 +3204,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -2681,13 +3220,20 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" name = "pdl" version = "0.5.1" dependencies = [ + "async-recursion", "base64ct", "dirs", "duct", "futures", + "indexmap 2.9.0", + "minijinja", + "ollama-rs", + "owo-colors", "rayon", + "rustpython-vm", "serde", "serde_json", + "serde_norway", "sha2", "tauri", "tauri-build", @@ -2696,6 +3242,8 @@ dependencies = [ "tauri-plugin-pty", "tauri-plugin-window-state", "tempfile", + "tokio", + "tokio-stream", "urlencoding", "yaml-rust2", ] @@ -2756,6 +3304,16 @@ dependencies = [ "phf_shared 0.10.0", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf_generator" version = "0.8.0" @@ -2876,12 +3434,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.8.0", + "indexmap 2.9.0", "quick-xml", "serde", "time", ] +[[package]] +name = "pmutil" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "png" version = "0.17.16" @@ -2903,7 +3472,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite", "rustix 0.38.44", "tracing", @@ -2945,7 +3514,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.23", ] [[package]] @@ -3045,6 +3614,22 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.7.3" @@ -3180,10 +3765,16 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", + "regex-automata 0.4.9", "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-automata" version = "0.4.9" @@ -3215,19 +3806,23 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-util", "tower", "tower-service", @@ -3239,12 +3834,40 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "result-like" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc7ce6435c33898517a30e85578cd204cbb696875efb93dec19a2d31294f810" +dependencies = [ + "result-like-derive", +] + +[[package]] +name = "result-like-derive" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "syn 1.0.109", + "syn-ext", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.1" @@ -3268,24 +3891,341 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "1.0.3" +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustpython-ast" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdaf8ee5c1473b993b398c174641d3aa9da847af36e8d5eb8291930b72f31a5" +dependencies = [ + "is-macro", + "malachite-bigint", + "rustpython-literal", + "rustpython-parser-core", + "static_assertions", +] + +[[package]] +name = "rustpython-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f101783403a69155ba7b52d8365d796c772a0bfca7df0a5f16d267f3443986" +dependencies = [ + "ahash", + "bitflags 2.9.0", + "indexmap 2.9.0", + "itertools", + "log", + "num-complex", + "num-traits", + "rustpython-ast", + "rustpython-compiler-core", + "rustpython-parser-core", +] + +[[package]] +name = "rustpython-common" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22a5c520662f0ff98d717e2c4e52d8ba35eb1d99ee771dbdba7f09908b75bbb" +dependencies = [ + "ascii", + "bitflags 2.9.0", + "bstr", + "cfg-if", + "itertools", + "libc", + "lock_api", + "malachite-base", + "malachite-bigint", + "malachite-q", + "num-complex", + "num-traits", + "once_cell", + "radium", + "rand 0.8.5", + "rustpython-format", + "siphasher 0.3.11", + "volatile", + "widestring", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustpython-compiler" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c0ad9d5b948970d41b113cfc9ffe2337b10fdd41e0c92a859b9ed33c218efe" +dependencies = [ + "rustpython-codegen", + "rustpython-compiler-core", + "rustpython-parser", +] + +[[package]] +name = "rustpython-compiler-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd4e0c9fb7b3c70eb27b38d533edc0aa4875ea38cb06e12d76e234d00ef9766" +dependencies = [ + "bitflags 2.9.0", + "itertools", + "lz4_flex", + "malachite-bigint", + "num-complex", + "rustpython-parser-core", +] + +[[package]] +name = "rustpython-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c39620497116ce2996bcc679f9be4f47c1e8915c7ff9a9f0324e9584280660" +dependencies = [ + "rustpython-compiler", + "rustpython-derive-impl", + "syn 1.0.109", +] + +[[package]] +name = "rustpython-derive-impl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd18fa95c71a08ecc9cce739a608f5bff38805963152e671b66f5f266f0e58d" +dependencies = [ + "itertools", + "maplit", + "once_cell", + "proc-macro2", + "quote", + "rustpython-compiler-core", + "rustpython-doc", + "rustpython-parser-core", + "syn 1.0.109", + "syn-ext", + "textwrap", +] + +[[package]] +name = "rustpython-doc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885d19895d9d29656a8a2b33e967a482b92f3d891b4fd923e40849714051bcd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "rustpython-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0389039b132ad8e350552d771270ccd03186985696764bcee2239694e7839942" +dependencies = [ + "bitflags 2.9.0", + "itertools", + "malachite-bigint", + "num-traits", + "rustpython-literal", +] + +[[package]] +name = "rustpython-literal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8304be3cae00232a1721a911033e55877ca3810215f66798e964a2d8d22281d" +dependencies = [ + "hexf-parse", + "is-macro", + "lexical-parse-float", + "num-traits", + "unic-ucd-category", +] + +[[package]] +name = "rustpython-parser" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868f724daac0caf9bd36d38caf45819905193a901e8f1c983345a68e18fb2abb" +dependencies = [ + "anyhow", + "is-macro", + "itertools", + "lalrpop-util", + "log", + "malachite-bigint", + "num-traits", + "phf 0.11.3", + "phf_codegen 0.11.3", + "rustc-hash", + "rustpython-ast", + "rustpython-parser-core", + "tiny-keccak", + "unic-emoji-char", + "unic-ucd-ident", + "unicode_names2", +] + +[[package]] +name = "rustpython-parser-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b6c12fa273825edc7bccd9a734f0ad5ba4b8a2f4da5ff7efe946f066d0f4ad" +dependencies = [ + "is-macro", + "memchr", + "rustpython-parser-vendored", +] + +[[package]] +name = "rustpython-parser-vendored" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04fcea49a4630a3a5d940f4d514dc4f575ed63c14c3e3ed07146634aed7f67a6" +dependencies = [ + "memchr", + "once_cell", +] + +[[package]] +name = "rustpython-sre_engine" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39367be5d48e1e5caaa146904ea8d35fe43928168fbeb5c1ab295a0031b179c6" +dependencies = [ + "bitflags 2.9.0", + "num_enum", + "optional", +] + +[[package]] +name = "rustpython-vm" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2878cc4b5679f35fa762891d812ca7e011ae7cd41b5c532eb0ad13959b522493" +dependencies = [ + "ahash", + "ascii", + "atty", + "bitflags 2.9.0", + "bstr", + "caseless", + "cfg-if", + "chrono", + "crossbeam-utils", + "exitcode", + "getrandom 0.2.15", + "glob", + "half", + "hex", + "indexmap 2.9.0", + "is-macro", + "itertools", + "junction", + "libc", + "log", + "malachite-bigint", + "memchr", + "memoffset", + "nix 0.27.1", + "num-complex", + "num-integer", + "num-traits", + "num_cpus", + "num_enum", + "once_cell", + "optional", + "parking_lot", + "paste", + "rand 0.8.5", + "result-like", + "rustc_version", + "rustpython-ast", + "rustpython-codegen", + "rustpython-common", + "rustpython-compiler", + "rustpython-compiler-core", + "rustpython-derive", + "rustpython-format", + "rustpython-literal", + "rustpython-parser", + "rustpython-parser-core", + "rustpython-sre_engine", + "rustyline", + "schannel", + "static_assertions", + "strum", + "strum_macros", + "thiserror 1.0.69", + "thread_local", + "timsort", + "uname", + "unic-ucd-bidi", + "unic-ucd-category", + "unic-ucd-ident", + "unicode-casing", + "unicode_names2", + "wasm-bindgen", + "which", + "widestring", + "windows 0.52.0", + "windows-sys 0.52.0", + "winreg 0.10.1", +] + +[[package]] +name = "rustversion" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" dependencies = [ "bitflags 2.9.0", - "errno", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", "libc", - "linux-raw-sys 0.9.3", - "windows-sys 0.59.0", + "log", + "memchr", + "nix 0.28.0", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "windows-sys 0.52.0", ] -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - [[package]] name = "ryu" version = "1.0.20" @@ -3301,6 +4241,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3334,6 +4283,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -3342,7 +4314,7 @@ checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" dependencies = [ "bitflags 1.3.2", "cssparser", - "derive_more", + "derive_more 0.99.19", "fxhash", "log", "matches", @@ -3417,6 +4389,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_norway" +version = "0.9.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e408f29489b5fd500fab51ff1484fc859bb655f32c671f307dcd733b72e8168c" +dependencies = [ + "indexmap 2.9.0", + "itoa 1.0.15", + "ryu", + "serde", + "unsafe-libyaml-norway", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -3459,7 +4444,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.8.0", + "indexmap 2.9.0", "serde", "serde_derive", "serde_json", @@ -3626,7 +4611,7 @@ dependencies = [ "bytemuck", "cfg_aliases 0.2.1", "core-graphics", - "foreign-types", + "foreign-types 0.5.0", "js-sys", "log", "objc2 0.5.2", @@ -3708,6 +4693,25 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "swift-rs" version = "1.0.7" @@ -3741,6 +4745,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-ext" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b86cb2b68c5b3c078cac02588bc23f3c04bb828c5d3aedd17980876ec6a7be6" +dependencies = [ + "syn 1.0.109", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3781,7 +4794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.10.0", "core-graphics", "crossbeam-channel", "dispatch", @@ -3807,7 +4820,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.60.0", "windows-core 0.60.1", "windows-version", "x11-dl", @@ -3877,7 +4890,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.60.0", ] [[package]] @@ -3993,7 +5006,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.12", "url", - "windows", + "windows 0.60.0", "zbus", ] @@ -4042,7 +5055,7 @@ dependencies = [ "tauri-utils", "thiserror 2.0.12", "url", - "windows", + "windows 0.60.0", ] [[package]] @@ -4068,7 +5081,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.60.0", "wry", ] @@ -4144,6 +5157,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" + [[package]] name = "thin-slice" version = "0.1.1" @@ -4190,6 +5209,16 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.40" @@ -4221,6 +5250,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "timsort" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639ce8ef6d2ba56be0383a94dd13b92138d58de44c62618303bb798fa92bdc00" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4231,6 +5275,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.44.1" @@ -4241,11 +5300,46 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.14" @@ -4286,7 +5380,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.9.0", "toml_datetime", "winnow 0.5.40", ] @@ -4297,7 +5391,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.9.0", "toml_datetime", "winnow 0.5.40", ] @@ -4308,7 +5402,7 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -4401,6 +5495,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + [[package]] name = "typeid" version = "1.0.3" @@ -4424,6 +5528,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4445,6 +5558,40 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +[[package]] +name = "unic-emoji-char" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-bidi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-category" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0" +dependencies = [ + "matches", + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + [[package]] name = "unic-ucd-ident" version = "0.9.0" @@ -4465,18 +5612,73 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-casing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "623f59e6af2a98bdafeb93fa277ac8e1e40440973001ca15cf4ae1541cd16d56" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_names2" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd" +dependencies = [ + "phf 0.11.3", + "unicode_names2_generator", +] + +[[package]] +name = "unicode_names2_generator" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91e5b84611016120197efd7dc93ef76774f4e084cd73c9fb3ea4a86c570c56e" +dependencies = [ + "getopts", + "log", + "phf_codegen 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "unsafe-libyaml-norway" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39abd59bf32521c7f2301b52d05a6a2c975b6003521cbd0c6dc1582f0a22104" + [[package]] name = "url" version = "2.5.4" @@ -4541,6 +5743,12 @@ dependencies = [ "serde", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -4553,6 +5761,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "volatile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e76fae08f03f96e166d2dfda232190638c10e0383841252416f9cfe2ae60e6" + [[package]] name = "vswhom" version = "0.1.0" @@ -4759,7 +5973,7 @@ checksum = "b0d606f600e5272b514dbb66539dd068211cc20155be8d3958201b4b5bd79ed3" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.60.0", "windows-core 0.60.1", "windows-implement", "windows-interface", @@ -4783,10 +5997,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac" dependencies = [ "thiserror 2.0.12", - "windows", + "windows 0.60.0", "windows-core 0.60.1", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + [[package]] name = "winapi" version = "0.3.9" @@ -4833,6 +6065,16 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.60.0" @@ -5337,7 +6579,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.60.0", "windows-core 0.60.1", "windows-version", "x11-dl", @@ -5472,13 +6714,33 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.23", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index eb1ec1b4f..b751b9166 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -33,6 +33,15 @@ futures = "0.3.31" sha2 = "0.10.8" base64ct = { version = "1.7.1", features = ["alloc"] } dirs = "6.0.0" +serde_norway = "0.9.42" +minijinja = { version = "2.9.0", features = ["custom_syntax"] } +ollama-rs = { version = "0.3.0", features = ["stream"] } +owo-colors = "4.2.0" +rustpython-vm = "0.4.0" +async-recursion = "1.1.1" +tokio-stream = "0.1.17" +tokio = { version = "1.44.1", features = ["io-std"] } +indexmap = { version = "2.9.0", features = ["serde"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/pdl-live-react/src-tauri/src/cli.rs b/pdl-live-react/src-tauri/src/cli.rs index 60605fc96..06e15541b 100644 --- a/pdl-live-react/src-tauri/src/cli.rs +++ b/pdl-live-react/src-tauri/src/cli.rs @@ -5,6 +5,7 @@ use urlencoding::encode; use crate::compile; use crate::gui::new_window; +use crate::pdl::interpreter::run_file_sync as runr; use crate::pdl::run::run_pdl_program; #[cfg(desktop)] @@ -49,6 +50,18 @@ pub fn setup(app: &mut tauri::App) -> Result> _ => Err(Box::from("Unsupported compile command")), } } + "runr" => runr( + subcommand_args + .get("source") + .and_then(|a| a.value.as_str()) + .expect("valid positional source arg"), + subcommand_args + .get("debug") + .and_then(|a| a.value.as_bool()) + .or(Some(false)) + == Some(true), + ) + .and_then(|_trace| Ok(true)), "run" => run_pdl_program( subcommand_args .get("source") diff --git a/pdl-live-react/src-tauri/src/commands/interpreter.rs b/pdl-live-react/src-tauri/src/commands/interpreter.rs new file mode 100644 index 000000000..ff59f47e2 --- /dev/null +++ b/pdl-live-react/src-tauri/src/commands/interpreter.rs @@ -0,0 +1,10 @@ +use crate::pdl::interpreter::{pretty_print, run_string}; + +#[tauri::command] +pub async fn run_pdl_program(program: String, debug: bool) -> Result { + let (_, messages, _) = run_string(&program, debug) + .await + .map_err(|err| err.to_string())?; + + Ok(pretty_print(&messages)) +} diff --git a/pdl-live-react/src-tauri/src/commands/mod.rs b/pdl-live-react/src-tauri/src/commands/mod.rs index a3b78ac1c..5c98f7441 100644 --- a/pdl-live-react/src-tauri/src/commands/mod.rs +++ b/pdl-live-react/src-tauri/src/commands/mod.rs @@ -1,2 +1,3 @@ +pub mod interpreter; pub mod read_trace; pub mod replay_prep; diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index 53a6b9355..c686f2119 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -8,10 +8,14 @@ use ::std::path::{Path, PathBuf}; use duct::cmd; use futures::executor::block_on; use serde::Deserialize; -use serde_json::{from_reader, json, to_string, Value}; +use serde_json::{from_reader, json, to_string, Map, Value}; use tempfile::Builder; -use crate::pdl::ast::{PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType}; +use crate::pdl::ast::{ + ArrayBlock, CallBlock, FunctionBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, + PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, + TextBlock, +}; use crate::pdl::pip::pip_install_if_needed; use crate::pdl::requirements::BEEAI_FRAMEWORK; @@ -54,7 +58,7 @@ struct BeeAiToolState { name: String, description: Option, input_schema: BeeAiToolSchema, - options: Option>, + // options: Option>, } #[derive(Deserialize, Debug)] struct BeeAiTool { @@ -133,13 +137,36 @@ fn a_tool(tool: &BeeAiToolState) -> Value { "description": tool.description, "parameters": json!({ "type": "object", - "properties": tool.input_schema.properties, + "properties": strip_nulls(&tool.input_schema.properties), }), - "options": tool.options + // "options": tool.options }) }) } +// Strip null values out of the given HashMap +fn strip_nulls(parameters: &HashMap) -> HashMap { + parameters + .into_iter() + .filter_map(|(k, v)| match v { + Value::Null => None, + Value::Object(m) => Some((k.clone(), Value::Object(strip_nulls2(m)))), + _ => Some((k.clone(), v.clone())), + }) + .collect() +} +// sigh, i need to figure out generics IntoIterator, FromIterator +fn strip_nulls2(parameters: &Map) -> Map { + parameters + .into_iter() + .filter_map(|(k, v)| match v { + Value::Null => None, + Value::Object(m) => Some((k.clone(), Value::Object(strip_nulls2(&m)))), + _ => Some((k.clone(), v.clone())), + }) + .collect() +} + fn with_tools( tools: &Option>, parameters: &HashMap, @@ -147,9 +174,9 @@ fn with_tools( match tools { Some(tools) => { match tools.len() { - 0 => parameters.clone(), // litellm barfs on tools: [] + 0 => strip_nulls(parameters), // Note: litellm barfs on tools: [] _ => { - let mut copy = parameters.clone(); + let mut copy = strip_nulls(parameters); copy.insert( "tools".to_string(), tools.into_iter().map(|tool| a_tool(&tool.state)).collect(), @@ -158,71 +185,71 @@ fn with_tools( } } } - _ => parameters.clone(), + _ => strip_nulls(parameters), } } fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { - let repeat = PdlBlock::Text { + let repeat = PdlBlock::Text(TextBlock { + def: None, defs: None, role: None, parser: None, description: Some("Calling tool ${ tool.function.name }".to_string()), - text: vec![PdlBlock::Model { - parameters: parameters.clone(), - description: None, /*Some( - "Sending tool ${ tool.function.name } response back to model".to_string(), - ),*/ - def: None, - model_response: None, - model: model.clone(), - input: Some(Box::new(PdlBlock::Array { - array: vec![PdlBlock::Message { - role: "tool".to_string(), - description: None, - name: Some("${ tool.function.name }".to_string()), - tool_call_id: Some("${ tool.id }".to_string()), - content: Box::new(PdlBlock::Call { - defs: json_loads(&"args", &"pdl__args", &"${ tool.function.arguments }"), - call: "${ pdl__tools[tool.function.name] }".to_string(), // look up tool in tool_declarations def (see below) - args: Some("${ args }".to_string()), // invoke with arguments as specified by the model - }), - }], - })), - }], - }; + text: vec![PdlBlock::Model( + ModelBlock::new(model.as_str()) + .parameters(&strip_nulls(parameters)) + .input(PdlBlock::Array(ArrayBlock { + array: vec![PdlBlock::Message(MessageBlock { + role: Role::Tool, + description: None, + name: Some("${ tool.function.name }".to_string()), + tool_call_id: Some("${ tool.id }".to_string()), + content: Box::new(PdlBlock::Call(CallBlock { + defs: json_loads( + &"args", + &"pdl__args", + &"${ tool.function.arguments }", + ), + call: "${ pdl__tools[tool.function.name] }".to_string(), // look up tool in tool_declarations def (see below) + args: Some("${ args }".into()), // invoke with arguments as specified by the model + })), + })], + })) + .build(), + )], + }); let mut for_ = HashMap::new(); for_.insert( "tool".to_string(), - "${ response.choices[0].message.tool_calls }".to_string(), + ListOrString::String("${ response.choices[0].message.tool_calls }".to_string()), ); // response.choices[0].message.tool_calls - PdlBlock::Repeat { + PdlBlock::Repeat(RepeatBlock { for_: for_, repeat: Box::new(repeat), - } + }) } fn json_loads( outer_name: &str, inner_name: &str, value: &str, -) -> Option> { - let mut m = HashMap::new(); +) -> Option> { + let mut m = indexmap::IndexMap::new(); m.insert( outer_name.to_owned(), - PdlBlock::Text { - defs: None, - role: None, - description: Some(format!("Parsing json for {}={}", inner_name, value)), - text: vec![PdlBlock::String(format!( + PdlBlock::Text( + TextBlock::new(vec![PdlBlock::String(format!( "{{\"{}\": {}}}", inner_name, value - ))], - parser: Some(PdlParser::Json), - }, + ))]) + .description(format!("Parsing json for {}={}", inner_name, value)) + .parser(PdlParser::Json) + .build(), + ), ); Some(m) } @@ -235,7 +262,10 @@ fn json_schema_type_to_pdl_type(spec: &Value) -> PdlType { "boolean" => PdlBaseType::Bool, "integer" => PdlBaseType::Int, "null" => PdlBaseType::Null, - _ => PdlBaseType::Null, + x => { + eprintln!("Warning: unhandled JSONSchema type mapping to PDL {:?}", x); + PdlBaseType::Null + } }; match spec.get("default") { Some(_) => PdlType::Optional(PdlOptionalType { optional: base }), @@ -254,10 +284,16 @@ fn json_schema_type_to_pdl_type(spec: &Value) -> PdlType { optional: t.clone(), }) } - _ => PdlType::Base(PdlBaseType::Null), + x => { + eprintln!("Warning: unhandled JSONSchema type mapping to PDL {:?}", x); + PdlType::Base(PdlBaseType::Null) + } } } - _ => PdlType::Base(PdlBaseType::Null), + x => { + eprintln!("Warning: unhandled JSONSchema type mapping to PDL {:?}", x); + PdlType::Base(PdlBaseType::Null) + } }, } } @@ -337,7 +373,7 @@ pub fn compile( } }?; - // Read the JSON contents of the file as an instance of `User`. + // Read the JSON contents of the file as a BeeAIProgram let reader = BufReader::new(file); let bee: BeeAiProgram = from_reader(reader)?; @@ -368,8 +404,9 @@ pub fn compile( .map(|((import_from, import_fn), tool_name, schema)| { ( tool_name.clone(), - PdlBlock::Function { - return_: Box::new(PdlBlock::PythonCode { + PdlBlock::Function(FunctionBlock { + function: schema, + return_: Box::new(PdlBlock::PythonCode(PythonCodeBlock { // tool function definition lang: "python".to_string(), code: format!( @@ -402,9 +439,8 @@ asyncio.run(invoke()) "".to_string() } ), - }), - function: schema, - }, + })), + }), ) }) }) @@ -432,13 +468,14 @@ asyncio.run(invoke()) let model = format!("{}/{}", provider, model); if let Some(instructions) = instructions { - model_call.push(PdlBlock::Text { - role: Some(String::from("system")), + model_call.push(PdlBlock::Text(TextBlock { + role: Some(Role::System), text: vec![PdlBlock::String(instructions)], + def: None, defs: None, parser: None, - description: None, - }); + description: Some("Model instructions".into()), + })); } let model_response = if let Some(tools) = &tools { @@ -450,14 +487,16 @@ asyncio.run(invoke()) None }; - model_call.push(PdlBlock::Model { + model_call.push(PdlBlock::Model(ModelBlock { input: None, description: Some(description), def: None, model: model.clone(), model_response: model_response, - parameters: with_tools(&tools, ¶meters.state.dict), - }); + pdl_result: None, + pdl_usage: None, + parameters: Some(with_tools(&tools, ¶meters.state.dict)), + })); if let Some(tools) = tools { if tools.len() > 0 { @@ -466,31 +505,32 @@ asyncio.run(invoke()) } let closure_name = format!("agent_closure_{}", agent_name); - let mut defs = HashMap::new(); + let mut defs = indexmap::IndexMap::new(); defs.insert( closure_name.clone(), - PdlBlock::Function { + PdlBlock::Function(FunctionBlock { function: HashMap::new(), - return_: Box::new(PdlBlock::Text { + return_: Box::new(PdlBlock::Text(TextBlock { + def: None, defs: None, role: None, parser: None, - description: None, + description: Some(format!("Model call {}", &model)), text: model_call, - }), - }, + })), + }), ); - PdlBlock::Text { + PdlBlock::Text(TextBlock { + def: None, defs: Some(defs), role: None, parser: None, description: Some("Model call wrapper".to_string()), - text: vec![PdlBlock::Call { - call: format!("${{ {} }}", closure_name), - defs: None, - args: None, - }], - } + text: vec![PdlBlock::Call(CallBlock::new(format!( + "${{ {} }}", + closure_name + )))], + }) }, ) .collect::>(); @@ -499,16 +539,17 @@ asyncio.run(invoke()) .flat_map(|(a, b)| [a, b]) .collect::>(); - let pdl: PdlBlock = PdlBlock::Text { + let pdl: PdlBlock = PdlBlock::Text(TextBlock { + def: None, defs: if tool_declarations.len() == 0 { None } else { - let mut m = HashMap::new(); + let mut m = indexmap::IndexMap::new(); m.insert( "pdl__tools".to_string(), - PdlBlock::Object { + PdlBlock::Object(ObjectBlock { object: tool_declarations, - }, + }), ); Some(m) }, @@ -516,7 +557,7 @@ asyncio.run(invoke()) role: None, parser: None, text: body, - }; + }); match output_path { "-" => println!("{}", to_string(&pdl)?), diff --git a/pdl-live-react/src-tauri/src/lib.rs b/pdl-live-react/src-tauri/src/lib.rs index e696b571d..86b0b444c 100644 --- a/pdl-live-react/src-tauri/src/lib.rs +++ b/pdl-live-react/src-tauri/src/lib.rs @@ -33,6 +33,7 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ commands::read_trace::read_trace, commands::replay_prep::replay_prep, + commands::interpreter::run_pdl_program, ]) .run(tauri::generate_context!()) .expect("GUI opens") diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index b08092641..d655efe21 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -1,18 +1,32 @@ use ::std::collections::HashMap; -use serde::Serialize; -use serde_json::Value; +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_json::{to_string, Number, Value}; -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] +//why doesn't this work? #[serde(rename_all_fields(serialize = "lowercase"))] +pub enum Role { + #[serde(rename = "user")] + User, + #[serde(rename = "assistant")] + Assistant, + #[serde(rename = "system")] + System, + #[serde(rename = "tool")] + Tool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum PdlParser { #[serde(rename = "json")] Json, /*#[serde(rename = "jsonl")] - Jsonl, + Jsonl,*/ #[serde(rename = "yaml")] - Yaml,*/ + Yaml, } -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum PdlBaseType { #[serde(rename = "str")] Str, @@ -24,12 +38,12 @@ pub enum PdlBaseType { Null, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct PdlOptionalType { pub optional: PdlBaseType, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum PdlType { Base(PdlBaseType), @@ -37,74 +51,578 @@ pub enum PdlType { Object(HashMap), } -#[derive(Serialize, Debug)] +/// Call a function +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CallBlock { + /// Function to call + pub call: String, + + /// Arguments of the function with their values + #[serde(skip_serializing_if = "Option::is_none")] + pub args: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub defs: Option>, +} + +impl CallBlock { + pub fn new(call: String) -> Self { + CallBlock { + call: call, + args: None, + defs: None, + } + } +} + +pub trait SequencingBlock { + fn kind(&self) -> &str; + fn description(&self) -> &Option; + fn role(&self) -> &Option; + fn def(&self) -> &Option; + fn defs(&self) -> &Option>; + fn items(&self) -> &Vec; + fn with_items(&self, items: Vec) -> Self; + fn parser(&self) -> &Option; + fn to_block(&self) -> PdlBlock; + fn result_for(&self, output_results: Vec) -> PdlResult; + fn messages_for(&self, output_messages: Vec) -> Vec; +} + +/// Return the value of the last block if the list of blocks +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct LastOfBlock { + /// Sequence of blocks to execute + #[serde(rename = "lastOf")] + pub last_of: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub defs: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parser: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub def: Option, +} +impl SequencingBlock for LastOfBlock { + fn kind(&self) -> &str { + "lastOf" + } + fn description(&self) -> &Option { + &self.description + } + fn role(&self) -> &Option { + &self.role + } + fn def(&self) -> &Option { + return &self.def; + } + fn defs(&self) -> &Option> { + &self.defs + } + fn items(&self) -> &Vec { + &self.last_of + } + fn with_items(&self, items: Vec) -> Self { + let mut b = self.clone(); + b.last_of = items; + b + } + fn parser(&self) -> &Option { + &self.parser + } + fn to_block(&self) -> PdlBlock { + PdlBlock::LastOf(self.clone()) + } + fn result_for(&self, output_results: Vec) -> PdlResult { + match output_results.last() { + Some(result) => result.clone(), + None => "".into(), + } + } + fn messages_for(&self, output_messages: Vec) -> Vec { + match output_messages.last() { + Some(m) => vec![m.clone()], + None => vec![], + } + } +} + +/// Create the concatenation of the stringify version of the result of +/// each block of the list of blocks. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TextBlock { + /// Body of the text + pub text: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub defs: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parser: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub def: Option, +} +impl SequencingBlock for TextBlock { + fn kind(&self) -> &str { + "text" + } + fn description(&self) -> &Option { + &self.description + } + fn role(&self) -> &Option { + &self.role + } + fn def(&self) -> &Option { + return &self.def; + } + fn defs(&self) -> &Option> { + &self.defs + } + fn items(&self) -> &Vec { + &self.text + } + fn with_items(&self, items: Vec) -> Self { + let mut b = self.clone(); + b.text = items; + b + } + fn parser(&self) -> &Option { + &self.parser + } + fn to_block(&self) -> PdlBlock { + PdlBlock::Text(self.clone()) + } + fn result_for(&self, output_results: Vec) -> PdlResult { + PdlResult::String( + output_results + .into_iter() + .map(|m| m.to_string()) + .collect::>() + .join("\n"), + ) + } + fn messages_for(&self, output_messages: Vec) -> Vec { + output_messages + } +} + +impl TextBlock { + pub fn new(text: Vec) -> Self { + TextBlock { + def: None, + defs: None, + description: None, + role: None, + parser: None, + text: text, + } + } + + pub fn def(&mut self, def: &str) -> &mut Self { + self.def = Some(def.into()); + self + } + + pub fn description(&mut self, description: String) -> &mut Self { + self.description = Some(description); + self + } + + pub fn parser(&mut self, parser: PdlParser) -> &mut Self { + self.parser = Some(parser); + self + } + + pub fn build(&self) -> Self { + self.clone() + } +} + +impl From> for TextBlock { + fn from(v: Vec) -> Self { + TextBlock::new(v).build() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FunctionBlock { + pub function: HashMap, + #[serde(rename = "return")] + pub return_: Box, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PdlUsage { + // Completion tokens consumed + pub completion_tokens: u64, + // Prompt tokens consumed + pub prompt_tokens: u64, + // Completion nanos + pub completion_nanos: u64, + // Prompt nanos + pub prompt_nanos: u64, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ModelBlock { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + pub model: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub def: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "modelResponse")] + pub model_response: Option, + #[serde(rename = "pdl__result")] + #[serde(skip_serializing_if = "Option::is_none")] + pub pdl_result: Option, + #[serde(rename = "pdl__usage")] + #[serde(skip_serializing_if = "Option::is_none")] + pub pdl_usage: Option, +} + +impl ModelBlock { + pub fn new(model: &str) -> Self { + ModelBlock { + def: None, + description: None, + model_response: None, + parameters: None, + pdl_result: None, + pdl_usage: None, + model: model.into(), + input: None, + } + } + + pub fn input(&mut self, input: PdlBlock) -> &mut Self { + self.input = Some(Box::new(input)); + self + } + + pub fn input_str(&mut self, input: &str) -> &mut Self { + self.input = Some(Box::new(PdlBlock::String(input.into()))); + self + } + + pub fn parameters(&mut self, parameters: &HashMap) -> &mut Self { + self.parameters = Some(parameters.clone()); + self + } + + pub fn build(&self) -> Self { + self.clone() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum ListOrString { + String(String), + List(Vec), +} + +/// Repeat the execution of a block. +/// +/// For loop example: +/// ```PDL +/// for: +/// number: [1, 2, 3, 4] +/// name: ["Bob", "Carol", "David", "Ernest"] +/// repeat: +/// "${ name }'s number is ${ number }\\n" +/// ``` +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RepeatBlock { + /// Arrays to iterate over + #[serde(rename = "for")] + pub for_: HashMap, + + /// Body of the loop + pub repeat: Box, +} + +/// Create a message +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MessageBlock { + /// Role of associated to the message, e.g. User or Assistant + pub role: Role, + + /// Content of the message + pub content: Box, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + /// For example, the name of the tool that was invoked, for which this message is the tool response + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + /// The id of the tool invocation for which this message is the tool response + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + +/// Return the object where the value of each field is defined by a +/// block. If the body of the object is an array, the resulting object +/// is the union of the objects computed by each element of the array. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ObjectBlock { + pub object: HashMap, +} + +/// Arbitrary value, equivalent to JSON. +/// +/// Example. As part of a `defs` section, set `numbers` to the list `[1, 2, 3, 4]`: +/// ```PDL +/// defs: +/// numbers: +/// data: [1, 2, 3, 4] +/// ``` +/// +/// Example. Evaluate `${ TEST.answer }` in +/// [Jinja](https://jinja.palletsprojects.com/en/stable/), passing +/// the result to a regex parser with capture groups. Set +/// `EXTRACTED_GROUND_TRUTH` to an object with attribute `answer`, +/// a string, containing the value of the capture group. +/// ```PDL +/// - data: ${ TEST.answer } +/// parser: +/// regex: "(.|\\n)*#### (?P([0-9])*)\\n*" +/// spec: +/// answer: str +/// def: EXTRACTED_GROUND_TRUTH +/// ``` +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DataBlock { + pub data: Value, + + /// Do not evaluate expressions inside strings. + #[serde(skip_serializing_if = "Option::is_none")] + pub raw: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub def: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parser: Option, +} + +/// Execute a piece of Python code. +/// +/// Example: +/// ```yaml +/// lang: python +/// code: | +/// import random +/// # (In PDL, set `result` to the output you wish for your code block.) +/// result = random.randint(1, 20) +/// ``` +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PythonCodeBlock { + pub lang: String, + pub code: String, +} + +/// Read from a file or standard input. +/// +/// Example. Read from the standard input with a prompt starting with `> `. +/// ```PDL +/// read: +/// message: "> " +/// ``` +/// +/// Example. Read the file `./data.yaml` in the same directory of the PDL file containing the block and parse it into YAML. +/// ```PDL +/// read: ./data.yaml +/// parser: yaml +/// ``` +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ReadBlock { + /// Name of the file to read. If `None`, read the standard input. + pub read: Value, + + /// Name of the file to read. If `None`, read the standard input. + pub message: Option, + + /// Indicate if one or multiple lines should be read. + pub multiline: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub def: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parser: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum StringOrBoolean { + String(String), + Boolean(bool), +} + +/// Conditional control structure. +/// +/// Example: +/// ```PDL +/// defs: +/// answer: +/// read: +/// message: "Enter a number? " +/// if: ${ (answer | int) == 42 } +/// then: You won! +/// ``` +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct IfBlock { + /// The condition to check + #[serde(rename = "if")] + pub condition: StringOrBoolean, + + /// Branch to execute if the condition is true + pub then: Box, + + /// Branch to execute if the condition is false. + #[serde(rename = "else")] + #[serde(skip_serializing_if = "Option::is_none")] + pub else_: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub defs: Option>, +} + +/// Return the array of values computed by each block of the list of blocks +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ArrayBlock { + /// Elements of the array + pub array: Vec, +} + +/// Include a PDL file +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct IncludeBlock { + /// Name of the file to include. + pub include: String, +} + +/// Import a PDL file +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ImportBlock { + /// Name of the file to include. + pub import: String, +} + +/// Block containing only defs +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EmptyBlock { + pub defs: IndexMap, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum PdlBlock { + Bool(bool), + Number(Number), String(String), - /*If { - #[serde(rename = "if")] - condition: String, - then: Box, - },*/ - Object { - object: HashMap, - }, - Call { - call: String, - #[serde(skip_serializing_if = "Option::is_none")] - args: Option, - #[serde(skip_serializing_if = "Option::is_none")] - defs: Option>, - }, - Array { - array: Vec, - }, - Message { - role: String, - content: Box, - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - tool_call_id: Option, - }, - Repeat { - #[serde(rename = "for")] - for_: HashMap, - repeat: Box, - }, - Text { - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - role: Option, - text: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - defs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - parser: Option, - }, - Model { - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - model: String, - #[serde(skip_serializing_if = "Option::is_none")] - def: Option, - parameters: HashMap, - #[serde(skip_serializing_if = "Option::is_none")] - input: Option>, // really this should be restricted to be PdlBlock::Array; how do we do this in rust? - #[serde(rename = "modelResponse")] - #[serde(skip_serializing_if = "Option::is_none")] - model_response: Option, - }, - Function { - function: HashMap, - #[serde(rename = "return")] - return_: Box, - }, - PythonCode { - lang: String, - code: String, - }, + If(IfBlock), + Import(ImportBlock), + Include(IncludeBlock), + Data(DataBlock), + Object(ObjectBlock), + Call(CallBlock), + Array(ArrayBlock), + Message(MessageBlock), + Repeat(RepeatBlock), + Text(TextBlock), + LastOf(LastOfBlock), + Model(ModelBlock), + Function(FunctionBlock), + PythonCode(PythonCodeBlock), + Read(ReadBlock), + + // must be last to prevent serde from aggressively matching on it, since other block types also (may) have a `defs` + Empty(EmptyBlock), +} + +impl From<&str> for PdlBlock { + fn from(s: &str) -> Self { + PdlBlock::String(s.into()) + } +} + +impl From for PdlBlock { + fn from(s: String) -> Self { + PdlBlock::String(s.clone()) + } +} + +impl From<&str> for Box { + fn from(s: &str) -> Self { + Box::new(PdlBlock::String(s.into())) + } +} + +pub type Scope = HashMap; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Closure { + pub scope: Scope, + pub function: FunctionBlock, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum PdlResult { + Number(Number), + String(String), + Bool(bool), + Block(PdlBlock), + Closure(Closure), + List(Vec), + Dict(HashMap), +} +impl ::std::fmt::Display for PdlResult { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + let s = to_string(&self).unwrap(); // TODO: .map_err(|e| e.to_string())?; + write!(f, "{}", s) + } +} +impl From<&str> for PdlResult { + fn from(s: &str) -> Self { + PdlResult::String(s.to_string()) + } +} +impl From for PdlResult { + fn from(s: String) -> Self { + PdlResult::String(s) + } +} +impl From<&bool> for PdlResult { + fn from(b: &bool) -> Self { + PdlResult::Bool(*b) + } +} +impl From for PdlResult { + fn from(n: Number) -> Self { + PdlResult::Number(n) + } } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs new file mode 100644 index 000000000..4364f1d6d --- /dev/null +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -0,0 +1,974 @@ +// use ::std::cell::LazyCell; +use ::std::collections::HashMap; +use ::std::env::current_dir; +use ::std::error::Error; +use ::std::fs::{read_to_string as read_file_to_string, File}; +use ::std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +use async_recursion::async_recursion; +use minijinja::{syntax::SyntaxConfig, Environment}; +use owo_colors::OwoColorize; +use tokio::io::{stdout, AsyncWriteExt}; +use tokio_stream::StreamExt; + +use ollama_rs::{ + generation::{ + chat::{request::ChatMessageRequest, ChatMessage, ChatMessageResponse, MessageRole}, + tools::ToolInfo, + }, + models::ModelOptions, + Ollama, +}; + +use serde_json::{from_str, to_string, Value}; +use serde_norway::{from_reader, from_str as from_yaml_str}; + +use crate::pdl::ast::{ + ArrayBlock, CallBlock, Closure, DataBlock, EmptyBlock, FunctionBlock, IfBlock, ImportBlock, + IncludeBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, PdlBlock, PdlParser, + PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, RepeatBlock, Role, Scope, SequencingBlock, + StringOrBoolean, +}; + +type Context = Vec; +type PdlError = Box; +type Interpretation = Result<(PdlResult, Context, PdlBlock), PdlError>; +type InterpretationSync = Result<(PdlResult, Context, PdlBlock), Box>; + +struct Interpreter<'a> { + // batch: u32, + // role: Role, + cwd: PathBuf, + // id_stack: Vec, + jinja_env: Environment<'a>, + scope: Vec, + debug: bool, + emit: bool, +} + +impl<'a> Interpreter<'a> { + fn new() -> Self { + let mut jinja_env = Environment::new(); + // PDL uses custom variable delimeters, because {{ }} have pre-defined meaning in yaml + jinja_env.set_syntax( + SyntaxConfig::builder() + .variable_delimiters("${", "}") + .build() + .unwrap(), + ); + + Self { + // batch: 0, + // role: Role::User, + cwd: current_dir().unwrap_or(PathBuf::from("/")), + // id_stack: vec![], + jinja_env: jinja_env, + scope: vec![Scope::new()], + debug: false, + emit: true, + } + } + + async fn run_with_emit( + &mut self, + program: &PdlBlock, + context: Context, + emit: bool, + ) -> Interpretation { + if self.debug { + if let Some(scope) = self.scope.last() { + if scope.len() > 0 { + eprintln!("Run with Scope {:?}", scope); + } + } + } + + let prior_emit = self.emit; + self.emit = emit; + + let (result, messages, trace) = match program { + PdlBlock::Number(n) => Ok(( + n.clone().into(), + vec![ChatMessage::user(format!("{n}"))], + PdlBlock::Number(n.clone()), + )), + PdlBlock::Function(f) => Ok(( + PdlResult::Closure(self.closure(&f)), + vec![], + PdlBlock::Function(f.clone()), + )), + PdlBlock::String(s) => self.run_string(s, context).await, + PdlBlock::Call(block) => self.run_call(block, context).await, + PdlBlock::Empty(block) => self.run_empty(block, context).await, + PdlBlock::If(block) => self.run_if(block, context).await, + PdlBlock::Import(block) => self.run_import(block, context).await, + PdlBlock::Include(block) => self.run_include(block, context).await, + PdlBlock::Model(block) => self.run_model(block, context).await, + PdlBlock::Data(block) => self.run_data(block, context).await, + PdlBlock::Object(block) => self.run_object(block, context).await, + PdlBlock::PythonCode(block) => self.run_python_code(block, context).await, + PdlBlock::Read(block) => self.run_read(block, context).await, + PdlBlock::Repeat(block) => self.run_repeat(block, context).await, + PdlBlock::LastOf(block) => self.run_sequence(block, context).await, + PdlBlock::Text(block) => self.run_sequence(block, context).await, + PdlBlock::Array(block) => self.run_array(block, context).await, + PdlBlock::Message(block) => self.run_message(block, context).await, + _ => Err(Box::from(format!("Unsupported block {:?}", program))), + }?; + + if match program { + PdlBlock::Call(_) | PdlBlock::Model(_) => false, + _ => self.emit, + } { + println!("{}", pretty_print(&messages)); + } + self.emit = prior_emit; + + Ok((result, messages, trace)) + } + + #[async_recursion] + async fn run_quiet(&mut self, program: &PdlBlock, context: Context) -> Interpretation { + self.run_with_emit(program, context, false).await + } + + #[async_recursion] + async fn run(&mut self, program: &PdlBlock, context: Context) -> Interpretation { + self.run_with_emit(program, context, self.emit).await + } + + /// Evaluate String as a Jinja2 expression + fn eval(&self, expr: &String) -> Result { + let result = self + .jinja_env + .render_str(expr.as_str(), self.scope.last().unwrap_or(&HashMap::new()))?; + if self.debug { + eprintln!("Eval {} -> {}", expr, result); + } + + let backup = result.clone(); + Ok(from_str(&result).unwrap_or_else(|err| { + if self.debug { + eprintln!("Treating as plain string {}", &result); + eprintln!("... due to {}", err); + } + backup.into() + })) + } + + /// Evaluate String as a Jinja2 expression, expecting a string in response + fn eval_to_string(&self, expr: &String) -> Result { + match self.eval(expr)? { + PdlResult::String(s) => Ok(s), + x => Err(Box::from(format!( + "Expression {expr} evaluated to non-string {:?}", + x + ))), + } + } + + /// Traverse the given JSON Value, applying `self.eval()` to the value elements within. + fn eval_json(&self, expr: &Value) -> Result { + match expr { + Value::Null => Ok("".into()), + Value::Bool(b) => Ok(PdlResult::Bool(*b)), + Value::Number(n) => Ok(PdlResult::Number(n.clone())), + Value::String(s) => self.eval(s), + Value::Array(a) => Ok(PdlResult::List( + a.iter() + .map(|v| self.eval_json(v)) + .collect::>()?, + )), + Value::Object(o) => Ok(PdlResult::Dict( + o.iter() + .map(|(k, v)| match self.eval_json(v) { + Ok(v) => Ok((k.clone(), v)), + Err(e) => Err(e), + }) + .collect::>()?, + )), + } + } + + /// Evaluate an string or list of Values into a list of Values + fn eval_list_or_string(&self, expr: &ListOrString) -> Result, PdlError> { + match expr { + ListOrString::String(s) => match self.eval(s)? { + PdlResult::List(a) => Ok(a), + x => Err(Box::from(format!( + "Jinja string expanded to non-list. {} -> {:?}", + s, x + ))), + }, + ListOrString::List(l) => l.iter().map(|v| self.eval_json(v)).collect(), + } + } + + /// Create a closure for the given function `f` + fn closure(&self, f: &FunctionBlock) -> Closure { + Closure { + function: f.clone(), + scope: self.scope.last().unwrap_or(&HashMap::new()).clone(), + } + } + + /// Run a PdlBlock::String + async fn run_string(&self, msg: &String, _context: Context) -> Interpretation { + let trace = self.eval(msg)?; + if self.debug { + eprintln!("String {} -> {:?}", msg, trace); + } + + let result_string = match &trace { + PdlResult::String(s) => s.clone(), + x => to_string(&x)?, + }; + let messages = vec![ChatMessage::user(result_string)]; + + Ok((trace, messages, PdlBlock::String(msg.clone()))) + } + + /// If `file_path` is not absolute, join it with self.cwd + fn path_to(&self, file_path: &String) -> PathBuf { + let mut path = self.cwd.clone(); + path.push(file_path); + if path.extension().is_none() { + path.with_extension("pdl") + } else { + path + } + } + + fn def( + &mut self, + variable: &Option, + value: &PdlResult, + parser: &Option, + ) -> Result { + let result = if let Some(parser) = parser { + if let PdlResult::String(s) = value { + self.parse_result(parser, s) + } else { + Err(Box::from(format!( + "Cannot parse as {:?} a non-string value {:?}", + parser, value + ))) + } + } else { + //self.eval_json(value) + Ok(value.clone()) + }?; + + if let Some(def) = &variable { + if let Some(scope) = self.scope.last_mut() { + if self.debug { + eprintln!("Def {} -> {}", def, result); + } + scope.insert(def.clone(), result.clone()); + } + } + + Ok(result) + } + + /// Run a PdlBlock::Read + async fn run_read(&mut self, block: &ReadBlock, _context: Context) -> Interpretation { + let trace = block.clone(); + + if let Some(message) = &block.message { + println!("{}", message); + } + + let buffer = match &block.read { + Value::String(file_path) => Ok(read_file_to_string(self.path_to(file_path))?), + Value::Null => { + let mut buffer = String::new(); + ::std::io::stdin().read_line(&mut buffer)?; + Ok(buffer) + } + x => Err(Box::::from(format!( + "Unsupported value for read field: {:?}", + x + ))), + }?; + + let result = self.def(&block.def, &buffer.clone().into(), &block.parser)?; + + Ok(( + result, + vec![ChatMessage::user(buffer)], + PdlBlock::Read(trace), + )) + } + + /// Run a PdlBlock::Call + async fn run_call(&mut self, block: &CallBlock, context: Context) -> Interpretation { + if self.debug { + eprintln!("Call {:?}({:?})", block.call, block.args); + eprintln!("Call scope {:?}", self.scope.last()); + } + + let res = match self.eval(&block.call)? { + PdlResult::Closure(c) => { + if let Some(args) = &block.args { + match self.eval_json(args)? { + PdlResult::Dict(m) => { + self.push_and_extend_scope_with(m, c.scope); + Ok(()) + } + x => Err(Box::::from(format!( + "Call arguments not a map: {:?}", + x + ))), + }?; + } + + self.run(&c.function.return_, context.clone()).await + } + _ => Err(Box::from(format!("call of non-function {:?}", &block.call))), + }; + + if let Some(_) = block.args { + self.scope.pop(); + } + + res + } + + /// Run a PdlBlock::Empty + async fn run_empty(&mut self, block: &EmptyBlock, _context: Context) -> Interpretation { + if self.debug { + eprintln!("Empty"); + } + + let trace = block.clone(); + self.process_defs(&Some(block.defs.clone())).await?; + Ok(( + PdlResult::Dict(self.scope.last().unwrap_or(&HashMap::new()).clone()), + vec![], + PdlBlock::Empty(trace), + )) + } + + /// Run a PdlBlock::Call + async fn run_if(&mut self, block: &IfBlock, context: Context) -> Interpretation { + if self.debug { + eprintln!("If {:?}({:?})", block.condition, block.then); + } + + self.process_defs(&block.defs).await?; + + let cond = match &block.condition { + StringOrBoolean::Boolean(b) => PdlResult::Bool(*b), + StringOrBoolean::String(s) => self.eval(s)?, + }; + let res = match cond { + PdlResult::Bool(true) => self.run_quiet(&block.then, context).await, + PdlResult::Bool(false) => match &block.else_ { + Some(else_block) => self.run_quiet(&else_block, context).await, + None => Ok(("".into(), vec![], PdlBlock::If(block.clone()))), + }, + x => Err(Box::from(format!( + "if block condition evaluated to non-boolean value: {:?}", + x + ))), + }; + + self.scope.pop(); + res + } + + /// Run a PdlBlock::Include + async fn run_include(&mut self, block: &IncludeBlock, context: Context) -> Interpretation { + if self.debug { + eprintln!("Include {:?}", block.include); + } + + let path = self.path_to(&block.include); + let old_cwd = self.cwd.clone(); + if let Some(cwd) = path.parent() { + self.cwd = cwd.to_path_buf() + } + let res = self.run_quiet(&parse_file(&path)?, context.clone()).await; + self.cwd = old_cwd; + res + } + + /// Run a PdlBlock::Import + async fn run_import(&mut self, block: &ImportBlock, context: Context) -> Interpretation { + if self.debug { + eprintln!("Import {:?}", block.import); + } + + let path = self.path_to(&block.import); + let old_cwd = self.cwd.clone(); + if let Some(cwd) = path.parent() { + self.cwd = cwd.to_path_buf() + } + let res = self.run_quiet(&parse_file(&path)?, context.clone()).await; + self.cwd = old_cwd; + res + } + + fn to_ollama_model_options( + &self, + maybe_parameters: &Option>, + ) -> (ModelOptions, Vec) { + // for some reason temp=0 isn't the default + let options = ModelOptions::default().temperature(0.0); + + if let Some(parameters) = maybe_parameters { + let temp = if let Some(Value::Number(num)) = parameters.get(&"temperature".to_string()) + { + if let Some(temp) = num.as_f64() { + temp as f32 + } else if let Some(temp) = num.as_i64() { + temp as f32 + } else { + 0.0 + } + } else { + 0.0 + }; + + let tools = if let Some(Value::Array(_tools)) = parameters.get(&"tools".to_string()) { + // TODO + //tools.into_iter().map(|tool| function!()).collect() + vec![] + } else { + vec![] + }; + + (options.temperature(temp), tools) + } else { + (options, vec![]) + } + } + + /// Run a PdlBlock::PythonCode + async fn run_python_code( + &mut self, + block: &PythonCodeBlock, + _context: Context, + ) -> Interpretation { + use rustpython_vm as vm; + vm::Interpreter::without_stdlib(Default::default()).enter(|vm| -> Interpretation { + let scope = vm.new_scope_with_builtins(); + + // TODO vm.new_syntax_error(&err, Some(block.code.as_str())) + let code_obj = vm + .compile( + block.code.as_str(), + vm::compiler::Mode::Exec, + "".to_owned(), + ) + .map_err(|_err| { + panic!("Syntax error in Python code"); + }) + .unwrap(); + + let _output = vm + .run_code_obj(code_obj, scope.clone()) + .map_err(|_err| { + // TODO vm.print_exception(exc); + println!("Error executing Python code"); + }) + .unwrap(); + + match scope.globals.get_item("result", vm) { + Ok(result) => { + let result_string = result + .str(vm) + .map_err(|e| { + panic!("Unable to stringify Python 'result' value {:?}", e); + }) + .unwrap(); + let messages = vec![ChatMessage::user(result_string.as_str().to_string())]; + let trace = PdlBlock::PythonCode(block.clone()); + Ok((messages[0].content.clone().into(), messages, trace)) + } + Err(_) => Err(Box::from( + "Python code block failed to assign a 'result' variable", + )), + } + }) + } + + /// Run a PdlBlock::Model + async fn run_model(&mut self, block: &ModelBlock, context: Context) -> Interpretation { + match &block.model { + pdl_model + if pdl_model.starts_with("ollama/") || pdl_model.starts_with("ollama_chat/") => + { + let ollama = Ollama::default(); + let model = if pdl_model.starts_with("ollama/") { + &pdl_model[7..] + } else { + &pdl_model[12..] + }; + + let (options, tools) = self.to_ollama_model_options(&block.parameters); + if self.debug { + println!("Model options {:?}", options); + } + + let input_messages = match &block.input { + Some(input) => { + // TODO ignoring result, trace + let (_result, messages, _trace) = self.run_quiet(&*input, context).await?; + messages + } + None => context, + }; + let (prompt, history_slice): (&ChatMessage, &[ChatMessage]) = + match input_messages.split_last() { + Some(x) => x, + None => (&ChatMessage::user("".into()), &[]), + }; + let history = Vec::from(history_slice); + if self.debug { + eprintln!( + "Ollama {:?} model={:?} prompt={:?} history={:?}", + block.description.clone().unwrap_or("".into()), + block.model, + prompt, + history + ); + } + + if self.emit { + println!("{}", pretty_print(&input_messages)); + } + + let req = ChatMessageRequest::new(model.into(), vec![prompt.clone()]) + .options(options) + .tools(tools); + /* if we ever want non-streaming: + let res = ollama + .send_chat_messages_with_history( + &mut history, + req, + //ollama.generate(GenerationRequest::new(model.into(), prompt), + ) + .await?; + // dbg!("Model result {:?}", &res); + + let mut trace = block.clone(); + trace.pdl_result = Some(res.message.content.clone()); + + if let Some(usage) = res.final_data { + trace.pdl_usage = Some(PdlUsage { + prompt_tokens: usage.prompt_eval_count, + prompt_nanos: usage.prompt_eval_duration, + completion_tokens: usage.eval_count, + completion_nanos: usage.eval_duration, + }); + } + // dbg!(history); + Ok((vec![res.message], PdlBlock::Model(trace))) + */ + let mut stream = ollama + .send_chat_messages_with_history_stream( + Arc::new(Mutex::new(history)), + req, + //ollama.generate(GenerationRequest::new(model.into(), prompt), + ) + .await?; + // dbg!("Model result {:?}", &res); + + let mut last_res: Option = None; + let mut response_string = String::new(); + let mut stdout = stdout(); + stdout.write_all(b"\x1b[1mAssistant: \x1b[0m").await?; + while let Some(Ok(res)) = stream.next().await { + stdout.write_all(b"\x1b[32m").await?; // green + stdout.write_all(res.message.content.as_bytes()).await?; + stdout.flush().await?; + stdout.write_all(b"\x1b[0m").await?; // reset color + response_string += res.message.content.as_str(); + last_res = Some(res); + } + stdout.write_all(b"\n").await?; + + let mut trace = block.clone(); + trace.pdl_result = Some(response_string.clone()); + + if let Some(res) = last_res { + if let Some(usage) = res.final_data { + trace.pdl_usage = Some(PdlUsage { + prompt_tokens: usage.prompt_eval_count, + prompt_nanos: usage.prompt_eval_duration, + completion_tokens: usage.eval_count, + completion_nanos: usage.eval_duration, + }); + } + let output_messages = vec![ChatMessage::assistant(response_string)]; + Ok(( + res.message.content.into(), + output_messages, + PdlBlock::Model(trace), + )) + } else { + // nothing came out of the model + Ok(("".into(), vec![], PdlBlock::Model(trace))) + } + // dbg!(history); + } + _ => Err(Box::from(format!("Unsupported model {}", block.model))), + } + } + + /// Transform a JSON Value into a PdlResult object + fn resultify(&self, value: &Value) -> PdlResult { + match value { + Value::Null => "".into(), + Value::Bool(b) => b.into(), + Value::Number(n) => n.clone().into(), + Value::String(s) => s.clone().into(), + Value::Array(a) => { + PdlResult::List(a.iter().map(|v| self.resultify(v)).collect::>()) + } + Value::Object(m) => PdlResult::Dict( + m.iter() + .map(|(k, v)| (k.clone(), self.resultify(v))) + .collect::>(), + ), + } + } + + /// Run a PdlBlock::Data + async fn run_data(&mut self, block: &DataBlock, _context: Context) -> Interpretation { + if self.debug { + eprintln!("Data raw={:?} {:?}", block.raw, block.data); + } + + let mut trace = block.clone(); + if let Some(true) = block.raw { + let result = self.def(&block.def, &self.resultify(&block.data), &block.parser)?; + Ok((result, vec![], PdlBlock::Data(trace))) + } else { + let result = self.def(&block.def, &self.eval_json(&block.data)?, &block.parser)?; + trace.data = from_str(to_string(&result)?.as_str())?; + Ok((result, vec![], PdlBlock::Data(trace))) + } + } + + async fn run_object(&mut self, block: &ObjectBlock, context: Context) -> Interpretation { + if self.debug { + eprintln!("Object {:?}", block.object); + } + + let mut messages = vec![]; + let mut result_map = HashMap::new(); + let mut trace_map = HashMap::new(); + + let mut iter = block.object.iter(); + while let Some((k, v)) = iter.next() { + let (this_result, this_messages, this_trace) = + self.run_quiet(v, context.clone()).await?; + messages.extend(this_messages); + result_map.insert(k.clone(), this_result); + trace_map.insert(k.clone(), this_trace); + } + + Ok(( + PdlResult::Dict(result_map), + messages, + PdlBlock::Object(ObjectBlock { object: trace_map }), + )) + } + + /// Run a PdlBlock::Repeat + async fn run_repeat(&mut self, block: &RepeatBlock, context: Context) -> Interpretation { + // { i:[1,2,3], j: [4,5,6]} -> ([i,j], [[1,2,3],[4,5,6]]) + // let (variables, values): (Vec<_>, Vec>) = block + // .into_iter() + // .unzip(); + let iter_scopes = block + .for_ + .iter() + .map(|(var, values)| match self.eval_list_or_string(values) { + Ok(value) => Ok((var.clone(), value)), + Err(e) => Err(e), + }) + .collect::, _>>()?; + + if self.debug { + eprintln!("Repeat {:?}", iter_scopes); + } + + let mut results = vec![]; + let mut messages = vec![]; + let mut trace = vec![]; + if let Some(n) = iter_scopes.iter().map(|(_, v)| v.len()).min() { + for iter in 0..n { + let this_iter_scope = iter_scopes + .iter() + .map(|(k, v)| (k.clone(), v[iter].clone())) + .collect(); + self.push_and_extend_scope(this_iter_scope); + let (result, ms, t) = self.run_quiet(&block.repeat, context.clone()).await?; + results.push(result); + messages.extend(ms); + trace.push(t); + self.pop_scope(); + } + } + + Ok(( + PdlResult::List(results), + messages, + PdlBlock::Repeat(block.clone()), + )) + } + + fn to_ollama_role(&self, role: &Role) -> MessageRole { + match role { + Role::User => MessageRole::User, + Role::Assistant => MessageRole::Assistant, + Role::System => MessageRole::System, + Role::Tool => MessageRole::Tool, + } + } + + fn parse_result(&self, parser: &PdlParser, result: &String) -> Result { + match parser { + PdlParser::Json => from_str(result).map_err(|e| Box::from(e)), + PdlParser::Yaml => from_yaml_str(result).map_err(|e| Box::from(e)), + } + } + + fn push_and_extend_scope(&mut self, scope: HashMap) { + let mut new_scope = self.scope.last().unwrap_or(&HashMap::new()).clone(); + new_scope.extend(scope); + self.scope.push(new_scope); + } + + fn push_and_extend_scope_with( + &mut self, + mut scope: HashMap, + other_scope: HashMap, + ) { + scope.extend(other_scope); + self.push_and_extend_scope(scope); + } + + fn pop_scope(&mut self) { + self.scope.pop(); + } + + async fn process_defs( + &mut self, + defs: &Option>, + ) -> Result<(), PdlError> { + let mut new_scope: Scope = HashMap::new(); + if let Some(cur_scope) = self.scope.last() { + new_scope.extend(cur_scope.clone()); + } + self.scope.push(new_scope); + + if let Some(defs) = defs { + let mut iter = defs.iter(); + while let Some((var, def)) = iter.next() { + let (result, _, _) = self.run_quiet(def, vec![]).await?; + let _ = self.def(&Some(var.clone()), &result, &None); + } + } + + Ok(()) + } + + /// Run a sequencing block (e.g. TextBlock, LastOfBlock) + async fn run_sequence( + &mut self, + block: &impl SequencingBlock, + context: Context, + ) -> Interpretation { + if self.debug { + let description = if let Some(d) = block.description() { + d + } else { + &"".to_string() + }; + eprintln!("{} {description}", block.kind()); + } + + let mut input_messages = context.clone(); + let mut output_results = vec![]; + let mut output_messages = vec![]; + let mut output_blocks = vec![]; + + self.process_defs(block.defs()).await?; + + let mut iter = block.items().iter(); + while let Some(block) = iter.next() { + // run each element of the Text block + let (this_result, this_messages, trace) = + self.run_quiet(&block, input_messages.clone()).await?; + input_messages.extend(this_messages.clone()); + output_results.push(this_result); + + output_messages.extend(this_messages); + output_blocks.push(trace); + } + + self.scope.pop(); + + let trace = block.with_items(output_blocks); + let result = self.def( + trace.def(), + &trace.result_for(output_results), + trace.parser(), + )?; + let result_messages = trace.messages_for::(output_messages); + Ok(( + result, + match block.role() { + Some(role) => result_messages + .into_iter() + .map(|m| ChatMessage::new(self.to_ollama_role(role), m.content)) + .collect(), + None => result_messages, + }, + trace.to_block(), + )) + } + + /// Run a PdlBlock::Array + async fn run_array(&mut self, block: &ArrayBlock, context: Context) -> Interpretation { + let mut result_items = vec![]; + let mut all_messages = vec![]; + let mut trace_items = vec![]; + + let mut iter = block.array.iter(); + while let Some(item) = iter.next() { + // TODO accumulate messages + let (result, messages, trace) = self.run_quiet(item, context.clone()).await?; + result_items.push(result); + all_messages.extend(messages); + trace_items.push(trace); + } + + Ok(( + PdlResult::List(result_items), + all_messages, + PdlBlock::Array(ArrayBlock { array: trace_items }), + )) + } + + /// Run a PdlBlock::Message + async fn run_message(&mut self, block: &MessageBlock, context: Context) -> Interpretation { + let (content_result, content_messages, content_trace) = + self.run(&block.content, context).await?; + let name = if let Some(name) = &block.name { + Some(self.eval_to_string(&name)?) + } else { + None + }; + let tool_call_id = if let Some(tool_call_id) = &block.tool_call_id { + Some(self.eval_to_string(&tool_call_id)?) + } else { + None + }; + + let mut dict: HashMap = HashMap::new(); + dict.insert("role".into(), PdlResult::String(to_string(&block.role)?)); + dict.insert("content".into(), content_result); + if let Some(name) = &name { + dict.insert("name".into(), PdlResult::String(name.clone())); + } + if let Some(tool_call_id) = &tool_call_id { + dict.insert( + "tool_call_id".into(), + PdlResult::String(tool_call_id.clone()), + ); + } + + Ok(( + PdlResult::Dict(dict), + content_messages + .into_iter() + .map(|m| ChatMessage::new(self.to_ollama_role(&block.role), m.content)) + .collect(), + PdlBlock::Message(MessageBlock { + role: block.role.clone(), + content: Box::new(content_trace), + description: block.description.clone(), + name: name, + tool_call_id: tool_call_id, + }), + )) + } +} + +pub async fn run(program: &PdlBlock, cwd: Option, debug: bool) -> Interpretation { + let mut interpreter = Interpreter::new(); + interpreter.debug = debug; + if let Some(cwd) = cwd { + interpreter.cwd = cwd + }; + interpreter.run(&program, vec![]).await +} + +pub fn run_sync(program: &PdlBlock, cwd: Option, debug: bool) -> InterpretationSync { + tauri::async_runtime::block_on(run(program, cwd, debug)) + .map_err(|err| Box::::from(err.to_string())) +} + +/// Read in a file from disk and parse it as a PDL program +fn parse_file(path: &PathBuf) -> Result { + from_reader(File::open(path)?) + .map_err(|err| Box::::from(err.to_string())) +} + +pub async fn run_file(source_file_path: &str, debug: bool) -> Interpretation { + let path = PathBuf::from(source_file_path); + let cwd = path.parent().and_then(|cwd| Some(cwd.to_path_buf())); + let program = parse_file(&path)?; + + run(&program, cwd, debug).await +} + +pub fn run_file_sync(source_file_path: &str, debug: bool) -> InterpretationSync { + tauri::async_runtime::block_on(run_file(source_file_path, debug)) + .map_err(|err| Box::::from(err.to_string())) +} + +pub async fn run_string(source: &str, debug: bool) -> Interpretation { + run(&from_yaml_str(source)?, None, debug).await +} + +pub async fn run_json(source: Value, debug: bool) -> Interpretation { + run_string(&to_string(&source)?, debug).await +} + +pub fn run_json_sync(source: Value, debug: bool) -> InterpretationSync { + tauri::async_runtime::block_on(run_json(source, debug)) + .map_err(|err| Box::::from(err.to_string())) +} + +pub fn pretty_print(messages: &Vec) -> String { + messages + .into_iter() + .map( + |ChatMessage { + role: r, + content: c, + .. + }| { + format!( + "{:?}: {}", + r.bold(), + match r { + MessageRole::Assistant => c.green().to_string(), + MessageRole::System => c.cyan().to_string(), + MessageRole::Tool => c.magenta().to_string(), + _ => c.to_string(), + } + ) + }, + ) + .collect::>() + .join("\n") +} diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs new file mode 100644 index 000000000..7d9f3c278 --- /dev/null +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -0,0 +1,537 @@ +#[cfg(test)] +mod tests { + // use super::*; + use ::std::error::Error; + use serde_json::json; + + use crate::pdl::{ + ast::{ModelBlock, PdlBlock}, + interpreter::{run_json_sync as run_json, run_sync as run}, + }; + + use ollama_rs::generation::chat::MessageRole; + + const DEFAULT_MODEL: &'static str = "ollama/granite3.2:2b"; + + #[test] + fn string() -> Result<(), Box> { + let (_, messages, _) = run(&"hello".into(), None, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "hello"); + Ok(()) + } + + #[test] + fn single_model_via_input_string() -> Result<(), Box> { + let (_, messages, _) = run( + &PdlBlock::Model(ModelBlock::new(DEFAULT_MODEL).input_str("hello").build()), + None, + false, + )?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::Assistant); + assert!(messages[0].content.contains("Hello!")); + Ok(()) + } + + #[test] + fn single_model_via_text_chain() -> Result<(), Box> { + let (_, messages, _) = run_json( + json!({ + "text": [ + "hello", + { "model": DEFAULT_MODEL } + ] + }), + false, + )?; + assert_eq!(messages.len(), 2); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "hello"); + assert_eq!(messages[1].role, MessageRole::Assistant); + assert!(messages[1].content.contains("Hello!")); + Ok(()) + } + + #[test] + fn single_model_via_input_array() -> Result<(), Box> { + let (_, messages, _) = run_json( + json!({ + "model": DEFAULT_MODEL, + "input": { + "array": [ + { "role": "system", "content": "answer as if you live in europe" }, + { "role": "user", "content": "what is the fastest animal where you live?" }, + ] + } + }), + false, + )?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::Assistant); + let m = messages[0].content.to_lowercase(); + assert!( + m.contains("pronghorn") + || m.contains("falcon") + || m.contains("bison") + || m.contains("native") + ); + Ok(()) + } + + #[test] + fn two_models_via_text_chain() -> Result<(), Box> { + let (_, messages, _) = run_json( + json!({ + "text": [ + "what is the fastest animal?", + { "model": DEFAULT_MODEL }, + "in europe?", + { "model": DEFAULT_MODEL }, + ] + }), + false, + )?; + assert_eq!(messages.len(), 4); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "what is the fastest animal?"); + assert_eq!(messages[1].role, MessageRole::Assistant); + let m1 = messages[1].content.to_lowercase(); + assert!(m1.contains("cheetah") || m1.contains("springbok")); + assert_eq!(messages[2].role, MessageRole::User); + assert_eq!(messages[2].content, "in europe?"); + assert_eq!(messages[3].role, MessageRole::Assistant); + + let m3 = messages[3].content.to_lowercase(); + assert!( + m3.contains("peregrine") + || m3.contains("bison") + || m3.contains("hare") + || m3.contains("golden eagle") + || m3.contains("greyhound") + || m3.contains("gazelle") + || m3.contains("lynx") + || m3.contains("boar") + || m3.contains("sailfish") + || m3.contains("pronghorn") + ); + Ok(()) + } + + #[test] + fn text_parser_json() -> Result<(), Box> { + let json = "{\"key\":\"value\"}"; + let program = json!({ + "text": [ + { "def": "foo", "parser": "json", "text": [json] }, + "${ foo.key }" + ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 2); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, json); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "value"); + Ok(()) + } + + #[test] + fn last_of_parser_json() -> Result<(), Box> { + let json = "{\"key\":\"value\"}"; + let program = json!({ + "lastOf": [ + { "def": "foo", "parser": "json", "text": [json] }, + "${ foo.key }" + ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "value"); + Ok(()) + } + + #[test] + fn text_call_function_no_args() -> Result<(), Box> { + let program = json!({ + "defs": { + "foo": { + "function": {}, + "return": { + "description": "nullary function", + "text": [ + "hello world" + ] + } + } + }, + "text": [ + { "call": "${ foo }" }, + ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "hello world"); + Ok(()) + } + + #[test] + fn text_call_function_with_args() -> Result<(), Box> { + let program = json!({ + "defs": { + "foo": { + "function": { + "x": "int" + }, + "return": { + "description": "unary function", + "text": [ + "hello world ${x+1}" + ] + } + } + }, + "text": [ + { "call": "${ foo }", "args": { "x": 3 } }, + ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "hello world 4"); + Ok(()) + } + + #[test] + fn text_python_code_result_int() -> Result<(), Box> { + let program = json!({ + "lang": "python", + "code":"print('hi ho'); result = 33" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "33"); + Ok(()) + } + + #[test] + fn text_python_code_result_str() -> Result<(), Box> { + let program = json!({ + "lang": "python", + "code":"print('hi ho'); result = 'foo'" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "foo"); + Ok(()) + } + + #[test] + fn text_python_code_result_dict() -> Result<(), Box> { + let program = json!({ + "lang": "python", + "code":"print('hi ho'); result = {\"foo\": 3}" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "{'foo': 3}"); + Ok(()) + } + + #[test] + fn text_read_file_text() -> Result<(), Box> { + let program = json!({ + "message": "Read a file", + "read":"./tests/data/foo.txt" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "this should be foo\n"); + Ok(()) + } + + #[test] + fn text_read_file_struct() -> Result<(), Box> { + let program = json!({ + "text": [ + { "read": "./tests/data/struct.yaml", "def": "struct", "parser": "yaml" }, + "${ struct.a.b }" + ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 2); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!( + messages[0].content, + "a: + b: 3 +" + ); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "3"); + Ok(()) + } + + #[test] + fn text_repeat_numbers_1d() -> Result<(), Box> { + let program = json!({ + "for": { + "x": [1,2,3] + }, + "repeat": { + "text": [ + "${ x + 1 }" + ] + } + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 3); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "2"); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "3"); + assert_eq!(messages[2].role, MessageRole::User); + assert_eq!(messages[2].content, "4"); + Ok(()) + } + + #[test] + fn text_repeat_numbers_2d() -> Result<(), Box> { + let program = json!({ + "for": { + "x": [1,2,3], + "y": [4,5,6] + }, + "repeat": { + "text": [ + "${ x + y }" + ] + } + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 3); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "5"); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "7"); + assert_eq!(messages[2].role, MessageRole::User); + assert_eq!(messages[2].content, "9"); + Ok(()) + } + + #[test] + fn text_repeat_mix_2d() -> Result<(), Box> { + let program = json!({ + "for": { + "x": [{"z": 4}, {"z": 5}, {"z": 6}], + "y": ["a","b","c"] + }, + "repeat": { + "text": [ + "${ x.z ~ y }" // ~ is string concatenation in jinja + ] + } + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 3); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "4a"); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "5b"); + assert_eq!(messages[2].role, MessageRole::User); + assert_eq!(messages[2].content, "6c"); + Ok(()) + } + + #[test] + fn text_if_true() -> Result<(), Box> { + let program = json!({ + "if": true, + "then": "good" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "good"); + Ok(()) + } + + #[test] + fn text_if_false() -> Result<(), Box> { + let program = json!({ + "if": false, + "then": "bug", + "else": "good" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "good"); + Ok(()) + } + + #[test] + fn text_if_with_defs() -> Result<(), Box> { + let program = json!({ + "defs": { + "x": 5 + }, + "if": "${x!=5}", + "then": "bug", + "else": "good" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "good"); + Ok(()) + } + + #[test] + fn text_object_via_defs_1() -> Result<(), Box> { + let program = json!({ + "defs": { + "obj": { + "object": { + "a": { + "text": [ "good on object" ] + } + } + } + }, + "text": [ "${ obj.a }" ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "good on object"); + Ok(()) + } + + #[test] + fn text_object_via_defs_2() -> Result<(), Box> { + let program = json!({ + "defs": { + "obj": { + "object": { + "a": { + "object": { + "b": { + "text": [ "good on object" ] + } + } + } + } + } + }, + "text": [ "${ obj.a.b }" ] + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "good on object"); + Ok(()) + } + + #[test] + fn include() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/call-with-args.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "hello world 4 bye"); + Ok(()) + } + + #[test] + fn data_1() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/data1.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "xxxx3true"); + Ok(()) + } + + #[test] + fn data_2() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/data2.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "xxxx3true"); + Ok(()) + } + + #[test] + fn data_3() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/data3.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "${x}3true"); + Ok(()) + } + + #[test] + fn data_4() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/data4.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "yyyyxxxx3true"); + Ok(()) + } + + #[test] + fn import_1() -> Result<(), Box> { + let program = json!({ + "include": "../../examples/tutorial/import.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "Bye!"); + Ok(()) + } +} diff --git a/pdl-live-react/src-tauri/src/pdl/mod.rs b/pdl-live-react/src-tauri/src/pdl/mod.rs index a0c327300..fcdcab28b 100644 --- a/pdl-live-react/src-tauri/src/pdl/mod.rs +++ b/pdl-live-react/src-tauri/src/pdl/mod.rs @@ -1,5 +1,7 @@ pub mod ast; pub mod extract; +pub mod interpreter; +mod interpreter_tests; pub mod pip; pub mod pull; pub mod requirements; diff --git a/pdl-live-react/src-tauri/tauri.conf.json b/pdl-live-react/src-tauri/tauri.conf.json index c8f055057..e35d1fd32 100644 --- a/pdl-live-react/src-tauri/tauri.conf.json +++ b/pdl-live-react/src-tauri/tauri.conf.json @@ -47,6 +47,20 @@ } } }, + "runr": { + "args": [ + { + "name": "source", + "index": 1, + "required": true, + "takesValue": true + }, + { + "name": "debug", + "short": "g" + } + ] + }, "run": { "description": "Run a PDL program", "args": [ diff --git a/pdl-live-react/src-tauri/tests/cli/call-no-args.pdl b/pdl-live-react/src-tauri/tests/cli/call-no-args.pdl new file mode 100644 index 000000000..08ff6012c --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/call-no-args.pdl @@ -0,0 +1,9 @@ +defs: + foo: + function: {} + return: + description: nullary function + text: + - hello world +text: + - call: ${ foo} diff --git a/pdl-live-react/src-tauri/tests/cli/call-with-args.pdl b/pdl-live-react/src-tauri/tests/cli/call-with-args.pdl new file mode 100644 index 000000000..eecaad795 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/call-with-args.pdl @@ -0,0 +1,12 @@ +defs: + foo: + function: + x: int + return: + description: nullary function + text: + - hello world ${x+1} bye +text: + - call: ${ foo } + args: + x: 3 diff --git a/pdl-live-react/src-tauri/tests/cli/code-python.pdl b/pdl-live-react/src-tauri/tests/cli/code-python.pdl new file mode 100644 index 000000000..674e60aff --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/code-python.pdl @@ -0,0 +1,2 @@ +lang: python +code: 'print(''hi ho''); result = {"foo": 3}' diff --git a/pdl-live-react/src-tauri/tests/cli/data1.pdl b/pdl-live-react/src-tauri/tests/cli/data1.pdl new file mode 100644 index 000000000..fbe59f6ce --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/data1.pdl @@ -0,0 +1,11 @@ +lastOf: + - def: x + text: + - xxxx + - def: y + data: + n: 3 + x: ${x} + b: true + - ${y.x~y.n~y.b} + diff --git a/pdl-live-react/src-tauri/tests/cli/data2.pdl b/pdl-live-react/src-tauri/tests/cli/data2.pdl new file mode 100644 index 000000000..d43d30787 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/data2.pdl @@ -0,0 +1,12 @@ +defs: + x: + text: + - xxxx + y: + data: + n: 3 + x: ${x} + b: true +lastOf: + - ${y.x~y.n~y.b} + diff --git a/pdl-live-react/src-tauri/tests/cli/data3.pdl b/pdl-live-react/src-tauri/tests/cli/data3.pdl new file mode 100644 index 000000000..0cfe9bbca --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/data3.pdl @@ -0,0 +1,12 @@ +lastOf: + - def: x + text: + - xxxx + - def: y + raw: true + data: + n: 3 + x: ${x} + b: true + - ${y.x~y.n~y.b} + diff --git a/pdl-live-react/src-tauri/tests/cli/data4.pdl b/pdl-live-react/src-tauri/tests/cli/data4.pdl new file mode 100644 index 000000000..1051eb087 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/data4.pdl @@ -0,0 +1,16 @@ +defs: + x: + description: Outer x + text: + - xxxx + y: + data: + n: 3 + x: ${x} + b: true +lastOf: + - defs: + x: yyyy + description: Inner x + text: + - ${x~y.x~y.n~y.b} diff --git a/pdl-live-react/src-tauri/tests/cli/if1.pdl b/pdl-live-react/src-tauri/tests/cli/if1.pdl new file mode 100644 index 000000000..48ffecdb1 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/if1.pdl @@ -0,0 +1,2 @@ +if: true +then: hi diff --git a/pdl-live-react/src-tauri/tests/cli/if2.pdl b/pdl-live-react/src-tauri/tests/cli/if2.pdl new file mode 100644 index 000000000..ab0cde28e --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/if2.pdl @@ -0,0 +1,5 @@ +defs: + x: 5 +if: ${x!=5} +then: bug +else: good diff --git a/pdl-live-react/src-tauri/tests/cli/include1.pdl b/pdl-live-react/src-tauri/tests/cli/include1.pdl new file mode 100644 index 000000000..bec9cc8d5 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/include1.pdl @@ -0,0 +1 @@ +include: ./call-with-args.pdl \ No newline at end of file diff --git a/pdl-live-react/src-tauri/tests/cli/json-parser-lastOf.pdl b/pdl-live-react/src-tauri/tests/cli/json-parser-lastOf.pdl new file mode 100644 index 000000000..778c3434d --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/json-parser-lastOf.pdl @@ -0,0 +1,6 @@ +lastOf: + - text: + - '{"key": "value"}' + parser: json + def: foo + - ${ foo.key } diff --git a/pdl-live-react/src-tauri/tests/cli/json-parser.pdl b/pdl-live-react/src-tauri/tests/cli/json-parser.pdl new file mode 100644 index 000000000..c5ceea120 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/json-parser.pdl @@ -0,0 +1,6 @@ +text: + - text: + - '{"key": "value"}' + parser: json + def: foo + - ${ foo.key } diff --git a/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl b/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl new file mode 100644 index 000000000..7fbce7342 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl @@ -0,0 +1,7 @@ +model: ollama/granite3.2:2b +input: + array: + - role: system + content: answer as if you live in europe + - role: user + content: what is the fastest animal where i live? diff --git a/pdl-live-react/src-tauri/tests/cli/model-input-string.pdl b/pdl-live-react/src-tauri/tests/cli/model-input-string.pdl new file mode 100644 index 000000000..cb071f72b --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/model-input-string.pdl @@ -0,0 +1,2 @@ +model: ollama/granite3.2:2b +input: what is the fastest animal? \ No newline at end of file diff --git a/pdl-live-react/src-tauri/tests/cli/object1.pdl b/pdl-live-react/src-tauri/tests/cli/object1.pdl new file mode 100644 index 000000000..8d63e0814 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/object1.pdl @@ -0,0 +1,9 @@ +defs: + obj: + object: + a: + text: + - foo +text: + - ${ obj.a } + diff --git a/pdl-live-react/src-tauri/tests/cli/object2.pdl b/pdl-live-react/src-tauri/tests/cli/object2.pdl new file mode 100644 index 000000000..7251cd626 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/object2.pdl @@ -0,0 +1,11 @@ +defs: + obj: + object: + a: + object: + b: + text: + - foo2 +text: + - ${ obj.a.b } + diff --git a/pdl-live-react/src-tauri/tests/cli/read-file.pdl b/pdl-live-react/src-tauri/tests/cli/read-file.pdl new file mode 100644 index 000000000..2986783de --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/read-file.pdl @@ -0,0 +1,5 @@ +text: + - read: ../data/struct.yaml + def: struct + parser: yaml + - ${ struct.a.b } diff --git a/pdl-live-react/src-tauri/tests/cli/read-stdin.pdl b/pdl-live-react/src-tauri/tests/cli/read-stdin.pdl new file mode 100644 index 000000000..209fa0e5d --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/read-stdin.pdl @@ -0,0 +1,3 @@ +text: + - message: How are you? + read: null diff --git a/pdl-live-react/src-tauri/tests/cli/repeat1.pdl b/pdl-live-react/src-tauri/tests/cli/repeat1.pdl new file mode 100644 index 000000000..0135f9bc6 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/repeat1.pdl @@ -0,0 +1,5 @@ +for: + x: [1,2,3] +repeat: + text: + - "${ x + 1 }" diff --git a/pdl-live-react/src-tauri/tests/cli/repeat2.pdl b/pdl-live-react/src-tauri/tests/cli/repeat2.pdl new file mode 100644 index 000000000..6542d07ee --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/repeat2.pdl @@ -0,0 +1,6 @@ +for: + x: [1,2,3] + y: ["a","b","c"] +repeat: + text: + - "${ x ~ y }" diff --git a/pdl-live-react/src-tauri/tests/cli/repeat3.pdl b/pdl-live-react/src-tauri/tests/cli/repeat3.pdl new file mode 100644 index 000000000..42d4e7cf7 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/repeat3.pdl @@ -0,0 +1,9 @@ +for: + x: + - z: 4 + - z: 5 + - z: 6 + y: ["a","b","c"] +repeat: + text: + - "${ x.z ~ y }" diff --git a/pdl-live-react/src-tauri/tests/data/foo.txt b/pdl-live-react/src-tauri/tests/data/foo.txt new file mode 100644 index 000000000..6fed0195d --- /dev/null +++ b/pdl-live-react/src-tauri/tests/data/foo.txt @@ -0,0 +1 @@ +this should be foo diff --git a/pdl-live-react/src-tauri/tests/data/struct.yaml b/pdl-live-react/src-tauri/tests/data/struct.yaml new file mode 100644 index 000000000..2697412bf --- /dev/null +++ b/pdl-live-react/src-tauri/tests/data/struct.yaml @@ -0,0 +1,2 @@ +a: + b: 3 diff --git a/pdl-live-react/src/page/Run.css b/pdl-live-react/src/page/Run.css new file mode 100644 index 000000000..1d9fbceac --- /dev/null +++ b/pdl-live-react/src/page/Run.css @@ -0,0 +1 @@ +@import "../view/term/RunTerminal.css"; diff --git a/pdl-live-react/src/page/Run.tsx b/pdl-live-react/src/page/Run.tsx new file mode 100644 index 000000000..b59bad8a9 --- /dev/null +++ b/pdl-live-react/src/page/Run.tsx @@ -0,0 +1,145 @@ +import { createRef, useEffect, useState } from "react" +import { invoke } from "@tauri-apps/api/core" +import { Terminal } from "@xterm/xterm" +import { FitAddon } from "@xterm/addon-fit" +import { ClipboardAddon } from "@xterm/addon-clipboard" +import { CodeEditor, Language } from "@patternfly/react-code-editor" +import { + Button, + Card, + CardBody, + CardHeader, + CardTitle, + PageSection, + Toolbar, + ToolbarContent, + ToolbarItem, +} from "@patternfly/react-core" + +import Page from "./Page" +import "./Run.css" + +const initialInput = `text: + - text: + - '{"key": "value"}' + parser: json + def: foo + - \${ foo.key }` +export default function Run() { + const [running, setRunning] = useState(false) + const [input, setInput] = useState(initialInput) + const [_error, setError] = useState(false) + + const xtermRef = createRef() + const [term, setTerm] = useState(null) + + // Why a two-stage useEffect? Otherwise: cannot read properties of + // undefined (reading 'dimensions') + // See https://stackoverflow.com/a/78116690/5270773 + useEffect(() => { + const term = new Terminal({ + fontFamily: + '"Red Hat Mono", RedHatMono, "Courier New", Courier, monospace', + convertEol: true, + }) + setTerm(term) + return () => { + if (term) { + term.dispose() + } + } + }, []) + + useEffect(() => { + if (term && xtermRef.current) { + const fitAddon = new FitAddon() + term.loadAddon(fitAddon) + const clipboardAddon = new ClipboardAddon() + term.loadAddon(clipboardAddon) + + term.open(xtermRef.current) + fitAddon.fit() + // term.focus() + + // for debugging: + // term.writeln(`Running ${cmd} ${args.join(" ")}`) + } + }, [term, xtermRef]) + + const run = async () => { + try { + setRunning(true) + term?.reset() + const result = await invoke("run_pdl_program", { + program: input, + debug: false, + }) + term?.write(String(result)) + console.error(true) + } catch (err) { + term?.write(String(err)) + setError(true) + } finally { + setRunning(false) + } + } + + return ( + + + + + + + + + + + + + + + Program + + +
+ { + editor.layout() + }} + options={{ fontSize: 16 }} + aria-label="text area to provide PDL program source" + code={initialInput} + isDarkTheme + isFullHeight + language={Language.yaml} + onChange={(value) => { + setError(false) + setInput(value) + }} + /> +
+
+
+ + + + Output + + +
+ + + + + ) +} diff --git a/pdl-live-react/src/page/welcome/Links.tsx b/pdl-live-react/src/page/welcome/Links.tsx index 0046778d2..083242895 100644 --- a/pdl-live-react/src/page/welcome/Links.tsx +++ b/pdl-live-react/src/page/welcome/Links.tsx @@ -1,3 +1,4 @@ +import { Link } from "react-router" import { Button, Flex } from "@patternfly/react-core" import ExternalLinkSquareAltIcon from "@patternfly/react-icons/dist/esm/icons/external-link-square-alt-icon" @@ -29,6 +30,9 @@ export default function Links() { GitHub + ) } diff --git a/pdl-live-react/src/routes/PdlRoutes.tsx b/pdl-live-react/src/routes/PdlRoutes.tsx index fce6eed79..336a8dc4e 100644 --- a/pdl-live-react/src/routes/PdlRoutes.tsx +++ b/pdl-live-react/src/routes/PdlRoutes.tsx @@ -6,6 +6,7 @@ import MyTraces from "../page/MyTracesPage" import About from "../page/About" import Local from "../page/Local" import MyTrace from "../page/MyTrace" +import Run from "../page/Run" import Welcome from "../page/welcome/Welcome" import Uploader from "../page/Uploader" import ErrorBoundary from "../page/ErrorBoundary" @@ -25,6 +26,7 @@ export default function PdlRoutes() { } /> } /> } /> + } /> {demos.map((demo, idx) => (