diff --git a/vweb/build.sh b/vweb/build.sh index 69bbcb7..0294527 100755 --- a/vweb/build.sh +++ b/vweb/build.sh @@ -2,4 +2,4 @@ wasm-pack build --target web npm run bundle -cp pkg/vweb_bg.wasm src/index.html build \ No newline at end of file +cp pkg/vweb_bg.wasm src/index.html src/snippet_emulator_demo.html build diff --git a/vweb/esbuild.js b/vweb/esbuild.js index cca81b9..9746dc9 100644 --- a/vweb/esbuild.js +++ b/vweb/esbuild.js @@ -1,5 +1,5 @@ require('esbuild').build({ - entryPoints: ['src/app.js'], + entryPoints: ['src/app.js', 'src/snippet_emulator_demo.js'], bundle: true, outdir: 'build', format: 'esm', diff --git a/vweb/package-lock.json b/vweb/package-lock.json index 5bf825b..02a1d0e 100644 --- a/vweb/package-lock.json +++ b/vweb/package-lock.json @@ -6,6 +6,8 @@ "": { "dependencies": { "esbuild": "^0.16.12", + "react": "^18.2.0", + "react-dom": "^18.2.0", "xterm": "^5.0.0", "xterm-readline": "^1.1.1" } @@ -397,6 +399,53 @@ "node": ">=8" } }, + "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==" + }, + "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==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -615,6 +664,44 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "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==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/vweb/package.json b/vweb/package.json index 5280e23..978767f 100644 --- a/vweb/package.json +++ b/vweb/package.json @@ -1,6 +1,8 @@ { "dependencies": { "esbuild": "^0.16.12", + "react": "^18.2.0", + "react-dom": "^18.2.0", "xterm": "^5.0.0", "xterm-readline": "^1.1.1" }, diff --git a/vweb/src/snippet_emulator.css b/vweb/src/snippet_emulator.css new file mode 100644 index 0000000..96ba395 --- /dev/null +++ b/vweb/src/snippet_emulator.css @@ -0,0 +1,41 @@ +body { + display: grid; + grid-template-columns: 1fr auto 1fr; + grid-template-rows: 2em auto; + background: #777777; +} +.react-root { + grid-area: 2 / 2 / 3 / 3; +} + +.snippetEmulator { + display: grid; + grid-template-rows: 10em 1em 1em 2em; + grid-template-columns: 20em; + background: #333333; + font-family: monospace; + padding: 0.5em; +} + +.snippetEmulator textarea { grid-area: 1 / 1 / 2 / 2 } +.snippetEmulator .src { grid-area: 1 / 1 / 2 / 2 } +.snippetEmulator .message { grid-area: 2 / 1 / 3 / 2 } +.snippetEmulator .buttons { grid-area: 3 / 1 / 4 / 2 } +.snippetEmulator .stacks { grid-area: 4 / 1 / 5 / 2 } +.snippetEmulator textarea { + background: #0f0f0f; + color: #dddddd; + min-height: 10em; + border: none; + resize: vertical; + padding: 0; + border: none; +} +.snippetEmulator textarea:focus { outline: none; } +.snippetEmulator .buttons a { + margin-right: 0.5em; + cursor: pointer; +} +.snippetEmulator .step, .snippetEmulator .run { color: lightgreen } +.snippetEmulator .reset { color: lightcoral } +.snippetEmulator .build, .snippetEmulator .edit { color: dodgerblue } diff --git a/vweb/src/snippet_emulator.jsx b/vweb/src/snippet_emulator.jsx new file mode 100644 index 0000000..3f4d4d5 --- /dev/null +++ b/vweb/src/snippet_emulator.jsx @@ -0,0 +1,69 @@ +import './snippet_emulator.css' +import React, {useState, useCallback, useEffect} from 'react' +import { WasmCPU, assemble_snippet, source_map } from '../pkg/vweb.js' + +export default function({ snippet }) { + const [editing, setEditing] = useState(false) // Whether we're editing the snippet or running it + const [src, setSrc] = useState(snippet) // The source code currently set + const [message, setMessage] = useState('') // A status / error message + const onChangeSrc = useCallback((e) => setSrc(e.target.value), []) // Callback for the textarea + const [binary, setBinary] = useState(null) // The assembled binary + const [sourceMap, setSourceMap] = useState({}) // A map from byte address to line number + + // A callback to build the binary from source + const rebuild = useCallback((code) => { + try { + const bin = assemble_snippet(code) + const sm = source_map(code) + setBinary(bin) + setSourceMap(sm) + setMessage(`Assembled ${bin.length} bytes`) + console.log(sm.Ok) + return true + } catch(e) { + setMessage(e.message) + } + }, []) + + // On load, build the stuff + useEffect(() => { rebuild(snippet) }, [snippet]) + + if (editing) { + return ( +