diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index fe3997c..c72862e 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -28,6 +28,24 @@ jobs: ruby-version: "2.6" bundler-cache: true + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Build playground + run: | + npm ci --ignore-scripts --no-audit --no-fund + npm run build:playground + npm run test:playground + - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/.gitignore b/.gitignore index 23cf3de..b1b3066 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ _site/ .bundle/ vendor/ Gemfile.lock +node_modules/ +.playground-build/ +playground/runtime/ diff --git a/README.md b/README.md index 22a3c07..68cdb70 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,32 @@ bundle exec jekyll build ## Publishing The site is configured as a GitHub Pages project site at [https://liquid-java.github.io/liquidjava-docs/](https://liquid-java.github.io/liquidjava-docs/). + +## Browser playground + +The `/playground/` page runs LiquidJava in a browser worker with CheerpJ 4.3 and the official Z3 WebAssembly package. Code is checked locally. Stop terminates the worker, including a running solver, and the next check creates a fresh runtime. + +Build the runtime before building Jekyll: + +```bash +npm ci --ignore-scripts --no-audit --no-fund +npm run build:playground +npm run test:playground +bundle exec jekyll build +``` + +The build requires JDK 17, Python 3, Node.js, and access to Maven Central. It recompiles the published verifier 0.0.35 and annotation API 0.0.7 sources for Java 17 without changing them, packages their dependencies, and adds the docs-owned runner and Z3 loader. The standard-library classpath comes from the build JDK's `java.base.jmod`. Generated runtime files are ignored by Git and included in the Pages artifact. The normal Pages workflow builds everything automatically. + +The browser package also adapts Spoon 10.4.2's query initialization: when CheerpJ supplies an empty cast-exception stack trace, it selects Spoon's existing exotic-JVM query mode. The pinned source is downloaded and the adaptation checked during the build. This change is limited to the docs' generated dependency; the verifier repository and published sources remain unchanged. + +For local testing, after building the runtime: + +```bash +npm run serve:playground +``` + +Open `http://127.0.0.1:8770/playground/`. This uses a static server with HTTP range support, which CheerpJ needs when loading JARs. Ordinary `jekyll serve` does not supply the required range responses. + +`playground/isolation.js` is a service worker scoped to `/playground/`. It adds the cross-origin isolation response headers Z3 requires on static hosting, including GitHub Pages. The first visit registers it and reloads once. Other docs pages are outside its scope. CheerpJ loads from its official CDN inside the verification worker; its runtime is not copied into this repository. + +The playground checks a single Java file using Java 8 source syntax, bundled annotations and core standard-library types from `java.base`. External dependencies and other Java modules are not available. Z3 4.16 runs behind the verifier's 4.8.17 Java API; browser integration tests must be repeated when updating either version. Unsupported native operations and unknown solver results fail explicitly instead of reporting success. This integration is kept entirely in `liquidjava-docs`. diff --git a/_config.yml b/_config.yml index 8fdb797..6d27a9e 100644 --- a/_config.yml +++ b/_config.yml @@ -46,6 +46,11 @@ defaults: image: /assets/images/Liquid2.png exclude: + - node_modules + - scripts + - package.json + - package-lock.json + - .playground-build - .DS_Store - vendor - Gemfile diff --git a/_includes/nav_footer_custom.html b/_includes/nav_footer_custom.html index fc59d87..155ab07 100644 --- a/_includes/nav_footer_custom.html +++ b/_includes/nav_footer_custom.html @@ -1,4 +1,4 @@ -

- Help improve this documentation by contributing on +

diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss index f5bd9de..24b58e6 100644 --- a/_sass/custom/custom.scss +++ b/_sass/custom/custom.scss @@ -28,6 +28,10 @@ $sidebar-color; } +.site-footer { + color: $body-text-color; +} + .main-content a { color: #41586f; text-decoration-thickness: 0.08em; @@ -43,7 +47,9 @@ } .main-content pre.highlight, -.main-content div.highlighter-rouge { +.main-content div.highlighter-rouge, +.main-content #lj-editor, +.main-content .lj-output { overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; @@ -259,40 +265,30 @@ margin: 1.5rem 0 0; } -.home-button { +.main-content .home-button { display: inline-flex; align-items: center; justify-content: center; - min-height: 2.9rem; - padding: 0.8rem 1.1rem; - border-radius: 999px; - border: 1px solid transparent; - font-weight: 700; - line-height: 1.1; - text-decoration: none !important; - transition: transform 120ms ease, box-shadow 120ms ease, background-color 120ms ease, border-color 120ms ease; - box-shadow: 0 14px 26px rgba(51, 80, 106, 0.22); -} - -.home-button:hover, -.home-button:focus { - transform: translateY(-1px); -} - -.home-button:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(65, 88, 111, 0.16); + gap: 0.65rem; + padding: 0.7rem 1.1rem; + border-radius: 0.5rem; + color: #fff; + background: #334e68; + font-weight: 400; + line-height: 1.5; + text-decoration: none; + box-shadow: 0 2px 4px rgba(23, 26, 31, 0.12); } -.home-button.primary { +.main-content .home-button:hover { color: #fff; - background: linear-gradient(135deg, #63c093 0%, #4e98a8 50%, #4a80b0 100%); - box-shadow: 0 18px 10px rgba(51, 80, 106, 0.22); + background: #253b50; + text-decoration: none; } -.home-button.secondary { - color: #2a3642; - border-color: rgba(42, 54, 66, 0.15); +.main-content .home-button:focus-visible { + outline: 3px solid #5f7ea1; + outline-offset: 3px; } .card-panel { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..aed9e06 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1329 @@ +{ + "name": "liquidjava-docs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "liquidjava-docs", + "dependencies": { + "@codemirror/autocomplete": "6.20.3", + "@codemirror/commands": "6.11.1", + "@codemirror/lang-java": "6.0.2", + "@codemirror/language": "6.12.4", + "@codemirror/lint": "6.8.5", + "@codemirror/search": "6.7.2", + "@codemirror/state": "6.7.6", + "@codemirror/view": "6.43.13", + "@lezer/highlight": "1.2.5", + "ansi_up": "6.0.6" + }, + "devDependencies": { + "esbuild": "0.25.10", + "http-server": "14.1.1", + "z3-solver": "4.16.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz", + "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.13", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.13.tgz", + "integrity": "sha512-sihaFrUzAsYBQsL9J2t69y8nfMQGwcYmggAZsk+kjPbjYZMyuf2hU8tUNTZ+P+isb6XRr8JE22TZlJxBoVdH1A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.3.tgz", + "integrity": "sha512-H0iErY4e43LpXbYDyBci5W4v/RwTgGV3YzYOlJPiGZ8RY8w51kVE+xLn1tg3Z6UmSfyETvtsUK3r7YsgDQEI7Q==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/java": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.4.tgz", + "integrity": "sha512-gQylJ2xHV2xXsVqtnAcJrcROvLZJaQiC0tR//+q0k961BxqvyYttusPkJ8d9T1fPDJKHJW2etUH8XbIn9jhVUg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, + "node_modules/ansi_up": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.6.tgz", + "integrity": "sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/style-mod": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/z3-solver": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/z3-solver/-/z3-solver-4.16.0.tgz", + "integrity": "sha512-vWT+I5Yz0dXtseLVZsnkwAIrRmbxw0rCX5a6Xglc6n1CyPESNMnHB3iAWh7XTQQHZRlpWYnKJXTJ0ahTTNHZEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-mutex": "^0.3.2" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e061e57 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "liquidjava-docs", + "private": true, + "scripts": { + "build:playground": "python3 scripts/playground/build.py && node scripts/playground/build.mjs", + "test:playground": "node --test scripts/playground/*.test.mjs", + "serve:playground": "bundle exec jekyll build --baseurl \"\" && http-server _site -a 127.0.0.1 -p 8770 -c-1" + }, + "devDependencies": { + "esbuild": "0.25.10", + "http-server": "14.1.1", + "z3-solver": "4.16.0" + }, + "dependencies": { + "@codemirror/autocomplete": "6.20.3", + "@codemirror/commands": "6.11.1", + "@codemirror/lang-java": "6.0.2", + "@codemirror/language": "6.12.4", + "@codemirror/lint": "6.8.5", + "@codemirror/search": "6.7.2", + "@codemirror/state": "6.7.6", + "@codemirror/view": "6.43.13", + "@lezer/highlight": "1.2.5", + "ansi_up": "6.0.6" + } +} diff --git a/pages/index.md b/pages/index.md index 4375c71..32d1cc4 100644 --- a/pages/index.md +++ b/pages/index.md @@ -10,6 +10,7 @@ description: Documentation for LiquidJava, a lightweight verification system for

Extending Java with Liquid Types

LiquidJava is an additional type system for Java that uses liquid types to express constraints programs must follow, helping catch more bugs before they run.

+

Try LiquidJava

LiquidJava banner diff --git a/playground/editor.css b/playground/editor.css new file mode 100644 index 0000000..f49fb8a --- /dev/null +++ b/playground/editor.css @@ -0,0 +1,47 @@ +.lj-playground { margin: 1.5rem 0; } +.lj-toolbar { display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 1rem; } +.lj-example-picker { position: relative; display: flex; flex-direction: column; flex: 0 1 15rem; gap: .4rem; min-width: 0; } +.lj-example-picker::after { content: ''; position: absolute; right: 1rem; bottom: 1.1rem; width: .4rem; height: .4rem; border-right: 2px solid #5f7ea1; border-bottom: 2px solid #5f7ea1; transform: rotate(45deg); pointer-events: none; } +.lj-toolbar label { color: #555c65; font-size: .8rem; font-weight: 400; } +.lj-toolbar select, +.lj-toolbar button { min-height: 2.5rem; border: 1px solid #cbd2d9; border-radius: .5rem; font: inherit; font-size: .875rem; font-weight: 400; line-height: 1.4; } +.lj-toolbar select { appearance: none; width: 100%; padding: .6rem 2.5rem .6rem .85rem; color: #2a2f36; background: #fff; border-color: #c7d7ea; box-shadow: 0 1px 2px rgba(23, 26, 31, .05); cursor: pointer; } +.lj-toolbar select:hover { border-color: #5f7ea1; background: #fafcfe; } +.lj-examples { display: flex; align-items: flex-end; flex: 1 1 25rem; gap: .5rem; min-width: 0; } +.lj-toolbar button { padding: .5rem .9rem; color: #334e68; background: transparent; box-shadow: none; cursor: pointer; } +.lj-toolbar button:hover:not(:disabled) { background: #e5eef7; border-color: #5f7ea1; } +.lj-toolbar #lj-verify, +.lj-toolbar #lj-reset { min-width: 5rem; padding: .6rem .9rem; color: #fff; background: #334e68; border-color: #334e68; white-space: nowrap; flex-shrink: 0; } +.lj-toolbar #lj-verify:hover:not(:disabled), +.lj-toolbar #lj-reset:hover { background: #253b50; border-color: #253b50; } +.lj-toolbar #lj-verify[data-state="busy"] { background: #c62828; border-color: #c62828; } +.lj-toolbar #lj-verify[data-state="busy"]:hover { background: #a61f1f; border-color: #a61f1f; } +.lj-toolbar button:disabled { color: #8a929b; background: #efefec; border-color: #e4e4e7; cursor: default; } +.lj-toolbar #lj-verify:disabled { color: #fff; background: #7f90a1; border-color: #7f90a1; } +.lj-toolbar select:focus-visible, +.lj-toolbar button:focus-visible { outline: 2px solid #5f7ea1; outline-offset: 3px; } +#lj-editor { margin: 1rem 0; overflow: hidden; } +.lj-playground .cm-editor { font-size: .75em; border-radius: inherit; overflow: hidden; } +.lj-playground .cm-scroller { min-height: 200px; max-height: 560px; overflow: auto; font-family: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace; line-height: 1.6; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, .72) rgba(255, 255, 255, .16); } +.lj-playground .cm-focused { outline: 2px solid #7dd3fc; outline-offset: -2px; } +.lj-output { color: #e6edf7; } +#lj-status { margin: 0; padding: 1rem 1.15rem; font-family: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace; font-size: .75em; line-height: 1.6; } +#lj-status.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } +#lj-status[data-state="success"] { color: #a7f3c1; } +#lj-status[data-state="warning"] { color: #f6c177; } +#lj-status:not(.sr-only) + #lj-results:not(:empty) { border-top: 1px solid #ffffff1a; } +.main-content #lj-results pre { margin: 0; } +.main-content #lj-results a { color: #e6edf7; text-decoration: underline; text-underline-offset: .2em; } +.main-content #lj-results a:hover { color: #e6edf7; } +.main-content #lj-results a:focus-visible { outline: 2px solid #7dd3fc; outline-offset: 3px; } +#lj-results .ansi-black-fg { color: #111827; } +#lj-results .ansi-bright-black-fg { color: #9ca3af; } +#lj-status[data-state="error"], +#lj-status[data-state="failure"], +#lj-results :is(.ansi-red-fg, .ansi-bright-red-fg) { color: #f29e9e; } +#lj-results :is(.ansi-green-fg, .ansi-bright-green-fg) { color: #a7f3c1; } +#lj-results :is(.ansi-yellow-fg, .ansi-bright-yellow-fg) { color: #f6c177; } +#lj-results :is(.ansi-blue-fg, .ansi-bright-blue-fg) { color: #93b4f4; } +#lj-results :is(.ansi-magenta-fg, .ansi-bright-magenta-fg) { color: #c4b5fd; } +#lj-results :is(.ansi-cyan-fg, .ansi-bright-cyan-fg) { color: #7dd3fc; } +#lj-results :is(.ansi-white-fg, .ansi-bright-white-fg) { color: #e6edf7; } diff --git a/playground/index.md b/playground/index.md new file mode 100644 index 0000000..a979057 --- /dev/null +++ b/playground/index.md @@ -0,0 +1,39 @@ +--- +title: Playground +nav_order: 8 +permalink: /playground/ +has_toc: false +description: Try LiquidJava refinements and typestates directly in your browser. +--- + +# Playground + +Run the LiquidJava verification directly in your browser. Edit an example and select **Verify**. + + +
+
+
+
+ + +
+ +
+ +
+
+ + +
+ + diff --git a/playground/isolation.js b/playground/isolation.js new file mode 100644 index 0000000..aef32c9 --- /dev/null +++ b/playground/isolation.js @@ -0,0 +1,14 @@ +// GitHub Pages cannot set these response headers; isolate only the playground. +self.addEventListener('install', () => self.skipWaiting()); +self.addEventListener('activate', event => event.waitUntil(self.clients.claim())); +self.addEventListener('fetch', event => { + if (new URL(event.request.url).origin !== self.location.origin) return; + event.respondWith((async () => { + const response = await fetch(event.request); + if (response.type === 'opaque') return response; + const headers = new Headers(response.headers); + headers.set('Cross-Origin-Opener-Policy', 'same-origin'); + headers.set('Cross-Origin-Embedder-Policy', 'require-corp'); + return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); + })()); +}); diff --git a/scripts/playground/adapter.mjs b/scripts/playground/adapter.mjs new file mode 100644 index 0000000..e0875ab --- /dev/null +++ b/scripts/playground/adapter.mjs @@ -0,0 +1,34 @@ +export function createNatives(Z3, methods) { + const natives = { Java_com_microsoft_z3_Native_setInternalErrorHandler() {} }; + for (const { name, types, api } of methods) { + natives[`Java_com_microsoft_z3_Native_${name}`] = async (_lib, ...args) => { + const input = args.map((value, i) => types[i] === 'long' ? Number(value) : value); + // numeric int64 arguments are values, whereas other longs are WASM pointers + if (api === 'mk_int64' || api === 'mk_unsigned_int64') input[1] = BigInt(args[1]); + if (api === 'model_eval') { + const value = await Z3.model_eval(...input.slice(0, 4)); + if (value === null) return false; + args[4].value = value; + return true; + } + if (types.some(type => type.endsWith('Ptr'))) { + throw new Error(`Unsupported Z3 output parameter: ${api}`); + } + // the JS binding derives array counts from the array itself + for (let i = types.length - 1; i >= 1; i--) { + if (types[i] === 'long[]' && types[i - 1] === 'int') { + input[i] = Array.from(input[i], Number); + input.splice(i - 1, 1); + } + } + if (typeof Z3[api] !== 'function') throw new Error(`Unsupported Z3 operation: ${api}`); + const result = await Z3[api](...input); + // the desktop verifier treats unknown as success; the playground must not + if ((api === 'solver_check' || api === 'solver_check_assumptions') && result === 0) { + throw new Error('Z3 could not determine whether this refinement holds.'); + } + return result; + }; + } + return natives; +} diff --git a/scripts/playground/adapter.test.mjs b/scripts/playground/adapter.test.mjs new file mode 100644 index 0000000..ab41234 --- /dev/null +++ b/scripts/playground/adapter.test.mjs @@ -0,0 +1,51 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import z3 from 'z3-solver'; +const { init } = z3; +import { createNatives } from './adapter.mjs'; + +const methods = JSON.parse(await readFile(new URL('../../playground/runtime/native-methods.json', import.meta.url))); + +test('existing Java ABI checks bounds, evaluates models and passes counted arrays to WASM', async () => { + const { Z3, em } = await init(); + const natives = createNatives(Z3, methods); + const call = (name, ...args) => natives[`Java_com_microsoft_z3_Native_INTERNAL${name}`](null, ...args); + const ctx = await call('mkContext', 0); + try { + const sort = await call('mkIntSort', ctx); + const name = await call('mkStringSymbol', ctx, 'x'); + const x = await call('mkConst', ctx, name, sort); + const zero = await call('mkInt', ctx, 0, sort); + const two = await call('mkInt', ctx, 2, sort); + const large = await call('mkInt64', ctx, 9007199254740993n, sort); + assert.equal(await call('getNumeralString', ctx, large), '9007199254740993'); + const lower = await call('mkGt', ctx, x, zero); + const upper = await call('mkLt', ctx, x, two); + const bounds = await call('mkAnd', ctx, 2, new BigInt64Array([BigInt(lower), BigInt(upper)])); + const solver = await call('mkSolver', ctx); + await call('solverIncRef', ctx, solver); + await call('solverAssert', ctx, solver, bounds); + assert.equal(await call('solverCheck', ctx, solver), 1); + const model = await call('solverGetModel', ctx, solver); + const output = { value: 0 }; + assert.equal(await call('modelEval', ctx, model, x, true, output), true); + assert.equal(await call('getNumeralString', ctx, output.value), '1'); + await call('solverAssert', ctx, solver, await call('mkLt', ctx, x, zero)); + assert.equal(await call('solverCheck', ctx, solver), -1); + await call('solverDecRef', ctx, solver); + } finally { + await call('delContext', ctx); + em.PThread.terminateAllThreads(); + } +}); + +test('an unknown solver result cannot become a successful verification', async () => { + const natives = createNatives({ solver_check: async () => 0 }, methods); + await assert.rejects(natives.Java_com_microsoft_z3_Native_INTERNALsolverCheck(null, 1, 2), /could not determine/); +}); + +test('unimplemented ABI operations fail explicitly', async () => { + const natives = createNatives({}, methods); + await assert.rejects(natives.Java_com_microsoft_z3_Native_INTERNALgetNumeralInt(null, 1, 2, { value: 0 }), /Unsupported Z3 output parameter/); +}); diff --git a/scripts/playground/build.mjs b/scripts/playground/build.mjs new file mode 100644 index 0000000..298f3f7 --- /dev/null +++ b/scripts/playground/build.mjs @@ -0,0 +1,8 @@ +import { build } from 'esbuild'; +import { copyFile } from 'node:fs/promises'; +const output = 'playground/runtime'; +await build({ entryPoints: ['scripts/playground/worker.js'], bundle: true, format: 'iife', platform: 'browser', outfile: `${output}/worker.js` }); +await build({ entryPoints: ['scripts/playground/editor.js'], bundle: true, format: 'esm', platform: 'browser', outfile: `${output}/editor.js` }); +for (const file of ['z3-built.js', 'z3-built.wasm']) { + await copyFile(`node_modules/z3-solver/build/${file}`, `${output}/${file}`); +} diff --git a/scripts/playground/build.py b/scripts/playground/build.py new file mode 100644 index 0000000..d4ea394 --- /dev/null +++ b/scripts/playground/build.py @@ -0,0 +1,97 @@ +"""Build the browser runner from unmodified published LiquidJava sources.""" +import json +import re +import subprocess +import shutil +import urllib.request +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +WORK = ROOT / '.playground-build' +OUTPUT = ROOT / 'playground/runtime' +VERIFIER = '0.0.35' +API = '0.0.7' +Z3 = '4.8.17' +SPOON = '10.4.2' + + +def artifact(group, name, version, classifier=''): + filename = f'{name}-{version}{classifier}.jar' + target = WORK / filename + if not target.exists(): + url = f'https://repo.maven.apache.org/maven2/{group.replace(".", "/")}/{name}/{version}/{filename}' + print(f'Downloading {filename}', flush=True) + urllib.request.urlretrieve(url, target) + return target + + +def main(): + properties = subprocess.run(['java', '-XshowSettings:properties', '-version'], capture_output=True, text=True, check=True).stderr + java_home = Path(re.search(r'java.home = (.+)', properties).group(1).strip()) + if not re.search(r'java.version = 17[.\s]', properties): + raise RuntimeError('Build the playground with JDK 17 (set JAVA_HOME and PATH)') + WORK.mkdir(exist_ok=True) + OUTPUT.mkdir(parents=True, exist_ok=True) + verifier = artifact('io.github.liquid-java', 'liquidjava-verifier', VERIFIER) + sources = artifact('io.github.liquid-java', 'liquidjava-verifier', VERIFIER, '-sources') + annotations = artifact('io.github.liquid-java', 'liquidjava-api', API, '-sources') + z3_sources = artifact('tools.aqua', 'z3-turnkey', Z3, '-sources') + spoon_sources = artifact('fr.inria.gforge.spoon', 'spoon-core', SPOON, '-sources') + source_dir = WORK / 'sources' + if source_dir.exists(): + shutil.rmtree(source_dir) + source_dir.mkdir() + for source in [sources, annotations]: + with zipfile.ZipFile(source) as jar: + for item in jar.namelist(): + if item.endswith('.java'): + path = source_dir / item + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(jar.read(item)) + # CheerpJ casts can have empty stack traces. Select Spoon's existing + # exotic-JVM query mode rather than failing its static initialization. + query_path = 'spoon/reflect/visitor/chain/CtQueryImpl.java' + with zipfile.ZipFile(spoon_sources) as jar: + query = jar.read(query_path).decode() + original = 'StackTraceElement[] stack = e.getStackTrace();' + if query.count(original) != 1: + raise RuntimeError('Spoon query adaptation no longer matches its source') + query = query.replace(original, original + '\n\t\t\tif (stack.length == 0) return -1;') + path = source_dir / query_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(query) + classes = WORK / 'classes' + if classes.exists(): + shutil.rmtree(classes) + classes.mkdir() + files = list(source_dir.rglob('*.java')) + list((ROOT / 'scripts/playground/java').rglob('*.java')) + args = WORK / 'javac.args' + args.write_text('\n'.join('"' + str(file) + '"' for file in files)) + subprocess.run(['javac', '--release', '17', '-cp', str(verifier), '-d', str(classes), '@' + str(args)], check=True) + with zipfile.ZipFile(verifier) as jar, zipfile.ZipFile(OUTPUT / 'liquidjava.jar', 'w', zipfile.ZIP_DEFLATED) as out: + for item in jar.infolist(): + # replace compiled classes, keep the remaining dependencies and resources + if (classes / item.filename).is_file() or item.filename.endswith(('.dll', '.so', '.dylib')): + continue + if item.filename.startswith('META-INF/versions/') or item.filename.endswith(('.SF', '.RSA', '.DSA')): + continue + out.writestr(item.filename, jar.read(item.filename)) + for file in classes.rglob('*.class'): + out.writestr(file.relative_to(classes).as_posix(), file.read_bytes()) + with zipfile.ZipFile(java_home / 'jmods/java.base.jmod') as jar, zipfile.ZipFile(OUTPUT / 'java-base.jar', 'w', zipfile.ZIP_DEFLATED) as out: + for name in jar.namelist(): + if name.startswith('classes/') and name.endswith('.class') and name != 'classes/module-info.class': + out.writestr(name[len('classes/'):], jar.read(name)) + with zipfile.ZipFile(z3_sources) as jar: + native = jar.read('com/microsoft/z3/Native.java').decode() + methods = [] + for name, params in re.findall(r'native\s+\w+\s+(INTERNAL\w+)\(([^)]*)\)', native): + types = [param.strip().split()[0] for param in params.split(',') if param.strip()] + api = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name[len('INTERNAL'):]).lower() + methods.append(dict(name=name, types=types, api=api)) + (OUTPUT / 'native-methods.json').write_text(json.dumps(methods)) + + +if __name__ == '__main__': + main() diff --git a/scripts/playground/diagnostics.mjs b/scripts/playground/diagnostics.mjs new file mode 100644 index 0000000..358de4f --- /dev/null +++ b/scripts/playground/diagnostics.mjs @@ -0,0 +1,19 @@ +import { AnsiUp } from 'ansi_up'; + +function shortenLocations(output) { + return output.replace(/^\/files\/playground\/(?=[^/\r\n]+:\d+)/gm, '/playground/'); +} + +export function editorDiagnostic(issue, length) { + const { from, to } = issue; + if (!Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to <= from || to > length) return null; + return { from, to, severity: issue.severity, message: issue.output === undefined ? undefined : shortenLocations(issue.output).replace(/\u001b\[[0-9;]*m/g, '') }; +} + +export function diagnosticHtml(output) { + const ansi = new AnsiUp(); + ansi.use_classes = true; + return ansi.ansi_to_html(shortenLocations(output).replace(/^(?:\r?\n)+/, '')) + .replace(/^(\/playground\/[^<\r\n]+:(\d+))(?=\r?\n|$|<\/span>)/gm, + '$1'); +} diff --git a/scripts/playground/diagnostics.test.mjs b/scripts/playground/diagnostics.test.mjs new file mode 100644 index 0000000..e6d9146 --- /dev/null +++ b/scripts/playground/diagnostics.test.mjs @@ -0,0 +1,65 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { diagnosticHtml, editorDiagnostic } from './diagnostics.mjs'; + +test('make diagnostic and declaration locations editor links without linking source excerpts', () => { + const html = diagnosticHtml('Error\n6 | String path = "/playground/Example.java:99";\n\n/files/playground/Example.java:6\u001b[0m\n\n--> Refinement declared here:\n/files/playground/Example.java:5\u001b[0m\n'); + assert.match(html, /\/playground\/Example.java:6<\/a>/); + assert.match(html, /\/playground\/Example.java:5<\/a>/); + assert.equal((html.match(/ { + const diagnostic = '\u001b[31mError\u001b[0m\n6 | code\n | ^^^\n\nExample.java:6\n'; + assert.equal(diagnosticHtml('\n\n' + diagnostic), diagnosticHtml(diagnostic)); +}); + +test('show the playground path, filename and line in diagnostic locations', () => { + const output = '\u001b[31mError\u001b[0m\n\n/files/playground/Example.java:6\u001b[0m\n\n--> Refinement declared here:\n/files/playground/Example.java:5\u001b[0m\n'; + const expected = output.replaceAll('/files/playground/', '/playground/'); + assert.equal(diagnosticHtml(output), diagnosticHtml(expected)); + assert.equal(editorDiagnostic({ from: 0, to: 1, output }, 1).message, + expected.replace(/\u001b\[[0-9;]*m/g, '')); +}); + +test('render CLI colors and bold with resets and escaped source text', () => { + const html = diagnosticHtml('\u001b[1;31mRefinement Error\u001b[0m: \n\u001b[38;5;208m^^^\u001b[0m\n'); + assert.match(html, /font-weight:bold/); + assert.match(html, /ansi-red-fg/); + assert.match(html, /color:rgb\(/); + assert.match(html, /rgb\(255,135,0\)/); + assert.match(html, /<script>/); + assert.doesNotMatch(html, /