DECLARE FUNCTION lookupop$ (op%) DECLARE FUNCTION guessdef$ (ptr%) DECLARE FUNCTION loadimg! (filename$) DECLARE SUB bootstrap () DECLARE SUB saveimg (filename$) DECLARE SUB loadfile (filename$) DECLARE SUB vm () DECLARE SUB interpret (st$) DECLARE SUB execute (op%) DECLARE SUB assemble (name$, asm$) DECLARE FUNCTION lookup! (w$) DECLARE FUNCTION rpeek! () DECLARE FUNCTION nextword$ () DECLARE FUNCTION mkstr% (st$) DECLARE SUB opcode (opname$, op%) DECLARE SUB rpush (v%) DECLARE FUNCTION rpop! () DECLARE SUB push (v%) DECLARE FUNCTION pop! () DECLARE FUNCTION top! () TYPE word nm AS STRING * 32 df AS INTEGER i AS INTEGER END TYPE CONST WORDCOUNT = 256 CONST memcount = 10000 DIM SHARED words(WORDCOUNT) AS word DIM SHARED strings$(32) DIM SHARED messages$(32) DIM SHARED mem(memcount) AS INTEGER DIM SHARED stack(64) AS INTEGER DIM SHARED rstack(64) AS INTEGER COMMON SHARED sp AS INTEGER, rsp AS INTEGER, w AS INTEGER, ip AS INTEGER COMMON SHARED tracevm COMMON SHARED in$, inline% COMMON SHARED interpretcp AS INTEGER COMMON SHARED trace% inline% = -1 interpretcp = -1 trace% = 10 ' 10 OPEN "messages.txt" FOR INPUT AS #1 FOR i = 0 TO 31 IF EOF(1) = 0 THEN LINE INPUT #1, messages$(i) NEXT CLOSE #1 OPEN "out.com" FOR OUTPUT AS #1 CLOSE #1 OPEN "out.com" FOR BINARY AS #1 IF trace% > 0 THEN OPEN "TRACE.TXT" FOR OUTPUT AS #trace% END IF CONST O.RET = -1 CONST O.DUP = -2 CONST O.SWAP = -3 CONST O.DROP = -4 CONST O.GET = -5 CONST O.SET = -6 CONST O.CREATE = -7 CONST O.IN = -8 CONST O.OUT = -9 CONST O.EXEC = -10 CONST O.LOOKUP = -11 CONST O.WORD = -12 CONST O.NUMBER = -13 CONST O.LIT = -14 CONST O.ADD = -15 CONST O.SUB = -16 CONST O.MUL = -17 CONST O.DIV = -18 CONST O.JZ = -19 CONST O.RPUSH = -20 CONST O.RPOP = -21 CONST O.COMMA = -22 CONST O.ROT = -23 CONST O.LT = -24 CONST O.EQ = -25 CONST O.JMP = -26 CONST O.YELP = -27 CONST O.IMMEDIATE = -28 CONST O.RDROP = -29 CONST O.RTOP = -30 CONST O.RSWAP = -31 CONST O.BITAND = -32 CONST O.BITOR = -33 CONST O.MESSAGE = -34 CONST O.TRACE = -35 CONST P.HERE = 0 CONST P.LATEST = 1 CONST P.NEXTSTR = 2 IF loadimg("asm.img") = 0 THEN IF loadimg("boot.img") = 0 THEN PRINT "Bootstrapping" CALL bootstrap CALL saveimg("boot.img") END IF CALL loadfile("asm.qf") CALL saveimg("asm.img") END IF CALL loadfile("in.asm") SUB assemble (name$, asm$) in$ = asm$ done = 0 CALL push(mkstr(name$)) CALL execute(O.CREATE) IF trace% > 0 THEN PRINT #trace%, name$; " -> "; mem(P.LATEST) END IF WHILE done = 0 CALL execute(O.WORD) IF top < 0 THEN done = 1 x = pop ELSE word$ = strings$(top) CALL execute(O.LOOKUP) IF pop <> 0 THEN x = pop ' immediate flag, ignore IF trace% > 0 THEN PRINT #trace%, word$; " => "; STR$(top); " => "; mem(P.HERE) CALL execute(O.COMMA) ELSE CALL execute(O.NUMBER) IF pop <> 0 THEN IF trace% > 0 THEN PRINT #trace%, word$; " => "; STR$(top); " => "; mem(P.HERE) CALL execute(O.COMMA) ELSE CALL execute(O.YELP) END IF END IF END IF WEND END SUB SUB bootstrap mem(P.LATEST) = -1 mem(P.HERE) = 3 CALL opcode("ret", O.RET) CALL opcode("dup", O.DUP) CALL opcode("swap", O.SWAP) CALL opcode(">rot", O.ROT) CALL opcode("@", O.GET) CALL opcode("!", O.SET) CALL opcode("create", O.CREATE) CALL opcode("in@", O.IN) CALL opcode("outb!", O.OUT) CALL opcode("immediate", O.IMMEDIATE) CALL opcode("drop", O.DROP) CALL opcode("execute", O.EXEC) CALL opcode("lookup", O.LOOKUP) CALL opcode("word@", O.WORD) CALL opcode("number", O.NUMBER) CALL opcode("lit_", O.LIT) CALL opcode("+", O.ADD) CALL opcode("-", O.SUB) CALL opcode("*", O.MUL) CALL opcode("/", O.DIV) CALL opcode("<", O.LT) CALL opcode("=", O.EQ) CALL opcode("jz_", O.JZ) CALL opcode("jmp_", O.JMP) CALL opcode(">r", O.RPUSH) CALL opcode("r>", O.RPOP) CALL opcode("r@", O.RTOP) CALL opcode("rdrop", O.RDROP) CALL opcode("rswap", O.RSWAP) CALL opcode(",", O.COMMA) CALL opcode("message", O.MESSAGE) CALL opcode("yelp", O.YELP) CALL opcode("trace", O.TRACE) CALL assemble("rot >rot ret") CALL assemble("over", "swap dup >rot ret") CALL assemble("2dup", "over over ret") CALL assemble("$var", "r> ret") CALL assemble("$const", "r> @ ret") CALL assemble("not", "jz_ 4 lit_ 0 ret lit_ 1 ret") CALL assemble("and", "jz_ 5 jz_ 4 jmp_ 5 drop lit_ 0 ret lit_ 1 ret") CALL assemble("or", "not swap not and not ret") CALL assemble(">=", "< not ret") CALL assemble("<=", "2dup < >rot = or ret") CALL assemble(">", "<= not ret") CALL assemble("state", "$var 0") CALL assemble("doword", "state @ not or jz_ 3 execute ret , ret") CALL assemble("donum", "state @ jz_ 5 lit_ lit_ , , ret") CALL assemble("donext", "lookup jz_ 3 doword ret number jz_ 3 donum ret yelp ret") CALL assemble("interpret", "word@ dup lit_ 0 >= jz_ 4 donext jmp_ -9 drop ret") CALL assemble("[", "lit_ 0 state ! ret"): CALL execute(O.IMMEDIATE) CALL assemble("]", "lit_ 1 state ! ret") CALL assemble(":", "word@ create ] ret") CALL assemble(";", "lit_ ret , [ ret"): CALL execute(O.IMMEDIATE) CALL assemble("lit", "lit_ lit_ , , ret") CALL assemble("'", "word@ lookup drop drop state @ jz_ 2 lit ret"): CALL execute(O.IMMEDIATE) CALL loadfile("boot.qf") END SUB SUB execute (op%) IF trace% > 0 AND tracevm THEN tp$ = "N/A" IF sp > 0 THEN tp$ = STR$(top) PRINT #trace%, "ip:"; ip; " sp:"; sp; " rp:"; rsp; " tp:"; tp$; " fn:"; guessdef$(ip); " op:"; lookupop$(op%); END IF SELECT CASE op% CASE IS >= 0 CALL rpush(ip) ip = op% CASE O.RET ip = rpop CASE O.DUP CALL push(top) CASE O.SWAP x = stack(sp - 1) y = stack(sp - 2) stack(sp - 1) = y stack(sp - 2) = x CASE O.ROT s3% = pop s2% = pop s1% = pop ' >rot ( 1 2 3 -- 3 1 2 ) CALL push(s3%) CALL push(s1%) CALL push(s2%) CASE O.DROP x = pop CASE O.GET ptr = pop CALL push(mem(ptr)) CASE O.SET ptr = pop value = pop mem(ptr) = value CASE O.CREATE word = mem(P.LATEST) + 1 mem(P.LATEST) = word sname = pop words(word).nm = strings$(sname) words(word).df = mem(P.HERE) CASE O.IN c$ = MID$(in$, 1, 1) in$ = MID$(in$, 2) IF c$ = "" THEN CALL push(-1) ELSE CALL push(ASC(c$)) END IF CASE O.OUT byte$ = CHR$(pop) PUT #1, , byte$ CASE O.LOOKUP istr% = pop iword = lookup(strings$(istr%)) IF iword < 0 THEN CALL push(istr%) CALL push(0) ELSE CALL push(words(iword).df) CALL push(words(iword).i) CALL push(1) END IF CASE O.WORD word$ = nextword$ IF word$ <> "" THEN IF trace% > 0 THEN PRINT #trace%, "read: "; word$ END IF CALL push(mkstr(word$)) ELSE CALL push(-1) END IF CASE O.NUMBER istr% = pop n$ = strings$(istr%) IF VAL(n$ + "1") = 0 THEN CALL push(istr%) CALL push(0) ELSE CALL push(VAL(n$)) CALL push(1) END IF CASE O.IMMEDIATE word = mem(P.LATEST) words(word).i = 1 CASE O.EXEC CALL execute(pop) CASE O.LIT CALL push(mem(ip)) ip = ip + 1 CASE O.ADD r = pop l = pop CALL push(l + r) CASE O.SUB r = pop l = pop CALL push(l - r) CASE O.MUL r = pop l = pop CALL push(l * r) CASE O.DIV r = pop l = pop CALL push(l / r) CASE O.BITAND r = pop l = pop CALL push(l AND r) CASE O.BITOR r = pop l = pop CALL push(l OR r) CASE O.LT r = pop l = pop CALL push(l < r) CASE O.EQ r = pop l = pop IF l = r THEN CALL push(1) ELSE CALL push(0) END IF CASE O.JZ IF pop = 0 THEN ip = ip + mem(ip) ELSE ip = ip + 1 END IF CASE O.JMP ip = ip + mem(ip) CASE O.RPUSH CALL rpush(pop) CASE O.RPOP CALL push(rpop) CASE O.RDROP z = rpop CASE O.RSWAP x% = rpop y% = rpop CALL rpush(x%) CALL rpush(y%) CASE O.RTOP CALL push(rpeek) CASE O.COMMA here = mem(P.HERE) mem(here) = pop mem(P.HERE) = here + 1 CASE O.YELP PRINT strings$(pop); "? line "; inline% CASE O.TRACE tracevm = pop CASE O.MESSAGE CALL push(mkstr(messages$(pop))) CASE ELSE PRINT "Unknown op "; op% ip = -1 END SELECT END SUB FUNCTION guessdef$ (ptr%) guessdef$ = "???" IF ptr% < 0 THEN guessdef$ = "**INJECTED**" ELSE FOR i = mem(P.LATEST) - 1 TO 0 STEP -1 IF words(i).df < ptr% THEN guessdef$ = RTRIM$(words(i).nm) GOTO doneguessdef END IF NEXT END IF doneguessdef: END FUNCTION SUB interpret (st$) IF interpretcp < 0 THEN CALL push(mkstr("interpret")) CALL execute(O.LOOKUP) z = pop z = pop interpretcp = pop END IF in$ = st$ ip = -1 CALL execute(interpretcp) CALL vm END SUB SUB loadfile (filename$) inline% = 0 OPEN filename$ FOR INPUT AS #2 WHILE EOF(2) = 0 LINE INPUT #2, line$ inline% = inline% + 1 CALL interpret(line$) WEND CLOSE #2 inline% = -1 END SUB FUNCTION loadimg (filename$) OPEN filename$ FOR BINARY AS #3 IF LOF(3) > 0 THEN FOR i = 0 TO WORDCOUNT - 1 GET #3, , words(i) NEXT filememsize = LOF(3) - SEEK(3) FOR i = 0 TO filememsize / 2 GET #3, , mem(i) NEXT loadimg = 1 ELSE loadimg = 0 END IF CLOSE #3 END FUNCTION FUNCTION lookup (w$) lookup = -1 FOR i = 0 TO mem(P.LATEST) IF RTRIM$(words(i).nm) = w$ THEN lookup = i GOTO found END IF NEXT found: END FUNCTION FUNCTION lookupop$ (op%) lookupop$ = STR$(op%) FOR i = 0 TO mem(P.LATEST) IF words(i).df = op% THEN lookupop$ = RTRIM$(words(i).nm) GOTO lookupopdone ELSEIF op% < 0 AND words(i).df > 0 THEN GOTO lookupopdone ELSEIF op% > 0 AND words(i).df > op% THEN GOTO lookupopdone END IF NEXT lookupopdone: END FUNCTION FUNCTION mkstr% (st$) istr = mem(P.NEXTSTR) mem(P.NEXTSTR) = (istr + 1) MOD 32 strings$(istr) = st$ mkstr% = istr END FUNCTION FUNCTION nextword$ wordstart = 0 length = 0 FOR i = 1 TO LEN(in$) c$ = MID$(in$, i, 1) IF c$ = " " THEN IF wordstart <> 0 THEN GOTO foundword END IF ELSE IF wordstart = 0 THEN wordstart = i END IF length = length + 1 END IF NEXT foundword: IF wordstart <> 0 THEN nextword$ = MID$(in$, wordstart, length) in$ = MID$(in$, wordstart + length + 1) ' consume the space END IF END FUNCTION SUB opcode (opname$, op%) word = mem(P.LATEST) + 1 mem(P.LATEST) = word words(word).nm = opname$ words(word).df = op% END SUB FUNCTION pop sp = sp - 1 pop = stack(sp) END FUNCTION SUB push (v%) stack(sp) = v% sp = sp + 1 END SUB FUNCTION rpeek rpeek = rstack(rsp - 1) END FUNCTION FUNCTION rpop rsp = rsp - 1 rpop = rstack(rsp) END FUNCTION SUB rpush (v%) rstack(rsp) = v% rsp = rsp + 1 END SUB SUB saveimg (filename$) OPEN filename$ FOR BINARY AS #3 FOR i = 0 TO WORDCOUNT - 1 PUT #3, , words(i) NEXT FOR i = 0 TO mem(P.HERE) PUT #3, , mem(i) NEXT CLOSE #3 END SUB FUNCTION top top = stack(sp - 1) END FUNCTION SUB vm WHILE ip >= 0 op% = mem(ip) ip = ip + 1 CALL execute(op%) WEND END SUB