diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..4a3f850 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,43 @@ +on: + push: + pull_request: + +name: CI +jobs: + + build: + runs-on: ubuntu-latest + steps: + - name: Check out source code + uses: actions/checkout@v4 + - name: Install and build + run: | + npm ci + npm run build + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + deploy: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - name: Disable Jekyll + run: | + touch dist/.nojekyll + - name: Publish artifact + if: "${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' }}" + uses: JamesIves/github-pages-deploy-action@releases/v4 + with: + folder: dist/ diff --git a/build.mjs b/build.mjs new file mode 100644 index 0000000..c7a3e2f --- /dev/null +++ b/build.mjs @@ -0,0 +1,55 @@ +import * as process from 'node:process'; +import * as esbuild from 'esbuild'; +import metaUrlPlugin from '@chialab/esbuild-plugin-meta-url'; + +const mode = (process.argv[2] ?? 'build'); +const options = { + logLevel: 'info', + plugins: [metaUrlPlugin()], + bundle: true, + loader: { + '.html': 'copy', + '.svg': 'dataurl', + '.ttf': 'file', + '.woff': 'file', + '.woff2': 'file', + '.json': 'file', + '.wasm': 'file', + '.asm.wasm': 'copy', + '.zip': 'file', + }, + external: [ + 'fs/promises', // @yowasp/yosys + 'node-fetch', // pyodide + ], + define: { + 'globalThis.IS_PRODUCTION': (mode === 'minify' ? 'true' : 'false'), + }, + target: 'es2021', + format: 'esm', + sourcemap: 'linked', + minify: (mode === 'minify'), + outdir: 'dist', + entryPoints: { + 'index': './src/index.html', + 'app': './src/app.tsx', + 'app.worker': './src/worker.ts', + 'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js', + 'pyodide.asm': 'pyodide/pyodide.asm.wasm', + }, +}; + +if (mode === 'build' || mode === 'minify') { + await esbuild.build(options); +} else if (mode === 'watch') { + const context = await esbuild.context(options); + await context.watch(); +} else if (mode === 'serve') { + const context = await esbuild.context(options); + await context.rebuild(); + await context.watch(); + // Specifying `servedir` is necessary for files built by meta URL plugin to be accessible. + await context.serve({ servedir: 'dist' }); +} else { + console.error(`Usage: ${process.argv0} [build|watch|serve|minify]`); +} diff --git a/package-lock.json b/package-lock.json index 3c49554..9cd87ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,19 @@ "name": "try-amaranth", "version": "0.0.0", "license": "BSD-2-Clause", - "dependencies": { + "devDependencies": { + "@chialab/esbuild-plugin-meta-url": "^0.18.0", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@fontsource/inter": "^5.0.16", + "@mui/icons-material": "^5.15.10", "@mui/joy": "^5.0.0-beta.28", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", + "@yowasp/yosys": "^0.39.37-dev.676", "esbuild": "^0.20.0", + "monaco-editor": "^0.46.0", + "pyodide": "^0.25.0", "react": "^18.0.0", "react-dom": "^18.0.0" } @@ -24,6 +29,7 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -36,6 +42,7 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, "dependencies": { "@babel/types": "^7.22.15" }, @@ -47,6 +54,7 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -55,6 +63,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -63,6 +72,7 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -76,6 +86,7 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -87,6 +98,7 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -96,10 +108,60 @@ "node": ">=6.9.0" } }, + "node_modules/@chialab/esbuild-plugin-meta-url": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.18.0.tgz", + "integrity": "sha512-4O6epI7sDfS+tQz0KEutKlpfe97u5//qz7PC25F9CCptzcyB75F023IH8+48OmiF2JeXuqHDYI3/T2jBpG+HmA==", + "dev": true, + "dependencies": { + "@chialab/esbuild-rna": "^0.18.0", + "@chialab/estransform": "^0.18.0", + "@chialab/node-resolve": "^0.18.0", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@chialab/esbuild-rna": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.18.0.tgz", + "integrity": "sha512-7UdivnoeYFcMMKTcM2R8EKSJsRRnVhjuS79tLtkBmcZl13ZeGoLAUxA/fLx2Godr3yG7dhyr4Fj0QfJeVjNPmQ==", + "dev": true, + "dependencies": { + "@chialab/estransform": "^0.18.0", + "@chialab/node-resolve": "^0.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@chialab/estransform": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.18.0.tgz", + "integrity": "sha512-GqQV36E/NZeAKjxq178i6XlbN9B6cGYYliw1grWghGo9YE4r1yO0eylYm9WaaDvvO1ozN/arVvyQ9G1TNJsRsQ==", + "dev": true, + "dependencies": { + "@parcel/source-map": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@chialab/node-resolve": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.18.0.tgz", + "integrity": "sha512-eV1m70Qn9pLY9xwFmZ2FlcOzwiaUywsJ7NB/ud8VB7DouvCQtIHkQ3Om7uPX0ojXGEG1LCyO96kZkvbNTxNu0Q==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", @@ -118,6 +180,7 @@ "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dev": true, "dependencies": { "@emotion/memoize": "^0.8.1", "@emotion/sheet": "^1.2.2", @@ -129,12 +192,14 @@ "node_modules/@emotion/hash": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", + "dev": true }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dev": true, "dependencies": { "@emotion/memoize": "^0.8.1" } @@ -142,12 +207,14 @@ "node_modules/@emotion/memoize": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "dev": true }, "node_modules/@emotion/react": { "version": "11.11.3", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "dev": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -171,6 +238,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "dev": true, "dependencies": { "@emotion/hash": "^0.9.1", "@emotion/memoize": "^0.8.1", @@ -182,12 +250,14 @@ "node_modules/@emotion/sheet": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", + "dev": true }, "node_modules/@emotion/styled": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dev": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -209,12 +279,14 @@ "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "dev": true }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "dev": true, "peerDependencies": { "react": ">=16.8.0" } @@ -222,12 +294,14 @@ "node_modules/@emotion/utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", + "dev": true }, "node_modules/@emotion/weak-memoize": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", + "dev": true }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.0", @@ -236,6 +310,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "aix" @@ -251,6 +326,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -266,6 +342,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -281,6 +358,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -296,6 +374,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -311,6 +390,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -326,6 +406,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -341,6 +422,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -356,6 +438,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -371,6 +454,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -386,6 +470,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -401,6 +486,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -416,6 +502,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -431,6 +518,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -446,6 +534,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -461,6 +550,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -476,6 +566,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -491,6 +582,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -506,6 +598,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -521,6 +614,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -536,6 +630,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -551,6 +646,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -566,6 +662,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -578,6 +675,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dev": true, "dependencies": { "@floating-ui/utils": "^0.2.1" } @@ -586,6 +684,7 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dev": true, "dependencies": { "@floating-ui/core": "^1.0.0", "@floating-ui/utils": "^0.2.0" @@ -595,6 +694,7 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dev": true, "dependencies": { "@floating-ui/dom": "^1.6.1" }, @@ -606,17 +706,20 @@ "node_modules/@floating-ui/utils": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", + "dev": true }, "node_modules/@fontsource/inter": { "version": "5.0.16", "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.0.16.tgz", - "integrity": "sha512-qF0aH5UiZvCmneX5orJbVRoc2VTyLTV3X/7laMp03Qt28L+B9tFlZODOGUL64wDWc69YVdi1LeJB0cIgd51lvw==" + "integrity": "sha512-qF0aH5UiZvCmneX5orJbVRoc2VTyLTV3X/7laMp03Qt28L+B9tFlZODOGUL64wDWc69YVdi1LeJB0cIgd51lvw==", + "dev": true }, "node_modules/@mui/base": { "version": "5.0.0-beta.36", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz", "integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.9", "@floating-ui/react-dom": "^2.0.8", @@ -648,15 +751,43 @@ "version": "5.15.10", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.10.tgz", "integrity": "sha512-qPv7B+LeMatYuzRjB3hlZUHqinHx/fX4YFBiaS19oC02A1e9JFuDKDvlyRQQ5oRSbJJt0QlaLTlr0IcauVcJRQ==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.15.10", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.10.tgz", + "integrity": "sha512-9cF8oUHZKo9oQ7EQ3pxPELaZuZVmphskU4OI6NiJNDVN7zcuvrEsuWjYo1Zh4fLiC39Nrvm30h/B51rcUjvSGA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/joy": { "version": "5.0.0-beta.28", "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.28.tgz", "integrity": "sha512-K3a2GhYix+oOGkJwDn3+SLL1+Z8ZXwJyQaEZPzQllMvU8fn4JyfFN4lGukJScVzTR0mWDG7MPxHsP0ozVNi8Mg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.36", @@ -693,10 +824,57 @@ } } }, + "node_modules/@mui/material": { + "version": "5.15.10", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.10.tgz", + "integrity": "sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.36", + "@mui/core-downloads-tracker": "^5.15.10", + "@mui/system": "^5.15.9", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.9", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/private-theming": { "version": "5.15.9", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.9.tgz", "integrity": "sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/utils": "^5.15.9", @@ -723,6 +901,7 @@ "version": "5.15.9", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.9.tgz", "integrity": "sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", @@ -754,6 +933,7 @@ "version": "5.15.9", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.9.tgz", "integrity": "sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.15.9", @@ -793,6 +973,7 @@ "version": "7.2.13", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", + "dev": true, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0" }, @@ -806,6 +987,7 @@ "version": "5.15.9", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.9.tgz", "integrity": "sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", @@ -829,10 +1011,23 @@ } } }, + "node_modules/@parcel/source-map": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", + "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": "^12.18.3 || >=14" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -841,17 +1036,20 @@ "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true }, "node_modules/@types/react": { "version": "18.2.55", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz", "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==", + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -862,6 +1060,17 @@ "version": "18.2.19", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dev": true, + "peer": true, "dependencies": { "@types/react": "*" } @@ -869,12 +1078,20 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "node_modules/@yowasp/yosys": { + "version": "0.39.37-dev.676", + "resolved": "https://registry.npmjs.org/@yowasp/yosys/-/yosys-0.39.37-dev.676.tgz", + "integrity": "sha512-+lPJ3yW0Csr6CKOy9TnytFKh8UhpU5J91WkETa4vhPGe5ufdm2q7g4ClWYpVdN2LP1yCl8I8ny0pEvU7GKoSVQ==", + "dev": true }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -886,6 +1103,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -896,10 +1114,17 @@ "npm": ">=6" } }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -908,6 +1133,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -921,6 +1147,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -929,6 +1156,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "dev": true, "engines": { "node": ">=6" } @@ -937,6 +1165,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -944,17 +1173,20 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -969,12 +1201,37 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -983,6 +1240,7 @@ "version": "0.20.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz", "integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -1020,6 +1278,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -1030,12 +1289,14 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": 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, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1044,6 +1305,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -1052,6 +1314,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -1063,6 +1326,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, "dependencies": { "react-is": "^16.7.0" } @@ -1070,12 +1334,14 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1090,12 +1356,14 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, "dependencies": { "hasown": "^2.0.0" }, @@ -1106,22 +1374,26 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -1129,10 +1401,38 @@ "loose-envify": "cli.js" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/monaco-editor": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.46.0.tgz", + "integrity": "sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -1141,6 +1441,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -1152,6 +1453,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -1168,12 +1470,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -1182,6 +1486,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -1191,12 +1496,24 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/pyodide": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.25.0.tgz", + "integrity": "sha512-RagtX3TfV2M0QAfThG2SMvwE31ikqAFDUXc5/4xhppEoVf4VbL7L0kbKOIdSNg7MbVsHELVxftk66WvT926GpA==", + "dev": true, + "dependencies": { + "base-64": "^1.0.0", + "ws": "^8.5.0" + } }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1208,6 +1525,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -1219,17 +1537,37 @@ "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -1246,6 +1584,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -1254,6 +1593,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -1262,6 +1602,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -1269,12 +1610,14 @@ "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "dev": true }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -1286,6 +1629,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -1297,14 +1641,37 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, "engines": { "node": ">=4" } }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index d1f4c4a..5f6a810 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,36 @@ { - "name": "try-amaranth", + "name": "amaranth-playground", "version": "0.0.0", "description": "Playground for experimenting with and sharing short Amaranth programs on the web", "author": "Catherine ", "license": "BSD-2-Clause", - "homepage": "https://github.com/whitequark/try-amaranth#readme", + "homepage": "https://github.com/whitequark/amaranth-playground#readme", "repository": { "type": "git", - "url": "git+https://github.com/whitequark/try-amaranth.git" + "url": "git+https://github.com/whitequark/amaranth-playground.git" }, "bugs": { - "url": "https://github.com/whitequark/try-amaranth/issues" + "url": "https://github.com/whitequark/amaranth-playground/issues" }, "scripts": { - "build": "esbuild index=src/index.html bundle=src/index.tsx --bundle --outdir=dist --loader:.html=copy --loader:.woff=copy --loader:.woff2=copy --loader:.ttf=copy --loader:.svg=dataurl --format=esm --target=es2015 --sourcemap --define:globalThis.IS_PRODUCTION=false", - "minify": "npm run build -- --minify --define:globalThis.IS_PRODUCTION=true", - "watch": "npm run build -- --watch", - "serve": "npm run build -- --watch --serve" + "build": "node build.mjs build", + "minify": "node build.mjs minify", + "watch": "node build.mjs watch", + "serve": "node build.mjs serve" }, - "dependencies": { + "devDependencies": { + "@chialab/esbuild-plugin-meta-url": "^0.18.0", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@fontsource/inter": "^5.0.16", + "@mui/icons-material": "^5.15.10", "@mui/joy": "^5.0.0-beta.28", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", + "@yowasp/yosys": "^0.39.37-dev.676", "esbuild": "^0.20.0", + "monaco-editor": "^0.46.0", + "pyodide": "^0.25.0", "react": "^18.0.0", "react-dom": "^18.0.0" } diff --git a/src/app.css b/src/app.css index b719c60..26cea58 100644 --- a/src/app.css +++ b/src/app.css @@ -1,3 +1,9 @@ -body { - color: red; -} \ No newline at end of file +.terminal-output { + height: 100%; + overflow-y: scroll; + overflow-x: auto; + white-space: pre-wrap; + font-family: monospace; +} +/* .terminal-stdout {} */ +.terminal-stderr { color: red; } \ No newline at end of file diff --git a/src/app.tsx b/src/app.tsx index 7817644..cb0b587 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,6 +1,265 @@ +import { createRoot } from 'react-dom/client'; + import * as React from 'react'; +import { useState, useEffect, useRef } from 'react'; + +import { CssVarsProvider, useColorScheme } from '@mui/joy/styles'; +import CssBaseline from '@mui/joy/CssBaseline'; +import '@fontsource/inter/latin-400'; + +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; +import Link from '@mui/joy/Link'; +import Alert from '@mui/joy/Alert'; +import Tabs from '@mui/joy/Tabs'; +import TabList from '@mui/joy/TabList'; +import Tab from '@mui/joy/Tab'; +import TabPanel from '@mui/joy/TabPanel'; + +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import LightModeIcon from '@mui/icons-material/LightMode'; + +import * as monaco from 'monaco-editor'; +import { EditorState, Editor } from './monaco'; +import { PythonError, runner } from './runner'; + import './app.css'; -export default function() { - return

Minimal React app using esbuild!

; -} \ No newline at end of file +import data from './data'; + +interface TerminalChunk { + stream: 'stdout' | 'stderr'; + text: string; +} + +function TerminalOutput(key, output) { + return output.map((chunk, index) => + {chunk.text}); +} + +function AppContent() { + const {mode, setMode} = useColorScheme(); + useEffect(() => monaco.editor.setTheme(mode === 'light' ? 'vs' : 'vs-dark'), [mode]); + + const [amaranthVersion, setAmaranthVersion] = useState('0.4.2'); + const [activeTab, setActiveTab] = useState('amaranth-source'); + const [running, setRunning] = useState(false); + const [sourceEditorState, setSourceEditorState] = useState(new EditorState(data.demoCode3)); + const [pythonOutput, setPythonOutput] = useState(null); + const [pythonOutputWasNull, setPythonOutputWasNull] = useState(true); + const [productsOutOfDate, setProductsOutOfDate] = useState(false); + const [rtlilProduct, setRtlilOutput] = useState(null); + const [verilogProduct, setVerilogOutput] = useState(null); + + async function runCode() { + if (running) + return; + try { + setRunning(true); + if (pythonOutput !== null) + setPythonOutput([]); + let gotVerilog = false; + let gotRtlil = false; + await runner.runPython(sourceEditorState.text, { + onStdout: (text) => + setPythonOutput(output => (output ?? []).concat([{stream: 'stdout', text }])), + onStderr: (text) => + setPythonOutput(output => (output ?? []).concat([{stream: 'stderr', text }])), + onShowVerilog: (code) => { gotVerilog = true; setVerilogOutput(code); }, + onShowRtlil: (code) => { gotRtlil = true; setRtlilOutput(code); }, + }); + if (rtlilProduct && !gotRtlil) { + setRtlilOutput(null); + setActiveTab(activeTab === 'rtlil-product' ? 'amaranth-source' : activeTab); + } + if (verilogProduct && !gotVerilog) { + setVerilogOutput(null); + setActiveTab(activeTab === 'verilog-product' ? 'amaranth-source' : activeTab); + } + setProductsOutOfDate(false); + } catch (e) { + if (e instanceof PythonError) { + setPythonOutput(output => (output ?? []).concat([{stream: 'stderr', text: e.message}])); + setActiveTab('python-output'); + } else { + throw e; + } + } finally { + setRunning(false); + } + } + + function tabAndPanel({ key, title, titleStyle = {}, content }) { + return [ + {title}, + {content} + ]; + } + + const tabsWithPanels = [ + tabAndPanel({ + key: 'amaranth-source', + title: 'Amaranth Source', + content: + }) + ]; + + const prevSourceCode = useRef(sourceEditorState.text); + useEffect(() => { + if (sourceEditorState.text != prevSourceCode.current) + setProductsOutOfDate(true); + prevSourceCode.current = sourceEditorState.text; + }, [sourceEditorState]); + + if (pythonOutput !== null) + tabsWithPanels.push(tabAndPanel({ + key: 'python-output', + title: 'Python Output', + content:
{TerminalOutput('python-output', pythonOutput)}
+ })); + + useEffect(() => { + // Open tab if we're running code for the first time, since it may not be clear that anything + // has happened otherwise. + if (pythonOutput !== null && pythonOutputWasNull) + setActiveTab('python-output'); + setPythonOutputWasNull(pythonOutput === null); + }, [pythonOutput]); + + if (rtlilProduct !== null) + tabsWithPanels.push(tabAndPanel({ + key: 'rtlil-product', + title: 'Generated RTLIL', + titleStyle: productsOutOfDate ? { textDecoration: 'line-through' } : {}, + content: + + {productsOutOfDate && + The generated RTLIL is out of date. Run the program again to refresh it. + } + + + + + })); + + if (verilogProduct !== null) + tabsWithPanels.push(tabAndPanel({ + key: 'verilog-product', + title: 'Generated Verilog', + titleStyle: productsOutOfDate ? { textDecoration: 'line-through' } : {}, + content: + + {productsOutOfDate && + The generated Verilog is out of date. Run the program again to refresh it. + } + + + + + })); + + // FIXME: populating `tabsWithPanels` this way leads to bugs + + return <> + + + + + + + + + Open documentation + + + {/* spacer */} + + setMode(mode === 'light' ? 'dark' : 'light')} + > + {mode === 'light' ? : } + + + setActiveTab(value as string)} + > + {tabsWithPanels.map(([tab, _panel]) => tab)} + {tabsWithPanels.map(([_tab, panel]) => panel)} + + + ; +} + +createRoot(document.getElementById('root')!).render( + + + + +); + +// https://esbuild.github.io/api/#live-reload +if (!globalThis.IS_PRODUCTION) + new EventSource('/esbuild').addEventListener('change', () => location.reload()); diff --git a/src/data.ts b/src/data.ts new file mode 100644 index 0000000..56dcc9f --- /dev/null +++ b/src/data.ts @@ -0,0 +1,27 @@ +export default { +demoCode3: `\ +from amaranth import * +from amaranth.sim import Simulator, Tick +from amaranth.back import rtlil, verilog +import amaranth_playground + + +count = Signal(4) +m = Module() +m.d.sync += count.eq(count + 1) + + +def testbench(): + for _ in range(10): + yield Tick("sync") + print(f"count: {yield count}") + +sim = Simulator(m) +sim.add_clock(1e-6) +sim.add_process(testbench) +sim.run() + +amaranth_playground.show_rtlil(rtlil.convert(m, ports=[count])) +amaranth_playground.show_verilog(verilog.convert(m, ports=[count])) +`, +}; \ No newline at end of file diff --git a/src/index.html b/src/index.html index 9d186f2..4987f3e 100644 --- a/src/index.html +++ b/src/index.html @@ -1,13 +1,13 @@ - Try Amaranth! - - - + Play with Amaranth HDL! + + + -
- +
+ diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 44d9ad8..0000000 --- a/src/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import App from "./app.tsx"; - -// https://esbuild.github.io/api/#live-reload -if (!globalThis.IS_PRODUCTION) - new EventSource('/esbuild').addEventListener('change', () => location.reload()); - -createRoot(document.getElementById('root')!).render(); diff --git a/src/monaco.tsx b/src/monaco.tsx new file mode 100644 index 0000000..48e2fd7 --- /dev/null +++ b/src/monaco.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import { Dispatch, SetStateAction, useEffect, useRef } from 'react'; +import * as monaco from 'monaco-editor'; + +window.MonacoEnvironment = { + getWorker: (moduleId, label) => + new Worker('editor.worker.js', { type: 'module' }), +}; + +export class EditorState { + constructor( + readonly text = '', + readonly viewState: monaco.editor.ICodeEditorViewState | null = null + ) {} + + updateText(newText: string = '') { + return new EditorState(newText, this.viewState); + } + + updateViewState(newViewState?: monaco.editor.ICodeEditorViewState | null) { + return new EditorState(this.text, newViewState); + } +} + +export interface EditorProps { + language?: string; + state: EditorState; + setState?: Dispatch>; + focus?: boolean; + actions?: monaco.editor.IActionDescriptor[]; +} + +export function Editor({ language, state, setState, focus = false, actions = [] }: EditorProps) { + const modelRef = useRef(null); + const editorRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + modelRef.current = monaco.editor.createModel(state.text, language); + if (setState) { + modelRef.current.onDidChangeContent(event => { + const newText = modelRef.current!.getValue(); + setState(state => state.updateText(newText)); + }); + } + editorRef.current = monaco.editor.create(containerRef.current!, { + model: modelRef.current, + readOnly: setState === undefined, + }); + actions.forEach(action => editorRef.current?.addAction(action)); + const resizeObserver = new ResizeObserver((events) => { + // without shrinking the editor, the browser will only stretch the viewport vertically + editorRef.current?.layout({ width: 0, height: 0 }); + editorRef.current?.layout(); + }); + resizeObserver.observe(containerRef.current!); // to detect expansion + resizeObserver.observe(window.document.body); // to detect shrinkage + editorRef.current.restoreViewState(state.viewState); + if (focus) + editorRef.current.focus(); + return () => { + setState?.(new EditorState(modelRef.current?.getValue(), editorRef.current?.saveViewState())); + resizeObserver.disconnect(); + editorRef.current?.dispose(); + editorRef.current = null; + modelRef.current?.dispose(); + modelRef.current = null; + }; + }, []); + + useEffect(() => { + monaco.editor.setModelLanguage(modelRef.current!, language ?? 'text'); + }, [language]); + + useEffect(() => { + if (modelRef.current?.getValue() !== state.text) + modelRef.current?.setValue(state.text); + }, [state]); + + return
; +} diff --git a/src/proto.ts b/src/proto.ts new file mode 100644 index 0000000..3e96aa9 --- /dev/null +++ b/src/proto.ts @@ -0,0 +1,39 @@ +export interface RunPythonMessage { + type: 'runPython'; + code: string; +} + +export type HostToWorkerMessage = +| RunPythonMessage; + +export interface StdoutWriteMessage { + type: 'stdoutWrite', + text: string +} + +export interface StderrWriteMessage { + type: 'stderrWrite', + text: string +} + +export interface ShowVerilogMessage { + type: 'showVerilog', + code: string +} + +export interface ShowRtlilMessage { + type: 'showRtlil', + code: string +} + +export interface PythonDoneMessage { + type: 'pythonDone'; + error: string | null; +} + +export type WorkerToHostMessage = +| StdoutWriteMessage +| StderrWriteMessage +| ShowVerilogMessage +| ShowRtlilMessage +| PythonDoneMessage; \ No newline at end of file diff --git a/src/pyodide.ts b/src/pyodide.ts new file mode 100644 index 0000000..83d77c9 --- /dev/null +++ b/src/pyodide.ts @@ -0,0 +1,17 @@ +// Slightly cursed wrapper to integrate with esbuild. + +import 'pyodide/pyodide.asm.js'; +// @ts-ignore +import pyodideStdLib from 'pyodide/python_stdlib.zip'; +import pyodideLockFile from 'pyodide/pyodide-lock.json'; +import { loadPyodide as originalLoadPyodide } from 'pyodide'; + +export const loadPyodide: typeof originalLoadPyodide = function(options) { + return originalLoadPyodide({ + indexURL: '.', + stdLibURL: pyodideStdLib, + // @ts-ignore + lockFileURL: pyodideLockFile, + ...options + }); +} \ No newline at end of file diff --git a/src/runner.ts b/src/runner.ts new file mode 100644 index 0000000..abce885 --- /dev/null +++ b/src/runner.ts @@ -0,0 +1,50 @@ +import { WorkerToHostMessage } from './proto'; + +export class PythonError extends Error {} + +export class ToolRunner { + #worker = new Worker('app.worker.js', { type: 'module' }); + + runPython(code: string, options: { + onStdout: (text: string) => void, + onStderr: (text: string) => void, + onShowVerilog: (code: string) => void, + onShowRtlil: (code: string) => void; + }): Promise { + console.log('[Host] Running', { code }); + const worker = this.#worker; + return new Promise((resolve, reject) => { + function onmessage(event: MessageEvent) { + console.log('[Host] Received', event.data); + if (event.data.type === 'stdoutWrite') { + options.onStdout(event.data.text); + } else if (event.data.type === 'stderrWrite') { + options.onStderr(event.data.text); + } else if (event.data.type === 'showVerilog') { + options.onShowVerilog(event.data.code); + } else if (event.data.type === 'showRtlil') { + options.onShowRtlil(event.data.code); + } else if (event.data.type === 'pythonDone') { + worker.removeEventListener('message', onmessage); + worker.removeEventListener('error', onerror); + if (event.data.error === null) + resolve(); + else + reject(new PythonError(event.data.error)); + } else { + reject(new Error(`[Host] Unexpected message ${(event.data as any).type}`)); + } + } + function onerror(event: ErrorEvent) { + console.log('[Host] Failure', event.error); + worker.removeEventListener('message', onmessage); + reject(event.error); + } + worker.addEventListener('message', onmessage); + worker.addEventListener('error', onerror, { once: true }); + worker.postMessage({ type: 'runPython', code }); + }); + } +} + +export let runner = new ToolRunner(); diff --git a/src/worker.ts b/src/worker.ts new file mode 100644 index 0000000..2a00b46 --- /dev/null +++ b/src/worker.ts @@ -0,0 +1,97 @@ +import { loadPyodide } from './pyodide'; +import { runYosys, Exit as YosysExit } from '@yowasp/yosys'; + +import { HostToWorkerMessage, WorkerToHostMessage } from './proto'; + +const pythonPackages = [ + 'https://files.pythonhosted.org/packages/98/8d/a0d8fb2b9611f3ae22ddc98890b346833fa2c645ad21fd282e61ccdad477/pyvcd-0.4.0-py2.py3-none-any.whl', + 'https://files.pythonhosted.org/packages/27/1c/39881fbd48f9de91d64955f206a7f32fd912d306d18e8c5f74126ee5962f/amaranth-0.4.2-py3-none-any.whl', +]; + +function postMessage(data: WorkerToHostMessage, transfer?: Transferable[]) { + console.log('[Worker] Sending', data); + self.postMessage(data, { transfer }); +} + +// Start preloading Yosys. +const yosysPromise = (runYosys() as unknown as Promise).then(() => { + console.log('[Worker] Preloaded Yosys'); +}); + +// Start preloading Pyodide. +const pyodidePromise = loadPyodide({ + env: { + HOME: '/', + AMARANTH_USE_YOSYS: 'javascript', + }, + stdout: line => postMessage({ type: 'stdoutWrite', text: `${line}\n` }), + stderr: line => postMessage({ type: 'stderrWrite', text: `${line}\n` }), + jsglobals: { + Object, + fetch: fetch.bind(globalThis), + setTimeout: setTimeout.bind(globalThis), + clearTimeout: clearTimeout.bind(globalThis), + runAmaranthYosys: (args, stdinText) => { + let stdin = new TextEncoder().encode(stdinText); + const stdout: string[] = []; + const stderr: string[] = []; + try { + runYosys(args.toJs(), {}, { + stdin: (length) => { + if (stdin.length === 0) + return null; + let chunk = stdin.subarray(0, length); + stdin = stdin.subarray(length); + return chunk; + }, + stdout: data => data ? stdout.push(new TextDecoder().decode(data)) : null, + stderr: data => data ? stderr.push(new TextDecoder().decode(data)) : null, + }); + return [0, stdout.join(""), stderr.join("")]; + } catch(e) { + if (e instanceof YosysExit) { + return [e.code, stdout.join(""), stderr.join("")]; + } else { + throw e; + } + } finally { + args.destroy(); + } + } + }, + packages: pythonPackages + }).then((pyodide) => { + pyodide.registerJsModule('amaranth_playground', { + show_rtlil: (code: string) => postMessage({ type: 'showRtlil', code }), + show_verilog: (code: string) => postMessage({ type: 'showVerilog', code }), + // show_waveforms: showWaveforms, + }); + console.log('[Worker] Pyodide loaded'); + return pyodide; + }); + +self.onmessage = async (event: MessageEvent) => { + console.log('[Worker] Received', event.data); + if (event.data.type === 'runPython') { + // Wait until all resources are loaded that the final `runYosys` call becomes synchronous. + await yosysPromise; + const pyodide = await pyodidePromise; + const dict = pyodide.globals.get('dict'); + const globals = dict(); + try { + pyodide.runPython(event.data.code, {globals, locals: globals}); + postMessage({ type: 'pythonDone', error: null }); + } catch (e) { + postMessage({ type: 'pythonDone', error: e.message }); + } finally { + globals.destroy(); + dict.destroy(); + } + } else { + throw new Error(`[Worker] Unexpected message ${(event.data as any).type}`); + } +} + +self.onerror = (event) => { + console.error('[Worker] Failure', event); +};