From 2dea244673a19629f55320b130641c742ba478e8 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sun, 1 Jan 2023 17:26:21 -0600 Subject: [PATCH] Web-based NovaForth repl --- .gitignore | 2 + vweb/4th/4th.rom | Bin 0 -> 3561 bytes vweb/4th/4th.rom.sym | 1 + vweb/4th/prelude.f | 25 ++ vweb/Cargo.toml | 13 + vweb/build.sh | 5 + vweb/esbuild.js | 9 + vweb/package-lock.json | 650 +++++++++++++++++++++++++++++++++++++++++ vweb/package.json | 10 + vweb/src/app.css | 34 +++ vweb/src/app.js | 72 +++++ vweb/src/index.html | 187 ++++++++++++ vweb/src/lib.rs | 89 ++++++ 13 files changed, 1097 insertions(+) create mode 100644 vweb/4th/4th.rom create mode 100644 vweb/4th/4th.rom.sym create mode 100644 vweb/4th/prelude.f create mode 100644 vweb/Cargo.toml create mode 100755 vweb/build.sh create mode 100644 vweb/esbuild.js create mode 100644 vweb/package-lock.json create mode 100644 vweb/package.json create mode 100644 vweb/src/app.css create mode 100644 vweb/src/app.js create mode 100644 vweb/src/index.html create mode 100644 vweb/src/lib.rs 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 0000000000000000000000000000000000000000..723be219fe0dc7a1e9854b2ad59e5e2c304dd713 GIT binary patch literal 3561 zcmd^BU5s318GcUBoZb1^`Oegev{K@Sro zIpdr&?Fb9oCL2b?3k_hlGMYwH!vz=81TUHx6D@iHQE5z!QZS7+Ai+cvXaWl3^PV$X zNsqwivuR7{qJkZaTa$7+6M`cRfEC__G$Q z)S`}GIhP3ePV!>86z>|`?WEwoI2bJ5TY!IG3VP}O;lSx`oeVdCn)&B>Y77`3$n`aY znw%8%r?`B*7?Ro5&u3FBmcd187UTi<;$YB^b?(;;k#^#pBCXwNA+Ujdz92{342L4< zzCim+ly>5a`CevM&2Z&`$-9CPPK%B+xxc*oQyYUcXCI3d8NQemJ>Ay^13h%>IzB{I z!eoO!(t5?|p6YIy2xybj4TsMfmeIG275CCm-6glc-C%;p#earljm#L!;++}8GM8n$ z$B8x4<4BjxsXY8^Qt)r0>-MeLWX9*Ml3it~l3Z zMS!*^A#9tuW{4@RB&q%CzO8W4!An)q)!DjmCdfVBZl}oH$F5Pc^DY@el zDT(dyhN}>q86tRTp@8uR#}G1t{+S$locnbPJz65xbc0F619{MI(ljxV&b}hVb-ASK zo@dx(_diK?L;B%eVPKpSuyG?%nb7!~fa`no{kxsHI{R;eZQeKrm&h{@jKxN2|07u# z*Sgua2Uo87A)z2XAf+obx$`|1jrcvlx zEy>hjD)*h3YZ4f)_>U#Z3iS&j`@$Iggj80H^?qRl{)NN~nbfH*Ld$)T(|&Pem?@Nw z@yeT|*vNQ}%o;zi;=h?3?r#wdO|Bao#EsfKoGACL%*aY<4GibhuHagd*P_s5)hNcb z)pTEp>D`=9gmpr;i*e6#nK#qP*kL}AfR~2?2^;IBWiqbBrPrZOl4Y?$aioO3yRkpn zY8l4)zm)~b=y8@Llw-BvDBq&6>-lnWkG0La$m3qjs(7&Y zxDa%j)uP8=uu!ZuTb&3~Q$%Bsk6G}wF4YQ}QLWkW@cU8p@^qpXPUDd@ z9?moU!>AQBJzN~c4|rnyPJAYdC-SJ*BClPkgE@OPkK1u~8b8kq4G@9PzPzvOWr{+h$9c^tyCV|Xo(3DBqi%Hu}Rul(GEF$JZN(v$dr!g__Y z-j1TeW`!|*Gqx-2P$=lFxK-h!3h&oKiTf2ksTeMyMn~a*;-A8pBEOW!iNa1mXTlExv ztZ+u*HoXsL6@IF)WkPSm(+a;**r*rr8-?c-iuy4Apzu7mweb5Tg_jg2wAc0APUPWr zh06+u?gUfiZ3VaORk+Fum9wz}Gij`|F%A~b>ul`Bd>S{{s5Kg1wI*_V(zwN@doO2j zyNycN04v%ZHg1q1Q@(6tT83s8vo-`sh|*ZF(PA-Z*@$dNeQS0a^IjX@9K{J6R1K{x z9SPXf0o%04B6nQ@A1dGo zGhchWYF!rFDdvSi{ip|0#cc&*>s$`|3;1e>TD(4n&oX}mUSB9A@R}`Lf!8Z*!M`U@ brN9z!^(Up^k(0@nsjHX&FUO>!|MBPFteqnV literal 0 HcmV?d00001 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 -- cAddition
-a b -- cSubtraction
*a b -- cMultiplication
/a b -- cDivision (truncates to integer)
%a b -- cModulus
popa --Throw away the top element of the stack
dupa -- a aDuplicate the top element of the stack
swapa b -- b aSwap the top two elements of the stack
pick* index -- * elCopy 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
rota b c -- b c aRotate the top three elements on the stack
-rota b c -- c a bRotate the top three elements on the stack the other direction (prelude word)
?dupa -- a a OR 0 -- 0Duplicates the top of stack if it's nonzero
>ra --Pushes the top of the stack to the R stack
r>-- aPushes the top of the R stack to the stack
r@-- aCopies the top of the R stack to the stack, leaving the R stack unchanged
rpickindex -- aPicks 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)
lshifta b -- cBinary-shifts "a" left "b" bits, shifting in zeroes (prelude word)
rshifta b -- cBinary-shifts "a" right "b" bits, shifting in zeroes (prelude word)
arshifta b -- cArithmetic right shift: right-shift leaving the high order bit untouched (to preserve sign) (prelude word)
rora -- bRoll one bit right, moving the low bit to the high bit (prelude word)
rola -- bRoll one bit left, moving the high bit to the low bit (prelude word)
overa b -- a b aDuplicate the value one below top (prelude word)
nipa b -- bDrop the value one below top (prelude word)
tucka b -- b a bDuplicate the top value, placing it underneath the one below top (prelude word)
2dupa b -- a b a bDuplicate the top two values (prelude word)
negatea -- bMultiply the top of stack by -1 (prelude word)
absa -- bReturn the absolute value of the top of stack (prelude word)
+

I/O

+ + + + + + + + + + + + + + +
word word-- addrCopies word to the pad, leaving its address on the stack
pad-- addrPushes the address of the pad to the stack
number str-- num 1 OR -- 0Tries 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"-- addrReads 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
emitch --Emits a byte (as ASCII) to the console
printaddr --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 -- cReturns a 1 if a is greater than b, 0 otherwise (signed comparison)
<a b -- cReturns a 1 if a is less than b, 0 otherwise (signed comparison)
u>a b -- cReturns a 1 if a is greater than b, 0 otherwise (unsigned comparison) (prelude word)
u<a b -- cReturns a 1 if a is less than b, 0 otherwise (unsigned comparison) (prelude word)
=a b -- cReturns a 1 if a is equal to b, 0 otherwise
&a b -- cBitwise-and of a and b
|a b -- cBitwise-or of a and b
^a b -- cBitwise-xor of a and b
nota -- bIf the argument is a 0, returns 1. Otherwise returns 0
comparea b -- cCompares two strings for equality, returns 1 if they're equal and 0 otherwise
if / else / thencond --A conditional branch: "condition if truepath else falsepath then". The "else" branch is optional (prelude words)
+

Memory management

+ + + + + + + + + + + + +
@addr -- valRead the 3-byte word at the given address
!val addr --Write a 3-byte word to the given address
c@addr -- valRead one byte from the given address
c!val addr --Write one byte to the given address
&heap-- addrPushes the address of the heap pointer
here-- addrPushes 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)
allotval -- addrIncrement the heap by "val" bytes, returning the start address of the allotted region (prelude word)
freeval -- addrDecrement the heap by "val" bytes, returning the new heap address (prelude word)
+

Assembly and compilation

+ + + + + + + + + + + + + + + + + + +
executeaddr --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-- aPushes the equivalent opcode to the given instruction mnemonic
asmop --Compiles an instruction of the given opcode (with no argument)
#asmop arg --Compiles an instruction of the given opcode with an argument
>asmop --Compiles an instruction of the given opcode with a blank argument, and stores the argument's address to the R stack
resolveaddr --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
}-- addrEnds 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-- addrCompiles 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>-- addrDefines the runtime behavior of a defining word (see the NovaForth guide)
' word-- addrConsumes a word, looks it up in the dictionary, and returns its address
[']-- addrConsumes 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) + } +}