summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2025-09-12 17:20:19 +1200
committerBen Bridle <ben@derelict.engineering>2025-09-12 17:28:44 +1200
commit19c5679b8be4fe879e165576e97f96b85f5b5044 (patch)
treede10dbc9a23514adbddfd563d38b1238dfce6241
parent4c67a10f265dde1c3aa430cb851062a38646d6f2 (diff)
downloadbedrock-js-19c5679b8be4fe879e165576e97f96b85f5b5044.zip
Implement a faster core in WebAssembly
This is a massive commit that restructures a lot of the library. The primary change is the implementation of a second Bedrock core using WebAssembly, which performs much better than the existing JavaScript core. The JavaScript core has been retained as a fallback for browsers that don't support WebAssembly. Benchmarking both cores using the numbers benchmark and with all of the devices stubbed out in the emulator demonstrates a 40x speedup for the WebAssembly implementation (going from 4800ms to 120ms).
-rw-r--r--bedrock-core.wat484
-rw-r--r--bedrock.js2211
2 files changed, 1728 insertions, 967 deletions
diff --git a/bedrock-core.wat b/bedrock-core.wat
new file mode 100644
index 0000000..4aa8173
--- /dev/null
+++ b/bedrock-core.wat
@@ -0,0 +1,484 @@
+(module
+ ;; Import device functions from JavaScript.
+ (import "bedrock" "dget1" (func $dget1 (param $p i32) (result i32)))
+ (import "bedrock" "dset1" (func $dset1 (param $p i32) (param $v i32) (result i32)))
+
+ ;; We instantiate and export two pages of memory (65536*2 bytes).
+ ;; The first 65535 bytes are program memory, the next 256 bytes are the
+ ;; working stack, and the following 256 bytes are the return stack.
+ ;; Stacks will be downward-growing so that we can push and pop doubles
+ ;; at a time with a single 16-bit instruction (access is little-endian).
+ (memory (export "memory") 2)
+
+ ;; Cycle counter and pointer declarations. Stack pointers are downward-growing
+ ;; and point to the highest byte on the stack, not to the next empty byte.
+ (global $cc (mut i64) (i64.const 0))
+ (global $ip (mut i32) (i32.const 0))
+ (global $wp (mut i32) (i32.const 0x10100))
+ (global $rp (mut i32) (i32.const 0x10200))
+ ;; Memory offset constants.
+ (global $ip-base i32 (i32.const 0))
+ (global $wp-base i32 (i32.const 0x10100))
+ (global $rp-base i32 (i32.const 0x10200))
+ ;; Signal constants.
+ (global $SIGNAL.BREAK i32 (i32.const 0))
+ (global $SIGNAL.HALT i32 (i32.const 1))
+ (global $SIGNAL.DB1 i32 (i32.const 2))
+ (global $SIGNAL.DB2 i32 (i32.const 3))
+ (global $SIGNAL.DB3 i32 (i32.const 4))
+ (global $SIGNAL.DB4 i32 (i32.const 5))
+ (global $SIGNAL.DB5 i32 (i32.const 6))
+ (global $SIGNAL.DB6 i32 (i32.const 7))
+ (global $SIGNAL.SLEEP i32 (i32.const 8))
+ (global $SIGNAL.RESET i32 (i32.const 9))
+ (global $SIGNAL.FORK i32 (i32.const 10))
+
+ ;; Re-initialise pointers.
+ (func (export "reset")
+ (global.set $ip (global.get $ip-base))
+ (global.set $wp (global.get $wp-base))
+ (global.set $rp (global.get $rp-base)) )
+
+ ;; Provide external access to normal pointer values.
+ (func (export "cc") (result i64) global.get $cc )
+ (func (export "ip") (result i32) global.get $ip )
+ (func (export "wp") (result i32) (i32.sub (global.get $wp-base) (global.get $wp)) )
+ (func (export "rp") (result i32) (i32.sub (global.get $rp-base) (global.get $rp)) )
+
+ ;; Push value micro-instructions.
+ (func $wpsh1 (export "wpsh1") (param $v i32)
+ (global.set $wp (i32.sub (global.get $wp) (i32.const 1)))
+ (i32.store8 (global.get $wp) (local.get $v)) )
+ (func $wpsh2 (export "wpsh2") (param $v i32)
+ (global.set $wp (i32.sub (global.get $wp) (i32.const 2)))
+ (i32.store16 (global.get $wp) (local.get $v)) )
+ (func $rpsh1 (export "rpsh1") (param $v i32)
+ (global.set $rp (i32.sub (global.get $rp) (i32.const 1)))
+ (i32.store8 (global.get $rp) (local.get $v)) )
+ (func $rpsh2 (export "rpsh2") (param $v i32)
+ (global.set $rp (i32.sub (global.get $rp) (i32.const 2)))
+ (i32.store16 (global.get $rp) (local.get $v)) )
+ (func $wpshb (export "wpshb") (param $b i32)
+ (select (i32.const 0xFF) (i32.const 0) (local.get $b))
+ (call $wpsh1) )
+ (func $rpshb (export "rpshb") (param $b i32)
+ (select (i32.const 0xFF) (i32.const 0) (local.get $b))
+ (call $rpsh1) )
+ ;; Pop value micro-instructions.
+ (func $wpop1 (export "wpop1") (result i32)
+ (i32.load8_u (global.get $wp))
+ (global.set $wp (i32.add (global.get $wp) (i32.const 1))) )
+ (func $wpop2 (export "wpop2") (result i32)
+ (i32.load16_u (global.get $wp))
+ (global.set $wp (i32.add (global.get $wp) (i32.const 2))) )
+ (func $rpop1 (export "rpop1") (result i32)
+ (i32.load8_u (global.get $rp))
+ (global.set $rp (i32.add (global.get $rp) (i32.const 1))) )
+ (func $rpop2 (export "rpop2") (result i32)
+ (i32.load16_u (global.get $rp))
+ (global.set $rp (i32.add (global.get $rp) (i32.const 2))) )
+ (func $mpop1 (export "mpop1") (result i32)
+ (i32.load8_u (global.get $ip))
+ (global.set $ip (i32.add (global.get $ip) (i32.const 1))) )
+ (func $mpop2 (export "mpop2") (result i32)
+ (i32.shl (i32.load8_u (global.get $ip)) (i32.const 8))
+ (i32.load8_u (i32.add (global.get $ip) (i32.const 1)))
+ (i32.or)
+ (global.set $ip (i32.add (global.get $ip) (i32.const 2))) )
+ ;; Get value micro-instructions.
+ (func $wget1 (export "wget1") (result i32)
+ (i32.load8_u (global.get $wp)) )
+ (func $wget2 (export "wget2") (result i32)
+ (i32.load16_u (global.get $wp)) )
+ (func $rget1 (export "rget1") (result i32)
+ (i32.load8_u (global.get $rp)) )
+ (func $rget2 (export "rget2") (result i32)
+ (i32.load16_u (global.get $rp)) )
+ (func $mget1 (export "mget1") (param $a i32) (result i32)
+ (i32.load8_u (local.get $a)) )
+ (func $mget2 (export "mget2") (param $a i32) (result i32)
+ (i32.shl (i32.load8_u (local.get $a)) (i32.const 8))
+ (i32.load8_u (i32.add (local.get $a) (i32.const 1)))
+ (i32.or) )
+ (func $dget2 (export "dget2") (param $p i32) (result i32)
+ (i32.shl (call $dget1 (local.get $p)) (i32.const 8))
+ (call $dget1 (i32.add (local.get $p) (i32.const 1)))
+ (i32.or) )
+ ;; Set value micro-instructions.
+ (func $mset1 (export "mset1") (param $a i32) (param $v i32)
+ (i32.store8 (local.get $a) (local.get $v)) )
+ (func $mset2 (export "mset2") (param $a i32) (param $v i32)
+ (i32.store8 (local.get $a) (i32.shr_u (local.get $v) (i32.const 8)))
+ (i32.store8 (i32.add (local.get $a) (i32.const 1)) (i32.and (local.get $v) (i32.const 0xFF))) )
+ (func $dset2 (export "dset2") (param $p i32) (param $v i32) (result i32)
+ (call $dset1 (local.get $p) (i32.shr_u (local.get $v) (i32.const 8)))
+ (call $dset1 (i32.add (local.get $p) (i32.const 1)) (i32.and (local.get $v) (i32.const 0xFF)))
+ (i32.or) ) ;; TODO: Signals are or'd together, at least one must be zero for this to work.
+ ;; Pointer movement micro-instructions.
+ (func $ipmov (export "ipmov") (param $d i32)
+ (global.set $ip (i32.add (global.get $ip) (local.get $d))) )
+ (func $wpmov (export "wpmov") (param $d i32)
+ (global.set $wp (i32.sub (global.get $wp) (local.get $d))) )
+ (func $rpmov (export "rpmov") (param $d i32)
+ (global.set $rp (i32.sub (global.get $rp) (local.get $d))) )
+ ;; Math operations
+ (func $rol1 (export "rol1") (param $v i32) (param $d i32) (result i32)
+ (local.set $d (i32.and (local.get $d) (i32.const 0x7)))
+ (i32.shl (local.get $v) (local.get $d))
+ (i32.shr_u (local.get $v) (i32.sub (i32.const 8) (local.get $d)))
+ (i32.or) )
+ (func $ror1 (export "ror1") (param $v i32) (param $d i32) (result i32)
+ (local.set $d (i32.and (local.get $d) (i32.const 0x7)))
+ (i32.shl (local.get $v) (i32.sub (i32.const 8) (local.get $d)))
+ (i32.shr_u (local.get $v) (local.get $d))
+ (i32.or) )
+ (func $rol2 (export "rol2") (param $v i32) (param $d i32) (result i32)
+ (local.set $d (i32.and (local.get $d) (i32.const 0xF)))
+ (i32.shl (local.get $v) (local.get $d))
+ (i32.shr_u (local.get $v) (i32.sub (i32.const 16) (local.get $d)))
+ (i32.or) )
+ (func $ror2 (export "ror2") (param $v i32) (param $d i32) (result i32)
+ (local.set $d (i32.and (local.get $d) (i32.const 0xF)))
+ (i32.shl (local.get $v) (i32.sub (i32.const 16) (local.get $d)))
+ (i32.shr_u (local.get $v) (local.get $d))
+ (i32.or) )
+
+
+ ;; TODO: Add a max-cycles parameter, and also keep track of total cycles with an i64.
+ (func (export "eval") (param $c i32) (result i32)
+ ;; Declare local variables.
+ (local $m i64) ;; end mark
+ (local $x i32) ;; register
+
+ (local.set $m (i64.add (global.get $cc) (i64.extend_i32_u (local.get $c))))
+
+ (loop $loop
+ (block $break
+ ;; Increment the cycle counter if we haven't reached our mark.
+ (if (i64.eq (local.get $m) (global.get $cc)) (then (br $break)))
+ (global.set $cc (i64.add (global.get $cc) (i64.const 1)))
+
+ ;; Switch statement for every instruction variant. The list is in reverse order.
+ (block $NOTr*: (block $ANDr*: (block $XORr*: (block $IORr*: (block $RORr*: (block $ROLr*: (block $SHRr*: (block $SHLr*:
+ (block $NQKr*: (block $EQUr*: (block $GTHr*: (block $LTHr*: (block $DECr*: (block $INCr*: (block $SUBr*: (block $ADDr*:
+ (block $STDr*: (block $LDDr*: (block $STAr*: (block $LDAr*: (block $JCSr*: (block $JCNr*: (block $JMSr*: (block $JMPr*:
+ (block $ROTr*: (block $SWPr*: (block $OVRr*: (block $DUPr*: (block $CPYr*: (block $POPr*: (block $PSHr*: (block $DB6
+ (block $NOTr* (block $ANDr* (block $XORr* (block $IORr* (block $RORr* (block $ROLr* (block $SHRr* (block $SHLr*
+ (block $NQKr* (block $EQUr* (block $GTHr* (block $LTHr* (block $DECr* (block $INCr* (block $SUBr* (block $ADDr*
+ (block $STDr* (block $LDDr* (block $STAr* (block $LDAr* (block $JCSr* (block $JCNr* (block $JMSr* (block $JMPr*
+ (block $ROTr* (block $SWPr* (block $OVRr* (block $DUPr* (block $CPYr* (block $POPr* (block $PSHr* (block $DB5
+ (block $NOTr: (block $ANDr: (block $XORr: (block $IORr: (block $RORr: (block $ROLr: (block $SHRr: (block $SHLr:
+ (block $NQKr: (block $EQUr: (block $GTHr: (block $LTHr: (block $DECr: (block $INCr: (block $SUBr: (block $ADDr:
+ (block $STDr: (block $LDDr: (block $STAr: (block $LDAr: (block $JCSr: (block $JCNr: (block $JMSr: (block $JMPr:
+ (block $ROTr: (block $SWPr: (block $OVRr: (block $DUPr: (block $CPYr: (block $POPr: (block $PSHr: (block $DB4
+ (block $NOTr (block $ANDr (block $XORr (block $IORr (block $RORr (block $ROLr (block $SHRr (block $SHLr
+ (block $NQKr (block $EQUr (block $GTHr (block $LTHr (block $DECr (block $INCr (block $SUBr (block $ADDr
+ (block $STDr (block $LDDr (block $STAr (block $LDAr (block $JCSr (block $JCNr (block $JMSr (block $JMPr
+ (block $ROTr (block $SWPr (block $OVRr (block $DUPr (block $CPYr (block $POPr (block $PSHr (block $DB3
+ (block $NOT*: (block $AND*: (block $XOR*: (block $IOR*: (block $ROR*: (block $ROL*: (block $SHR*: (block $SHL*:
+ (block $NQK*: (block $EQU*: (block $GTH*: (block $LTH*: (block $DEC*: (block $INC*: (block $SUB*: (block $ADD*:
+ (block $STD*: (block $LDD*: (block $STA*: (block $LDA*: (block $JCS*: (block $JCN*: (block $JMS*: (block $JMP*:
+ (block $ROT*: (block $SWP*: (block $OVR*: (block $DUP*: (block $CPY*: (block $POP*: (block $PSH*: (block $DB2
+ (block $NOT* (block $AND* (block $XOR* (block $IOR* (block $ROR* (block $ROL* (block $SHR* (block $SHL*
+ (block $NQK* (block $EQU* (block $GTH* (block $LTH* (block $DEC* (block $INC* (block $SUB* (block $ADD*
+ (block $STD* (block $LDD* (block $STA* (block $LDA* (block $JCS* (block $JCN* (block $JMS* (block $JMP*
+ (block $ROT* (block $SWP* (block $OVR* (block $DUP* (block $CPY* (block $POP* (block $PSH* (block $DB1
+ (block $NOT: (block $AND: (block $XOR: (block $IOR: (block $ROR: (block $ROL: (block $SHR: (block $SHL:
+ (block $NQK: (block $EQU: (block $GTH: (block $LTH: (block $DEC: (block $INC: (block $SUB: (block $ADD:
+ (block $STD: (block $LDD: (block $STA: (block $LDA: (block $JCS: (block $JCN: (block $JMS: (block $JMP:
+ (block $ROT: (block $SWP: (block $OVR: (block $DUP: (block $CPY: (block $POP: (block $PSH: (block $NOP
+ (block $NOT (block $AND (block $XOR (block $IOR (block $ROR (block $ROL (block $SHR (block $SHL
+ (block $NQK (block $EQU (block $GTH (block $LTH (block $DEC (block $INC (block $SUB (block $ADD
+ (block $STD (block $LDD (block $STA (block $LDA (block $JCS (block $JCN (block $JMS (block $JMP
+ (block $ROT (block $SWP (block $OVR (block $DUP (block $CPY (block $POP (block $PSH (block $HLT
+ ;; Branch based on the instruction on the stack.
+ (br_table
+ $HLT $PSH $POP $CPY $DUP $OVR $SWP $ROT $JMP $JMS $JCN $JCS $LDA $STA $LDD $STD
+ $ADD $SUB $INC $DEC $LTH $GTH $EQU $NQK $SHL $SHR $ROL $ROR $IOR $XOR $AND $NOT
+ $NOP $PSH: $POP: $CPY: $DUP: $OVR: $SWP: $ROT: $JMP: $JMS: $JCN: $JCS: $LDA: $STA: $LDD: $STD:
+ $ADD: $SUB: $INC: $DEC: $LTH: $GTH: $EQU: $NQK: $SHL: $SHR: $ROL: $ROR: $IOR: $XOR: $AND: $NOT:
+ $DB1 $PSH* $POP* $CPY* $DUP* $OVR* $SWP* $ROT* $JMP* $JMS* $JCN* $JCS* $LDA* $STA* $LDD* $STD*
+ $ADD* $SUB* $INC* $DEC* $LTH* $GTH* $EQU* $NQK* $SHL* $SHR* $ROL* $ROR* $IOR* $XOR* $AND* $NOT*
+ $DB2 $PSH*: $POP*: $CPY*: $DUP*: $OVR*: $SWP*: $ROT*: $JMP*: $JMS*: $JCN*: $JCS*: $LDA*: $STA*: $LDD*: $STD*:
+ $ADD*: $SUB*: $INC*: $DEC*: $LTH*: $GTH*: $EQU*: $NQK*: $SHL*: $SHR*: $ROL*: $ROR*: $IOR*: $XOR*: $AND*: $NOT*:
+ $DB3 $PSHr $POPr $CPYr $DUPr $OVRr $SWPr $ROTr $JMPr $JMSr $JCNr $JCSr $LDAr $STAr $LDDr $STDr
+ $ADDr $SUBr $INCr $DECr $LTHr $GTHr $EQUr $NQKr $SHLr $SHRr $ROLr $RORr $IORr $XORr $ANDr $NOTr
+ $DB4 $PSHr: $POPr: $CPYr: $DUPr: $OVRr: $SWPr: $ROTr: $JMPr: $JMSr: $JCNr: $JCSr: $LDAr: $STAr: $LDDr: $STDr:
+ $ADDr: $SUBr: $INCr: $DECr: $LTHr: $GTHr: $EQUr: $NQKr: $SHLr: $SHRr: $ROLr: $RORr: $IORr: $XORr: $ANDr: $NOTr:
+ $DB5 $PSHr* $POPr* $CPYr* $DUPr* $OVRr* $SWPr* $ROTr* $JMPr* $JMSr* $JCNr* $JCSr* $LDAr* $STAr* $LDDr* $STDr*
+ $ADDr* $SUBr* $INCr* $DECr* $LTHr* $GTHr* $EQUr* $NQKr* $SHLr* $SHRr* $ROLr* $RORr* $IORr* $XORr* $ANDr* $NOTr*
+ $DB6 $PSHr*: $POPr*: $CPYr*: $DUPr*: $OVRr*: $SWPr*: $ROTr*: $JMPr*: $JMSr*: $JCNr*: $JCSr*: $LDAr*: $STAr*: $LDDr*: $STDr*:
+ $ADDr*: $SUBr*: $INCr*: $DECr*: $LTHr*: $GTHr*: $EQUr*: $NQKr*: $SHLr*: $SHRr*: $ROLr*: $RORr*: $IORr*: $XORr*: $ANDr*: $NOTr*:
+ ;; Load an instruction from memory.
+ (call $mpop1) )
+
+ ) (; HLT ;) (return (global.get $SIGNAL.HALT))
+ ) (; PSH ;) (call $rpop1) (call $wpsh1) (br $loop)
+ ) (; POP ;) (call $wpmov (i32.const -1)) (br $loop)
+ ) (; CPY ;) (call $rget1) (call $wpsh1) (br $loop)
+ ) (; DUP ;) (call $wget1) (call $wpsh1) (br $loop)
+ ) (; OVR ;) (call $wpop1) (call $wget1) (local.set $x) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; SWP ;) (call $wpop1) (call $wpop1) (local.set $x) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; ROT ;) (call $wpop1) (call $wpop1) (call $wpop1) (local.set $x) (call $wpsh1) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; JMP ;) (call $wpop2) (global.set $ip) (br $loop)
+ ) (; JMS ;) (call $wpop2) (global.get $ip) (call $rpsh2) (global.set $ip) (br $loop)
+ ) (; JCN ;) (call $wpop2) (local.set $x) (if (call $wpop1) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCS ;) (call $wpop2) (local.set $x) (if (call $wpop1) (then (global.get $ip) (call $rpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDA ;) (call $wpop2) (call $mget1) (call $wpsh1) (br $loop)
+ ) (; STA ;) (call $wpop2) (call $wpop1) (call $mset1) (br $loop)
+ ) (; LDD ;) (call $wpop1) (call $dget1) (call $wpsh1) (br $loop)
+ ) (; STD ;) (call $wpop1) (call $wpop1) (call $dset1) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADD ;) (call $wpop1) (call $wpop1) (i32.add) (call $wpsh1) (br $loop)
+ ) (; SUB ;) (call $wpop1) (local.set $x) (call $wpop1) (local.get $x) (i32.sub) (call $wpsh1) (br $loop)
+ ) (; INC ;) (call $wpop1) (i32.add (i32.const 1)) (call $wpsh1) (br $loop)
+ ) (; DEC ;) (call $wpop1) (i32.sub (i32.const 1)) (call $wpsh1) (br $loop)
+ ) (; LTH ;) (call $wpop1) (call $wpop1) (i32.gt_u) (call $wpshb) (br $loop)
+ ) (; GTH ;) (call $wpop1) (call $wpop1) (i32.lt_u) (call $wpshb) (br $loop)
+ ) (; EQU ;) (call $wpop1) (call $wpop1) (i32.eq) (call $wpshb) (br $loop)
+ ) (; NQK ;) (call $wpop1) (local.tee $x) (call $wget1) (local.get $x) (call $wpsh1) (i32.ne) (call $wpshb) (br $loop)
+ ) (; SHL ;) (call $wpop1) (local.set $x) (call $wpop1) (local.get $x) (i32.shl) (call $wpsh1) (br $loop)
+ ) (; SHR ;) (call $wpop1) (local.set $x) (call $wpop1) (local.get $x) (i32.shr_u) (call $wpsh1) (br $loop)
+ ) (; ROL ;) (call $wpop1) (local.set $x) (call $wpop1) (local.get $x) (call $rol1) (call $wpsh1) (br $loop)
+ ) (; ROR ;) (call $wpop1) (local.set $x) (call $wpop1) (local.get $x) (call $ror1) (call $wpsh1) (br $loop)
+ ) (; IOR ;) (call $wpop1) (call $wpop1) (i32.or) (call $wpsh1) (br $loop)
+ ) (; XOR ;) (call $wpop1) (call $wpop1) (i32.xor) (call $wpsh1) (br $loop)
+ ) (; AND ;) (call $wpop1) (call $wpop1) (i32.and) (call $wpsh1) (br $loop)
+ ) (; NOT ;) (call $wpop1) (i32.xor (i32.const 0xFF)) (call $wpsh1) (br $loop)
+
+ ) (; NOP ;) (br $loop)
+ ) (; PSH: ;) (call $mpop1) (call $wpsh1) (br $loop)
+ ) (; POP: ;) (call $ipmov (i32.const 1)) (br $loop)
+ ) (; CPY: ;) (call $mpop1) (local.tee $x) (call $wpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; DUP: ;) (call $mpop1) (local.tee $x) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; OVR: ;) (call $mpop1) (call $wget1) (local.set $x) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; SWP: ;) (call $mpop1) (call $wpop1) (local.set $x) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; ROT: ;) (call $mpop1) (call $wpop1) (call $wpop1) (local.set $x) (call $wpsh1) (call $wpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; JMP: ;) (call $mpop2) (global.set $ip) (br $loop)
+ ) (; JMS: ;) (call $mpop2) (global.get $ip) (call $rpsh2) (global.set $ip) (br $loop)
+ ) (; JCN: ;) (call $mpop2) (local.set $x) (if (call $wpop1) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCS: ;) (call $mpop2) (local.set $x) (if (call $wpop1) (then (global.get $ip) (call $rpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDA: ;) (call $mpop2) (call $mget1) (call $wpsh1) (br $loop)
+ ) (; STA: ;) (call $mpop2) (call $wpop1) (call $mset1) (br $loop)
+ ) (; LDD: ;) (call $mpop1) (call $dget1) (call $wpsh1) (br $loop)
+ ) (; STD: ;) (call $mpop1) (call $wpop1) (call $dset1) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADD: ;) (call $mpop1) (call $wpop1) (i32.add) (call $wpsh1) (br $loop)
+ ) (; SUB: ;) (call $mpop1) (local.set $x) (call $wpop1) (local.get $x) (i32.sub) (call $wpsh1) (br $loop)
+ ) (; INC: ;) (call $mpop1) (i32.add (i32.const 1)) (call $wpsh1) (br $loop)
+ ) (; DEC: ;) (call $mpop1) (i32.sub (i32.const 1)) (call $wpsh1) (br $loop)
+ ) (; LTH: ;) (call $mpop1) (call $wpop1) (i32.gt_u) (call $wpshb) (br $loop)
+ ) (; GTH: ;) (call $mpop1) (call $wpop1) (i32.lt_u) (call $wpshb) (br $loop)
+ ) (; EQU: ;) (call $mpop1) (call $wpop1) (i32.eq) (call $wpshb) (br $loop)
+ ) (; NQK: ;) (call $mpop1) (local.tee $x) (call $wget1) (local.get $x) (call $wpsh1) (i32.ne) (call $wpshb) (br $loop)
+ ) (; SHL: ;) (call $mpop1) (local.set $x) (call $wpop1) (local.get $x) (i32.shl) (call $wpsh1) (br $loop)
+ ) (; SHR: ;) (call $mpop1) (local.set $x) (call $wpop1) (local.get $x) (i32.shr_u) (call $wpsh1) (br $loop)
+ ) (; ROL: ;) (call $mpop1) (local.set $x) (call $wpop1) (local.get $x) (call $rol1) (call $wpsh1) (br $loop)
+ ) (; ROR: ;) (call $mpop1) (local.set $x) (call $wpop1) (local.get $x) (call $ror1) (call $wpsh1) (br $loop)
+ ) (; IOR: ;) (call $mpop1) (call $wpop1) (i32.or) (call $wpsh1) (br $loop)
+ ) (; XOR: ;) (call $mpop1) (call $wpop1) (i32.xor) (call $wpsh1) (br $loop)
+ ) (; AND: ;) (call $mpop1) (call $wpop1) (i32.and) (call $wpsh1) (br $loop)
+ ) (; NOT: ;) (call $mpop1) (i32.xor (i32.const 0xFF)) (call $wpsh1) (br $loop)
+
+ ) (; DB1* ;) (return (global.get $SIGNAL.DB1))
+ ) (; PSH* ;) (call $rpop2) (call $wpsh2) (br $loop)
+ ) (; POP* ;) (call $wpmov (i32.const -2)) (br $loop)
+ ) (; CPY* ;) (call $rget2) (call $wpsh2) (br $loop)
+ ) (; DUP* ;) (call $wget2) (call $wpsh2) (br $loop)
+ ) (; OVR* ;) (call $wpop2) (call $wget2) (local.set $x) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; SWP* ;) (call $wpop2) (call $wpop2) (local.set $x) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; ROT* ;) (call $wpop2) (call $wpop2) (call $wpop2) (local.set $x) (call $wpsh2) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; JMP* ;) (call $wpop2) (global.set $ip) (br $loop)
+ ) (; JMS* ;) (call $wpop2) (global.get $ip) (call $rpsh2) (global.set $ip) (br $loop)
+ ) (; JCN* ;) (call $wpop2) (local.set $x) (if (call $wpop2) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCS* ;) (call $wpop2) (local.set $x) (if (call $wpop2) (then (global.get $ip) (call $rpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDA* ;) (call $wpop2) (call $mget2) (call $wpsh2) (br $loop)
+ ) (; STA* ;) (call $wpop2) (call $wpop2) (call $mset2) (br $loop)
+ ) (; LDD* ;) (call $wpop1) (call $dget2) (call $wpsh2) (br $loop)
+ ) (; STD* ;) (call $wpop1) (call $wpop2) (call $dset2) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADD* ;) (call $wpop2) (call $wpop2) (i32.add) (call $wpsh2) (br $loop)
+ ) (; SUB* ;) (call $wpop2) (local.set $x) (call $wpop2) (local.get $x) (i32.sub) (call $wpsh2) (br $loop)
+ ) (; INC* ;) (call $wpop2) (i32.add (i32.const 1)) (call $wpsh2) (br $loop)
+ ) (; DEC* ;) (call $wpop2) (i32.sub (i32.const 1)) (call $wpsh2) (br $loop)
+ ) (; LTH* ;) (call $wpop2) (call $wpop2) (i32.gt_u) (call $wpshb) (br $loop)
+ ) (; GTH* ;) (call $wpop2) (call $wpop2) (i32.lt_u) (call $wpshb) (br $loop)
+ ) (; EQU* ;) (call $wpop2) (call $wpop2) (i32.eq) (call $wpshb) (br $loop)
+ ) (; NQK* ;) (call $wpop2) (local.tee $x) (call $wget2) (local.get $x) (call $wpsh2) (i32.ne) (call $wpshb) (br $loop)
+ ) (; SHL* ;) (call $wpop1) (local.set $x) (call $wpop2) (local.get $x) (i32.shl) (call $wpsh2) (br $loop)
+ ) (; SHR* ;) (call $wpop1) (local.set $x) (call $wpop2) (local.get $x) (i32.shr_u) (call $wpsh2) (br $loop)
+ ) (; ROL* ;) (call $wpop1) (local.set $x) (call $wpop2) (local.get $x) (call $rol2) (call $wpsh2) (br $loop)
+ ) (; ROR* ;) (call $wpop1) (local.set $x) (call $wpop2) (local.get $x) (call $ror2) (call $wpsh2) (br $loop)
+ ) (; IOR* ;) (call $wpop2) (call $wpop2) (i32.or) (call $wpsh2) (br $loop)
+ ) (; XOR* ;) (call $wpop2) (call $wpop2) (i32.xor) (call $wpsh2) (br $loop)
+ ) (; AND* ;) (call $wpop2) (call $wpop2) (i32.and) (call $wpsh2) (br $loop)
+ ) (; NOT* ;) (call $wpop2) (i32.xor (i32.const 0xFFFF)) (call $wpsh2) (br $loop)
+
+ ) (; DB2 ;) (return (global.get $SIGNAL.DB2))
+ ) (; PSH*: ;) (call $mpop2) (call $wpsh2) (br $loop)
+ ) (; POP*: ;) (call $ipmov (i32.const 2)) (br $loop)
+ ) (; CPY*: ;) (call $mpop2) (local.tee $x) (call $wpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; DUP*: ;) (call $mpop2) (local.tee $x) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; OVR*: ;) (call $mpop2) (call $wget2) (local.set $x) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; SWP*: ;) (call $mpop2) (call $wpop2) (local.set $x) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; ROT*: ;) (call $mpop2) (call $wpop2) (call $wpop2) (local.set $x) (call $wpsh2) (call $wpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; JMP*: ;) (call $mpop2) (global.set $ip) (br $loop)
+ ) (; JMS*: ;) (call $mpop2) (global.get $ip) (call $rpsh2) (global.set $ip) (br $loop)
+ ) (; JCN*: ;) (call $mpop2) (local.set $x) (if (call $wpop2) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCS*: ;) (call $mpop2) (local.set $x) (if (call $wpop2) (then (global.get $ip) (call $rpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDA*: ;) (call $mpop2) (call $mget2) (call $wpsh2) (br $loop)
+ ) (; STA*: ;) (call $mpop2) (call $wpop2) (call $mset2) (br $loop)
+ ) (; LDD*: ;) (call $mpop1) (call $dget2) (call $wpsh2) (br $loop)
+ ) (; STD*: ;) (call $mpop1) (call $wpop2) (call $dset2) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADD*: ;) (call $mpop2) (call $wpop2) (i32.add) (call $wpsh2) (br $loop)
+ ) (; SUB*: ;) (call $mpop2) (local.set $x) (call $wpop2) (local.get $x) (i32.sub) (call $wpsh2) (br $loop)
+ ) (; INC*: ;) (call $mpop2) (i32.add (i32.const 1)) (call $wpsh2) (br $loop)
+ ) (; DEC*: ;) (call $mpop2) (i32.sub (i32.const 1)) (call $wpsh2) (br $loop)
+ ) (; LTH*: ;) (call $mpop2) (call $wpop2) (i32.gt_u) (call $wpshb) (br $loop)
+ ) (; GTH*: ;) (call $mpop2) (call $wpop2) (i32.lt_u) (call $wpshb) (br $loop)
+ ) (; EQU*: ;) (call $mpop2) (call $wpop2) (i32.eq) (call $wpshb) (br $loop)
+ ) (; NQK*: ;) (call $mpop2) (local.tee $x) (call $wget2) (local.get $x) (call $wpsh2) (i32.ne) (call $wpshb) (br $loop)
+ ) (; SHL*: ;) (call $mpop1) (local.set $x) (call $wpop2) (local.get $x) (i32.shl) (call $wpsh2) (br $loop)
+ ) (; SHR*: ;) (call $mpop1) (local.set $x) (call $wpop2) (local.get $x) (i32.shr_u) (call $wpsh2) (br $loop)
+ ) (; ROL*: ;) (call $mpop1) (local.set $x) (call $wpop2) (local.get $x) (call $rol2) (call $wpsh2) (br $loop)
+ ) (; ROR*: ;) (call $mpop1) (local.set $x) (call $wpop2) (local.get $x) (call $ror2) (call $wpsh2) (br $loop)
+ ) (; IOR*: ;) (call $mpop2) (call $wpop2) (i32.or) (call $wpsh2) (br $loop)
+ ) (; XOR*: ;) (call $mpop2) (call $wpop2) (i32.xor) (call $wpsh2) (br $loop)
+ ) (; AND*: ;) (call $mpop2) (call $wpop2) (i32.and) (call $wpsh2) (br $loop)
+ ) (; NOT*: ;) (call $mpop2) (i32.xor (i32.const 0xFFFF)) (call $wpsh2) (br $loop)
+
+ ) (; DB3 ;) (return (global.get $SIGNAL.DB3))
+ ) (; PSHr ;) (call $wpop1) (call $rpsh1) (br $loop)
+ ) (; POPr ;) (call $rpmov (i32.const -1)) (br $loop)
+ ) (; CPYr ;) (call $wget1) (call $rpsh1) (br $loop)
+ ) (; DUPr ;) (call $rget1) (call $rpsh1) (br $loop)
+ ) (; OVRr ;) (call $rpop1) (call $rget1) (local.set $x) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; SWPr ;) (call $rpop1) (call $rpop1) (local.set $x) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; ROTr ;) (call $rpop1) (call $rpop1) (call $rpop1) (local.set $x) (call $rpsh1) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; JMPr ;) (call $rpop2) (global.set $ip) (br $loop)
+ ) (; JMSr ;) (call $rpop2) (global.get $ip) (call $wpsh2) (global.set $ip) (br $loop)
+ ) (; JCNr ;) (call $rpop2) (local.set $x) (if (call $rpop1) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCSr ;) (call $rpop2) (local.set $x) (if (call $rpop1) (then (global.get $ip) (call $wpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDAr ;) (call $rpop2) (call $mget1) (call $rpsh1) (br $loop)
+ ) (; STAr ;) (call $rpop2) (call $rpop1) (call $mset1) (br $loop)
+ ) (; LDDr ;) (call $rpop1) (call $dget1) (call $rpsh1) (br $loop)
+ ) (; STDr ;) (call $rpop1) (call $rpop1) (call $dset1) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADDr ;) (call $rpop1) (call $rpop1) (i32.add) (call $rpsh1) (br $loop)
+ ) (; SUBr ;) (call $rpop1) (local.set $x) (call $rpop1) (local.get $x) (i32.sub) (call $rpsh1) (br $loop)
+ ) (; INCr ;) (call $rpop1) (i32.add (i32.const 1)) (call $rpsh1) (br $loop)
+ ) (; DECr ;) (call $rpop1) (i32.sub (i32.const 1)) (call $rpsh1) (br $loop)
+ ) (; LTHr ;) (call $rpop1) (call $rpop1) (i32.gt_u) (call $rpshb) (br $loop)
+ ) (; GTHr ;) (call $rpop1) (call $rpop1) (i32.lt_u) (call $rpshb) (br $loop)
+ ) (; EQUr ;) (call $rpop1) (call $rpop1) (i32.eq) (call $rpshb) (br $loop)
+ ) (; NQKr ;) (call $rpop1) (local.tee $x) (call $rget1) (local.get $x) (call $rpsh1) (i32.ne) (call $rpshb) (br $loop)
+ ) (; SHLr ;) (call $rpop1) (local.set $x) (call $rpop1) (local.get $x) (i32.shl) (call $rpsh1) (br $loop)
+ ) (; SHRr ;) (call $rpop1) (local.set $x) (call $rpop1) (local.get $x) (i32.shr_u) (call $rpsh1) (br $loop)
+ ) (; ROLr ;) (call $rpop1) (local.set $x) (call $rpop1) (local.get $x) (call $rol1) (call $rpsh1) (br $loop)
+ ) (; RORr ;) (call $rpop1) (local.set $x) (call $rpop1) (local.get $x) (call $ror1) (call $rpsh1) (br $loop)
+ ) (; IORr ;) (call $rpop1) (call $rpop1) (i32.or) (call $rpsh1) (br $loop)
+ ) (; XORr ;) (call $rpop1) (call $rpop1) (i32.xor) (call $rpsh1) (br $loop)
+ ) (; ANDr ;) (call $rpop1) (call $rpop1) (i32.and) (call $rpsh1) (br $loop)
+ ) (; NOTr ;) (call $rpop1) (i32.xor (i32.const 0xFF)) (call $rpsh1) (br $loop)
+
+ ) (; DB4 ;) (return (global.get $SIGNAL.DB4))
+ ) (; PSHr: ;) (call $mpop1) (call $rpsh1) (br $loop)
+ ) (; POPr: ;) (call $ipmov (i32.const 1)) (br $loop)
+ ) (; CPYr: ;) (call $mpop1) (local.tee $x) (call $rpsh1) (local.get $x) (call $wpsh1) (br $loop)
+ ) (; DUPr: ;) (call $mpop1) (local.tee $x) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; OVRr: ;) (call $mpop1) (call $rget1) (local.set $x) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; SWPr: ;) (call $mpop1) (call $rpop1) (local.set $x) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; ROTr: ;) (call $mpop1) (call $rpop1) (call $rpop1) (local.set $x) (call $rpsh1) (call $rpsh1) (local.get $x) (call $rpsh1) (br $loop)
+ ) (; JMPr: ;) (call $mpop2) (global.set $ip) (br $loop)
+ ) (; JMSr: ;) (call $mpop2) (global.get $ip) (call $wpsh2) (global.set $ip) (br $loop)
+ ) (; JCNr: ;) (call $mpop2) (local.set $x) (if (call $rpop1) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCSr: ;) (call $mpop2) (local.set $x) (if (call $rpop1) (then (global.get $ip) (call $wpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDAr: ;) (call $mpop2) (call $mget1) (call $rpsh1) (br $loop)
+ ) (; STAr: ;) (call $mpop2) (call $rpop1) (call $mset1) (br $loop)
+ ) (; LDDr: ;) (call $mpop1) (call $dget1) (call $rpsh1) (br $loop)
+ ) (; STDr: ;) (call $mpop1) (call $rpop1) (call $dset1) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADDr: ;) (call $mpop1) (call $rpop1) (i32.add) (call $rpsh1) (br $loop)
+ ) (; SUBr: ;) (call $mpop1) (local.set $x) (call $rpop1) (local.get $x) (i32.sub) (call $rpsh1) (br $loop)
+ ) (; INCr: ;) (call $mpop1) (i32.add (i32.const 1)) (call $rpsh1) (br $loop)
+ ) (; DECr: ;) (call $mpop1) (i32.sub (i32.const 1)) (call $rpsh1) (br $loop)
+ ) (; LTHr: ;) (call $mpop1) (call $rpop1) (i32.gt_u) (call $rpshb) (br $loop)
+ ) (; GTHr: ;) (call $mpop1) (call $rpop1) (i32.lt_u) (call $rpshb) (br $loop)
+ ) (; EQUr: ;) (call $mpop1) (call $rpop1) (i32.eq) (call $rpshb) (br $loop)
+ ) (; NQKr: ;) (call $mpop1) (local.tee $x) (call $rget1) (local.get $x) (call $rpsh1) (i32.ne) (call $rpshb) (br $loop)
+ ) (; SHLr: ;) (call $mpop1) (local.set $x) (call $rpop1) (local.get $x) (i32.shl) (call $rpsh1) (br $loop)
+ ) (; SHRr: ;) (call $mpop1) (local.set $x) (call $rpop1) (local.get $x) (i32.shr_u) (call $rpsh1) (br $loop)
+ ) (; ROLr: ;) (call $mpop1) (local.set $x) (call $rpop1) (local.get $x) (call $rol1) (call $rpsh1) (br $loop)
+ ) (; RORr: ;) (call $mpop1) (local.set $x) (call $rpop1) (local.get $x) (call $ror1) (call $rpsh1) (br $loop)
+ ) (; IORr: ;) (call $mpop1) (call $rpop1) (i32.or) (call $rpsh1) (br $loop)
+ ) (; XORr: ;) (call $mpop1) (call $rpop1) (i32.xor) (call $rpsh1) (br $loop)
+ ) (; ANDr: ;) (call $mpop1) (call $rpop1) (i32.and) (call $rpsh1) (br $loop)
+ ) (; NOTr: ;) (call $mpop1) (i32.xor (i32.const 0xFF)) (call $rpsh1) (br $loop)
+
+ ) (; DB5 ;) (return (global.get $SIGNAL.DB5))
+ ) (; PSHr* ;) (call $wpop2) (call $rpsh2) (br $loop)
+ ) (; POPr* ;) (call $rpmov (i32.const -2)) (br $loop)
+ ) (; CPYr* ;) (call $wget2) (call $rpsh2) (br $loop)
+ ) (; DUPr* ;) (call $rget2) (call $rpsh2) (br $loop)
+ ) (; OVRr* ;) (call $rpop2) (call $rget2) (local.set $x) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; SWPr* ;) (call $rpop2) (call $rpop2) (local.set $x) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; ROTr* ;) (call $rpop2) (call $rpop2) (call $rpop2) (local.set $x) (call $rpsh2) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; JMPr* ;) (call $rpop2) (global.set $ip) (br $loop)
+ ) (; JMSr* ;) (call $rpop2) (global.get $ip) (call $wpsh2) (global.set $ip) (br $loop)
+ ) (; JCNr* ;) (call $rpop2) (local.set $x) (if (call $rpop2) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCSr* ;) (call $rpop2) (local.set $x) (if (call $rpop2) (then (global.get $ip) (call $wpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDAr* ;) (call $rpop2) (call $mget2) (call $rpsh2) (br $loop)
+ ) (; STAr* ;) (call $rpop2) (call $rpop2) (call $mset2) (br $loop)
+ ) (; LDDr* ;) (call $rpop1) (call $dget2) (call $rpsh2) (br $loop)
+ ) (; STDr* ;) (call $rpop1) (call $rpop2) (call $dset2) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADDr* ;) (call $rpop2) (call $rpop2) (i32.add) (call $rpsh2) (br $loop)
+ ) (; SUBr* ;) (call $rpop2) (local.set $x) (call $rpop2) (local.get $x) (i32.sub) (call $rpsh2) (br $loop)
+ ) (; INCr* ;) (call $rpop2) (i32.add (i32.const 1)) (call $rpsh2) (br $loop)
+ ) (; DECr* ;) (call $rpop2) (i32.sub (i32.const 1)) (call $rpsh2) (br $loop)
+ ) (; LTHr* ;) (call $rpop2) (call $rpop2) (i32.gt_u) (call $rpshb) (br $loop)
+ ) (; GTHr* ;) (call $rpop2) (call $rpop2) (i32.lt_u) (call $rpshb) (br $loop)
+ ) (; EQUr* ;) (call $rpop2) (call $rpop2) (i32.eq) (call $rpshb) (br $loop)
+ ) (; NQKr* ;) (call $rpop2) (local.tee $x) (call $rget2) (local.get $x) (call $rpsh2) (i32.ne) (call $rpshb) (br $loop)
+ ) (; SHLr* ;) (call $rpop1) (local.set $x) (call $rpop2) (local.get $x) (i32.shl) (call $rpsh2) (br $loop)
+ ) (; SHRr* ;) (call $rpop1) (local.set $x) (call $rpop2) (local.get $x) (i32.shr_u) (call $rpsh2) (br $loop)
+ ) (; ROLr* ;) (call $rpop1) (local.set $x) (call $rpop2) (local.get $x) (call $rol2) (call $rpsh2) (br $loop)
+ ) (; RORr* ;) (call $rpop1) (local.set $x) (call $rpop2) (local.get $x) (call $ror2) (call $rpsh2) (br $loop)
+ ) (; IORr* ;) (call $rpop2) (call $rpop2) (i32.or) (call $rpsh2) (br $loop)
+ ) (; XORr* ;) (call $rpop2) (call $rpop2) (i32.xor) (call $rpsh2) (br $loop)
+ ) (; ANDr* ;) (call $rpop2) (call $rpop2) (i32.and) (call $rpsh2) (br $loop)
+ ) (; NOTr* ;) (call $rpop2) (i32.xor (i32.const 0xFFFF)) (call $rpsh2) (br $loop)
+
+ ) (; DB6 ;) (return (global.get $SIGNAL.DB6))
+ ) (; PSHr*: ;) (call $mpop2) (call $rpsh2) (br $loop)
+ ) (; POPr*: ;) (call $ipmov (i32.const 2)) (br $loop)
+ ) (; CPYr*: ;) (call $mpop2) (local.tee $x) (call $rpsh2) (local.get $x) (call $wpsh2) (br $loop)
+ ) (; DUPr*: ;) (call $mpop2) (local.tee $x) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; OVRr*: ;) (call $mpop2) (call $rget2) (local.set $x) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; SWPr*: ;) (call $mpop2) (call $rpop2) (local.set $x) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; ROTr*: ;) (call $mpop2) (call $rpop2) (call $rpop2) (local.set $x) (call $rpsh2) (call $rpsh2) (local.get $x) (call $rpsh2) (br $loop)
+ ) (; JMPr*: ;) (call $mpop2) (global.set $ip) (br $loop)
+ ) (; JMSr*: ;) (call $mpop2) (global.get $ip) (call $wpsh2) (global.set $ip) (br $loop)
+ ) (; JCNr*: ;) (call $mpop2) (local.set $x) (if (call $rpop2) (then (local.get $x) (global.set $ip))) (br $loop)
+ ) (; JCSr*: ;) (call $mpop2) (local.set $x) (if (call $rpop2) (then (global.get $ip) (call $wpsh2) (local.get $x) (global.set $ip))) (br $loop)
+ ) (; LDAr*: ;) (call $mpop2) (call $mget2) (call $rpsh2) (br $loop)
+ ) (; STAr*: ;) (call $mpop2) (call $rpop2) (call $mset2) (br $loop)
+ ) (; LDDr*: ;) (call $mpop1) (call $dget2) (call $rpsh2) (br $loop)
+ ) (; STDr*: ;) (call $mpop1) (call $rpop2) (call $dset2) (local.set $x) (if (local.get $x) (then (return (local.get $x)))) (br $loop)
+ ) (; ADDr*: ;) (call $mpop2) (call $rpop2) (i32.add) (call $rpsh2) (br $loop)
+ ) (; SUBr*: ;) (call $mpop2) (local.set $x) (call $rpop2) (local.get $x) (i32.sub) (call $rpsh2) (br $loop)
+ ) (; INCr*: ;) (call $mpop2) (i32.add (i32.const 1)) (call $rpsh2) (br $loop)
+ ) (; DECr*: ;) (call $mpop2) (i32.sub (i32.const 1)) (call $rpsh2) (br $loop)
+ ) (; LTHr*: ;) (call $mpop2) (call $rpop2) (i32.gt_u) (call $rpshb) (br $loop)
+ ) (; GTHr*: ;) (call $mpop2) (call $rpop2) (i32.lt_u) (call $rpshb) (br $loop)
+ ) (; EQUr*: ;) (call $mpop2) (call $rpop2) (i32.eq) (call $rpshb) (br $loop)
+ ) (; NQKr*: ;) (call $mpop2) (local.tee $x) (call $rget2) (local.get $x) (call $rpsh2) (i32.ne) (call $rpshb) (br $loop)
+ ) (; SHLr*: ;) (call $mpop1) (local.set $x) (call $rpop2) (local.get $x) (i32.shl) (call $rpsh2) (br $loop)
+ ) (; SHRr*: ;) (call $mpop1) (local.set $x) (call $rpop2) (local.get $x) (i32.shr_u) (call $rpsh2) (br $loop)
+ ) (; ROLr*: ;) (call $mpop1) (local.set $x) (call $rpop2) (local.get $x) (call $rol2) (call $rpsh2) (br $loop)
+ ) (; RORr*: ;) (call $mpop1) (local.set $x) (call $rpop2) (local.get $x) (call $ror2) (call $rpsh2) (br $loop)
+ ) (; IORr*: ;) (call $mpop2) (call $rpop2) (i32.or) (call $rpsh2) (br $loop)
+ ) (; XORr*: ;) (call $mpop2) (call $rpop2) (i32.xor) (call $rpsh2) (br $loop)
+ ) (; ANDr*: ;) (call $mpop2) (call $rpop2) (i32.and) (call $rpsh2) (br $loop)
+ ) (; NOTr*: ;) (call $mpop2) (i32.xor (i32.const 0xFFFF)) (call $rpsh2) (br $loop)
+ )
+ ) (; break ;)
+
+ ;; Default return signal.
+ (return (global.get $SIGNAL.BREAK))
+ )
+)
diff --git a/bedrock.js b/bedrock.js
index 0022346..2130283 100644
--- a/bedrock.js
+++ b/bedrock.js
@@ -3,52 +3,138 @@
let systemName = "bedrock-js";
let systemVersion = "1.0.1";
let systemAuthors = "Ben Bridle";
-let initialScreenScale = 2;
+let defaultToWasm = true; // default to using WASM core for emulators
+let maxFrameTime = 500; // milliseconds before a render is forced
+let initialScreenScale = 2; // screen pixel scale
+let tabSize = 2; // width of tabs in assembler
+let maxTransmissions = 100; // maximum number of transmissions to retain
-// Upgrade every pre.bedrock element to a full assembler, and every
-// bedrock to a full emulator.
-window.addEventListener('DOMContentLoaded', function() {
- injectStyles();
+// Number of emulator cycles to run between updates. This value massively affects
+// performance: if it's too high, the UI will lag, and if it's too low, the
+// emulator will run slow. We must take the middle way.
+// TODO: Find a way to dynamically calculate the optimal value at runtime,
+// based on time taken to run each batch.
+let cyclesPerBatch = defaultToWasm ? 800000 : 50000 ;
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: WEBASSEMBLY MODULE :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+// WebAssembly module bytecode embedded as a base64 string.
+let wasmBytecode = Uint8Array.from(atob(
+'AGFzbQEAAAABIAdgAX8Bf2ACf38Bf2AAAGAAAX5gAAF/YAF/AGACf38AAiECB2JlZHJvY2sFZGdldDEAAAdiZWRyb2NrBWRzZXQxAAEDJCMCAwQEBAUFBQUFBQQEBAQEBAQEBAQAAAAGBgEFBQUBAQEB' +
+'AAUDAQACBmMSfgFCAAt/AUEAC38BQYCCBAt/AUGAhAQLfwBBAAt/AEGAggQLfwBBgIQEC38AQQALfwBBAQt/AEECC38AQQMLfwBBBAt/AEEFC38AQQYLfwBBBwt/AEEIC38AQQkLfwBBCgsHkQIkBm1l' +
+'bW9yeQIABXJlc2V0AAICY2MAAwJpcAAEAndwAAUCcnAABgV3cHNoMQAHBXdwc2gyAAgFcnBzaDEACQVycHNoMgAKBXdwc2hiAAsFcnBzaGIADAV3cG9wMQANBXdwb3AyAA4FcnBvcDEADwVycG9wMgAQ' +
+'BW1wb3AxABEFbXBvcDIAEgV3Z2V0MQATBXdnZXQyABQFcmdldDEAFQVyZ2V0MgAWBW1nZXQxABcFbWdldDIAGAVkZ2V0MgAZBW1zZXQxABoFbXNldDIAGwVkc2V0MgAcBWlwbW92AB0Fd3Btb3YAHgVy' +
+'cG1vdgAfBHJvbDEAIARyb3IxACEEcm9sMgAiBHJvcjIAIwRldmFsACQK6CMjDgAjBCQBIwUkAiMGJAMLBAAjAAsEACMBCwcAIwUjAmsLBwAjBiMDawsQACMCQQFrJAIjAiAAOgAACxAAIwJBAmskAiMC' +
+'IAA7AQALEAAjA0EBayQDIwMgADoAAAsQACMDQQJrJAMjAyAAOwEACwwAQf8BQQAgABsQBwsMAEH/AUEAIAAbEAkLDgAjAi0AACMCQQFqJAILDgAjAi8BACMCQQJqJAILDgAjAy0AACMDQQFqJAMLDgAj' +
+'Ay8BACMDQQJqJAMLDgAjAS0AACMBQQFqJAELGgAjAS0AAEEIdCMBQQFqLQAAciMBQQJqJAELBwAjAi0AAAsHACMCLwEACwcAIwMtAAALBwAjAy8BAAsHACAALQAACxMAIAAtAABBCHQgAEEBai0AAHIL' +
+'EQAgABAAQQh0IABBAWoQAHILCQAgACABOgAACxoAIAAgAUEIdjoAACAAQQFqIAFB/wFxOgAACxkAIAAgAUEIdhABIABBAWogAUH/AXEQAXILCQAjASAAaiQBCwkAIwIgAGskAgsJACMDIABrJAMLFwAg' +
+'AUEHcSEBIAAgAXQgAEEIIAFrdnILFwAgAUEHcSEBIABBCCABa3QgACABdnILFwAgAUEPcSEBIAAgAXQgAEEQIAFrdnILFwAgAUEPcSEBIABBECABa3QgACABdnIL7R8CAX4BfyMAIACtfCEBA0ACQCAB' +
+'IwBRBEAMAQsjAEIBfCQAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC' +
+'QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC' +
+'QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC' +
+'QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC' +
+'QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAQEQ7/AQABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQl' +
+'JicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsB' +
+'jAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AbgBuQG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQB' +
+'xQHGAccByAHJAcoBywHMAc0BzgHPAdAB0QHSAdMB1AHVAdYB1wHYAdkB2gHbAdwB3QHeAd8B4AHhAeIB4wHkAeUB5gHnAegB6QHqAesB7AHtAe4B7wHwAfEB8gHzAfQB9QH2AfcB+AH5AfoB+wH8Af0B' +
+'/gH/AQsjCA8LEA8QBwz/AQtBfxAeDP4BCxAVEAcM/QELEBMQBwz8AQsQDRATIQIQByACEAcM+wELEA0QDSECEAcgAhAHDPoBCxANEA0QDSECEAcQByACEAcM+QELEA4kAQz4AQsQDiMBEAokAQz3AQsQ' +
+'DiECEA0EQCACJAELDPYBCxAOIQIQDQRAIwEQCiACJAELDPUBCxAOEBcQBwz0AQsQDhANEBoM8wELEA0QABAHDPIBCxANEA0QASECIAIEQCACDwsM8QELEA0QDWoQBwzwAQsQDSECEA0gAmsQBwzvAQsQ' +
+'DUEBahAHDO4BCxANQQFrEAcM7QELEA0QDUsQCwzsAQsQDRANSRALDOsBCxANEA1GEAsM6gELEA0iAhATIAIQB0cQCwzpAQsQDSECEA0gAnQQBwzoAQsQDSECEA0gAnYQBwznAQsQDSECEA0gAhAgEAcM' +
+'5gELEA0hAhANIAIQIRAHDOUBCxANEA1yEAcM5AELEA0QDXMQBwzjAQsQDRANcRAHDOIBCxANQf8BcxAHDOEBCwzgAQsQERAHDN8BC0EBEB0M3gELEBEiAhAHIAIQCQzdAQsQESICEAcgAhAHDNwBCxAR' +
+'EBMhAhAHIAIQBwzbAQsQERANIQIQByACEAcM2gELEBEQDRANIQIQBxAHIAIQBwzZAQsQEiQBDNgBCxASIwEQCiQBDNcBCxASIQIQDQRAIAIkAQsM1gELEBIhAhANBEAjARAKIAIkAQsM1QELEBIQFxAH' +
+'DNQBCxASEA0QGgzTAQsQERAAEAcM0gELEBEQDRABIQIgAgRAIAIPCwzRAQsQERANahAHDNABCxARIQIQDSACaxAHDM8BCxARQQFqEAcMzgELEBFBAWsQBwzNAQsQERANSxALDMwBCxAREA1JEAsMywEL' +
+'EBEQDUYQCwzKAQsQESICEBMgAhAHRxALDMkBCxARIQIQDSACdBAHDMgBCxARIQIQDSACdhAHDMcBCxARIQIQDSACECAQBwzGAQsQESECEA0gAhAhEAcMxQELEBEQDXIQBwzEAQsQERANcxAHDMMBCxAR' +
+'EA1xEAcMwgELEBFB/wFzEAcMwQELIwkPCxAQEAgMvwELQX4QHgy+AQsQFhAIDL0BCxAUEAgMvAELEA4QFCECEAggAhAIDLsBCxAOEA4hAhAIIAIQCAy6AQsQDhAOEA4hAhAIEAggAhAIDLkBCxAOJAEM' +
+'uAELEA4jARAKJAEMtwELEA4hAhAOBEAgAiQBCwy2AQsQDiECEA4EQCMBEAogAiQBCwy1AQsQDhAYEAgMtAELEA4QDhAbDLMBCxANEBkQCAyyAQsQDRAOEBwhAiACBEAgAg8LDLEBCxAOEA5qEAgMsAEL' +
+'EA4hAhAOIAJrEAgMrwELEA5BAWoQCAyuAQsQDkEBaxAIDK0BCxAOEA5LEAsMrAELEA4QDkkQCwyrAQsQDhAORhALDKoBCxAOIgIQFCACEAhHEAsMqQELEA0hAhAOIAJ0EAgMqAELEA0hAhAOIAJ2EAgM' +
+'pwELEA0hAhAOIAIQIhAIDKYBCxANIQIQDiACECMQCAylAQsQDhAOchAIDKQBCxAOEA5zEAgMowELEA4QDnEQCAyiAQsQDkH//wNzEAgMoQELIwoPCxASEAgMnwELQQIQHQyeAQsQEiICEAggAhAKDJ0B' +
+'CxASIgIQCCACEAgMnAELEBIQFCECEAggAhAIDJsBCxASEA4hAhAIIAIQCAyaAQsQEhAOEA4hAhAIEAggAhAIDJkBCxASJAEMmAELEBIjARAKJAEMlwELEBIhAhAOBEAgAiQBCwyWAQsQEiECEA4EQCMB' +
+'EAogAiQBCwyVAQsQEhAYEAgMlAELEBIQDhAbDJMBCxAREBkQCAySAQsQERAOEBwhAiACBEAgAg8LDJEBCxASEA5qEAgMkAELEBIhAhAOIAJrEAgMjwELEBJBAWoQCAyOAQsQEkEBaxAIDI0BCxASEA5L' +
+'EAsMjAELEBIQDkkQCwyLAQsQEhAORhALDIoBCxASIgIQFCACEAhHEAsMiQELEBEhAhAOIAJ0EAgMiAELEBEhAhAOIAJ2EAgMhwELEBEhAhAOIAIQIhAIDIYBCxARIQIQDiACECMQCAyFAQsQEhAOchAI' +
+'DIQBCxASEA5zEAgMgwELEBIQDnEQCAyCAQsQEkH//wNzEAgMgQELIwsPCxANEAkMfwtBfxAfDH4LEBMQCQx9CxAVEAkMfAsQDxAVIQIQCSACEAkMewsQDxAPIQIQCSACEAkMegsQDxAPEA8hAhAJEAkg' +
+'AhAJDHkLEBAkAQx4CxAQIwEQCCQBDHcLEBAhAhAPBEAgAiQBCwx2CxAQIQIQDwRAIwEQCCACJAELDHULEBAQFxAJDHQLEBAQDxAaDHMLEA8QABAJDHILEA8QDxABIQIgAgRAIAIPCwxxCxAPEA9qEAkM' +
+'cAsQDyECEA8gAmsQCQxvCxAPQQFqEAkMbgsQD0EBaxAJDG0LEA8QD0sQDAxsCxAPEA9JEAwMawsQDxAPRhAMDGoLEA8iAhAVIAIQCUcQDAxpCxAPIQIQDyACdBAJDGgLEA8hAhAPIAJ2EAkMZwsQDyEC' +
+'EA8gAhAgEAkMZgsQDyECEA8gAhAhEAkMZQsQDxAPchAJDGQLEA8QD3MQCQxjCxAPEA9xEAkMYgsQD0H/AXMQCQxhCyMMDwsQERAJDF8LQQEQHQxeCxARIgIQCSACEAcMXQsQESICEAkgAhAJDFwLEBEQ' +
+'FSECEAkgAhAJDFsLEBEQDyECEAkgAhAJDFoLEBEQDxAPIQIQCRAJIAIQCQxZCxASJAEMWAsQEiMBEAgkAQxXCxASIQIQDwRAIAIkAQsMVgsQEiECEA8EQCMBEAggAiQBCwxVCxASEBcQCQxUCxASEA8Q' +
+'GgxTCxAREAAQCQxSCxAREA8QASECIAIEQCACDwsMUQsQERAPahAJDFALEBEhAhAPIAJrEAkMTwsQEUEBahAJDE4LEBFBAWsQCQxNCxAREA9LEAwMTAsQERAPSRAMDEsLEBEQD0YQDAxKCxARIgIQFSAC' +
+'EAlHEAwMSQsQESECEA8gAnQQCQxICxARIQIQDyACdhAJDEcLEBEhAhAPIAIQIBAJDEYLEBEhAhAPIAIQIRAJDEULEBEQD3IQCQxECxAREA9zEAkMQwsQERAPcRAJDEILEBFB/wFzEAkMQQsjDQ8LEA4Q' +
+'Cgw/C0F+EB8MPgsQFBAKDD0LEBYQCgw8CxAQEBYhAhAKIAIQCgw7CxAQEBAhAhAKIAIQCgw6CxAQEBAQECECEAoQCiACEAoMOQsQECQBDDgLEBAjARAIJAEMNwsQECECEBAEQCACJAELDDYLEBAhAhAQ' +
+'BEAjARAIIAIkAQsMNQsQEBAYEAoMNAsQEBAQEBsMMwsQDxAZEAoMMgsQDxAQEBwhAiACBEAgAg8LDDELEBAQEGoQCgwwCxAQIQIQECACaxAKDC8LEBBBAWoQCgwuCxAQQQFrEAoMLQsQEBAQSxAMDCwL' +
+'EBAQEEkQDAwrCxAQEBBGEAwMKgsQECICEBYgAhAKRxAMDCkLEA8hAhAQIAJ0EAoMKAsQDyECEBAgAnYQCgwnCxAPIQIQECACECIQCgwmCxAPIQIQECACECMQCgwlCxAQEBByEAoMJAsQEBAQcxAKDCML' +
+'EBAQEHEQCgwiCxAQQf//A3MQCgwhCyMODwsQEhAKDB8LQQIQHQweCxASIgIQCiACEAgMHQsQEiICEAogAhAKDBwLEBIQFiECEAogAhAKDBsLEBIQECECEAogAhAKDBoLEBIQEBAQIQIQChAKIAIQCgwZ' +
+'CxASJAEMGAsQEiMBEAgkAQwXCxASIQIQEARAIAIkAQsMFgsQEiECEBAEQCMBEAggAiQBCwwVCxASEBgQCgwUCxASEBAQGwwTCxAREBkQCgwSCxAREBAQHCECIAIEQCACDwsMEQsQEhAQahAKDBALEBIh' +
+'AhAQIAJrEAoMDwsQEkEBahAKDA4LEBJBAWsQCgwNCxASEBBLEAwMDAsQEhAQSRAMDAsLEBIQEEYQDAwKCxASIgIQFiACEApHEAwMCQsQESECEBAgAnQQCgwICxARIQIQECACdhAKDAcLEBEhAhAQIAIQ' +
+'IhAKDAYLEBEhAhAQIAIQIxAKDAULEBIQEHIQCgwECxASEBBzEAoMAwsQEhAQcRAKDAILEBJB//8DcxAKDAELCyMHDws='), c => c.charCodeAt(0));
+
+// WebAssembly module compiled and ready to be instantiated.
+let wasmModule;
+
+async function loadWasmModule() {
+ // // Uncomment this to load the WebAssembly module bytecode from a separate file.
+ // wasmBytecode = await fetch('/bedrock.wasm')
+ // .then((r) => { return r.ok ? r.arrayBuffer() : null })
+ // .then((a) => { return new Uint8Array(a) })
+ // .catch(() => { return null });
+ wasmModule = await WebAssembly.compile(wasmBytecode).then((m) => m);
+}
+
+
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: ASSEMBLER AND EMULATOR INSTANTIATION :::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+// Automatically upgrade all eligible elements to assemblers or emulators.
+window.addEventListener('DOMContentLoaded', ()=>{
+ // Attempt to load and compile the WebAssembly module, then start upgrading.
+ loadWasmModule().finally(upgradeAll);
+})
+
+
+// Upgrade every `pre.bedrock`/`bedrock` element to an assembler/emulator.
+function upgradeAll() {
+ // Upgrade every `pre.bedrock` element to an assembler.
for (let element of document.querySelectorAll('pre.bedrock')) {
- // Click on the element to upgrade to an assembler.
+ // Defer the upgrade until the element is clicked.
element.style.cursor = 'pointer';
- element.addEventListener('click', function(e) {
+ element.addEventListener('click', (e)=>{
e.preventDefault();
upgradeToAssembler(element);
})
- // Prevent text selection on the element.
- element.addEventListener('selectstart', function(e) {
+ // Prevent the click from selecting any text.
+ element.addEventListener('selectstart', (e)=>{
e.preventDefault();
})
}
+ // Upgrade every `bedrock` element to an emulator.
for (let element of document.querySelectorAll('bedrock')) {
upgradeToEmulator(element);
}
-})
-
-
-// Insert the styles for the assembler and emulator into the page.
-function injectStyles() {
+ // Insert the CSS for the assembler and emulator into the page head.
let css = document.createElement('style');
- css.textContent = bedrockStyles;
+ css.textContent = bedrockCSS;
document.head.appendChild(css);
}
-// Upgrade a text element to a full assembler.
+// Upgrade a text-containing element to a full assembler.
function upgradeToAssembler(element) {
let assembler = new AssemblerElement(element);
// HACK 2: Track the top of the original element relative to the viewport.
let topStart = element.getBoundingClientRect().top;
let scrollStart = window.scrollY;
-
// HACK 1: Insert a fake spacer element following the original element to
// prevent the page from jumping when the original element is replaced.
- // Spacer ensures that there is at least element height below the
- // viewport, filling the gap created while element is being replaced.
+ // Spacer ensures that there is at minimum an element-sized span below
+ // the viewport, filling the gap created when the element momentarily
+ // disappears while being replaced.
let windowHeight = document.documentElement.clientHeight;
let documentHeight = document.documentElement.scrollHeight;
let belowViewport = documentHeight - (window.scrollY + windowHeight);
@@ -58,18 +144,17 @@ function upgradeToAssembler(element) {
spacer.style.height = spacerHeight + 'px';
element.parentElement.insertBefore(spacer, element.nextSibling);
- element.replaceWith(assembler);
+ element.replaceWith(assembler.el);
// HACK 1: Remove the spacer after a short delay.
setTimeout(()=>spacer.remove());
-
// HACK 2: Check if the page jumped as the assembler was inserted.
- let topEnd = assembler.getBoundingClientRect().top;
+ let topEnd = assembler.el.getBoundingClientRect().top;
if (topStart != topEnd) {
window.scrollTo({top: scrollStart, behavior: 'instant'});
// Wait for the document layout to settle.
setTimeout(() => {
- topEnd = assembler.getBoundingClientRect().top;
+ topEnd = assembler.el.getBoundingClientRect().top;
let topTarget = (topEnd - topStart) + window.scrollY;
window.scrollTo({top: topTarget, behavior: 'instant'});
});
@@ -79,708 +164,47 @@ function upgradeToAssembler(element) {
}
-// Upgrade a bedrock element with attributes to a full emulator.
+// Upgrade a bedrock element to a full emulator. Supported attributes are:
+// src: URL to the Bedrock program to run
+// controls: if present, show frame with controls around emulator
+// nocursor: if present, hide the regular mouse cursor when hovering
+// scale: initial screen scale factor (1-10)
function upgradeToEmulator(element) {
+ // Extract attribute values.
let attr = element.attributes;
- if (!attr.src || !attr.src.value) {
- console.error("No valid src attribute provided for <bedrock> element");
- return;
- }
- let scale = 1;
- try { scale = clamp(parseInt(attr.scale.value), 1, 10) } catch {}
- let options = {
- scale,
- controls: 'controls' in attr,
- nocursor: 'nocursor' in attr,
- autoplay: true,
- };
- let url = URL.parse(attr.src.value, window.location.href);
- if (!url) {
- console.error(`Invalid src attribute ${attr.src.value} provided for <bedrock> element`);
- return;
- }
- let request = new XMLHttpRequest();
- request.open("GET", url, true);
- request.responseType = "arraybuffer";
- request.onreadystatechange = function() {
- if (request.readyState == 4) {
- if (request.status == 200) {
- options.bytecode = new Uint8Array(request.response);
- let emulator = new EmulatorElement(options);
- element.replaceWith(emulator);
- } else {
- console.error(`Could not load Bedrock program from ${url.href}`);
- }
- }
- }
- request.send();
-}
-
-
-// ----------------------------------------------------------------------------------------------- +
-// :::::: ELEMENTS :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
-// ----------------------------------------------------------------------------------------------- +
-
-
-// Constructor for an interactive assembler element.
-function AssemblerElement(element) {
- let assembler = elementFromHTML(assemblerTemplate);
- let textarea = assembler.querySelector('textarea');
- let viewer = assembler.querySelector('.viewer');
- let editor = assembler.querySelector('.editor');
- let errorPanel = assembler.querySelector('.panel.errors');
- let bytecodePanel = assembler.querySelector('.panel.bytecode');
- let status = assembler.querySelector('.status');
- let checkButton = assembler.querySelector('button[name="check"]');
- let runButton = assembler.querySelector('button[name="run"]');
- let fullscreenButton = assembler.querySelector('button[name="fullscreen"]');
- assembler.autoGrow = true;
- assembler.emulator = null;
-
-
- // Show and hide the error and bytecode panels.
- assembler.showErrorPanel = function() {
- errorPanel.classList.remove('hidden'); }
- assembler.hideErrorPanel = function() {
- errorPanel.classList.add('hidden'); }
- assembler.showBytecodePanel = function() {
- bytecodePanel.classList.remove('hidden'); }
- assembler.hideBytecodePanel = function() {
- bytecodePanel.classList.add('hidden'); }
-
-
- // Place focus on the editor.
- assembler.focus = function() {
- textarea.focus();
- }
-
- // Lock the viewer to the textarea by syncing their scroll positions.
- assembler.syncScroll = function() {
- viewer.scrollTop = textarea.scrollTop;
- viewer.scrollLeft = textarea.scrollLeft;
- }
-
- // Lock the viewer to the textarea by syncing their sizes.
- assembler.syncSize = function() {
- // Disable auto-grow if editor was made smaller by the user.
- if (textarea.clientHeight < viewer.clientHeight) {
- assembler.autoGrow = false; }
- viewer.style.height = textarea.clientHeight + 'px';
- editor.style.height = textarea.clientHeight + 'px';
- }
-
- // Copy text from textarea to viewer with syntax highlighting.
- assembler.renderText = function() {
- // Force viewer to show trailing newlines.
- let text = textarea.value;
- if (text.slice(-1) == "\n") text += ' ';
- // Replace contents of viewer.
- viewer.innerHTML = '';
- for (let child of highlightSource(text)) {
- viewer.appendChild(child); }
- // Auto-grow the text area as text is entered.
- if (assembler.autoGrow && textarea.scrollHeight > textarea.clientHeight) {
- textarea.style.height = textarea.scrollHeight + 'px'; }
- // Lock viewer to textarea.
- assembler.syncScroll();
- }
-
- // Assemble the program and show bytecode or errors.
- assembler.checkProgram = function() {
- let { bytecode, symbols, errors } = assembleProgram(textarea.value);
- errorPanel.innerHTML = '';
- bytecodePanel.innerHTML = '';
- if (errors.length) {
- let list = document.createElement('ul');
- for (let error of errors) {
- let item = document.createElement('li');
- let line = error.start.line + 1;
- let column = error.start.column + 1;
- item.textContent = `[${line}:${column}] ${error.text}`;
- list.appendChild(item); }
- errorPanel.appendChild(list);
- let unit = errors.length == 1 ? 'error' : 'errors';
- status.textContent = `${errors.length} ${unit}`;
- assembler.hideBytecodePanel();
- assembler.showErrorPanel();
- } else {
- let unit = bytecode.length == 1 ? 'byte' : 'bytes';
- status.textContent = `no errors (${bytecode.length} ${unit})`;
- let hexString = '';
- for (let byte of bytecode) hexString += hex(byte, 2) + ' ';
- let programListing = document.createElement('div');
- programListing.textContent = hexString;
- bytecodePanel.appendChild(programListing);
- assembler.hideErrorPanel();
- assembler.hideBytecodePanel();
- if (bytecode.length) assembler.showBytecodePanel();
- return { bytecode, symbols };
- }
- }
-
- assembler.runProgram = function() {
- // Create an emulator if none exists.
- if (!assembler.emulator) {
- assembler.emulator = new EmulatorElement();
- assembler.emulator.assembler = assembler;
- assembler.parentElement.insertBefore(assembler.emulator, assembler.nextSibling); }
- let program = assembler.checkProgram();
- if (program) {
- assembler.hideBytecodePanel();
- let { bytecode, symbols } = program;
- assembler.emulator.showStatePanel();
- assembler.emulator.startProgram(bytecode, symbols);
- }
- }
-
- // Change the fullscreen state of the whole assembler.
- assembler.toggleFullscreen = function() {
- if (document.fullscreenElement != assembler) {
- assembler.requestFullscreen();
- } else {
- document.exitFullscreen();
- }
- }
-
- // Handle keypresses on the whole assembler.
- assembler.onKeyPress = function(event) {
- if (event.key == "Escape") {
- event.preventDefault();
- assembler.hideErrorPanel();
- assembler.hideBytecodePanel();
- } else if (event.key == "s" && event.ctrlKey) {
- event.preventDefault();
- assembler.checkProgram();
- }
- }
-
- // Handle keypresses on just the editor.
- assembler.onEditorKeyPress = function(event) {
- let size = 2; let indent = ' ';
- let text = textarea.value;
- let selectionStart = textarea.selectionStart;
- let selectionEnd = textarea.selectionEnd;
-
- // Find the character index following the previous newline character.
- function findLineStart(i) {
- while (i>0 && text[i-1] != '\n') i -= 1;
- return i; }
-
- if (event.key == "Enter" && event.ctrlKey) {
- event.preventDefault();
- assembler.runProgram();
- } else if (event.key == "Tab") {
- event.preventDefault();
- // Find the start index of every line in the selection.
- let i = findLineStart(selectionStart);
- let lineStarts = [i];
- for (; i<selectionEnd; i++) if (text[i] == '\n') lineStarts.push(i+1);
- if (event.shiftKey) {
- // Find length of the leading whitespace in each line.
- let whitespaceLengths = [];
- for (let start of lineStarts) {
- let end = start;
- while ('\t '.includes(text[end])) end++;
- whitespaceLengths.push(end-start); }
- // Unindent each line in reverse order.
- selectionStart -= Math.min(size, whitespaceLengths[0]);
- for (let i=lineStarts.length-1; i>=0; i--) {
- let start = lineStarts[i];
- let trim = Math.min(size, whitespaceLengths[i]);
- text = text.slice(0, start) + text.slice(start+trim);
- selectionEnd -= trim; }
- } else {
- // Indent each line in reverse order.
- selectionStart += size;
- for (let i=lineStarts.length-1; i>=0; i--) {
- let start = lineStarts[i];
- text = text.slice(0, start) + indent + text.slice(start);
- selectionEnd += size; }
- }
- // Update textarea content and viewer.
- textarea.value = text;
- textarea.selectionStart = selectionStart;
- textarea.selectionEnd = selectionEnd;
- assembler.renderText();
- }
- }
-
- textarea.addEventListener('input', assembler.renderText);
- textarea.addEventListener('scroll', assembler.syncScroll);
- textarea.addEventListener('keydown', assembler.onEditorKeyPress);
- assembler.addEventListener('keydown', assembler.onKeyPress);
- checkButton.addEventListener('click', assembler.checkProgram);
- runButton.addEventListener('click', assembler.runProgram);
- fullscreenButton.addEventListener('click', assembler.toggleFullscreen);
-
- assembler.resizeObserver = new ResizeObserver(assembler.syncSize).observe(textarea);
- textarea.value = element.textContent.trim();
- textarea.selectionStart = 0;
- textarea.selectionEnd = 0;
- textarea.scrollLeft = 0;
- textarea.style.height = element.clientHeight + 'px';
- assembler.renderText();
-
- return assembler;
-}
-
-
-/// Constructor for a interactive emulator element.
-function EmulatorElement(options) {
- let emulator = elementFromHTML(emulatorTemplate);
- // Menu bar
- let menuBar = emulator.querySelector('.menubar');
- let fullscreenButton = menuBar.querySelector('button[name="fullscreen"]');
- let stateButton = menuBar.querySelector('button[name="state"]');
- let runButton = menuBar.querySelector('button[name="run"]');
- let pauseButton = menuBar.querySelector('button[name="pause"]');
- let stopButton = menuBar.querySelector('button[name="stop"]');
- let stepButton = menuBar.querySelector('button[name="step"]');
- let status = menuBar.querySelector('.status');
- // Screen panel
- let screenPanel = emulator.querySelector('.screen');
- let canvas = screenPanel.querySelector('canvas');
- // Stream panel
- let streamPanel = emulator.querySelector('.stream');
- let transmissions = emulator.querySelector('.transmissions');
- // State panel
- let statePanel = emulator.querySelector('.panel.state');
- let pcState = statePanel.querySelector('.pc');
- let labelState = statePanel.querySelector('.label');
- let wstState = statePanel.querySelector('.wst');
- let rstState = statePanel.querySelector('.rst');
-
- let loadedBytecode = options ? options.bytecode : new Uint8Array(0);
- let loadedSymbols = new SymbolTable();
- let loadedMetadata = parseMetadata(loadedBytecode);
- let currentTransmission = null;
- let transmissionParser = new Utf8StreamParser();
-
- emulator.screen = screenPanel;
- emulator.canvas = canvas;
- let br = new Bedrock(emulator);
- br.reset();
- br.onUpdate = () => emulator.updateDOM();
-
- emulator.showStatePanel = function() {
- statePanel.classList.remove('hidden'); }
- emulator.toggleStatePanel = function() {
- statePanel.classList.toggle('hidden'); }
- emulator.showStreamPanel = function() {
- streamPanel.classList.remove('hidden'); }
- emulator.hideStreamPanel = function() {
- streamPanel.classList.add('hidden'); }
- emulator.showScreenPanel = function() {
- screenPanel.classList.remove('hidden'); }
- emulator.hideScreenPanel = function() {
- screenPanel.classList.add('hidden'); }
- emulator.hideMenuBar = function() {
- menuBar.classList.add('hidden'); }
-
- // Reset the emulator and load a new program.
- emulator.startProgram = function(bytecode, symbols) {
- emulator.stopProgram();
- loadedBytecode = bytecode;
- loadedSymbols = symbols;
- loadedMetadata = parseMetadata(bytecode);
- br.loadProgram(bytecode);
- br.run();
- }
- // Fires when the play button is pressed.
- emulator.runProgram = function() {
- br.blank ? emulator.startProgram(loadedBytecode, loadedSymbols) : br.run();
- }
- // Fires when the pause button is pressed.
- emulator.pauseProgram = function() {
- br.pause();
- }
- // Fires when the stop button is pressed.
- emulator.stopProgram = function() {
- br.stop();
- emulator.updateDOM();
- transmissions.innerHTML = '';
- emulator.hideStreamPanel();
- emulator.hideScreenPanel();
- currentTransmission = null;
- }
-
- emulator.updateDOM = function() {
- function renderStack(stack) {
- let string = '';
- for (let i=0; i<stack.p && i<stack.mem.length; i++) {
- string += hex(stack.mem[i], 2) + ' ';
- }
- return string;
- }
- pcState.textContent = hex(br.p, 4);
- labelState.textContent = loadedSymbols.findSymbol(br.p);
- wstState.textContent = renderStack(br.wst);
- rstState.textContent = renderStack(br.rst);
- if (br.halted) {
- status.textContent = 'ended';
- } else if (br.blank) {
- status.textContent = '';
- } else if (br.paused) {
- status.textContent = 'paused';
- } else if (br.asleep) {
- let wakeMask = br.dev.system.wakeMask;
- status.textContent = `sleeping / 0x${hex(wakeMask, 4)}`;
- } else {
- status.textContent = `running / ${br.cycle}`;
- }
- emulator.flushTransmission();
- }
-
- emulator.updateScreenSize = function() {
- if (!screenPanel.classList.contains('hidden')) {
- if (document.fullscreenElement == emulator) {
- let width = screenPanel.clientWidth;
- let height = screenPanel.clientHeight;
- br.dev.screen.resizeToFill(width, height);
- } else {
- let width = screenPanel.clientWidth;
- let height = width * 9/16;
- br.dev.screen.resizeToFill(width, height);
- }
- }
- }
-
- // Receive an incoming byte from the local stream.
- emulator.receiveTransmissionByte = function(byte) {
- transmissionParser.push(byte);
- }
-
- // Push all received bytes to the DOM.
- emulator.flushTransmission = function() {
- let string = transmissionParser.read();
- if (string) {
- if (!currentTransmission) {
- emulator.showStreamPanel();
- let element = document.createElement('li');
- element.addEventListener('click', function() {
- copyText(element.textContent); })
- currentTransmission = element;
- transmissions.appendChild(element); }
- currentTransmission.textContent += string;
- streamPanel.scrollTop = streamPanel.scrollHeight;
- }
- }
-
- // End the current incoming transmission.
- emulator.endTransmission = function() {
- emulator.flushTransmission();
- streamPanel.scrollTop = streamPanel.scrollHeight;
- currentTransmission = null;
-
- }
-
- // Change the fullscreen state of the whole emulator.
- emulator.toggleFullscreen = function() {
- if (document.fullscreenElement != emulator) {
- emulator.requestFullscreen();
- } else {
- document.exitFullscreen();
- }
- }
-
- emulator.mouseMove = function(e) {
- let bounds = canvas.getBoundingClientRect();
- let scale = br.dev.screen.scale;
- let x = ((e.clientX - bounds.left) / scale) & 0xFFFF;
- let y = ((e.clientY - bounds.top ) / scale) & 0xFFFF;
- br.dev.input.applyPosition(x, y);
- }
- emulator.mouseDown = function(e) {
- br.dev.input.applyButtons(e.buttons);
- }
- emulator.mouseUp = function(e) {
- br.dev.input.applyButtons(e.buttons);
- }
- emulator.mouseEnter = function(e) {
- br.dev.input.applyActive(true);
- emulator.mouseMove(e);
- }
- emulator.mouseExit = function(e) {
- br.dev.input.applyActive(false);
- emulator.mouseMove(e);
- }
- emulator.mouseScroll = function(e) {
- // Only capture scroll events if canvas is focused.
- if (document.activeElement == canvas) {
- e.preventDefault();
- let scale;
- // TODO: Dial these numbers in a bit.
- switch (e.deltaMode) {
- case (e.DOM_DELTA_PIXEL): scale = 1/10; break;
- case (e.DOM_DELTA_LINE): scale = 1; break;
- case (e.DOM_DELTA_PAGE): scale = 20; break;
- };
- br.dev.input.applyHScroll(e.deltaX * scale);
- br.dev.input.applyVScroll(e.deltaY * scale);
- }
- }
- emulator.keyInput = function(e, pressed) {
- e.preventDefault();
- br.dev.input.applyModifiers(e);
- if (!e.repeat && !e.isComposing) {
- br.dev.input.applyKey(e, pressed);
- }
- }
-
- emulator.touchStart = function(e) {
- if (e.changedTouches.length) {
- emulator.mouseMove(e.changedTouches[0]); }
- br.dev.input.applyActive(true);
- br.dev.input.applyButtons(0x01);
- }
- emulator.touchEnd = function(e) {
- br.dev.input.applyActive(false);
- br.dev.input.applyButtons(0x00);
- }
-
- fullscreenButton.addEventListener('click', emulator.toggleFullscreen);
- stateButton.addEventListener('click', emulator.toggleStatePanel);
- runButton.addEventListener('click', emulator.runProgram);
- pauseButton.addEventListener('click', emulator.pauseProgram);
- stopButton.addEventListener('click', emulator.stopProgram);
- stepButton.addEventListener('click', br.step);
-
- canvas.addEventListener('mousemove', emulator.mouseMove);
- canvas.addEventListener('pointermove', emulator.mouseMove);
- canvas.addEventListener('mousedown', emulator.mouseDown);
- canvas.addEventListener('mouseup', emulator.mouseUp);
- canvas.addEventListener('touchstart', emulator.touchStart);
- canvas.addEventListener('touchend', emulator.touchEnd);
- canvas.addEventListener('touchcancel', emulator.touchEnd);
- canvas.addEventListener('mouseenter', emulator.mouseEnter);
- canvas.addEventListener('mouseleave', emulator.mouseExit);
- canvas.addEventListener('wheel', emulator.mouseScroll);
- canvas.addEventListener('contextmenu', (e)=>{e.preventDefault()});
- canvas.addEventListener('keydown', (e)=>emulator.keyInput(e, true));
- canvas.addEventListener('keyup', (e)=>emulator.keyInput(e, false));
- // Make canvas focusable to be able to receive keydown and keyup events.
- canvas.tabIndex = 0;
-
- emulator.resizeObserver = new ResizeObserver(emulator.updateScreenSize).observe(screenPanel);
-
- if (options && !options.controls) emulator.hideMenuBar();
- if (options && options.autoplay) emulator.runProgram();
- if (options && options.nocursor) canvas.style.cursor = 'none';
-
- return emulator;
-}
-
-
-// Create an element from an HTML string.
-function elementFromHTML(html) {
- let template = document.createElement('template');
- template.innerHTML = html.trim();
- return template.content.firstChild;
-}
-
-
-// Convert an integer to a hexadecimal string.
-function hex(value, pad) {
- return value.toString(16).toUpperCase().padStart(pad, '0');
-}
-
-
-function parseMetadata(bytecode) {
- // Test identifier to see if program has metadata.
- let id = [0xE8,0x00,0x18,0x42,0x45,0x44,0x52,0x4F,0x43,0x4B];
- for (let i=0; i<10; i++) if (bytecode[i] != id[i]) return;
- // Parse each metadata item.
- let name = getString(0x000A);
- let authors = getString(0x000C);
- let description = getString(0x000E);
- let bgColour = getColour(0x0010);
- let fgColour = getColour(0x0012);
- let smallIcon = getIcon(0x0014, 24);
- let largeIcon = getIcon(0x0016, 64);
- return { name, authors, description, bgColour, fgColour, smallIcon, largeIcon };
-
- function getByte(addr) {
- return bytecode[addr] || 0;
- }
- function getDouble(addr) {
- return getByte(addr) << 8 | getByte(addr+1);
- }
- function getString(addr) {
- addr = getDouble(addr);
- if (addr) {
- let parser = new Utf8StreamParser();
- while (true) {
- let byte = bytecode[addr++];
- if (!byte) break;
- parser.push(byte);
- }
- return parser.read();
- }
- }
- function getColour(addr) {
- addr = getDouble(addr);
- if (addr) {
- let colour = getDouble(addr);
- let r = (colour >> 8 & 0xF) * 17;
- let g = (colour >> 8 & 0xF) * 17;
- let b = (colour >> 8 & 0xF) * 17;
- return [r, g, b];
- }
- }
- function getIcon(addr, size) {
- // TODO: Return a custom type containing the dimensions and raw
- // pixel data, and a colourise method to generate a coloured ImageData.
- }
-}
-
-
-// Copy a string to the clipboard.
-function copyText(text) {
- navigator.clipboard.writeText(text);
-}
-
-let encoder = new TextEncoder();
-function encodeUtf8(text) {
- return encoder.encode(text);
-}
-
-// Incrementally parse a UTF-8 string.
-function Utf8StreamParser() {
- // Full characters parsed so far.
- this.string = '';
- // Current partial code point.
- this.codePoint;
- // True if the current code point is being dropped.
- this.dropBytes = false;
- // Number of bytes received of the current code point.
- this.i = 0;
- // Expected byte length of the current code point.
- this.len = 0;
-
- // Push a code point to the string.
- let pushCodePoint = (codePoint) => {
- this.string += String.fromCodePoint(codePoint);
- }
- // Drop the current partial code point, if any.
- let dropCodePoint = () => {
- // Push a 'replacement character' if there is a partial code point
- // that hasn't already been dropped.
- if (this.i && !this.dropBytes) pushCodePoint(0xFFFD);
- this.codePoint = 0;
- this.dropBytes = false;
- this.i = 0;
- this.len = 0;
- }
- // Parse the start of an encoded code point.
- let parseStartByte = (byte, length) => {
- dropCodePoint();
- this.len = length;
- parseContinuationByte(byte);
- }
- // Parse the remaining bytes of an encoded code point.
- let parseContinuationByte = (byte) => {
- if (!this.dropBytes) {
- if (this.len) {
- this.codePoint = this.codePoint << 6 | (byte & 0x3F);
- this.i += 1;
- if (this.i == this.len) {
- pushCodePoint(this.codePoint);
- this.len = 0;
- }
- } else {
- this.dropBytes = true;
- pushCodePoint(0xFFFD);
- }
- }
- }
+ let getString = (key) => attr[key] ? attr[key].value : null;
+ let getBool = (key) => attr[key] ? true : false;
+ let src = getString("src");
+ let scale = getString("scale") || "1";
+ let controls = getBool("controls");
+ let nocursor = getBool("nocursor");
- // Parse a UTF-8 encoded byte.
- this.push = (byte) => {
- if (byte < 0x80) { // start of a 1-byte code point
- dropCodePoint();
- pushCodePoint(byte);
- } else if (byte < 0xC0) { // not a start byte
- parseContinuationByte(byte);
- } else if (byte < 0xE0) { // start of a 2-byte code point
- parseStartByte(byte, 2);
- } else if (byte < 0xF0) { // start of a 3-byte code point
- parseStartByte(byte, 3);
- } else if (byte < 0xF8) { // start of a 4-byte code point
- parseStartByte(byte, 4);
+ // Validate options.
+ if (!src) { console.error("No src attribute provided for emulator", element); return; }
+ try { scale = clamp(parseInt(attr.scale.value), 1, 10) } catch { scale = 1 }
+ let options = { scale, controls, nocursor, autoplay: true };
+ let url = new URL(src, window.location.href).href;
+ // Instantiate emulator once program has loaded in.
+ fetch(url).then((r) => {
+ if (r.ok) { r.arrayBuffer().then((a) => {
+ options.bytecode = new Uint8Array(a);
+ let emulator = new EmulatorElement(options);
+ emulator.init().then(() => { element.replaceWith(emulator.el) });
+ })} else {
+ console.log(`Could not load Bedrock program from ${url}`);
}
- }
- // Parse an array of bytes.
- this.pushList = (list) => {
- for (let byte of list) this.push(byte);
- }
-
- // Take the characters that have been parsed so far.
- this.read = () => {
- let output = this.string;
- this.string = '';
- return output;
- }
+ });
}
-
-
-
// ----------------------------------------------------------------------------------------------- +
// :::::: ASSEMBLER:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
// ----------------------------------------------------------------------------------------------- +
-// Convert a string of Bedrock source code into syntax-highlighted nodes.
-function highlightSource(source) {
- let currentText = '', currentClass = '';
- let items = [];
- let typeLookup = {
- 'comment': 'comment',
- 'markOpen': 'comment',
- 'markClose': 'comment',
- 'blockOpen': '',
- 'blockClose': '',
- 'rawString': 'value',
- 'terminatedString': 'value',
- 'macroDefinition': 'definition',
- 'globalLabel': 'definition',
- 'localLabel': 'definition',
- 'macroTerminator': 'definition',
- 'spacer': '',
- 'symbol': 'reference',
- 'byteLiteral': 'value',
- 'doubleLiteral': 'value',
- 'instruction': '',
- 'whitespace': '',
- };
- function pushItem(text, className) {
- if (currentClass != className) {
- if (currentClass) {
- let span = document.createElement('span');
- span.textContent = currentText;
- span.className = currentClass;
- items.push(span);
- } else {
- items.push(document.createTextNode(currentText));
- }
- currentClass = className;
- currentText = text;
- } else {
- currentText += text;
- }
- }
- for (let token of tokeniseProgram(source).tokens) {
- pushItem(token.text, typeLookup[token.type]);
- }
- pushItem(' ');
- return items;
-}
-
-
// Tokenise a string of Bedrock source code.
-function tokeniseProgram(source) {
+function tokeniseSource(source) {
let tokens = [], errors = [];
let currentText = '', endChar = '', label = '';
let currentLine = 0, currentColumn = 0; // position of next character
@@ -845,10 +269,10 @@ function tokeniseProgram(source) {
} else if (firstChar == ';') {
token.type = 'macroTerminator';
} else if (firstChar == '#') {
- token.type = 'spacer';
+ token.type = 'padding';
token.value = parseLiteral(currentText.slice(1));
if (token.value == undefined) {
- error(`Invalid value for spacer: ${currentText.slice(1)}`)
+ error(`Invalid value for padding: ${currentText.slice(1)}`)
}
} else if (firstChar == '~') {
token.type = 'symbol';
@@ -926,9 +350,56 @@ function tokeniseProgram(source) {
}
+// Convert a string of Bedrock source code into syntax-highlighted nodes.
+function highlightSource(source) {
+ let currentText = '', currentClass = '';
+ let items = [];
+ let typeLookup = {
+ 'comment': 'comment',
+ 'markOpen': 'comment',
+ 'markClose': 'comment',
+ 'blockOpen': '',
+ 'blockClose': '',
+ 'rawString': 'value',
+ 'terminatedString': 'value',
+ 'macroDefinition': 'definition',
+ 'globalLabel': 'definition',
+ 'localLabel': 'definition',
+ 'macroTerminator': 'definition',
+ 'padding': '',
+ 'symbol': 'reference',
+ 'byteLiteral': 'value',
+ 'doubleLiteral': 'value',
+ 'instruction': '',
+ 'whitespace': '',
+ };
+ function pushItem(text, className) {
+ if (currentClass != className) {
+ if (currentClass) {
+ let span = document.createElement('span');
+ span.textContent = currentText;
+ span.className = currentClass;
+ items.push(span);
+ } else {
+ items.push(document.createTextNode(currentText));
+ }
+ currentClass = className;
+ currentText = text;
+ } else {
+ currentText += text;
+ }
+ }
+ for (let token of tokeniseSource(source).tokens) {
+ pushItem(token.text, typeLookup[token.type]);
+ }
+ pushItem(' ');
+ return items;
+}
+
+
// Assemble a string of Bedrock source code.
-function assembleProgram(source) {
- let { tokens, errors } = tokeniseProgram(source);
+function assembleSource(source) {
+ let { tokens, errors } = tokeniseSource(source);
let program = new Uint8Array(65536);
// Populated up front with all names.
let macros = {}, labels = {}, address = 0;
@@ -971,7 +442,7 @@ function assembleProgram(source) {
} else if (['rawString', 'terminatedString'].includes(token.type)) {
for (let byte of encodeUtf8(token.value)) program[address++] = byte;
if (token.type == 'terminatedString') pushByte(0);
- } else if (token.type == 'spacer') {
+ } else if (token.type == 'padding') {
for (let i=0; i<token.value; i++) pushByte(0);
} else if (token.type == 'blockOpen') {
stack.push({ token, address });
@@ -1082,6 +553,7 @@ function assembleProgram(source) {
}
+// Efficient lookup table for finding the closest label to a given address.
function SymbolTable() {
this.names = [];
this.addresses = [];
@@ -1117,177 +589,144 @@ function SymbolTable() {
}
+// Map the predefined instruction names to bytes for the assembler.
+const instructionTable = {
+ 'HLT': 0x00, 'NOP' : 0x20, 'DB1' : 0x40, 'DB2' : 0x60, 'DB3' : 0x80, 'DB4' : 0xA0, 'DB5' : 0xC0, 'DB6' : 0xE0,
+ 'PSH': 0x01, 'PSH:': 0x21, 'PSH*': 0x41, 'PSH*:': 0x61, 'PSHr': 0x81, 'PSHr:': 0xA1, 'PSHr*': 0xC1, 'PSHr*:': 0xE1,
+ ':': 0x21, '*:': 0x61, 'r:': 0xA1, 'r*:': 0xE1,
+ 'POP': 0x02, 'POP:': 0x22, 'POP*': 0x42, 'POP*:': 0x62, 'POPr': 0x82, 'POPr:': 0xA2, 'POPr*': 0xC2, 'POPr*:': 0xE2,
+ 'CPY': 0x03, 'CPY:': 0x23, 'CPY*': 0x43, 'CPY*:': 0x63, 'CPYr': 0x83, 'CPYr:': 0xA3, 'CPYr*': 0xC3, 'CPYr*:': 0xE3,
+ 'DUP': 0x04, 'DUP:': 0x24, 'DUP*': 0x44, 'DUP*:': 0x64, 'DUPr': 0x84, 'DUPr:': 0xA4, 'DUPr*': 0xC4, 'DUPr*:': 0xE4,
+ 'OVR': 0x05, 'OVR:': 0x25, 'OVR*': 0x45, 'OVR*:': 0x65, 'OVRr': 0x85, 'OVRr:': 0xA5, 'OVRr*': 0xC5, 'OVRr*:': 0xE5,
+ 'SWP': 0x06, 'SWP:': 0x26, 'SWP*': 0x46, 'SWP*:': 0x66, 'SWPr': 0x86, 'SWPr:': 0xA6, 'SWPr*': 0xC6, 'SWPr*:': 0xE6,
+ 'ROT': 0x07, 'ROT:': 0x27, 'ROT*': 0x47, 'ROT*:': 0x67, 'ROTr': 0x87, 'ROTr:': 0xA7, 'ROTr*': 0xC7, 'ROTr*:': 0xE7,
+ 'JMP': 0x08, 'JMP:': 0x28, 'JMP*': 0x48, 'JMP*:': 0x68, 'JMPr': 0x88, 'JMPr:': 0xA8, 'JMPr*': 0xC8, 'JMPr*:': 0xE8,
+ 'JMS': 0x09, 'JMS:': 0x29, 'JMS*': 0x49, 'JMS*:': 0x69, 'JMSr': 0x89, 'JMSr:': 0xA9, 'JMSr*': 0xC9, 'JMSr*:': 0xE9,
+ 'JCN': 0x0A, 'JCN:': 0x2A, 'JCN*': 0x4A, 'JCN*:': 0x6A, 'JCNr': 0x8A, 'JCNr:': 0xAA, 'JCNr*': 0xCA, 'JCNr*:': 0xEA,
+ 'JCS': 0x0B, 'JCS:': 0x2B, 'JCS*': 0x4B, 'JCS*:': 0x6B, 'JCSr': 0x8B, 'JCSr:': 0xAB, 'JCSr*': 0xCB, 'JCSr*:': 0xEB,
+ 'LDA': 0x0C, 'LDA:': 0x2C, 'LDA*': 0x4C, 'LDA*:': 0x6C, 'LDAr': 0x8C, 'LDAr:': 0xAC, 'LDAr*': 0xCC, 'LDAr*:': 0xEC,
+ 'STA': 0x0D, 'STA:': 0x2D, 'STA*': 0x4D, 'STA*:': 0x6D, 'STAr': 0x8D, 'STAr:': 0xAD, 'STAr*': 0xCD, 'STAr*:': 0xED,
+ 'LDD': 0x0E, 'LDD:': 0x2E, 'LDD*': 0x4E, 'LDD*:': 0x6E, 'LDDr': 0x8E, 'LDDr:': 0xAE, 'LDDr*': 0xCE, 'LDDr*:': 0xEE,
+ 'STD': 0x0F, 'STD:': 0x2F, 'STD*': 0x4F, 'STD*:': 0x6F, 'STDr': 0x8F, 'STDr:': 0xAF, 'STDr*': 0xCF, 'STDr*:': 0xEF,
+ 'ADD': 0x10, 'ADD:': 0x30, 'ADD*': 0x50, 'ADD*:': 0x70, 'ADDr': 0x90, 'ADDr:': 0xB0, 'ADDr*': 0xD0, 'ADDr*:': 0xF0,
+ 'SUB': 0x11, 'SUB:': 0x31, 'SUB*': 0x51, 'SUB*:': 0x71, 'SUBr': 0x91, 'SUBr:': 0xB1, 'SUBr*': 0xD1, 'SUBr*:': 0xF1,
+ 'INC': 0x12, 'INC:': 0x32, 'INC*': 0x52, 'INC*:': 0x72, 'INCr': 0x92, 'INCr:': 0xB2, 'INCr*': 0xD2, 'INCr*:': 0xF2,
+ 'DEC': 0x13, 'DEC:': 0x33, 'DEC*': 0x53, 'DEC*:': 0x73, 'DECr': 0x93, 'DECr:': 0xB3, 'DECr*': 0xD3, 'DECr*:': 0xF3,
+ 'LTH': 0x14, 'LTH:': 0x34, 'LTH*': 0x54, 'LTH*:': 0x74, 'LTHr': 0x94, 'LTHr:': 0xB4, 'LTHr*': 0xD4, 'LTHr*:': 0xF4,
+ 'GTH': 0x15, 'GTH:': 0x35, 'GTH*': 0x55, 'GTH*:': 0x75, 'GTHr': 0x95, 'GTHr:': 0xB5, 'GTHr*': 0xD5, 'GTHr*:': 0xF5,
+ 'EQU': 0x16, 'EQU:': 0x36, 'EQU*': 0x56, 'EQU*:': 0x76, 'EQUr': 0x96, 'EQUr:': 0xB6, 'EQUr*': 0xD6, 'EQUr*:': 0xF6,
+ 'NQK': 0x17, 'NQK:': 0x37, 'NQK*': 0x57, 'NQK*:': 0x77, 'NQKr': 0x97, 'NQKr:': 0xB7, 'NQKr*': 0xD7, 'NQKr*:': 0xF7,
+ 'SHL': 0x18, 'SHL:': 0x38, 'SHL*': 0x58, 'SHL*:': 0x78, 'SHLr': 0x98, 'SHLr:': 0xB8, 'SHLr*': 0xD8, 'SHLr*:': 0xF8,
+ 'SHR': 0x19, 'SHR:': 0x39, 'SHR*': 0x59, 'SHR*:': 0x79, 'SHRr': 0x99, 'SHRr:': 0xB9, 'SHRr*': 0xD9, 'SHRr*:': 0xF9,
+ 'ROL': 0x1A, 'ROL:': 0x3A, 'ROL*': 0x5A, 'ROL*:': 0x7A, 'ROLr': 0x9A, 'ROLr:': 0xBA, 'ROLr*': 0xDA, 'ROLr*:': 0xFA,
+ 'ROR': 0x1B, 'ROR:': 0x3B, 'ROR*': 0x5B, 'ROR*:': 0x7B, 'RORr': 0x9B, 'RORr:': 0xBB, 'RORr*': 0xDB, 'RORr*:': 0xFB,
+ 'IOR': 0x1C, 'IOR:': 0x3C, 'IOR*': 0x5C, 'IOR*:': 0x7C, 'IORr': 0x9C, 'IORr:': 0xBC, 'IORr*': 0xDC, 'IORr*:': 0xFC,
+ 'XOR': 0x1D, 'XOR:': 0x3D, 'XOR*': 0x5D, 'XOR*:': 0x7D, 'XORr': 0x9D, 'XORr:': 0xBD, 'XORr*': 0xDD, 'XORr*:': 0xFD,
+ 'AND': 0x1E, 'AND:': 0x3E, 'AND*': 0x5E, 'AND*:': 0x7E, 'ANDr': 0x9E, 'ANDr:': 0xBE, 'ANDr*': 0xDE, 'ANDr*:': 0xFE,
+ 'NOT': 0x1F, 'NOT:': 0x3F, 'NOT*': 0x5F, 'NOT*:': 0x7F, 'NOTr': 0x9F, 'NOTr:': 0xBF, 'NOTr*': 0xDF, 'NOTr*:': 0xFF,
+};
+
+
// ----------------------------------------------------------------------------------------------- +
-// :::::: EMULATOR::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// :::::: EMULATOR CORE (WEBASSEMBLY) ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
// ----------------------------------------------------------------------------------------------- +
-// The core Bedrock emulator. Receives an EmulatorElement.
-function Bedrock(e) {
- this.e = e;
- this.mem = new Uint8Array(65536);
- this.wst = new Stack();
- this.rst = new Stack();
- this.dev = new DeviceBus(this);
- this.mark = performance.now();
- // Callback functions.
- this.onUpdate;
+// WebAssembly implementation of the Bedrock core.
+function BedrockWasm() {
+ let wasm, wst, rst;
+ this.mem;
+ this.ldd;
+ this.std;
- // Reset the emulator, preserving the contents of program memory.
- this.reset = () => {
- this.p = 0;
- this.wst.reset();
- this.rst.reset();
- this.dev.reset();
- this.cycle = 0;
- this.blank = true; // true when emulator has been reset.
- this.paused = true; // true when program is paused for the user.
- this.asleep = false; // true when program is waiting for input.
- this.halted = false; // true when program has halted.
- this.frameLag = 0; // time since runLoop started
+ this.init = async (dget1, dset1) => {
+ const imported = { bedrock: { dget1, dset1 } };
+ wasm = (await WebAssembly.instantiate(wasmModule, imported)).exports;
+ this.mem = new Uint8Array(wasm.memory.buffer, 0, 0x10000);
+ wst = new Uint8Array(wasm.memory.buffer, 0x10000, 0x100);
+ rst = new Uint8Array(wasm.memory.buffer, 0x10100, 0x100);
+ this.ldd = dget1;
+ this.std = dset1;
+ this.cc = wasm.cc;
+ this.ip = wasm.ip;
+ this.wp = wasm.wp;
+ this.rp = wasm.rp;
+ this.wst = (i) => { return wst[0xFF-i]; }
+ this.rst = (i) => { return rst[0xFF-i]; }
+ this.eval = wasm.eval;
+ this.reset = wasm.reset;
}
- // Reset the emulator and load in a new program.
- this.loadProgram = (bytecode) => {
+ // Load an assembled program into program memory.
+ this.load = (bytes) => {
this.reset();
this.mem.fill(0);
- this.mem.set(bytecode.slice(0, 65536));
- this.blank = false;
- this.mark = performance.now();
- console.log("Started new run");
+ this.mem.set(bytes.slice(0, 65536));
}
+}
- this.run = () => {
- this.paused = false;
- this.update();
- this.runLoop();
- }
- this.step = () => {
- if (this.paused && !this.halted) {
- if (this.asleep) {
- this.sleepLoop()
- } else {
- this.blank = false;
- switch (this.evaluate(1)) {
- case 5: this.halt(); break; // HALT
- case 4: this.reset(); break; // RESET
- case 3: this.reset(); break; // FORK
- case 2: this.sleep(); break; // SLEEP
- case 1: this.pause(); break; // DEBUG
- default: break; // NORMAL
- }
- this.update();
- this.render();
- }
- }
- }
- this.pause = () => {
- this.paused = true;
- this.update();
- this.render();
- }
-
- this.stop = () => {
- this.reset();
- this.update();
- this.render();
- }
+// ----------------------------------------------------------------------------------------------- +
+// :::::: EMULATOR CORE (JAVASCRIPT) :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
- this.halt = () => {
- this.halted = true;
- this.update();
- this.render();
- console.log("Run completed in", performance.now() - this.mark, "milliseconds");
- }
- this.sleep = () => {
- this.asleep = true;
- this.update();
- this.render();
- this.sleepLoop();
- }
+// Pure JavaScript implementation of the Bedrock core.
+function BedrockJs() {
+ this.mem;
+ this.ldd;
+ this.std;
+ let cc, ip, wst, rst;
- this.update = () => {
- if (this.onUpdate) this.onUpdate(this);
+ this.init = async (dget1, dset1) => {
+ this.mem = new Uint8Array(65536);
+ this.ldd = dget1;
+ this.std = dset1;
+ cc = 0;
+ ip = 0;
+ wst = new Stack();
+ rst = new Stack();
}
- // Render the screen contents to the canvas.
- this.render = () => {
- if (this.dev.input.accessed || this.dev.screen.accessed) {
- e.showScreenPanel();
- }
- let scr = this.dev.screen;
- if (scr.dirty()) {
- scr.render();
- let image = new ImageData(scr.pixels, scr.width, scr.height);
- scr.ctx.putImageData(image, 0, 0, scr.dx0, scr.dy0, scr.dx1, scr.dy1);
- scr.unmark();
- this.frameLag = performance.now();
- }
- }
+ this.cc = () => { return cc };
+ this.ip = () => { return ip };
+ this.wp = () => { return wst.p };
+ this.rp = () => { return rst.p };
+ this.wst = (i) => { return wst.mem[i] };
+ this.rst = (i) => { return rst.mem[i] };
- this.runLoop = () => {
- if (!this.halted && !this.paused) {
- if (this.asleep) {
- setTimeout(this.sleepLoop.bind(this));
- } else {
- // Tail-recursive with timeout to allow other code to run.
- switch (this.evaluate(100000)) {
- case 5: this.halt(); return; // HALT
- case 4: this.reset(); break; // RESET
- case 3: this.reset(); break; // FORK
- case 2: this.sleep(); return; // SLEEP
- case 1: this.pause(); break; // DEBUG
- default: // NORMAL
- }
- this.update();
- // Force a render if the frame has been running for >500ms.
- if (performance.now() - this.frameLag > 500) {
- this.render(); }
- setTimeout(this.runLoop.bind(this), 0);
- }
- }
+ // Load an assembled program into program memory.
+ this.load = (bytes) => {
+ this.reset();
+ this.mem.fill(0);
+ this.mem.set(bytes.slice(0, 65536));
}
- this.sleepLoop = () => {
- this.frameLag = performance.now();
- if (!this.halted) {
- if (!this.asleep) {
- setTimeout(this.runLoop.bind(this), 0);
- } else {
- let queue = this.dev.queue.get(this.dev.system.wakeMask);
- for (let slot of queue) {
- if (this.dev.devices[slot].wake()) {
- this.asleep = false;
- this.dev.system.wakeSlot = slot;
- setTimeout(this.runLoop.bind(this));
- return;
- }
- }
- setTimeout(this.sleepLoop.bind(this), 0);
- }
- }
+ // Reset all pointers and counters to zero.
+ this.reset = () => {
+ cc = 0;
+ ip = 0;
+ wst.reset();
+ rst.reset();
}
- // Evaluate at most n processor cycles, returning a signal value.
- this.evaluate = (n) => {
- for (let i=0; i<n; i++) {
- this.cycle += 1;
- let instr = this.mem[this.p++];
+ this.eval = (maxCycles) => {
+ for (let i=0; i<maxCycles; i++) {
+ cc += 1;
+ let instr = this.mem[ip++];
let rMode = instr & 0x80;
let wMode = instr & 0x40;
let iMode = instr & 0x20;
- let w = rMode ? this.rst : this.wst;
- let r = rMode ? this.wst : this.rst;
+ let w = rMode ? rst : wst;
+ let r = rMode ? wst : rst;
// Memory access functions.
- let mpop1 = ( ) => { return this.mem[this.p++]; }
+ let mpop1 = ( ) => { return this.mem[ip++]; }
let mpop2 = ( ) => { return mpop1() << 8 | mpop1(); }
let mpopx = ( ) => { return wMode ? mpop2() : mpop1(); }
- let mpsh1 = (v) => { this.mem[this.p++] = v; }
+ let mpsh1 = (v) => { this.mem[ip++] = v; }
let mpsh2 = (v) => { this.mpsh1(v >> 8); this.mpsh1(v); }
let mpshx = (v) => { wMode ? mpsh2(v) : mpsh1(v); }
let mget1 = (a) => { return this.mem[a]; }
@@ -1297,18 +736,18 @@ function Bedrock(e) {
let mset2 = (a,v) => { mset1(a, v >> 8); mset1(a+1, v); }
let msetx = (a,v) => { wMode ? mset2(a,v) : mset1(a,v); }
// Device access functions.
- let dget1 = (p) => { return this.dev.read(p); }
+ let dget1 = this.ldd;
let dget2 = (p) => { return dget1(p) << 8 | dget1(p+1); }
let dgetx = (p) => { return wMode ? dget2(p) : dget1(p); }
- let dset1 = (p,v) => { return this.dev.write(p,v); }
+ let dset1 = this.std;
let dset2 = (p,v) => { let s1 = dset1(p, v >> 8); let s2 = dset1(p+1, v & 0xFF); return s1 || s2; }
let dsetx = (p,v) => { if (wMode) { return dset2(p,v) } else { return dset1(p,v) } }
// Stack access functions.
- let wpop1 = ( ) => { return iMode ? (iMode=0, mpop1()) : w.pop1(); }
- let wpop2 = ( ) => { return iMode ? (iMode=0, mpop2()) : w.pop2(); }
+ let wpop1 = ( ) => { return w.pop1(); }
+ let wpop2 = ( ) => { return w.pop2(); }
let wpopx = ( ) => { return wMode ? wpop2() : wpop1(); }
- let rpop1 = ( ) => { return iMode ? (iMode=0, mpop1()) : r.pop1(); }
- let rpop2 = ( ) => { return iMode ? (iMode=0, mpop2()) : r.pop2(); }
+ let rpop1 = ( ) => { return r.pop1(); }
+ let rpop2 = ( ) => { return r.pop2(); }
let rpopx = ( ) => { return wMode ? rpop2() : rpop1(); }
let wpsh1 = (v) => { w.psh1(v); }
let wpsh2 = (v) => { w.psh2(v); }
@@ -1316,6 +755,13 @@ function Bedrock(e) {
let rpsh1 = (v) => { r.psh1(v); }
let rpsh2 = (v) => { r.psh2(v); }
let rpshx = (v) => { wMode ? r.psh2(v) : r.psh1(v); }
+ // Immediate-mode aware stack access functions.
+ let iwpop1 = ( ) => { return iMode ? mpop1() : w.pop1(); }
+ let iwpop2 = ( ) => { return iMode ? mpop2() : w.pop2(); }
+ let iwpopx = ( ) => { return wMode ? iwpop2() : iwpop1(); }
+ let irpop1 = ( ) => { return iMode ? mpop1() : r.pop1(); }
+ let irpop2 = ( ) => { return iMode ? mpop2() : r.pop2(); }
+ let irpopx = ( ) => { return wMode ? irpop2() : irpop1(); }
// Shifting operations.
let shl1 = (v,d) => { return d < 8 ? v << d & 0xFF : 0; }
let shl2 = (v,d) => { return d < 16 ? v << d & 0xFFFF : 0; }
@@ -1332,45 +778,55 @@ function Bedrock(e) {
let a, p, s, t, v, x, y, z;
switch (instr & 0x1F) {
- /* HLT */ case 0x00: switch(instr) { case 0x00: return 5; case 0x40: return 1; default: } break;
- /* PSH */ case 0x01: x=rpopx(); wpshx(x); break;
- /* POP */ case 0x02: wpopx(); break;
- /* CPY */ case 0x03: x=rpopx(); rpshx(x); wpshx(x); break;
- /* DUP */ case 0x04: x=wpopx(); wpshx(x); wpshx(x); break;
- /* OVR */ case 0x05: y=wpopx(); x=wpopx(); wpshx(x); wpshx(y); wpshx(x); break;
- /* SWP */ case 0x06: y=wpopx(); x=wpopx(); wpshx(y); wpshx(x); break;
- /* ROT */ case 0x07: z=wpopx(); y=wpopx(); x=wpopx(); wpshx(y); wpshx(z); wpshx(x); break;
- /* JMP */ case 0x08: a=wpop2(); this.p = a; break;
- /* JMS */ case 0x09: a=wpop2(); rpsh2(this.p); this.p = a; break;
- /* JCN */ case 0x0A: a=wpop2(); t=wpopx(); if (t) { this.p = a; } break;
- /* JCS */ case 0x0B: a=wpop2(); t=wpopx(); if (t) { rpsh2(this.p); this.p = a; } break;
- /* LDA */ case 0x0C: a=wpop2(); v=mgetx(a); wpshx(v); break;
- /* STA */ case 0x0D: a=wpop2(); v=wpopx(); msetx(a,v); break;
- /* LDD */ case 0x0E: p=wpop1(); v=dgetx(p); wpshx(v); break;
- /* STD */ case 0x0F: p=wpop1(); v=wpopx(); s=dsetx(p,v); if (s) { return s; } break;
- /* ADD */ case 0x10: y=wpopx(); x=wpopx(); wpshx(x+y); break;
- /* SUB */ case 0x11: y=wpopx(); x=wpopx(); wpshx(x-y); break;
- /* INC */ case 0x12: x=wpopx(); wpshx(x+1); break;
- /* DEC */ case 0x13: x=wpopx(); wpshx(x-1); break;
- /* LTH */ case 0x14: y=wpopx(); x=wpopx(); wpsh1(0-(x <y)); break;
- /* GTH */ case 0x15: y=wpopx(); x=wpopx(); wpsh1(0-(x >y)); break;
- /* EQU */ case 0x16: y=wpopx(); x=wpopx(); wpsh1(0-(x==y)); break;
- /* NQK */ case 0x17: y=wpopx(); x=wpopx(); wpshx(x); wpshx(y); wpsh1(0-(x!=y)); break;
- /* SHL */ case 0x18: y=wpop1(); x=wpopx(); wpshx(shlx(x,y)); break;
- /* SHR */ case 0x19: y=wpop1(); x=wpopx(); wpshx(shrx(x,y)); break;
- /* ROL */ case 0x1A: y=wpop1(); x=wpopx(); wpshx(rolx(x,y)); break;
- /* ROR */ case 0x1B: y=wpop1(); x=wpopx(); wpshx(rorx(x,y)); break;
- /* IOR */ case 0x1C: y=wpopx(); x=wpopx(); wpshx(x|y); break;
- /* XOR */ case 0x1D: y=wpopx(); x=wpopx(); wpshx(x^y); break;
- /* AND */ case 0x1E: y=wpopx(); x=wpopx(); wpshx(x&y); break;
- /* NOT */ case 0x1F: x=wpopx(); wpshx(~x ); break;
+ /* HLT */ case 0x00: switch(instr) {
+ case 0x00: return Signal.HALT;
+ case 0x20: continue;
+ case 0x40: return Signal.DB1;
+ case 0x60: return Signal.DB2;
+ case 0x80: return Signal.DB3;
+ case 0xA0: return Signal.DB4;
+ case 0xC0: return Signal.DB5;
+ case 0xE0: return Signal.DB6;
+ }
+ /* PSH */ case 0x01: x=irpopx(); wpshx(x); break;
+ /* POP */ case 0x02: iwpopx(); break;
+ /* CPY */ case 0x03: x=irpopx(); rpshx(x); wpshx(x); break;
+ /* DUP */ case 0x04: x=iwpopx(); wpshx(x); wpshx(x); break;
+ /* OVR */ case 0x05: y=iwpopx(); x=wpopx(); wpshx(x); wpshx(y); wpshx(x); break;
+ /* SWP */ case 0x06: y=iwpopx(); x=wpopx(); wpshx(y); wpshx(x); break;
+ /* ROT */ case 0x07: z=iwpopx(); y=wpopx(); x=wpopx(); wpshx(y); wpshx(z); wpshx(x); break;
+ /* JMP */ case 0x08: a=iwpop2(); ip = a; break;
+ /* JMS */ case 0x09: a=iwpop2(); rpsh2(ip); ip = a; break;
+ /* JCN */ case 0x0A: a=iwpop2(); t=wpopx(); if (t) { ip = a; } break;
+ /* JCS */ case 0x0B: a=iwpop2(); t=wpopx(); if (t) { rpsh2(ip); ip = a; } break;
+ /* LDA */ case 0x0C: a=iwpop2(); v=mgetx(a); wpshx(v); break;
+ /* STA */ case 0x0D: a=iwpop2(); v=wpopx(); msetx(a,v); break;
+ /* LDD */ case 0x0E: p=iwpop1(); v=dgetx(p); wpshx(v); break;
+ /* STD */ case 0x0F: p=iwpop1(); v=wpopx(); s=dsetx(p,v); if (s) { return s; } break;
+ /* ADD */ case 0x10: y=iwpopx(); x=wpopx(); wpshx(x+y); break;
+ /* SUB */ case 0x11: y=iwpopx(); x=wpopx(); wpshx(x-y); break;
+ /* INC */ case 0x12: x=iwpopx(); wpshx(x+1); break;
+ /* DEC */ case 0x13: x=iwpopx(); wpshx(x-1); break;
+ /* LTH */ case 0x14: y=iwpopx(); x=wpopx(); wpsh1(0-(x <y)); break;
+ /* GTH */ case 0x15: y=iwpopx(); x=wpopx(); wpsh1(0-(x >y)); break;
+ /* EQU */ case 0x16: y=iwpopx(); x=wpopx(); wpsh1(0-(x==y)); break;
+ /* NQK */ case 0x17: y=iwpopx(); x=wpopx(); wpshx(x); wpshx(y); wpsh1(0-(x!=y)); break;
+ /* SHL */ case 0x18: y=iwpop1(); x=wpopx(); wpshx(shlx(x,y)); break;
+ /* SHR */ case 0x19: y=iwpop1(); x=wpopx(); wpshx(shrx(x,y)); break;
+ /* ROL */ case 0x1A: y=iwpop1(); x=wpopx(); wpshx(rolx(x,y)); break;
+ /* ROR */ case 0x1B: y=iwpop1(); x=wpopx(); wpshx(rorx(x,y)); break;
+ /* IOR */ case 0x1C: y=iwpopx(); x=wpopx(); wpshx(x|y); break;
+ /* XOR */ case 0x1D: y=iwpopx(); x=wpopx(); wpshx(x^y); break;
+ /* AND */ case 0x1E: y=iwpopx(); x=wpopx(); wpshx(x&y); break;
+ /* NOT */ case 0x1F: x=iwpopx(); wpshx(~x ); break;
}
}
- };
+ return Signal.BREAK;
+ }
}
-// Stack for the core system.
+// Stack implementation for the JavaScript core.
function Stack() {
this.mem = new Uint8Array(256);
this.p = 0;
@@ -1380,9 +836,222 @@ function Stack() {
this.psh2 = (v) => { this.psh1(v >> 8); this.psh1(v); }
this.pop1 = ( ) => { return this.mem[--this.p]; }
this.pop2 = ( ) => { return this.pop1() | this.pop1() << 8; }
+
+}
+
+
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: EMULATOR :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+// Signal enum for devices.
+const Signal = Object.freeze({
+ BREAK: 0,
+ HALT: 1,
+ DB1: 2,
+ DB2: 3,
+ DB3: 4,
+ DB4: 5,
+ DB5: 6,
+ DB6: 7,
+ SLEEP: 8,
+ RESET: 9,
+ FORK: 10,
+});
+
+
+// Main Bedrock emulator implementation.
+function Bedrock(emulatorElement, wasm=defaultToWasm) {
+ let core;
+ if (wasmModule && wasm) {
+ console.log('Instantiating Bedrock with WASM core');
+ core = new BedrockWasm();
+ } else {
+ console.log('Instantiating Bedrock with JS core');
+ core = new BedrockJs();
+ }
+
+ this.e = emulatorElement; // the linked EmulatorElement
+ this.dev = new DeviceBus(this);
+
+ this.init = async () => {
+ await core.init(this.dev.read, this.dev.write);
+ }
+
+ // Fully reset the emulator, and load in a new program.
+ this.load = (bytecode) => {
+ this.reset();
+ core.load(bytecode);
+ this.blank = false;
+ }
+
+ // Fully reset the emulator.
+ this.reset = () => {
+ core.reset();
+ this.dev.reset();
+ this.blank = true; // emulator has been fully reset, screen is blank.
+ this.paused = true; // program is not currently executing.
+ this.asleep = false; // program is waiting for input.
+ this.halted = false; // current program has ended.
+ this.frameMark = 0; // time when previous frame was rendered.
+ }
+
+ this.update = () => {
+ if (this.e) {
+ this.e.updateDOM();
+ }
+ }
+
+ this.render = () => {
+ if (this.e) {
+ if (this.dev.input.accessed || this.dev.screen.accessed) {
+ this.e.showScreenPanel();
+ }
+ let scr = this.dev.screen;
+ if (scr.dirty()) {
+ scr.render();
+ let image = new ImageData(scr.pixels, scr.width, scr.height);
+ scr.ctx.putImageData(image, 0, 0, scr.dx0, scr.dy0, scr.dx1, scr.dy1);
+ scr.unmark();
+ this.frameMark = performance.now();
+ }
+ }
+ }
+
+ // Start or unpause the program.
+ this.run = () => {
+ this.paused = false;
+ this.update();
+ this.runLoop();
+ }
+
+ // Pause the program, hold the current emulator state.
+ this.pause = () => {
+ this.paused = true;
+ this.update();
+ this.render();
+ }
+
+ // End and reset the program, allow it to run from the beginning.
+ this.stop = () => {
+ this.reset();
+ this.update();
+ this.render();
+ }
+
+ this.sleep = () => {
+ this.asleep = true;
+ this.update();
+ this.render();
+ this.sleepLoop();
+ }
+
+ // End the program, hold the final emulator state.
+ this.halt = () => {
+ this.halted = true;
+ this.update();
+ this.render();
+ }
+
+ // Run the program for a single cycle.
+ this.step = () => {
+ this.blank = false;
+ if (this.paused && !this.halted) {
+ if (this.asleep) {
+ setTimeout(this.sleepLoop.bind(this), 0);
+ return;
+ }
+ switch (core.eval(1)) {
+ case Signal.HALT: this.halt(); return;
+ case Signal.DB4: this.assert(); break;
+ // case Signal.RESET: this.reset(); break;
+ // case Signal.FORK: this.reset(); break;
+ case Signal.SLEEP: this.sleep(); return;
+ case Signal.DB1: this.pause(); break;
+ default: // NORMAL
+ }
+ this.update();
+ this.render();
+ }
+ }
+
+ // Run the program indefinitely.
+ this.runLoop = () => {
+ if (!this.halted && !this.paused) {
+ if (this.asleep) {
+ setTimeout(this.sleepLoop.bind(this), 0);
+ return;
+ }
+ // Tail-recursive with timeout to allow other code to run.
+ switch (core.eval(cyclesPerBatch)) {
+ case Signal.HALT: this.halt(); return;
+ case Signal.DB4: this.assert(); break;
+ // case Signal.RESET: this.reset(); break;
+ // case Signal.FORK: this.reset(); break;
+ case Signal.SLEEP: this.sleep(); return;
+ case Signal.DB1: this.pause(); break;
+ default: // NORMAL
+ }
+ this.update();
+ // Force a render if the frame has been running for too long.
+ if (performance.now() - this.frameMark > maxFrameTime) {
+ this.render();
+ }
+ setTimeout(this.runLoop.bind(this), 0);
+ }
+ }
+
+ // Wait for input before returning to runLoop.
+ this.sleepLoop = () => {
+ this.frameMark = performance.now();
+ if (!this.halted) {
+ if (!this.asleep) {
+ setTimeout(this.runLoop.bind(this), 0);
+ return;
+ }
+ let queue = this.dev.queue.get(this.dev.system.wakeMask);
+ for (let slot of queue) {
+ if (this.dev.devices[slot].wake()) {
+ this.asleep = false;
+ this.dev.system.wakeSlot = slot;
+ setTimeout(this.runLoop.bind(this));
+ return;
+ }
+ }
+ setTimeout(this.sleepLoop.bind(this), 0);
+ }
+ }
+
+ this.debugState = () => {
+ let wst = []; let wp = core.wp();
+ let rst = []; let rp = core.rp();
+ for (let i=0; i<wp; i++) wst.push(core.wst(i));
+ for (let i=0; i<rp; i++) rst.push(core.rst(i));
+ return {
+ cc: core.cc(),
+ ip: core.ip(),
+ wst, rst,
+ };
+ }
+
+ this.assert = () => {
+ if (core.wp() == 1 && core.rp() == 0 && core.wst(0) == 0xFF) {
+ core.std(0x86, 0x2E); // period
+ } else {
+ core.std(0x86, 0x58); // capital X
+ }
+ }
}
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: EMULATOR DEVICES :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
// Device bus for the core system.
function DeviceBus(br) {
this.system = new SystemDevice(br);
@@ -1414,12 +1083,13 @@ function DeviceBus(br) {
this.queue = new WakeQueue();
- this.reset = function() {
+ this.reset = () => {
for (let d of this.devices) d.reset(); }
- this.read = function(p) {
+ this.read = (p) => {
return this.devices[p >> 4].read(p & 0x0F); }
- this.write = function(p,v) {
- return this.devices[p >> 4].write(p & 0x0F, v); }
+ this.write = (p,v) => {
+ return this.devices[p >> 4].write(p & 0x0F, v);
+ }
}
@@ -1474,8 +1144,8 @@ function SystemDevice(br) {
this.write = function(p,v) {
switch (p) {
case 0x0: this.wakeMask = setH(this.wakeMask, v); break;
- case 0x1: this.wakeMask = setL(this.wakeMask, v); return 2;
- case 0x3: let s = v ? 3 : 4; return s;
+ case 0x1: this.wakeMask = setL(this.wakeMask, v); return Signal.SLEEP;
+ case 0x3: let s = v ? Signal.FORK : Signal.RESET; return s;
case 0x8: this.nameBuffer.reset(); break;
case 0x9: this.authorsBuffer.reset(); break;
default: return;
@@ -1772,8 +1442,8 @@ function InputDevice(br) {
this.write = function(p,v) {
this.accessed = true;
switch (p) {
- case 0xA: this.characterBytes = []; return;
- default: return;
+ case 0xA: this.characterBytes.clear(); return;
+ default: return;
}
}
this.wake = function() {
@@ -1882,7 +1552,7 @@ function InputDevice(br) {
function ScreenDevice(br) {
this.init = function() {
- this.ctx = br.e.canvas.getContext("2d", {
+ this.ctx = br.e.el.canvas.getContext("2d", {
alpha: false,
willReadFrequently: false,
});
@@ -1903,7 +1573,7 @@ function ScreenDevice(br) {
this.paletteW = 0;
this.selection = [0, 0, 0, 0];
this.sprite = new SpriteBuffer();
- let initialWidth = Math.trunc(br.e.clientWidth / initialScreenScale);
+ let initialWidth = Math.trunc(br.e.el.clientWidth / initialScreenScale);
let initialHeight = Math.trunc(initialWidth * 9/16);
this.width = -1; // screen won't resize if dimensions aren't different
this.resize(initialWidth, initialHeight, initialScreenScale);
@@ -2173,10 +1843,10 @@ function StreamDevice(br) {
}
this.write = function(p,v) {
switch (p) {
- case 0x3: br.e.endTransmission(); break;
+ case 0x3: br.e.endTransmission(); break;
case 0x6:
- case 0x7: br.e.receiveTransmissionByte(v); break;
- default: return;
+ case 0x7: br.e.receiveTransmissionByte(v); break;
+ default: return;
}
}
this.wake = function() {
@@ -2201,6 +1871,7 @@ function ClipboardDevice(br) {
}
this.write = function(p,v) {
// HACK: The -1 is to break the run loop to allow the br.pause() to take hold.
+ // A 0 wouldn't be raised, and the higher signals have other meanings.
switch (p) {
case 0x2: this.readEntry(v); return -1;
case 0x3: this.writeEntry(v); return -1;
@@ -2217,7 +1888,6 @@ function ClipboardDevice(br) {
this.readEntry = function(v) {
this.readQueue = [];
if (v) {
- // TODO: Figure out how to make this synchronous.
// HACK: Pause the emulator until promise resolves.
if (navigator.clipboard.readText) {
br.pause();
@@ -2238,20 +1908,13 @@ function ClipboardDevice(br) {
}
this.writeEntry = function(v) {
if (v) {
- // TODO: Figure out how to make this synchronous.
// HACK: Pause the emulator until promise resolves.
if (navigator.clipboard.writeText) {
br.pause();
let parser = new Utf8StreamParser();
parser.pushList(this.writeQueue);
- navigator.clipboard.writeText(parser.read()).then(
- () => {
- br.run();
- },
- () => {
- br.run();
- }
- );
+ navigator.clipboard.writeText(parser.read())
+ .finally(() => { br.run() });
}
} else {
// TODO: Look into valid clipboard binary formats.
@@ -2436,12 +2099,667 @@ function signed(v) { return v << 16 >> 16; }
function clamp(v,a,b) { return v>=a ? v<b ? v : b : a; } // inclusive a and b
+// Incrementally parse a UTF-8 string.
+function Utf8StreamParser() {
+ // Full characters parsed so far.
+ this.string = '';
+ // Current partial code point.
+ this.codePoint;
+ // True if the current code point is being dropped.
+ this.dropBytes = false;
+ // Number of bytes received of the current code point.
+ this.i = 0;
+ // Expected byte length of the current code point.
+ this.len = 0;
+
+ // Push a code point to the string.
+ let pushCodePoint = (codePoint) => {
+ this.string += String.fromCodePoint(codePoint);
+ }
+ // Drop the current partial code point, if any.
+ let dropCodePoint = () => {
+ // Push a 'replacement character' if there is a partial code point
+ // that hasn't already been dropped.
+ if (this.i && !this.dropBytes) pushCodePoint(0xFFFD);
+ this.codePoint = 0;
+ this.dropBytes = false;
+ this.i = 0;
+ this.len = 0;
+ }
+ // Parse the start of an encoded code point.
+ let parseStartByte = (byte, length) => {
+ dropCodePoint();
+ this.len = length;
+ parseContinuationByte(byte);
+ }
+ // Parse the remaining bytes of an encoded code point.
+ let parseContinuationByte = (byte) => {
+ if (!this.dropBytes) {
+ if (this.len) {
+ this.codePoint = this.codePoint << 6 | (byte & 0x3F);
+ this.i += 1;
+ if (this.i == this.len) {
+ pushCodePoint(this.codePoint);
+ this.len = 0;
+ }
+ } else {
+ this.dropBytes = true;
+ pushCodePoint(0xFFFD);
+ }
+ }
+ }
+
+ // Parse a UTF-8 encoded byte.
+ this.push = (byte) => {
+ if (byte < 0x80) { // start of a 1-byte code point
+ dropCodePoint();
+ pushCodePoint(byte);
+ } else if (byte < 0xC0) { // not a start byte
+ parseContinuationByte(byte);
+ } else if (byte < 0xE0) { // start of a 2-byte code point
+ parseStartByte(byte, 2);
+ } else if (byte < 0xF0) { // start of a 3-byte code point
+ parseStartByte(byte, 3);
+ } else if (byte < 0xF8) { // start of a 4-byte code point
+ parseStartByte(byte, 4);
+ }
+ }
+ // Parse an array of bytes.
+ this.pushList = (list) => {
+ for (let byte of list) this.push(byte);
+ }
+
+ // Take the characters that have been parsed so far.
+ this.read = () => {
+ let output = this.string;
+ this.string = '';
+ return output;
+ }
+}
+
+
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: ASSEMBLER ELEMENT ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+// Constructor for an interactive assembler element.
+function AssemblerElement(element) {
+ this.el = instantiateFromTemplate(assemblerTemplate);
+ this.el.textarea = this.el.querySelector('textarea');
+ this.el.viewer = this.el.querySelector('.viewer');
+ this.el.editor = this.el.querySelector('.editor');
+ this.el.errorPanel = this.el.querySelector('.panel.errors');
+ this.el.bytecodePanel = this.el.querySelector('.panel.bytecode');
+ this.el.status = this.el.querySelector('.status');
+ this.el.checkButton = this.el.querySelector('button[name="check"]');
+ this.el.runButton = this.el.querySelector('button[name="run"]');
+ this.el.fullscreenButton = this.el.querySelector('button[name="fullscreen"]');
+
+ // Copy the text content of a passed element into the editor.
+ if (element) {
+ this.el.textarea.value = element.textContent.trim();
+ this.el.textarea.style.height = element.clientHeight + 'px';
+ }
+
+ this.autoGrow = true; // increase height as text is entered
+ this.emulator = null; // linked emulator element, if any
+
+ // Show and hide the error and bytecode panels.
+ this.showErrorPanel = () => {
+ this.hideBytecodePanel();
+ this.el.errorPanel.classList.remove('hidden'); }
+ this.hideErrorPanel = () => {
+ this.el.errorPanel.classList.add('hidden'); }
+ this.showBytecodePanel = () => {
+ this.hideErrorPanel();
+ this.el.bytecodePanel.classList.remove('hidden'); }
+ this.hideBytecodePanel = () => {
+ this.el.bytecodePanel.classList.add('hidden'); }
+
+ // Re-align the viewer to the textarea by setting the viewer scroll position.
+ this.syncScroll = () => {
+ this.el.viewer.scrollTop = this.el.textarea.scrollTop;
+ this.el.viewer.scrollLeft = this.el.textarea.scrollLeft;
+ }
+
+ // Re-align the viewer to the textarea by setting the viewer height.
+ this.syncHeight = () => {
+ // Disable auto-grow if editor was shrunk by the user.
+ if (this.el.textarea.clientHeight < this.el.viewer.clientHeight) {
+ this.autoGrow = false; }
+ let height = this.el.textarea.clientHeight + 'px';
+ this.el.viewer.style.height = height;
+ this.el.editor.style.height = height;
+ }
+
+ // Copy text from textarea to viewer with syntax highlighting.
+ this.renderText = () => {
+ // HACK: Force viewer to show trailing newlines.
+ let text = this.el.textarea.value;
+ if (text.slice(-1) == "\n") text += ' ';
+ // Replace contents of viewer.
+ this.el.viewer.innerHTML = '';
+ for (let node of highlightSource(text)) {
+ this.el.viewer.appendChild(node); }
+ // Auto-grow the text area as text is entered.
+ if (this.autoGrow && this.el.textarea.scrollHeight > this.el.textarea.clientHeight) {
+ this.el.textarea.style.height = this.el.textarea.scrollHeight + 'px'; }
+ // Lock viewer to textarea.
+ this.syncScroll();
+ }
+
+ // Assemble the program and show bytecode or errors.
+ this.checkProgram = () => {
+ let { bytecode, symbols, errors } = assembleSource(this.el.textarea.value);
+ this.el.errorPanel.innerHTML = '';
+ this.el.bytecodePanel.innerHTML = '';
+ if (errors.length) {
+ let list = document.createElement('ul');
+ for (let error of errors) {
+ let item = document.createElement('li');
+ let line = error.start.line + 1;
+ let column = error.start.column + 1;
+ item.textContent = `[${line}:${column}] ${error.text}`;
+ list.appendChild(item); }
+ this.el.errorPanel.appendChild(list);
+ let unit = errors.length == 1 ? 'error' : 'errors';
+ this.el.status.textContent = `${errors.length} ${unit}`;
+ this.showErrorPanel();
+ } else {
+ let unit = bytecode.length == 1 ? 'byte' : 'bytes';
+ this.el.status.textContent = `no errors (${bytecode.length} ${unit})`;
+ let programListing = document.createElement('div');
+ programListing.textContent = hexArray(bytecode);
+ this.el.bytecodePanel.appendChild(programListing);
+ this.hideErrorPanel();
+ this.hideBytecodePanel();
+ if (bytecode.length) this.showBytecodePanel();
+ return { bytecode, symbols };
+ }
+ }
+
+ // Assemble the program and run in an emulator.
+ this.runProgram = () => {
+ // Check and assemble program.
+ let program = this.checkProgram();
+ if (program) {
+ this.hideBytecodePanel();
+ let { bytecode, symbols } = program;
+ if (this.emulator) {
+ this.emulator.showStatePanel();
+ this.emulator.startProgram(bytecode, symbols);
+ } else {
+ // Instantiate a new emulator element if none exists.
+ this.emulator = new EmulatorElement();
+ this.emulator.assembler = this;
+ this.el.parentElement.insertBefore(this.emulator.el, this.el.nextSibling);
+ this.emulator.init().then(() => {
+ this.emulator.showStatePanel();
+ this.emulator.startProgram(bytecode, symbols);
+ })
+ }
+ }
+ }
+
+ // Toggle the fullscreen state of the whole assembler element.
+ this.toggleFullscreen = () => {
+ if (document.fullscreenElement != this.el) {
+ this.el.requestFullscreen();
+ } else {
+ document.exitFullscreen();
+ }
+ }
+
+ // Handle keypresses on the whole assembler.
+ this.onKeyPress = (event) => {
+ if (event.key == "Escape") {
+ event.preventDefault();
+ this.hideErrorPanel();
+ this.hideBytecodePanel();
+ } else if (event.key == "s" && event.ctrlKey) {
+ event.preventDefault();
+ this.checkProgram();
+ }
+ }
+
+ // Handle keypresses on just the editor.
+ this.onEditorKeyPress = (event) => {
+ let indent = ' ';
+ let text = this.el.textarea.value;
+ let selectionStart = this.el.textarea.selectionStart;
+ let selectionEnd = this.el.textarea.selectionEnd;
+
+ // Find the character index following the previous newline character.
+ function findLineStart(i) {
+ while (i>0 && text[i-1] != '\n') i -= 1;
+ return i; }
+
+ if (event.key == "Enter" && event.ctrlKey) {
+ event.preventDefault();
+ this.runProgram();
+ } else if (event.key == "Tab") {
+ event.preventDefault();
+ // Find the start index of every line in the selection.
+ let i = findLineStart(selectionStart);
+ let lineStarts = [i];
+ for (; i<selectionEnd; i++) if (text[i] == '\n') lineStarts.push(i+1);
+ if (event.shiftKey) {
+ // Find length of the leading whitespace in each line.
+ let whitespaceLengths = [];
+ for (let start of lineStarts) {
+ let end = start;
+ while ('\t '.includes(text[end])) end++;
+ whitespaceLengths.push(end-start); }
+ // Unindent each line in reverse order.
+ selectionStart -= Math.min(tabSize, whitespaceLengths[0]);
+ for (let i=lineStarts.length-1; i>=0; i--) {
+ let start = lineStarts[i];
+ let trim = Math.min(tabSize, whitespaceLengths[i]);
+ text = text.slice(0, start) + text.slice(start+trim);
+ selectionEnd -= trim; }
+ } else {
+ // Indent each line in reverse order.
+ selectionStart += tabSize;
+ for (let i=lineStarts.length-1; i>=0; i--) {
+ let start = lineStarts[i];
+ text = text.slice(0, start) + indent + text.slice(start);
+ selectionEnd += tabSize; }
+ }
+ // Update textarea content and viewer.
+ this.el.textarea.value = text;
+ this.el.textarea.selectionStart = selectionStart;
+ this.el.textarea.selectionEnd = selectionEnd;
+ this.renderText();
+ }
+ }
+
+ this.el.textarea.addEventListener('input', this.renderText);
+ this.el.textarea.addEventListener('scroll', this.syncScroll);
+ this.el.textarea.addEventListener('keydown', this.onEditorKeyPress);
+ this.el.addEventListener('keydown', this.onKeyPress);
+ this.el.checkButton.addEventListener('click', this.checkProgram);
+ this.el.runButton.addEventListener('click', this.runProgram);
+ this.el.fullscreenButton.addEventListener('click', this.toggleFullscreen);
+
+ this.el.resizeObserver = new ResizeObserver(this.syncHeight).observe(this.el.textarea);
+ this.el.textarea.selectionStart = 0;
+ this.el.textarea.selectionEnd = 0;
+ this.el.textarea.scrollLeft = 0;
+ this.renderText();
+}
+
+
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: EMULATOR ELEMENT :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+/// Constructor for a interactive emulator element.
+function EmulatorElement(options) {
+ this.el = instantiateFromTemplate(emulatorTemplate);
+ // Menu bar
+ this.el.menuBar = this.el.querySelector('.menubar');
+ this.el.fullscreenButton = this.el.menuBar.querySelector('button[name="fullscreen"]');
+ this.el.stateButton = this.el.menuBar.querySelector('button[name="state"]');
+ this.el.runButton = this.el.menuBar.querySelector('button[name="run"]');
+ this.el.pauseButton = this.el.menuBar.querySelector('button[name="pause"]');
+ this.el.stopButton = this.el.menuBar.querySelector('button[name="stop"]');
+ this.el.stepButton = this.el.menuBar.querySelector('button[name="step"]');
+ this.el.status = this.el.menuBar.querySelector('.status');
+ // Screen panel
+ this.el.screenPanel = this.el.querySelector('.screen');
+ this.el.canvas = this.el.screenPanel.querySelector('canvas');
+ // Stream panel
+ this.el.streamPanel = this.el.querySelector('.stream');
+ this.el.transmissions = this.el.querySelector('.transmissions');
+ // State panel
+ this.el.statePanel = this.el.querySelector('.panel.state');
+ this.el.ipValue = this.el.statePanel.querySelector('#ip .value');
+ this.el.ipNote = this.el.statePanel.querySelector('#ip .note');
+ this.el.ccValue = this.el.statePanel.querySelector('#cc .value');
+ this.el.ccNote = this.el.statePanel.querySelector('#cc .note');
+ this.el.wstValue = this.el.statePanel.querySelector('#wst .value');
+ this.el.rstValue = this.el.statePanel.querySelector('#rst .value');
+
+ let withWasm = options ? options.wasm : undefined;
+ this.br = new Bedrock(this, withWasm);
+ this.bytecode = options ? options.bytecode : new Uint8Array(0);
+ this.symbols = new SymbolTable();
+
+ // Buffered transmission handling for stream device.
+ let currentTransmission = null;
+ let transmissionParser = new Utf8StreamParser();
+
+ // Initialise emulator and emulator element.
+ this.init = async () => {
+ await this.br.init();
+ this.br.reset();
+ this.br.onUpdate = () => this.updateDOM();
+
+ // Apply options.
+ if (options && !options.controls) this.hideMenuBar();
+ if (options && options.autoplay) this.runProgram();
+ if (options && options.nocursor) this.el.canvas.style.cursor = 'none';
+
+ // Make canvas focusable to be able to receive keydown and keyup events.
+ this.el.canvas.tabIndex = 0;
+
+ this.el.fullscreenButton.addEventListener('click', this.toggleFullscreen);
+ this.el.stateButton.addEventListener('click', this.toggleStatePanel);
+ this.el.runButton.addEventListener('click', this.runProgram);
+ this.el.pauseButton.addEventListener('click', this.pauseProgram);
+ this.el.stopButton.addEventListener('click', this.stopProgram);
+ this.el.stepButton.addEventListener('click', this.br.step);
+ this.resizeObserver = new ResizeObserver(this.updateScreenSize).observe(this.el.screenPanel);
+
+ this.el.canvas.addEventListener('mousemove', this.mouseMove);
+ this.el.canvas.addEventListener('pointermove', this.mouseMove);
+ this.el.canvas.addEventListener('mousedown', this.mouseDown);
+ this.el.canvas.addEventListener('mouseup', this.mouseUp);
+ this.el.canvas.addEventListener('touchstart', this.touchStart);
+ this.el.canvas.addEventListener('touchend', this.touchEnd);
+ this.el.canvas.addEventListener('touchcancel', this.touchEnd);
+ this.el.canvas.addEventListener('mouseenter', this.mouseEnter);
+ this.el.canvas.addEventListener('mouseleave', this.mouseExit);
+ this.el.canvas.addEventListener('wheel', this.mouseScroll);
+ this.el.canvas.addEventListener('contextmenu', (e)=>{e.preventDefault()});
+ this.el.canvas.addEventListener('keydown', (e)=>this.keyInput(e, true));
+ this.el.canvas.addEventListener('keyup', (e)=>this.keyInput(e, false));
+ }
+
+ this.showStatePanel = () => {
+ this.el.statePanel.classList.remove('hidden'); }
+ this.toggleStatePanel = () => {
+ this.el.statePanel.classList.toggle('hidden'); }
+ this.showStreamPanel = () => {
+ this.el.streamPanel.classList.remove('hidden'); }
+ this.hideStreamPanel = () => {
+ this.el.streamPanel.classList.add('hidden'); }
+ this.showScreenPanel = () => {
+ this.el.screenPanel.classList.remove('hidden'); }
+ this.hideScreenPanel = () => {
+ this.el.screenPanel.classList.add('hidden'); }
+ this.hideMenuBar = () => {
+ this.el.menuBar.classList.add('hidden'); }
+
+ // Reset the emulator, load and run a new program.
+ this.startProgram = (bytecode, symbols) => {
+ this.stopProgram();
+ this.bytecode = bytecode;
+ this.symbols = symbols;
+ this.br.load(bytecode);
+ this.br.run();
+ }
+
+ // Fires when the stop button is pressed.
+ this.stopProgram = () => {
+ this.br.stop();
+ this.updateDOM();
+ this.el.transmissions.innerHTML = '';
+ this.hideStreamPanel();
+ this.hideScreenPanel();
+ currentTransmission = null;
+ }
+
+ // Fires when the play button is pressed.
+ this.runProgram = () => {
+ if (this.br.blank) {
+ this.startProgram(this.bytecode, this.symbols);
+ } else {
+ this.br.run();
+ }
+ }
+
+ // Fires when the pause button is pressed.
+ this.pauseProgram = () => {
+ this.br.pause();
+ }
+
+ // Update information displayed in the emulator frame.
+ this.updateDOM = () => {
+ let { cc, ip, wst, rst } = this.br.debugState();
+ this.el.ipValue.textContent = hex(ip, 4);
+ this.el.ipNote.textContent = this.symbols.findSymbol(ip);
+ this.el.ccValue.textContent = cc;
+ this.el.wstValue.textContent = hexArray(wst);
+ this.el.rstValue.textContent = hexArray(rst);
+ if (this.br.halted) {
+ this.el.status.textContent = 'ended';
+ } else if (this.br.blank) {
+ this.el.status.textContent = '';
+ } else if (this.br.paused) {
+ this.el.status.textContent = 'paused';
+ } else if (this.br.asleep) {
+ let wakeMask = this.br.dev.system.wakeMask;
+ this.el.status.textContent = `sleeping / 0x${hex(wakeMask, 4)}`;
+ } else {
+ this.el.status.textContent = `running / ${cc}`;
+ }
+ this.flushTransmission();
+ }
+
+ this.updateScreenSize = () => {
+ if (!this.el.screenPanel.classList.contains('hidden')) {
+ if (document.fullscreenElement == this.el) {
+ let width = this.el.screenPanel.clientWidth;
+ let height = this.el.screenPanel.clientHeight;
+ this.br.dev.screen.resizeToFill(width, height);
+ } else {
+ let width = this.el.screenPanel.clientWidth;
+ let height = width * 9/16;
+ this.br.dev.screen.resizeToFill(width, height);
+ }
+ }
+ }
+
+ // Receive an incoming byte from the local stream.
+ this.receiveTransmissionByte = (byte) => {
+ transmissionParser.push(byte);
+ }
+
+ // Push all received bytes to the DOM.
+ this.flushTransmission = () => {
+ let string = transmissionParser.read();
+ if (string) {
+ if (!currentTransmission) {
+ this.showStreamPanel();
+ let element = document.createElement('li');
+ element.addEventListener('click', () => {
+ copyText(element.textContent); })
+ currentTransmission = element;
+ this.el.transmissions.appendChild(element);
+ // Prune old transmissions.
+ let count = Math.max(this.el.transmissions.children.length - maxTransmissions, 0);
+ for (let i=0; i<count; i++) this.el.transmissions.children[0].remove();
+ }
+ currentTransmission.textContent += string;
+ this.el.streamPanel.scrollTop = this.el.streamPanel.scrollHeight;
+ }
+ }
+
+ // End the current incoming transmission.
+ this.endTransmission = () => {
+ this.flushTransmission();
+ currentTransmission = null;
+ }
+
+ // Change the fullscreen state of the whole emulator.
+ this.toggleFullscreen = () => {
+ if (document.fullscreenElement != this.el) {
+ this.el.requestFullscreen();
+ } else {
+ document.exitFullscreen();
+ }
+ }
+
+ this.mouseMove = (e) => {
+ let bounds = this.el.canvas.getBoundingClientRect();
+ let scale = this.br.dev.screen.scale;
+ let x = ((e.clientX - bounds.left) / scale) & 0xFFFF;
+ let y = ((e.clientY - bounds.top ) / scale) & 0xFFFF;
+ this.br.dev.input.applyPosition(x, y);
+ }
+
+ this.mouseDown = (e) => { this.br.dev.input.applyButtons(e.buttons); }
+ this.mouseUp = (e) => { this.br.dev.input.applyButtons(e.buttons); }
+
+ this.mouseEnter = (e) => {
+ this.br.dev.input.applyActive(true);
+ this.mouseMove(e);
+ }
+
+ this.mouseExit = (e) => {
+ // HACK: Force buttons to release when the mouse leaves the screen,
+ // otherwise any buttons that were held down when the mouse leaves
+ // will be stuck down when the mouse re-enters, even if they were
+ // released off-screen.
+ // TODO: Figure out how to attach a listener to the window as a whole
+ // that can tell each emulator how the mouse behaves off-screen, so
+ // that the mouse can be dragged off and onto a window in one
+ // continuous stroke.
+ this.br.dev.input.applyButtons(0x00);
+ this.br.dev.input.applyActive(false);
+ this.mouseMove(e);
+ }
+
+ this.mouseScroll = (e) => {
+ // Only capture scroll events if canvas is focused.
+ if (document.activeElement == this.el.canvas) {
+ e.preventDefault();
+ let scale;
+ // TODO: Dial these numbers in a bit.
+ switch (e.deltaMode) {
+ case (e.DOM_DELTA_PIXEL): scale = 1/10; break;
+ case (e.DOM_DELTA_LINE): scale = 1; break;
+ case (e.DOM_DELTA_PAGE): scale = 20; break;
+ };
+ this.br.dev.input.applyHScroll(e.deltaX * scale);
+ this.br.dev.input.applyVScroll(e.deltaY * scale);
+ }
+ }
+
+ this.keyInput = (e, pressed) => {
+ e.preventDefault();
+ this.br.dev.input.applyModifiers(e);
+ if (!e.repeat && !e.isComposing) {
+ this.br.dev.input.applyKey(e, pressed);
+ }
+ }
+
+ this.touchStart = (e) => {
+ if (e.changedTouches.length) {
+ this.mouseMove(e.changedTouches[0]); }
+ this.br.dev.input.applyActive(true);
+ this.br.dev.input.applyButtons(0x01);
+ }
+
+ this.touchEnd = (e) => {
+ this.br.dev.input.applyActive(false);
+ this.br.dev.input.applyButtons(0x00);
+ }
+}
+
+
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: METADATA :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+function parseMetadata(bytecode) {
+ // Test identifier to see if program has metadata.
+ let id = [0xE8,0x00,0x18,0x42,0x45,0x44,0x52,0x4F,0x43,0x4B];
+ for (let i=0; i<10; i++) if (bytecode[i] != id[i]) return;
+ // Parse each metadata item.
+ let name = getString(0x000A);
+ let authors = getString(0x000C);
+ let description = getString(0x000E);
+ let bgColour = getColour(0x0010);
+ let fgColour = getColour(0x0012);
+ let smallIcon = getIcon(0x0014, 24);
+ let largeIcon = getIcon(0x0016, 64);
+ return { name, authors, description, bgColour, fgColour, smallIcon, largeIcon };
+
+ function getByte(addr) {
+ return bytecode[addr] || 0;
+ }
+ function getDouble(addr) {
+ return getByte(addr) << 8 | getByte(addr+1);
+ }
+ function getString(addr) {
+ addr = getDouble(addr);
+ if (addr) {
+ let parser = new Utf8StreamParser();
+ while (true) {
+ let byte = bytecode[addr++];
+ if (!byte) break;
+ parser.push(byte);
+ }
+ return parser.read();
+ }
+ }
+ function getColour(addr) {
+ addr = getDouble(addr);
+ if (addr) {
+ let colour = getDouble(addr);
+ let r = (colour >> 8 & 0xF) * 17;
+ let g = (colour >> 8 & 0xF) * 17;
+ let b = (colour >> 8 & 0xF) * 17;
+ return [r, g, b];
+ }
+ }
+ function getIcon(addr, size) {
+ // TODO: Return a custom type containing the dimensions and raw
+ // pixel data, and a colourise method to generate a coloured ImageData.
+ }
+}
+
+
// ----------------------------------------------------------------------------------------------- +
-// :::::: CONSTANTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// :::::: UTILITIES ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
// ----------------------------------------------------------------------------------------------- +
+// Convert an integer to a hexadecimal string.
+function hex(value, pad=2) {
+ return value.toString(16).toUpperCase().padStart(pad, '0');
+}
+
+// Convert a whole byte array to a hexadecimal string.
+function hexArray(array) {
+ let string = '';
+ for (let byte of array) string += hex(byte) + ' ';
+ return string;
+}
+
+// Instantiate an element from an HTML string.
+function instantiateFromTemplate(html) {
+ let template = document.createElement('template');
+ template.innerHTML = html.trim();
+ return template.content.firstChild;
+}
+
+// Copy a string to the clipboard.
+function copyText(text) {
+ navigator.clipboard.writeText(text);
+}
+
+let encoder = new TextEncoder();
+function encodeUtf8(text) {
+ return encoder.encode(text);
+}
+
+
+// ----------------------------------------------------------------------------------------------- +
+// :::::: HTML CSS SVG :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +
+// ----------------------------------------------------------------------------------------------- +
+
+
+// Link to the bedrock-js project page on benbridle.com. Includes the bedrock logo as an SVG.
const projectLink = `
<a href='https://benbridle.com/bedrock-js' target='_blank'>
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' width='8' height='8'>
@@ -2507,7 +2825,6 @@ const assemblerTemplate = `
</div>
`;
-
const emulatorTemplate = `
<div class='bedrock emulator'>
<div class='menubar'>
@@ -2521,17 +2838,17 @@ const emulatorTemplate = `
${projectLink}
</div>
<div class='hidden panel state'><ul>
- <li>PC: <span class='pc'></span> <span class='label'></span></li>
- <li>WST: <span class='wst'></span></li>
- <li>RST: <span class='rst'></span></li>
+ <li id='ip' > IP: <span class='value'></span> <span class='note'></span></li>
+ <li id='cc' > CC: <span class='value'></span> <span class='note'></span></li>
+ <li id='wst'>WST: <span class='value'></span></li>
+ <li id='rst'>RST: <span class='value'></span></li>
</ul></div>
<div class='hidden stream'><ul class='transmissions'></ul></div>
<div class='hidden screen'><canvas></canvas></div>
</div>
`;
-
-const bedrockStyles = `
+const bedrockCSS = `
div.bedrock {
--br-fg:var(--fg,#222); --br-bg:var(--bg,#eed); --br-accent:var(--accent,#467); }
div.bedrock * {
@@ -2586,7 +2903,7 @@ const bedrockStyles = `
overflow:scroll; margin:0; list-style:none; }
.bedrock .panel li {
white-space:pre; }
- .bedrock .panel .label {
+ .bedrock .panel .note {
opacity:60%; }
.bedrock .stream {
@@ -2606,43 +2923,3 @@ const bedrockStyles = `
.bedrock:fullscreen .stream {
max-height:none; }
`;
-
-
-const instructionTable = {
- 'HLT': 0x00, 'NOP' : 0x20, 'DB1' : 0x40, 'DB2' : 0x60, 'DB3' : 0x80, 'DB4' : 0xA0, 'DB5' : 0xC0, 'DB6' : 0xE0,
- 'PSH': 0x01, 'PSH:': 0x21, 'PSH*': 0x41, 'PSH*:': 0x61, 'PSHr': 0x81, 'PSHr:': 0xA1, 'PSHr*': 0xC1, 'PSHr*:': 0xE1,
- ':': 0x21, '*:': 0x61, 'r:': 0xA1, 'r*:': 0xE1,
- 'POP': 0x02, 'POP:': 0x22, 'POP*': 0x42, 'POP*:': 0x62, 'POPr': 0x82, 'POPr:': 0xA2, 'POPr*': 0xC2, 'POPr*:': 0xE2,
- 'CPY': 0x03, 'CPY:': 0x23, 'CPY*': 0x43, 'CPY*:': 0x63, 'CPYr': 0x83, 'CPYr:': 0xA3, 'CPYr*': 0xC3, 'CPYr*:': 0xE3,
- 'DUP': 0x04, 'DUP:': 0x24, 'DUP*': 0x44, 'DUP*:': 0x64, 'DUPr': 0x84, 'DUPr:': 0xA4, 'DUPr*': 0xC4, 'DUPr*:': 0xE4,
- 'OVR': 0x05, 'OVR:': 0x25, 'OVR*': 0x45, 'OVR*:': 0x65, 'OVRr': 0x85, 'OVRr:': 0xA5, 'OVRr*': 0xC5, 'OVRr*:': 0xE5,
- 'SWP': 0x06, 'SWP:': 0x26, 'SWP*': 0x46, 'SWP*:': 0x66, 'SWPr': 0x86, 'SWPr:': 0xA6, 'SWPr*': 0xC6, 'SWPr*:': 0xE6,
- 'ROT': 0x07, 'ROT:': 0x27, 'ROT*': 0x47, 'ROT*:': 0x67, 'ROTr': 0x87, 'ROTr:': 0xA7, 'ROTr*': 0xC7, 'ROTr*:': 0xE7,
- 'JMP': 0x08, 'JMP:': 0x28, 'JMP*': 0x48, 'JMP*:': 0x68, 'JMPr': 0x88, 'JMPr:': 0xA8, 'JMPr*': 0xC8, 'JMPr*:': 0xE8,
- 'JMS': 0x09, 'JMS:': 0x29, 'JMS*': 0x49, 'JMS*:': 0x69, 'JMSr': 0x89, 'JMSr:': 0xA9, 'JMSr*': 0xC9, 'JMSr*:': 0xE9,
- 'JCN': 0x0A, 'JCN:': 0x2A, 'JCN*': 0x4A, 'JCN*:': 0x6A, 'JCNr': 0x8A, 'JCNr:': 0xAA, 'JCNr*': 0xCA, 'JCNr*:': 0xEA,
- 'JCS': 0x0B, 'JCS:': 0x2B, 'JCS*': 0x4B, 'JCS*:': 0x6B, 'JCSr': 0x8B, 'JCSr:': 0xAB, 'JCSr*': 0xCB, 'JCSr*:': 0xEB,
- 'LDA': 0x0C, 'LDA:': 0x2C, 'LDA*': 0x4C, 'LDA*:': 0x6C, 'LDAr': 0x8C, 'LDAr:': 0xAC, 'LDAr*': 0xCC, 'LDAr*:': 0xEC,
- 'STA': 0x0D, 'STA:': 0x2D, 'STA*': 0x4D, 'STA*:': 0x6D, 'STAr': 0x8D, 'STAr:': 0xAD, 'STAr*': 0xCD, 'STAr*:': 0xED,
- 'LDD': 0x0E, 'LDD:': 0x2E, 'LDD*': 0x4E, 'LDD*:': 0x6E, 'LDDr': 0x8E, 'LDDr:': 0xAE, 'LDDr*': 0xCE, 'LDDr*:': 0xEE,
- 'STD': 0x0F, 'STD:': 0x2F, 'STD*': 0x4F, 'STD*:': 0x6F, 'STDr': 0x8F, 'STDr:': 0xAF, 'STDr*': 0xCF, 'STDr*:': 0xEF,
- 'ADD': 0x10, 'ADD:': 0x30, 'ADD*': 0x50, 'ADD*:': 0x70, 'ADDr': 0x90, 'ADDr:': 0xB0, 'ADDr*': 0xD0, 'ADDr*:': 0xF0,
- 'SUB': 0x11, 'SUB:': 0x31, 'SUB*': 0x51, 'SUB*:': 0x71, 'SUBr': 0x91, 'SUBr:': 0xB1, 'SUBr*': 0xD1, 'SUBr*:': 0xF1,
- 'INC': 0x12, 'INC:': 0x32, 'INC*': 0x52, 'INC*:': 0x72, 'INCr': 0x92, 'INCr:': 0xB2, 'INCr*': 0xD2, 'INCr*:': 0xF2,
- 'DEC': 0x13, 'DEC:': 0x33, 'DEC*': 0x53, 'DEC*:': 0x73, 'DECr': 0x93, 'DECr:': 0xB3, 'DECr*': 0xD3, 'DECr*:': 0xF3,
- 'LTH': 0x14, 'LTH:': 0x34, 'LTH*': 0x54, 'LTH*:': 0x74, 'LTHr': 0x94, 'LTHr:': 0xB4, 'LTHr*': 0xD4, 'LTHr*:': 0xF4,
- 'GTH': 0x15, 'GTH:': 0x35, 'GTH*': 0x55, 'GTH*:': 0x75, 'GTHr': 0x95, 'GTHr:': 0xB5, 'GTHr*': 0xD5, 'GTHr*:': 0xF5,
- 'EQU': 0x16, 'EQU:': 0x36, 'EQU*': 0x56, 'EQU*:': 0x76, 'EQUr': 0x96, 'EQUr:': 0xB6, 'EQUr*': 0xD6, 'EQUr*:': 0xF6,
- 'NQK': 0x17, 'NQK:': 0x37, 'NQK*': 0x57, 'NQK*:': 0x77, 'NQKr': 0x97, 'NQKr:': 0xB7, 'NQKr*': 0xD7, 'NQKr*:': 0xF7,
- 'SHL': 0x18, 'SHL:': 0x38, 'SHL*': 0x58, 'SHL*:': 0x78, 'SHLr': 0x98, 'SHLr:': 0xB8, 'SHLr*': 0xD8, 'SHLr*:': 0xF8,
- 'SHR': 0x19, 'SHR:': 0x39, 'SHR*': 0x59, 'SHR*:': 0x79, 'SHRr': 0x99, 'SHRr:': 0xB9, 'SHRr*': 0xD9, 'SHRr*:': 0xF9,
- 'ROL': 0x1A, 'ROL:': 0x3A, 'ROL*': 0x5A, 'ROL*:': 0x7A, 'ROLr': 0x9A, 'ROLr:': 0xBA, 'ROLr*': 0xDA, 'ROLr*:': 0xFA,
- 'ROR': 0x1B, 'ROR:': 0x3B, 'ROR*': 0x5B, 'ROR*:': 0x7B, 'RORr': 0x9B, 'RORr:': 0xBB, 'RORr*': 0xDB, 'RORr*:': 0xFB,
- 'IOR': 0x1C, 'IOR:': 0x3C, 'IOR*': 0x5C, 'IOR*:': 0x7C, 'IORr': 0x9C, 'IORr:': 0xBC, 'IORr*': 0xDC, 'IORr*:': 0xFC,
- 'XOR': 0x1D, 'XOR:': 0x3D, 'XOR*': 0x5D, 'XOR*:': 0x7D, 'XORr': 0x9D, 'XORr:': 0xBD, 'XORr*': 0xDD, 'XORr*:': 0xFD,
- 'AND': 0x1E, 'AND:': 0x3E, 'AND*': 0x5E, 'AND*:': 0x7E, 'ANDr': 0x9E, 'ANDr:': 0xBE, 'ANDr*': 0xDE, 'ANDr*:': 0xFE,
- 'NOT': 0x1F, 'NOT:': 0x3F, 'NOT*': 0x5F, 'NOT*:': 0x7F, 'NOTr': 0x9F, 'NOTr:': 0xBF, 'NOTr*': 0xDF, 'NOTr*:': 0xFF,
-
- // TODO: Remove.
- '?':0x40,
-};