diff --git a/.gitignore b/.gitignore
index ea8c4bf..635a898 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
/target
+/vweb/node_modules/
+/vweb/build/
diff --git a/vweb/4th/4th.rom b/vweb/4th/4th.rom
new file mode 100644
index 0000000..723be21
Binary files /dev/null and b/vweb/4th/4th.rom differ
diff --git a/vweb/4th/4th.rom.sym b/vweb/4th/4th.rom.sym
new file mode 100644
index 0000000..2ee40ef
--- /dev/null
+++ b/vweb/4th/4th.rom.sym
@@ -0,0 +1 @@
+{"$BRNZ":28,"$BRZ":27,"$CALL":25,"$JMP":23,"$JMPR":24,"$PUSH":0,"$RET":26,"$SWAP":20,"$end":4585,"$start":1024,"advance_entry":1141,"backslash":3276,"close_paren":3267,"close_paren_stub":3275,"compare":1414,"compile_dict_start":3429,"compile_dictionary":4222,"compile_handleword":3150,"compile_instruction":1537,"compile_instruction_arg":1511,"compile_tick":1196,"cr":1067,"cursor":4210,"data_start":3340,"dict_start":3588,"dictionary":4219,"does_at_runtime":2225,"does_word":2196,"dupnz":1074,"dupnz_done":1080,"emit":1025,"emit_cursor":1042,"emit_hook":4228,"end0_pop1":1093,"end0_pop2":1092,"end1_pop2":1096,"eval":1773,"expected_word_err":3388,"find_in_dict":1149,"find_in_dict_next":1179,"find_word":2584,"handleword_hook":4198,"heap":4195,"heap_start":4586,"hex_is_number":1280,"hex_itoa":1701,"immediate_handleword":3113,"input_number":1595,"invalid_mnemonic":3293,"invalid_mnemonic_str":3353,"is_digit":1084,"is_number":1205,"is_number_hook":4201,"itoa":1618,"itoa_hook":4204,"itoa_loop":1648,"itoa_pos":1637,"itoa_print_loop":1685,"lambda_nesting_level":4216,"lambda_start_ptr":4213,"line_len":4207,"linecomment":3209,"missing_word":3301,"missing_word_str":3340,"mnemonics":2258,"mnemonics_end":2469,"new_dict":1451,"new_dict_error":1497,"nova_asm_to":2563,"nova_bracket_tick":2634,"nova_char":2689,"nova_close_brace":3049,"nova_close_bracket":1998,"nova_colon":2146,"nova_comma":2133,"nova_compile_dotquote":2877,"nova_compile_opcode":2555,"nova_compile_open_brace":3024,"nova_compile_squote":2810,"nova_continue":2007,"nova_create":1946,"nova_dec":2660,"nova_dotquote":2856,"nova_emit":3007,"nova_exit":2099,"nova_here":2579,"nova_hex":2672,"nova_immediate":2105,"nova_immediate_open_brace":3012,"nova_literal":2654,"nova_number":1932,"nova_opcode":2550,"nova_opcode_for_word":2469,"nova_open_bracket":1990,"nova_peekr":2961,"nova_popr":2994,"nova_postpone":2017,"nova_print_stack":2907,"nova_pushr":2981,"nova_quote_string_to":2715,"nova_resolve":1568,"nova_rpick":2969,"nova_safe_opcode":2529,"nova_semicolon":2154,"nova_squote":2780,"nova_tick":2613,"nova_word":1852,"nova_word_to":1902,"nova_word_to_pad":2164,"open_paren":3250,"pad":4231,"parencomment":3211,"parse_hex_digit":1326,"pos_is_number":1234,"pos_is_number_bad":1271,"pos_is_number_done":1276,"pos_is_number_loop":1236,"print":1045,"print_number":1613,"print_stack_end":3426,"print_stack_start":3422,"push_jump":1552,"quit":3317,"quit_vector":4225,"r_stack":4490,"r_stack_ptr":4487,"skip_nonword":1388,"skip_word":1371,"stop":1024,"tick":1187,"unclosed_error":3372,"word_char":1081,"wordeq":1100}
\ No newline at end of file
diff --git a/vweb/4th/prelude.f b/vweb/4th/prelude.f
new file mode 100644
index 0000000..50e55b4
--- /dev/null
+++ b/vweb/4th/prelude.f
@@ -0,0 +1,25 @@
+: if $ brz >asm ; immediate
+: then resolve ; immediate
+: else r> $ jmpr >asm >r resolve ; immediate
+: variable create 0 , does> ;
+: arshift $ arshift asm ;
+: lshift [ $ lshift asm ] ;
+: rshift [ $ rshift asm ] ;
+: u> [ $ gt ] asm ;
+: u< [ $ lt ] asm ;
+: rdrop r> pop ;
+: over 1 pick ;
+: nip swap pop ;
+: -rot rot rot ;
+: tuck dup -rot ;
+: space 32 emit ;
+: cr 13 emit 10 emit ;
+: +! dup @ rot + swap ! ;
+: 2dup 1 pick 1 pick ;
+: allot here swap &heap +! ;
+: negate -1 ^ 1 + ;
+: free negate &heap +! here ;
+: c+! dup c@ rot + swap c! ;
+: ror dup 1 rshift swap 23 lshift | ;
+: rol dup 23 rshift swap 1 lshift | ;
+: abs dup 0 < if negate then ;
diff --git a/vweb/Cargo.toml b/vweb/Cargo.toml
new file mode 100644
index 0000000..1e6820f
--- /dev/null
+++ b/vweb/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "vweb"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+vcore = { path = "../vcore" }
+wasm-bindgen = "0.2"
diff --git a/vweb/build.sh b/vweb/build.sh
new file mode 100755
index 0000000..69bbcb7
--- /dev/null
+++ b/vweb/build.sh
@@ -0,0 +1,5 @@
+#!/bin/bash -e
+
+wasm-pack build --target web
+npm run bundle
+cp pkg/vweb_bg.wasm src/index.html build
\ No newline at end of file
diff --git a/vweb/esbuild.js b/vweb/esbuild.js
new file mode 100644
index 0000000..cca81b9
--- /dev/null
+++ b/vweb/esbuild.js
@@ -0,0 +1,9 @@
+require('esbuild').build({
+ entryPoints: ['src/app.js'],
+ bundle: true,
+ outdir: 'build',
+ format: 'esm',
+ loader: {
+ '.wasm': 'file'
+ }
+}).catch(() => process.exit(1))
\ No newline at end of file
diff --git a/vweb/package-lock.json b/vweb/package-lock.json
new file mode 100644
index 0000000..5bf825b
--- /dev/null
+++ b/vweb/package-lock.json
@@ -0,0 +1,650 @@
+{
+ "name": "vweb",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "esbuild": "^0.16.12",
+ "xterm": "^5.0.0",
+ "xterm-readline": "^1.1.1"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.12.tgz",
+ "integrity": "sha512-CTWgMJtpCyCltrvipZrrcjjRu+rzm6pf9V8muCsJqtKujR3kPmU4ffbckvugNNaRmhxAF1ZI3J+0FUIFLFg8KA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.12.tgz",
+ "integrity": "sha512-0LacmiIW+X0/LOLMZqYtZ7d4uY9fxYABAYhSSOu+OGQVBqH4N5eIYgkT7bBFnR4Nm3qo6qS3RpHKVrDASqj/uQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.12.tgz",
+ "integrity": "sha512-sS5CR3XBKQXYpSGMM28VuiUnbX83Z+aWPZzClW+OB2JquKqxoiwdqucJ5qvXS8pM6Up3RtJfDnRQZkz3en2z5g==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.12.tgz",
+ "integrity": "sha512-Dpe5hOAQiQRH20YkFAg+wOpcd4PEuXud+aGgKBQa/VriPJA8zuVlgCOSTwna1CgYl05lf6o5els4dtuyk1qJxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.12.tgz",
+ "integrity": "sha512-ApGRA6X5txIcxV0095X4e4KKv87HAEXfuDRcGTniDWUUN+qPia8sl/BqG/0IomytQWajnUn4C7TOwHduk/FXBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.12.tgz",
+ "integrity": "sha512-AMdK2gA9EU83ccXCWS1B/KcWYZCj4P3vDofZZkl/F/sBv/fphi2oUqUTox/g5GMcIxk8CF1CVYTC82+iBSyiUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.12.tgz",
+ "integrity": "sha512-KUKB9w8G/xaAbD39t6gnRBuhQ8vIYYlxGT2I+mT6UGRnCGRr1+ePFIGBQmf5V16nxylgUuuWVW1zU2ktKkf6WQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.12.tgz",
+ "integrity": "sha512-vhDdIv6z4eL0FJyNVfdr3C/vdd/Wc6h1683GJsFoJzfKb92dU/v88FhWdigg0i6+3TsbSDeWbsPUXb4dif2abg==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.12.tgz",
+ "integrity": "sha512-29HXMLpLklDfmw7T2buGqq3HImSUaZ1ArmrPOMaNiZZQptOSZs32SQtOHEl8xWX5vfdwZqrBfNf8Te4nArVzKQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.12.tgz",
+ "integrity": "sha512-JFDuNDTTfgD1LJg7wHA42o2uAO/9VzHYK0leAVnCQE/FdMB599YMH73ux+nS0xGr79pv/BK+hrmdRin3iLgQjg==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.12.tgz",
+ "integrity": "sha512-xTGzVPqm6WKfCC0iuj1fryIWr1NWEM8DMhAIo+4rFgUtwy/lfHl+Obvus4oddzRDbBetLLmojfVZGmt/g/g+Rw==",
+ "cpu": [
+ "loong64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.12.tgz",
+ "integrity": "sha512-zI1cNgHa3Gol+vPYjIYHzKhU6qMyOQrvZ82REr5Fv7rlh5PG6SkkuCoH7IryPqR+BK2c/7oISGsvPJPGnO2bHQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.12.tgz",
+ "integrity": "sha512-/C8OFXExoMmvTDIOAM54AhtmmuDHKoedUd0Otpfw3+AuuVGemA1nQK99oN909uZbLEU6Bi+7JheFMG3xGfZluQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.12.tgz",
+ "integrity": "sha512-qeouyyc8kAGV6Ni6Isz8hUsKMr00EHgVwUKWNp1r4l88fHEoNTDB8mmestvykW6MrstoGI7g2EAsgr0nxmuGYg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.12.tgz",
+ "integrity": "sha512-s9AyI/5vz1U4NNqnacEGFElqwnHusWa81pskAf8JNDM2eb6b2E6PpBmT8RzeZv6/TxE6/TADn2g9bb0jOUmXwQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.12.tgz",
+ "integrity": "sha512-e8YA7GQGLWhvakBecLptUiKxOk4E/EPtSckS1i0MGYctW8ouvNUoh7xnU15PGO2jz7BYl8q1R6g0gE5HFtzpqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.12.tgz",
+ "integrity": "sha512-z2+kUxmOqBS+6SRVd57iOLIHE8oGOoEnGVAmwjm2aENSP35HPS+5cK+FL1l+rhrsJOFIPrNHqDUNechpuG96Sg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.12.tgz",
+ "integrity": "sha512-PAonw4LqIybwn2/vJujhbg1N9W2W8lw9RtXIvvZoyzoA/4rA4CpiuahVbASmQohiytRsixbNoIOUSjRygKXpyA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.12.tgz",
+ "integrity": "sha512-+wr1tkt1RERi+Zi/iQtkzmMH4nS8+7UIRxjcyRz7lur84wCkAITT50Olq/HiT4JN2X2bjtlOV6vt7ptW5Gw60Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.12.tgz",
+ "integrity": "sha512-XEjeUSHmjsAOJk8+pXJu9pFY2O5KKQbHXZWQylJzQuIBeiGrpMeq9sTVrHefHxMOyxUgoKQTcaTS+VK/K5SviA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.12.tgz",
+ "integrity": "sha512-eRKPM7e0IecUAUYr2alW7JGDejrFJXmpjt4MlfonmQ5Rz9HWpKFGCjuuIRgKO7W9C/CWVFXdJ2GjddsBXqQI4A==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.12.tgz",
+ "integrity": "sha512-iPYKN78t3op2+erv2frW568j1q0RpqX6JOLZ7oPPaAV1VaF7dDstOrNw37PVOYoTWE11pV4A1XUitpdEFNIsPg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/esbuild": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.12.tgz",
+ "integrity": "sha512-eq5KcuXajf2OmivCl4e89AD3j8fbV+UTE9vczEzq5haA07U9oOTzBWlh3+6ZdjJR7Rz2QfWZ2uxZyhZxBgJ4+g==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.16.12",
+ "@esbuild/android-arm64": "0.16.12",
+ "@esbuild/android-x64": "0.16.12",
+ "@esbuild/darwin-arm64": "0.16.12",
+ "@esbuild/darwin-x64": "0.16.12",
+ "@esbuild/freebsd-arm64": "0.16.12",
+ "@esbuild/freebsd-x64": "0.16.12",
+ "@esbuild/linux-arm": "0.16.12",
+ "@esbuild/linux-arm64": "0.16.12",
+ "@esbuild/linux-ia32": "0.16.12",
+ "@esbuild/linux-loong64": "0.16.12",
+ "@esbuild/linux-mips64el": "0.16.12",
+ "@esbuild/linux-ppc64": "0.16.12",
+ "@esbuild/linux-riscv64": "0.16.12",
+ "@esbuild/linux-s390x": "0.16.12",
+ "@esbuild/linux-x64": "0.16.12",
+ "@esbuild/netbsd-x64": "0.16.12",
+ "@esbuild/openbsd-x64": "0.16.12",
+ "@esbuild/sunos-x64": "0.16.12",
+ "@esbuild/win32-arm64": "0.16.12",
+ "@esbuild/win32-ia32": "0.16.12",
+ "@esbuild/win32-x64": "0.16.12"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/xterm": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.1.0.tgz",
+ "integrity": "sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ=="
+ },
+ "node_modules/xterm-readline": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/xterm-readline/-/xterm-readline-1.1.1.tgz",
+ "integrity": "sha512-f87S2/jKwRZoZTxE2vkPgBCipDl6k6tTkMTb9pmwC4R6XkfR491fWBuToZd/nZasp6seD2u0jdABinUDWsK6dw==",
+ "dependencies": {
+ "string-width": "^4.0.0"
+ },
+ "peerDependencies": {
+ "xterm": "^5.0.0"
+ }
+ }
+ },
+ "dependencies": {
+ "@esbuild/android-arm": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.12.tgz",
+ "integrity": "sha512-CTWgMJtpCyCltrvipZrrcjjRu+rzm6pf9V8muCsJqtKujR3kPmU4ffbckvugNNaRmhxAF1ZI3J+0FUIFLFg8KA==",
+ "optional": true
+ },
+ "@esbuild/android-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.12.tgz",
+ "integrity": "sha512-0LacmiIW+X0/LOLMZqYtZ7d4uY9fxYABAYhSSOu+OGQVBqH4N5eIYgkT7bBFnR4Nm3qo6qS3RpHKVrDASqj/uQ==",
+ "optional": true
+ },
+ "@esbuild/android-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.12.tgz",
+ "integrity": "sha512-sS5CR3XBKQXYpSGMM28VuiUnbX83Z+aWPZzClW+OB2JquKqxoiwdqucJ5qvXS8pM6Up3RtJfDnRQZkz3en2z5g==",
+ "optional": true
+ },
+ "@esbuild/darwin-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.12.tgz",
+ "integrity": "sha512-Dpe5hOAQiQRH20YkFAg+wOpcd4PEuXud+aGgKBQa/VriPJA8zuVlgCOSTwna1CgYl05lf6o5els4dtuyk1qJxQ==",
+ "optional": true
+ },
+ "@esbuild/darwin-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.12.tgz",
+ "integrity": "sha512-ApGRA6X5txIcxV0095X4e4KKv87HAEXfuDRcGTniDWUUN+qPia8sl/BqG/0IomytQWajnUn4C7TOwHduk/FXBQ==",
+ "optional": true
+ },
+ "@esbuild/freebsd-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.12.tgz",
+ "integrity": "sha512-AMdK2gA9EU83ccXCWS1B/KcWYZCj4P3vDofZZkl/F/sBv/fphi2oUqUTox/g5GMcIxk8CF1CVYTC82+iBSyiUg==",
+ "optional": true
+ },
+ "@esbuild/freebsd-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.12.tgz",
+ "integrity": "sha512-KUKB9w8G/xaAbD39t6gnRBuhQ8vIYYlxGT2I+mT6UGRnCGRr1+ePFIGBQmf5V16nxylgUuuWVW1zU2ktKkf6WQ==",
+ "optional": true
+ },
+ "@esbuild/linux-arm": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.12.tgz",
+ "integrity": "sha512-vhDdIv6z4eL0FJyNVfdr3C/vdd/Wc6h1683GJsFoJzfKb92dU/v88FhWdigg0i6+3TsbSDeWbsPUXb4dif2abg==",
+ "optional": true
+ },
+ "@esbuild/linux-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.12.tgz",
+ "integrity": "sha512-29HXMLpLklDfmw7T2buGqq3HImSUaZ1ArmrPOMaNiZZQptOSZs32SQtOHEl8xWX5vfdwZqrBfNf8Te4nArVzKQ==",
+ "optional": true
+ },
+ "@esbuild/linux-ia32": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.12.tgz",
+ "integrity": "sha512-JFDuNDTTfgD1LJg7wHA42o2uAO/9VzHYK0leAVnCQE/FdMB599YMH73ux+nS0xGr79pv/BK+hrmdRin3iLgQjg==",
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.12.tgz",
+ "integrity": "sha512-xTGzVPqm6WKfCC0iuj1fryIWr1NWEM8DMhAIo+4rFgUtwy/lfHl+Obvus4oddzRDbBetLLmojfVZGmt/g/g+Rw==",
+ "optional": true
+ },
+ "@esbuild/linux-mips64el": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.12.tgz",
+ "integrity": "sha512-zI1cNgHa3Gol+vPYjIYHzKhU6qMyOQrvZ82REr5Fv7rlh5PG6SkkuCoH7IryPqR+BK2c/7oISGsvPJPGnO2bHQ==",
+ "optional": true
+ },
+ "@esbuild/linux-ppc64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.12.tgz",
+ "integrity": "sha512-/C8OFXExoMmvTDIOAM54AhtmmuDHKoedUd0Otpfw3+AuuVGemA1nQK99oN909uZbLEU6Bi+7JheFMG3xGfZluQ==",
+ "optional": true
+ },
+ "@esbuild/linux-riscv64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.12.tgz",
+ "integrity": "sha512-qeouyyc8kAGV6Ni6Isz8hUsKMr00EHgVwUKWNp1r4l88fHEoNTDB8mmestvykW6MrstoGI7g2EAsgr0nxmuGYg==",
+ "optional": true
+ },
+ "@esbuild/linux-s390x": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.12.tgz",
+ "integrity": "sha512-s9AyI/5vz1U4NNqnacEGFElqwnHusWa81pskAf8JNDM2eb6b2E6PpBmT8RzeZv6/TxE6/TADn2g9bb0jOUmXwQ==",
+ "optional": true
+ },
+ "@esbuild/linux-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.12.tgz",
+ "integrity": "sha512-e8YA7GQGLWhvakBecLptUiKxOk4E/EPtSckS1i0MGYctW8ouvNUoh7xnU15PGO2jz7BYl8q1R6g0gE5HFtzpqQ==",
+ "optional": true
+ },
+ "@esbuild/netbsd-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.12.tgz",
+ "integrity": "sha512-z2+kUxmOqBS+6SRVd57iOLIHE8oGOoEnGVAmwjm2aENSP35HPS+5cK+FL1l+rhrsJOFIPrNHqDUNechpuG96Sg==",
+ "optional": true
+ },
+ "@esbuild/openbsd-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.12.tgz",
+ "integrity": "sha512-PAonw4LqIybwn2/vJujhbg1N9W2W8lw9RtXIvvZoyzoA/4rA4CpiuahVbASmQohiytRsixbNoIOUSjRygKXpyA==",
+ "optional": true
+ },
+ "@esbuild/sunos-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.12.tgz",
+ "integrity": "sha512-+wr1tkt1RERi+Zi/iQtkzmMH4nS8+7UIRxjcyRz7lur84wCkAITT50Olq/HiT4JN2X2bjtlOV6vt7ptW5Gw60Q==",
+ "optional": true
+ },
+ "@esbuild/win32-arm64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.12.tgz",
+ "integrity": "sha512-XEjeUSHmjsAOJk8+pXJu9pFY2O5KKQbHXZWQylJzQuIBeiGrpMeq9sTVrHefHxMOyxUgoKQTcaTS+VK/K5SviA==",
+ "optional": true
+ },
+ "@esbuild/win32-ia32": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.12.tgz",
+ "integrity": "sha512-eRKPM7e0IecUAUYr2alW7JGDejrFJXmpjt4MlfonmQ5Rz9HWpKFGCjuuIRgKO7W9C/CWVFXdJ2GjddsBXqQI4A==",
+ "optional": true
+ },
+ "@esbuild/win32-x64": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.12.tgz",
+ "integrity": "sha512-iPYKN78t3op2+erv2frW568j1q0RpqX6JOLZ7oPPaAV1VaF7dDstOrNw37PVOYoTWE11pV4A1XUitpdEFNIsPg==",
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "esbuild": {
+ "version": "0.16.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.12.tgz",
+ "integrity": "sha512-eq5KcuXajf2OmivCl4e89AD3j8fbV+UTE9vczEzq5haA07U9oOTzBWlh3+6ZdjJR7Rz2QfWZ2uxZyhZxBgJ4+g==",
+ "requires": {
+ "@esbuild/android-arm": "0.16.12",
+ "@esbuild/android-arm64": "0.16.12",
+ "@esbuild/android-x64": "0.16.12",
+ "@esbuild/darwin-arm64": "0.16.12",
+ "@esbuild/darwin-x64": "0.16.12",
+ "@esbuild/freebsd-arm64": "0.16.12",
+ "@esbuild/freebsd-x64": "0.16.12",
+ "@esbuild/linux-arm": "0.16.12",
+ "@esbuild/linux-arm64": "0.16.12",
+ "@esbuild/linux-ia32": "0.16.12",
+ "@esbuild/linux-loong64": "0.16.12",
+ "@esbuild/linux-mips64el": "0.16.12",
+ "@esbuild/linux-ppc64": "0.16.12",
+ "@esbuild/linux-riscv64": "0.16.12",
+ "@esbuild/linux-s390x": "0.16.12",
+ "@esbuild/linux-x64": "0.16.12",
+ "@esbuild/netbsd-x64": "0.16.12",
+ "@esbuild/openbsd-x64": "0.16.12",
+ "@esbuild/sunos-x64": "0.16.12",
+ "@esbuild/win32-arm64": "0.16.12",
+ "@esbuild/win32-ia32": "0.16.12",
+ "@esbuild/win32-x64": "0.16.12"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "xterm": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.1.0.tgz",
+ "integrity": "sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ=="
+ },
+ "xterm-readline": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/xterm-readline/-/xterm-readline-1.1.1.tgz",
+ "integrity": "sha512-f87S2/jKwRZoZTxE2vkPgBCipDl6k6tTkMTb9pmwC4R6XkfR491fWBuToZd/nZasp6seD2u0jdABinUDWsK6dw==",
+ "requires": {
+ "string-width": "^4.0.0"
+ }
+ }
+ }
+}
diff --git a/vweb/package.json b/vweb/package.json
new file mode 100644
index 0000000..5280e23
--- /dev/null
+++ b/vweb/package.json
@@ -0,0 +1,10 @@
+{
+ "dependencies": {
+ "esbuild": "^0.16.12",
+ "xterm": "^5.0.0",
+ "xterm-readline": "^1.1.1"
+ },
+ "scripts": {
+ "bundle": "node esbuild.js"
+ }
+}
diff --git a/vweb/src/app.css b/vweb/src/app.css
new file mode 100644
index 0000000..af24972
--- /dev/null
+++ b/vweb/src/app.css
@@ -0,0 +1,34 @@
+body {
+ background-color: #333333;
+ color: #dddddd;
+ font-family: sans-serif;
+}
+
+.content {
+ display: grid;
+ grid-template-columns: 1fr 8fr 1fr;
+ grid-template-rows: auto auto;
+}
+
+.terminal {
+ grid-area: 1 / 2 / 1 / 2;
+ border: 2px solid #777777;
+}
+
+.instructions {
+ grid-area: 2 / 2 / 2 / 2;
+}
+
+a {
+ color: #ffffff;
+}
+
+pre {
+ background: #0f0f0f;
+}
+
+td.word, td.signature {
+ font-family: monospace;
+ font-weight: bold;
+ padding-right: 1em;
+}
diff --git a/vweb/src/app.js b/vweb/src/app.js
new file mode 100644
index 0000000..548dac3
--- /dev/null
+++ b/vweb/src/app.js
@@ -0,0 +1,72 @@
+import 'xterm/css/xterm.css'
+import './app.css'
+import { Terminal } from 'xterm'
+import { Readline } from 'xterm-readline'
+import init, { WasmCPU, NovaForth } from '../pkg/vweb.js'
+
+const term = new Terminal()
+const rl = new Readline()
+const TIB = 80000 // Just a convenient place to stick a terminal input buffer for the repl. Could be any otherwise-unused address.
+const OUT = 0x10000 // A place that NovaForth will use for its output buffer
+let syms
+
+term.loadAddon(rl)
+term.open(document.getElementsByClassName('terminal')[0])
+term.write('Loading...\r\n')
+
+const handleKey = ({key, domEvent: { key: mnemonic }}) => {
+ if (mnemonic === 'Enter') {
+ console.log(mnemonic)
+ } else {
+ term.write(key)
+ }
+}
+
+init().then(async () => {
+ const cpu = new WasmCPU()
+ syms = JSON.parse(NovaForth.symbols()) // Grab the symbol table for the ROM so we can poke things by name
+
+ NovaForth.rom().forEach((byte, idx) => {
+ cpu.poke(0x400 + idx, byte)
+ })
+
+ term.write(`Loaded ${NovaForth.rom().length} bytes\r\n`)
+ NovaForth.prelude().split(/\n/).forEach((line) => {
+ const result = eval4th(cpu, line) // If there's anything wrong with the prelude, say so
+ if (result !== '') {
+ console.log(`Error with "${line}": ${result}`)
+ }
+ })
+ term.write(`Loaded NovaForth Prelude\r\n`)
+ document.getElementsByClassName('prelude')[0].appendChild(document.createTextNode(NovaForth.prelude()))
+
+ term.focus()
+
+ while (true) {
+ const line = await rl.read('> ') // Read the line of input
+ const str = eval4th(cpu, line) // Run it on the CPU
+ rl.println(str) // Println the result
+ }
+})
+
+function eval4th(cpu, line) {
+ // Copy the line to TIB and null-terminate it
+ for (let i = 0; i < line.length; i++) {
+ cpu.poke(TIB + i, line.charCodeAt(i))
+ }
+ cpu.poke(TIB + line.length, 0)
+
+ cpu.push_call(syms.stop) // Tell the CPU to stop once we're done
+ cpu.push_data(TIB) // Push the TIB and call eval
+ cpu.set_pc(syms.eval)
+ cpu.run() // Run it!
+
+ const len = cpu.peek24(syms.emit_cursor) // Find the length of the output
+ let str = '' // Copy the output from the memory to a JS string
+ for (let i = OUT; i < OUT + len; i++) {
+ str = str + String.fromCharCode(cpu.peek(i))
+ }
+ cpu.poke24(syms.emit_cursor, 0) // Re-clear the output buffer
+
+ return str
+}
\ No newline at end of file
diff --git a/vweb/src/index.html b/vweb/src/index.html
new file mode 100644
index 0000000..6d4348e
--- /dev/null
+++ b/vweb/src/index.html
@@ -0,0 +1,187 @@
+
+
+
+
+
+ NovaForth REPL
+
+
+
+
+
+
+
NovaForth Console
+
+ This is a REPL for NovaForth, using the Vulcan imaginary CPU.
+ You can type lines of Forth code and have them evaluated, and see the results.
+
+
+ For a full introduction to NovaForth, read
+
+ this sample chapter
+
+ from the upcoming Vulcan book. A short tutorial is below:
+
+
Tutorial and examples:
+
+ Forth programs are a series of "words" separated by whitespace. NovaForth evaluates words line-by-line, and a
+ full dictionary of available words is listed below. Here are some example programs:
+
+
+
5 3 + .
+ This program pushes the numbers "5" and "3" to the stack, then adds the top two numbers on the stack, and
+ finally pops the top number from the stack (now "8") and prints it.
+
+
+
." Hello, world!"
+ This program prints a standard greeting.
+
+
+
: square dup * ;
+4 square .
+ This defines a new word, "square", which multiplies the top value on the stack by itself. The second line then
+ calls that word on the number "4" and prints the result.
+
+
+
: collatz
+ dup 2 % if
+ 3 * 1 +
+ else
+ 2 /
+ then ;
+
+4 collatz 5 collatz .s
+ This defines a word "collatz" which showcases conditionals: if the number given to it is odd, it returns triple
+ that number plus one; if it's even, it returns half that number. The ".s" word prints the stack without modifying
+ it and is a useful debugging tool.
+
+
NovaForth Dictionary
+
Arithmetic and stack juggling
+
+ + a b -- c Addition
+ - a b -- c Subtraction
+ * a b -- c Multiplication
+ / a b -- c Division (truncates to integer)
+ % a b -- c Modulus
+ pop a -- Throw away the top element of the stack
+ dup a -- a a Duplicate the top element of the stack
+ swap a b -- b a Swap the top two elements of the stack
+
+ pick
+ * index -- * el
+ Copy an element from deep in the stack to the top. "0 pick" is equivalent to dup,
+ "1 pick" pushes the value one below the top, and so on
+
+ rot
+ a b c -- b c a
+ Rotate the top three elements on the stack
+
+
+ -rot
+ a b c -- c a b
+ Rotate the top three elements on the stack the other direction (prelude word)
+
+ ?dup a -- a a OR 0 -- 0 Duplicates the top of stack if it's nonzero
+ >r a -- Pushes the top of the stack to the R stack
+ r> -- a Pushes the top of the R stack to the stack
+ r@ -- a Copies the top of the R stack to the stack, leaving the R stack unchanged
+ rpick index -- a Picks a value from deep in the R stack, much like "pick" does with the data stack
+ rdrop -- Drop the top value from the R stack (prelude word)
+ lshift a b -- c Binary-shifts "a" left "b" bits, shifting in zeroes (prelude word)
+ rshift a b -- c Binary-shifts "a" right "b" bits, shifting in zeroes (prelude word)
+ arshift a b -- c Arithmetic right shift: right-shift leaving the high order bit untouched (to preserve sign) (prelude word)
+ ror a -- b Roll one bit right, moving the low bit to the high bit (prelude word)
+ rol a -- b Roll one bit left, moving the high bit to the low bit (prelude word)
+ over a b -- a b a Duplicate the value one below top (prelude word)
+ nip a b -- b Drop the value one below top (prelude word)
+ tuck a b -- b a b Duplicate the top value, placing it underneath the one below top (prelude word)
+ 2dup a b -- a b a b Duplicate the top two values (prelude word)
+ negate a -- b Multiply the top of stack by -1 (prelude word)
+ abs a -- b Return the absolute value of the top of stack (prelude word)
+
+
I/O
+
+ word word -- addr Copies word to the pad, leaving its address on the stack
+ pad -- addr Pushes the address of the pad to the stack
+ number str -- num 1 OR -- 0 Tries to parse the given word as a number, pushes the number followed by 1 if it's parseable and 0 if it's not
+ hex -- Switches all I/O to hexadecimal mode
+ dec -- Switches all I/O to base-10 mode (the default)
+ . a -- Prints the number on the top of the stack
+ s" -- addr Reads a string until a double-quote, copies them to the heap, and returns the address of the string
+ ." -- Reads a string until a double-quote and prints it
+ emit ch -- Emits a byte (as ASCII) to the console
+ print addr -- Prints a null-terminated string to the console
+ .s -- Prints the stack contents as a debugging aid
+ space -- Prints a space (prelude word)
+ cr -- Prints a newline (10 followed by 13, carriage return and line feed) (prelude word)
+
+
Logic and control structures
+
+ > a b -- c Returns a 1 if a is greater than b, 0 otherwise (signed comparison)
+ < a b -- c Returns a 1 if a is less than b, 0 otherwise (signed comparison)
+ u> a b -- c Returns a 1 if a is greater than b, 0 otherwise (unsigned comparison) (prelude word)
+ u< a b -- c Returns a 1 if a is less than b, 0 otherwise (unsigned comparison) (prelude word)
+ = a b -- c Returns a 1 if a is equal to b, 0 otherwise
+ & a b -- c Bitwise-and of a and b
+ | a b -- c Bitwise-or of a and b
+ ^ a b -- c Bitwise-xor of a and b
+ not a -- b If the argument is a 0, returns 1. Otherwise returns 0
+ compare a b -- c Compares two strings for equality, returns 1 if they're equal and 0 otherwise
+ if / else / then cond -- A conditional branch: "condition if truepath else falsepath then". The "else" branch is optional (prelude words)
+
+
Memory management
+
+ @ addr -- val Read the 3-byte word at the given address
+ ! val addr -- Write a 3-byte word to the given address
+ c@ addr -- val Read one byte from the given address
+ c! val addr -- Write one byte to the given address
+ &heap -- addr Pushes the address of the heap pointer
+ here -- addr Pushes the current value of the heap pointer (equivalent to "&heap @")
+ variable name -- Creates a variable of the given name, which pushes its address when called (prelude word)
+ +! val addr -- Increments the 3-byte word at "addr" by "val" (prelude word)
+ c+! val addr -- Increments the byte at "addr" by "val" (prelude word)
+ allot val -- addr Increment the heap by "val" bytes, returning the start address of the allotted region (prelude word)
+ free val -- addr Decrement the heap by "val" bytes, returning the new heap address (prelude word)
+
+
Assembly and compilation
+
+ execute addr -- Calls the given address
+ [ -- Enters interpret mode
+ ] -- Enters compile mode
+ , a -- Compiles a number to the heap
+ : -- Begins definition of a new word: calls create and enters compile mode
+ ; -- Ends a word definition: compiles a return instruction and leaves compile mode
+ $ mnemonic -- a Pushes the equivalent opcode to the given instruction mnemonic
+ asm op -- Compiles an instruction of the given opcode (with no argument)
+ #asm op arg -- Compiles an instruction of the given opcode with an argument
+ >asm op -- Compiles an instruction of the given opcode with a blank argument, and stores the argument's address to the R stack
+ resolve addr -- Writes the top of the stack to the address on top of the R stack (used to resolve a blank argument left by >asm)
+ { -- Begins an anonymous function
+ } -- addr Ends an anonymous function and leaves its address on the stack
+ exit -- Early return from a word definition
+ continue word -- Jumps to the given word as a tail call (replacing the current stack frame)
+ postpone word -- Delays execution of the given word: compiles a call to a compile-mode word or compiles instructions to compile a call to an interpret-mode word (see the NovaForth guide)
+ literal -- addr Compiles an instruction to push the number at the top of the stack
+
+
Dictionary manipulation
+
+ create name -- Creates a dictionary entry for a given word (pointing to the heap)
+ immediate -- Moves the most recently defined word from the interpret dictionary to the compile dictionary
+ does> -- addr Defines the runtime behavior of a defining word (see the NovaForth guide)
+ ' word -- addr Consumes a word, looks it up in the dictionary, and returns its address
+ ['] -- addr Consumes a word at runtime, looks it up in the dictionary, and returns its address
+
+
Miscellaneous
+
+ \ -- Begins a comment that lasts to the end of the line
+ ( -- Begins a comment that lasts to the matching close paren
+
+
Prelude
+
These definitions are run when the console is loaded:
+
+
+
This early-access demo was created by Ross Andrews in January 2023
+
+
+
+
diff --git a/vweb/src/lib.rs b/vweb/src/lib.rs
new file mode 100644
index 0000000..a4363a5
--- /dev/null
+++ b/vweb/src/lib.rs
@@ -0,0 +1,89 @@
+use wasm_bindgen::prelude::*;
+use vcore::{CPU, Word};
+use vcore::memory::{PeekPoke, PeekPokeExt};
+
+#[wasm_bindgen]
+pub struct NovaForth;
+
+#[wasm_bindgen]
+impl NovaForth {
+ pub fn rom() -> Vec {
+ Vec::from(*include_bytes!("../4th/4th.rom"))
+ }
+
+ pub fn symbols() -> String {
+ include_str!("../4th/4th.rom.sym").into()
+ }
+ pub fn prelude() -> String { include_str!("../4th/prelude.f").into() }
+}
+
+#[wasm_bindgen]
+pub struct WasmCPU(CPU);
+
+#[wasm_bindgen]
+impl WasmCPU {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> WasmCPU {
+ WasmCPU(CPU::new_random())
+ }
+
+ pub fn push_data(&mut self, data: i32) {
+ self.0.push_data(data)
+ }
+
+ pub fn pop_data(&mut self) -> i32 {
+ self.0.pop_data().into()
+ }
+
+ pub fn push_call(&mut self, data: i32) {
+ self.0.push_call(data)
+ }
+
+ pub fn pop_call(&mut self) -> i32 {
+ self.0.pop_call().into()
+ }
+
+ pub fn poke(&mut self, addr: u32, val: u8) {
+ self.0.poke(addr.into(), val.into())
+ }
+
+ pub fn peek(&self, addr: u32) -> u8 {
+ self.0.peek(addr.into()).into()
+ }
+
+ pub fn poke24(&mut self, addr: u32, val: i32) {
+ self.0.poke24(Word::from(addr), Word::from(val))
+ }
+
+ pub fn peek24(&self, addr: u32) -> i32 {
+ self.0.peek24(Word::from(addr)).into()
+ }
+
+ pub fn pc(&self) -> u32 {
+ self.0.pc().into()
+ }
+
+ pub fn sp(&self) -> u32 {
+ self.0.sp().into()
+ }
+
+ pub fn dp(&self) -> u32 {
+ self.0.dp().into()
+ }
+
+ pub fn set_pc(&mut self, val: u32) {
+ self.0.set_pc(Word::from(val))
+ }
+
+ pub fn run(&mut self) {
+ self.0.run_to_halt()
+ }
+
+ pub fn load(&mut self, rom: Vec) {
+ for (i, b) in rom.iter().enumerate() {
+ self.poke(0x400 + i as u32, *b)
+ }
+
+ self.set_pc(0x400)
+ }
+}