1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-07-01 12:40:36 +00:00
QB64-PE/internal/support/converter/QB45BIN.bas
FellippeHeitor 898c0ffedb Begins folder reorganization (internal/support).
For .bas/.bm/.bi files that are required at compilation time, as opposed to `source`, which is not required for normal operation.
2021-07-22 18:39:20 -03:00

3231 lines
89 KiB
QBasic
Raw Blame History

'QB45BIN.BAS - written by qarnos
'Used by permission: http://www.qb64.net/forum/index.php?topic=1771.msg16215#msg16215
'Command line interface adapted by FellippeHeitor
REM $DYNAMIC
DEFINT A-Z
'----------------------------------------------------------------------------
' Used for sorting alphabetically.
'----------------------------------------------------------------------------
DIM SHARED QBBinProcedureIndex AS STRING
'----------------------------------------------------------------------------
' Internal constants used by parse rule decoder
'----------------------------------------------------------------------------
CONST TagType.Recursive = 1
CONST TagType.TokenData = 2
CONST TagType.StackABS = 3
CONST TagType.StackREL = 4
'----------------------------------------------------------------------------
' Constants returned by the Meta field of QBBinReadLine. I will probably
' use the high 16-bits for flags, so best to mask them out for now.
'----------------------------------------------------------------------------
CONST QBBinMeta.SUB = 1
CONST QBBinMeta.FUNCTION = 2
'----------------------------------------------------------------------------
' Not yet used since it only supports QB45 atm.
'----------------------------------------------------------------------------
CONST QBBinFileMode.QB45 = 1
'----------------------------------------------------------------------------
' Option variable declarations
'----------------------------------------------------------------------------
DIM SHARED QBBinOption.OmitIncludedLines AS INTEGER
DIM SHARED QBBinOption.SortProceduresAZ AS INTEGER
'----------------------------------------------------------------------------
' Option variable initialisation
'----------------------------------------------------------------------------
QBBinOption.OmitIncludedLines = -1
QBBinOption.SortProceduresAZ = -1
'----------------------------------------------------------------------------
' Errors only half-implemented so far.
'----------------------------------------------------------------------------
CONST QBErrBadFormat = 255
CONST QBErrBadToken = 254
CONST QBErrInsane = 253
'----------------------------------------------------------------------------
' You may use QBBinEOF, for now, to determine when EOF has been reached.
' QBBinDefType contains the current DEFxxx setting for each letter of the
' alphabet (1 = INT, 2 = LNG, 3 = SNG, 4 = DBL, 5 = STR).
'----------------------------------------------------------------------------
DIM SHARED QBBinDefType(1 TO 26) AS INTEGER
DIM SHARED QBBinLineReady AS INTEGER ' get rid of this
DIM SHARED QBBinProgramLine AS STRING ' and this
DIM SHARED QBBinFile AS INTEGER
DIM SHARED QBBinEOF AS INTEGER
'----------------------------------------------------------------------------
' A hash table is used for symbols defined in the parse rules. There aren't
' many of them, so a small table will do.
'----------------------------------------------------------------------------
CONST SymbolHashBuckets = 43
DIM SHARED SymbolHashTable(0 TO SymbolHashBuckets - 1) AS STRING
DIM SHARED SymbolHashEntries AS INTEGER
'----------------------------------------------------------------------------
' Not worth commenting on... oops.
'----------------------------------------------------------------------------
DIM SHARED TypeSpecifiers(0 TO 5, 1 TO 3) AS STRING
DIM SHARED ParseRules(0) AS STRING
'----------------------------------------------------------------------------
' We don't need a very big stack. I haven't seen it go beyond 8 or 9 entries
' so 255 is plenty. Also, STACK(0) is a special entry. IF SP = 0 then there
' is nothing on the stack.
'----------------------------------------------------------------------------
DIM SHARED STACK(0 TO 255) AS STRING
DIM SHARED SP AS INTEGER
'----------------------------------------------------------------------------
' Define global symbol table, code space and instruction pointer
'----------------------------------------------------------------------------
DIM SHARED SYMTBL(0) AS INTEGER
DIM SHARED CODE(0) AS INTEGER
DIM SHARED IP AS LONG
'----------------------------------------------------------------------------
' PCODE always contains the ID of the current token (the low 10 bits of the
' input word.
'
' HPARAM contains the high 6 bits of the input word and is used by some
' tokens. IE: Identifiers use it for the type suffix and integers
' smaller than 10 are encoded this way.
'
' TOKEN is a string containing the binary data for the current token (PCODE
' and HPARAM in the first word, the rest of the data follows). All the
' FetchXXX functions work on this variable
'----------------------------------------------------------------------------
DIM SHARED PCODE AS INTEGER
DIM SHARED HPARAM AS INTEGER
DIM SHARED TOKEN AS STRING
'----------------------------------------------------------------------------
' LastProcType is just a hack to keep track of the current SUB or FUNCTION
' status since END SUB and END FUNCTION share the same token.
'----------------------------------------------------------------------------
DIM SHARED LastProcType AS STRING ' Current procedure type
DIM SHARED QBTxtFile AS INTEGER
'----------------------------------------------------------------------------
' These variables contain the current prodecure name and type the parser
' is decoding.
'
' QBBinProcedureType = MAIN | SUB | FUNCTON | DEF
'----------------------------------------------------------------------------
DIM SHARED QBBinProcedureName AS STRING
DIM SHARED QBBinProcedureType AS STRING
'----------------------------------------------------------------------------
' Variables used to store common token codes referenced in the code. Faster
' than doing GetHashedSymbol("tokenname") every time, and flexible since the
' QB40 token codes are different from QB45.
'----------------------------------------------------------------------------
DIM SHARED QBBinTok.SUBDEF AS INTEGER
DIM SHARED QBBinTok.FUNCDEF AS INTEGER
DIM SHARED QBBinTok.DEFTYPE AS INTEGER
DIM SHARED OutputContents$
$CONSOLE:ONLY
_DEST _CONSOLE
'----------------------------------------------------------------------------
' Initialisation will eventually be automatic in QBBinOpenFile
'----------------------------------------------------------------------------
RESTORE TSPECS
FOR i = 0 TO 17: READ TypeSpecifiers(i \ 3, i MOD 3 + 1): NEXT i
'----------------------------------------------------------------------------
' Get file names, etc.
'----------------------------------------------------------------------------
'ON ERROR GOTO ErrorHandler
GetInputFileName:
IF _COMMANDCOUNT = 0 THEN
PRINT "QB45BIN"
PRINT
PRINT "Conversion utility from QuickBASIC 4.5 binary to plain text."
PRINT "by qarnos"
PRINT
PRINT " Syntax: QB45BIN <source.bas> [-o output.bas]"
PRINT
PRINT "If no output is specified, a backup file is saved and the original"
PRINT "file is overwritten."
PRINT
SYSTEM 1
END IF
IF _FILEEXISTS(COMMAND$(1)) = 0 THEN
IF INSTR(InputFile$, ".") = 0 THEN InputFile$ = InputFile$ + ".BAS"
PRINT "File not found: "; COMMAND$(1)
SYSTEM 1
ELSE
InputFile$ = COMMAND$(1)
END IF
IF LCASE$(COMMAND$(2)) = "-o" THEN
IF LEN(COMMAND$(3)) THEN
OutputFile$ = COMMAND$(3)
END IF
END IF
IF OutputFile$ = "" THEN
IF INSTR(InputFile$, "\") > 0 OR INSTR(InputFile$, "/") > 0 THEN
FOR i = LEN(InputFile$) TO 1 STEP -1
IF MID$(InputFile$, i, 1) = "/" OR MID$(InputFile$, i, 1) = "\" THEN
path$ = LEFT$(InputFile$, i)
InputFile$ = MID$(InputFile$, i + 1)
EXIT FOR
END IF
NEXT
END IF
OutputFile$ = path$ + InputFile$ + ".converted.bas"
END IF
PRINT UCASE$(InputFile$)
PRINT
PRINT "Loading parse rules... ";
LoadParseRules
PRINT "Done!": PRINT
QBBinOpenFile path$ + InputFile$
'---------------------------------------------------------------------------
' The main loop is pretty straight-forward these days.
'---------------------------------------------------------------------------
StartProcessing! = TIMER
DO WHILE NOT QBBinEOF
ProgramLine$ = QBBinReadLine$(Meta&)
'-----------------------------------------------------------------------
' Just an example of meta-data usage. Pretty limited at the moment,
' but could be helpful to QB64 IDE when building SUB/FUNCTION list.
'-----------------------------------------------------------------------
'IF Meta& = QBBinMeta.SUB THEN PRINT "----- SUBROUTINE -----"
'IF Meta& = QBBinMeta.FUNCTION THEN PRINT "----- FUNCTION -----"
'-----------------------------------------------------------------------
' AOutput has become a pretty-print function. All program lines are now
' retrieved by calling QBBinReadLine.
'-----------------------------------------------------------------------
AOutput ProgramLine$
'Quit after a number of seconds - likely an invalid file causing an endless loop
CONST TIMEOUT = 30
IF StartProcessing! > TIMER THEN StartProcessing! = StartProcessing! - 86400
IF TIMER - StartProcessing! > TIMEOUT THEN PRINT "Conversion failed.": SYSTEM 1
LOOP
'If we've made it this far, output the resulting file:
QBTxtFile = FREEFILE
OPEN OutputFile$ FOR BINARY AS #QBTxtFile
PUT #QBTxtFile, 1, OutputContents$
CLOSE #QBTxtFile
RESET
PRINT "Finished!"
SYSTEM 0
TSPECS:
DATA ANY,,
DATA INTEGER,INT,%
DATA LONG,LNG,&
DATA SINGLE,SNG,!
DATA DOUBLE,DBL,#
DATA STRING,STR,$
QB45TOKENS:
'
' Most of the tokens for QB45 are defined here, along with the length of the
' token (or '*' for variable length) and some parse rules.
'
' The first column determined the PCODE (the low 10 bits of the token)
' which the rule responds to. This is followed by the length of the token
' *data*, which may be omitted if the token has no data, or an asterisk to
' indicate a variable length token. Variable length tokens are always
' followed by a word indicating the length of the token.
'
' The final column is the parse rule itself. A token may have multiple
' parse rules. Multiple parse rules may be specified on a seperate line
' (without a PCODE or LENGTH field), or seperated by a pipe ('|') symbol.
'
' There is one important difference between the two methods. Some rules
' define a symbol which can be used to reference the rule, such as:
'
' declmod::=SHARED
'
' If a pipe symbol is used, the next rule will inherit the "declmod" (or
' whatever symbol), unless it exlicitly defines it's own. Rules defined
' on seperate lines use the default symbol which, initially, is nothing, but
' may be overridden using the ".default" directive. This is only really used
' in the second half of the rule list, where almost every token is an
' expression ('expr').
'
' Rules are matched on a first-come first-served basis. The first rule which
' can be successfully applied (see below) is accepted.
'
' The rules can have {tags} embedded in them. There are basically two types
' of tags - stack and data/format tags. I will discuss them briefly here:
'
' STACK tags can take these basic forms:
'
' {1}
' {*:1}
' {rulename:1}
' {$+1}
' {$-1}
' {rulename:$+1}
'
' The first type will be substituded for the text located 1 item from the
' top of the parse stack. If the stack isn't that deep, it will be replaced
' with the null string.
'
' The second type is just like the first, except the rule will be rejected
' if the stack item doesn't exist.
'
' The third type will only accept a rule if the stack item at the specified
' offset is of the correct rule type. So {declmod:1} will reject the rule
' if the stack entry at offset 1 is not a "declemod". There is also a special
' rule name, "self", which always refers to the current rule.
'
' The final three forms, use the '$' symbol. This symbol refers to a
' "relative" stack offset - an offset from the deepest stack item referenced
' in a normal tag. This is really a bit of a hack, due to me trying to avoid
' writing a full LALR(1) parser! This feature is rarely used.
'
' DATA/FORMAT tags
'
' Data and format tags being with a '#', such as {#id:2}. These tags are used
' either to interpret data from the token or to generate a dynamic parse
' rule (another hack).
'
' In the case of data tokens, the number refers to the offset into the token
' data on which the tag is to work.
'
' Format tokens usually have two '#' symbols, such as {##id(decl)}. The
' extra '#' causes the parser to re-scan the tag for other tags once it
' has been subsituted, allowing these tags to generate stack tags which can
' then be parsed.
'
' See the function GetTaggedItem for a list of tag names which can be used.
'
'
'
REM Token Length Rule(s)
REM -------+-------+-------
DATA 0x000,"newline::=.{#newline}{#tabh}"
DATA 0x001,2,"newline::=.{#newline}{#tabi}"
DATA 0x002,2,"newline::=.{#newline-include}"
DATA 0x003,4,"newline::=.{#newline-include}{#indent:2} "
DATA 0x004,4,".{#newline}{#thaddr:0}{#label:2}"
DATA 0x005,6,".{#newline}{#thaddr:0}{#label:2} {#indent:4}"
DATA 0x006,": "
DATA 0x007,2,":{#tabi}"
'----------------------------------------------------------------------------
' 0x008 = End of procedure/module code (watch list follows)
' 0x009 = End of watch list
'----------------------------------------------------------------------------
DATA 0x008,"."
DATA 0x009,
DATA 0x00a,*,"{#raw:2}"
DATA 0x00b,2,"expr::={#id+}"
DATA 0x00c,2,"consts::={const:1} {#id+} = {0}"
DATA "consts::={consts:1}, {#id+} = {0}"
DATA "{#id+} = {0}"
DATA 0x00d,2,"decls::={decls:1}, {#id+:0} {astype:0}"
DATA "decls::={decls:0}, {#id+:0}"
DATA "decls::={decl:1} {#id+:0} {astype:0}"
DATA "decls::={decl:0} {#id+:0}"
DATA "{#id+:0} {astype:0}"
DATA "{#id+:0}"
DATA 0x00e,4,"expr::={##id(expr)}"
DATA 0x00f,4,"{##id(expr)} = {$+0}"
DATA 0x010,4,"decls::={##id(decl)}"
DATA 0x011,2,"expr::={0}.{#id}"
DATA 0x012,2,"{0}.{#id} = {1}"
' 0x015 = AS USERTYPE
' 0x016 = AS BUILTINTYPE?
DATA 0x015,4,"astype::={#tabi:2}AS {#type:0}"
DATA 0x016,4,"astype::={#tabi:2}AS {#type:0}"
' 0x017 - used for unkown type assignments?
DATA 0x017,0,""
DATA 0x018,""
'----------------------------------------------------------------------------
' 0x019 = user-type field declaration.
'----------------------------------------------------------------------------
DATA 0x019,2,"{#id}"
DATA 0x01a,"declmod::=SHARED"
DATA 0x01b,6,"deftype::={#thaddr:0}{#DEFxxx}"
DATA 0x01c,"{self:1}, {0}|REDIM {declmod:1} {0}|REDIM {0}"
DATA 0x01d,2,"END TYPE"
DATA 0x01e,2,"decl::=SHARED"
DATA 0x01f,2,"decl::=STATIC"
DATA 0x020,4,"TYPE {#id:2}"
DATA 0x021,*,"$STATIC{#raw}"
DATA 0x022,*,"$DYNAMIC{#raw}"
DATA 0x023,"const::=CONST"
'----------------------------------------------------------------------------
' 0x024 = IDE breakpoint
'----------------------------------------------------------------------------
DATA 0x024,
DATA 0x025,"BYVAL {0}"
DATA 0x026,*,"{deffn:1} = {0}"
DATA 0x027,"COM({0})"
DATA 0x028,2,"ON {0} GOSUB {#id}"
DATA 0x029,"KEY({0})"
DATA 0x02a,"{0} OFF"
DATA 0x02b,"{0} ON"
DATA 0x02c,"{0} STOP"
DATA 0x02d,"PEN"
DATA 0x02e,"PLAY"
DATA 0x02f,"PLAY({0})"
DATA 0x030,"SIGNAL({0})"
DATA 0x031,"STRIG({0})"
DATA 0x032,"TIMER"
DATA 0x033,"TIMER({0})"
'----------------------------------------------------------------------------
' Labels used in $INCLUDEd lines
'----------------------------------------------------------------------------
DATA 0x034,4,"newline::={#thaddr:0}{#label:2} "
DATA 0x035,6,"newline::={#thaddr:0}{#label:2} {#indent:4}"
DATA 0x037,4,"CALL {#id:2}{##call()}"
DATA 0x038,4,"{#id:2}{##call}"
DATA 0x039,4,"CALLS {#id:2}{##call()}"
DATA 0x03a,"CASE ELSE"
DATA 0x03b,"case::={case:1}, {0}|CASE {0}"
DATA 0x03c,"case::={case:2}, {1} TO {0}|CASE {1} TO {0}"
DATA 0x03d,"case::={case:1}, IS = {0}|CASE IS = {0}"
DATA 0x03e,"case::={case:1}, IS < {0}|CASE IS < {0}"
DATA 0x03f,"case::={case:1}, IS > {0}|CASE IS > {0}"
DATA 0x040,"case::={case:1}, IS <= {0}|CASE IS <= {0}"
DATA 0x041,"case::={case:1}, IS >= {0}|CASE IS >= {0}"
DATA 0x042,"case::={case:1}, IS <> {0}|CASE IS <> {0}"
DATA 0x043,"ON"
DATA 0x044,*,"DECLARE {#procdecl()}"
DATA 0x045,*,"deffn::={#procdecl:2}"
DATA 0x046,"DO"
DATA 0x047,"DO UNTIL {0}"
DATA 0x048,2,"DO WHILE {0}"
DATA 0x049,2,"{newline:0}ELSE | ELSE "
' 0x04a = implicit GOTO linenumber used in 0x04c ELSE
DATA 0x04a,2,"{#id}"
DATA 0x04c," ELSE "
DATA 0x04d,2,"ELSEIF {0} THEN"
DATA 0x04e,"END"
DATA 0x04f,*,"END DEF"
DATA 0x050,"END IF"
DATA 0x051,"END {#proctype}"
DATA 0x052,"END SELECT"
DATA 0x053,2,"EXIT DO"
DATA 0x054,2,"EXIT FOR"
DATA 0x055,2,"EXIT {#proctype}"
DATA 0x056,4,"FOR {2} = {1} TO {0}"
DATA 0x057,4,"FOR {3} = {2} TO {1} STEP {0}"
DATA 0x058,*,"funcdef::={#procdecl}"
DATA 0x059,2,"GOSUB {#id}"
' 0x05a 2, "GOSUB {#id}"
DATA 0x05b,2,"GOTO {#id}"
' 0x05c 2, "GOTO {#id}"
DATA 0x05d,2,"IF {0} THEN "
DATA 0x05e,2,"IF {0} THEN {#id}"
' 0x05f, 2, "IF {0} THEN "
DATA 0x060,2,"IF {0} GOTO {#id}"
DATA 0x061,2,"IF {0} THEN"
DATA 0x062,2,"LOOP"
DATA 0x063,2,"LOOP UNTIL {0}"
DATA 0x064,2,"LOOP WHILE {0}"
DATA 0x065,4,"NEXT"
DATA 0x066,4,"{self:1}, {0}|NEXT {0}"
DATA 0x067,2,"ON ERROR GOTO {#id}"
DATA 0x068,*,"ON {0} GOSUB {#id-list}"
DATA 0x069,*,"ON {0} GOTO {#id-list}"
DATA 0x06a,"RESTORE"
DATA 0x06b,2,"RESTORE {#id}"
DATA 0x06c,"RESUME"
DATA 0x06d,2,"RESUME {#id}"
DATA 0x06e,"RESUME NEXT"
DATA 0x06f,"RETURN"
DATA 0x070,2,"RETURN {#id}"
DATA 0x071,"RUN {0}"
DATA 0x072,2,"RUN {#id}"
DATA 0x073,"RUN"
DATA 0x074,2,"SELECT CASE {0}"
DATA 0x075,2,"STOP"
DATA 0x076,*,"subdef::={#procdecl}"
DATA 0x077,"WAIT {1}, {0}"
DATA 0x078,"WAIT {2}, {1}, {0}"
DATA 0x079,2,"WEND"
DATA 0x07a,2,"WHILE {0}"
'----------------------------------------------------------------------------
' 0x07b used in IDE watch mode. Probably 0x07c, too.
'----------------------------------------------------------------------------
DATA 0x07b,
DATA 0x07c,
DATA 0x07d,"prnmod::={prnmod:1} {0},|PRINT {0},"
'----------------------------------------------------------------------------
' 3 dummy tokens used in LINE statements
'----------------------------------------------------------------------------
DATA 0x07e,"{0}"
DATA 0x07f,"{0}"
DATA 0x080,"{0}"
'----------------------------------------------------------------------------
' graphics co-ordinates
'----------------------------------------------------------------------------
DATA 0x081,"1st-coord::=({1}, {0})"
DATA 0x082,"1st-coord::=STEP({1}, {0})"
DATA 0x083,"{1st-coord:2}-({1}, {0})|({1}, {0})"
DATA 0x084,"{1st-coord:2}-STEP({1}, {0})|-STEP({1}, {0})"
DATA 0x085,"FIELD {0}"
DATA 0x086,", {1} AS {0}"
DATA 0x087,"finput::=INPUT {0},"
DATA 0x088,"{input:1} {inputs:0}"
DATA 0x089,*,"input::=INPUT {##input-args}"
DATA 0x08a,"#{0}"
'----------------------------------------------------------------------------
' These two consume data, but I have no idea what they do. I haven't seen
' one in the wild.
'----------------------------------------------------------------------------
DATA 0x08c,2,""
' 0x08d, 4, ""
'----------------------------------------------------------------------------
' Most of the PRINT stuff is here. The rules are pretty finicky. These
' sequences also apply to LPRINT and WRITE.
'----------------------------------------------------------------------------
DATA 0x08f,"prnsmc::={self|prncma|prnsrl:1} SPC({0});"
DATA "prnsmc::=SPC({0});"
DATA 0x090,"prnsmc::={self|prncma|prnsrl:1} TAB({0});"
DATA "prnsmc::=TAB({0});"
DATA 0x091,"prncma::={self|prnsmc|prnsrl:0} ,|,"
DATA 0x092,"prnsmc::={self:0}|{prncma|prnsrl:0} ;|;"
DATA 0x093,"{prnmod:2} {prnuse:1} {prnsrl|prnsmc|prncma:0}"
DATA "{prnmod:1} {prnsrl|prnsmc|prncma:0}"
DATA "{prnmod:1} {prnuse:0}"
DATA "{prnmod:1}"
DATA "PRINT {prnuse:1} {prnsrl|prnsmc|prncma:0}"
DATA "PRINT {prnsrl|prnsmc|prncma:0}"
DATA "PRINT {prnuse:0}"
DATA "PRINT"
DATA 0x094,"prnsrl::={prncma|prnsmc|self:1} {expr:0},|{expr:0},"
DATA 0x095,"prnsrl::={prncma|prnsmc|self:1} {expr:0};|{expr:0};"
DATA 0x096,"{prnmod:3} {prnuse:2} {prnsmc|prncma|prnsrl:1} {expr:0}"
DATA "{prnmod:2} {prnsmc|prncma|prnsrl:1} {expr:0}"
DATA "{prnmod:1} {prnsmc|prncma|prnsrl|expr:0}"
DATA "PRINT {prnuse:2} {prnsmc|prncma|prnsrl:1} {expr:0}"
DATA "PRINT {prnsmc|prncma|prnsrl:1} {expr:0}"
DATA "PRINT {prnsmc|prncma|prnsrl|expr:0}"
DATA 0x097,*,"{#tabi:0}'{#raw:2}"
' 0x098 nothing?
DATA 0x099,*,"$INCLUDE: '{#raw:0}"
DATA 0x09a,"BEEP"
DATA 0x09b,"BLOAD {0}"
DATA 0x09c,"BLOAD {1}, {0}"
DATA 0x09d,"BSAVE {2}, {1}, {0}"
DATA 0x09e,"CHDIR {0}"
DATA 0x09f,"CIRCLE {##circle-args}"
DATA 0x0a0,"CIRCLE {##circle-args}"
DATA 0x0a1,2,"CLEAR{##varargs}"
DATA 0x0a2,2,"CLOSE{##varargs}"
DATA 0x0a3,"CLS {expr:0}|CLS "
DATA 0x0a4,2,"COLOR{##varargs}"
DATA 0x0a5,4,"decl::=COMMON {declmod:0}{#blockname:2}"
DATA "decl::=COMMON{#blockname:2}"
DATA 0x0a6,*,"DATA{#cstr:2}"
DATA 0x0a7,"DATE$ = {0}"
DATA 0x0a8,"DEF SEG"
DATA 0x0a9,"DEF SEG = {0}"
DATA 0x0aa,"DRAW {0}"
DATA 0x0ab,"ENVIRON {0}"
DATA 0x0ac,2,"ERASE{##varargs}"
DATA 0x0ad,"ERROR {0}"
DATA 0x0ae,"FILES"
DATA 0x0af,"FILES {0}"
DATA 0x0b0,"GET {0}"
DATA 0x0b1,"GET {1}, {0}"
DATA 0x0b2,2,"GET {1}, , {0}"
DATA 0x0b3,2,"GET {2}, {1}, {0}"
DATA 0x0b4,"GET {1}, {0}"
DATA 0x0b5,2,"PUT {1}, {0}, {#action-verb}"
DATA 0x0b6,"inputs::={inputs:1}, {0}|{0}"
DATA 0x0b7,"IOCTL {1}, {0}"
DATA 0x0b8,2,"KEY {#keymode}"
DATA 0x0b9,"KEY {1}, {0}"
DATA 0x0ba,"KILL {0}"
DATA 0x0bb,2,"LINE {##line-args}"
DATA 0x0bc,2,"LINE {##line-args}"
DATA 0x0bd,2,"LINE {##line-args}"
DATA 0x0be,2,"LINE {##line-args}"
DATA 0x0bf,"LET "
DATA 0x0c0,2,"input::=LINE {finput:1} {0}"
DATA "input::=LINE INPUT {##input-args} {0}"
DATA 0x0c1,2,"LOCATE{##varargs}"
DATA 0x0c2,2,"LOCK {##lock-args}"
DATA 0x0c3,"prnmod::=LPRINT"
DATA 0x0c4,"LSET {0} = {1}"
DATA 0x0c5,"MID$({0}, {2}) = {1}"
DATA 0x0c6,"MID$({0}, {3}, {2}) = {1}"
DATA 0x0c7,"MKDIR {0}"
DATA 0x0c8,"NAME {1} AS {0}"
DATA 0x0c9,2,"OPEN {1} {#open-args} AS {0}"
DATA 0x0ca,2,"OPEN {2} {#open-args} AS {1} LEN = {0}"
DATA 0x0cb,"OPEN {2}, {1}, {0}"
DATA 0x0cc,"OPEN {3}, {2}, {1}, {0}"
DATA 0x0cd,"OPTION BASE 0"
DATA 0x0ce,"OPTION BASE 1"
DATA 0x0cf,"OUT {1}, {0}"
DATA 0x0d0,"PAINT {2}{nularg:1}{nularg:0}"
DATA "PAINT {2}, {nularg:1}, {0}"
DATA "PAINT {2}, {1}{nularg:0}"
DATA "PAINT {2}, {1}, {0}"
DATA 0x0d1,"PAINT {3}, {2}, {1}, {0}"
DATA 0x0d2,"PALETTE"
DATA 0x0d3,"PALETTE {1}, {0}"
DATA 0x0d4,"PALETTE {0}"
DATA 0x0d5,"PCOPY {1}, {0}"
DATA 0x0d6,"PLAY {0}"
DATA 0x0d7,"POKE {1}, {0}"
DATA 0x0d8,"PRESET {0}"
DATA 0x0d9,"PRESET {0}, {1}"
DATA 0x0da,"PSET {0}"
DATA 0x0db,"PSET {1}, {0}"
DATA 0x0dd,"PUT {1}, {0}"
DATA 0x0de,2,"PUT {1}, , {0}"
DATA 0x0df,2,"PUT {2}, {1}, {0}"
DATA 0x0e0,"RANDOMIZE"
DATA 0x0e1,"RANDOMIZE {0}"
DATA 0x0e2,"{self:1}, {0}|READ {0}"
DATA 0x0e3,*,"REM{#raw}"
DATA 0x0e4,"RESET"
DATA 0x0e5,"RMDIR {0}"
DATA 0x0e6,"RSET {0} = {1}"
DATA 0x0e7,2,"SCREEN{##varargs}"
DATA 0x0e8,"SEEK {1}, {0}"
DATA 0x0e9,"SHELL"
DATA 0x0ea,"SHELL {0}"
DATA 0x0eb,"SLEEP"
DATA 0x0ec,"SOUND {1}, {0}"
DATA 0x0ed,2,"SWAP {1}, {0}"
DATA 0x0ee,"SYSTEM"
DATA 0x0ef,"TIME$ = {0}"
DATA 0x0f0,"TROFF"
DATA 0x0f1,"TRON"
DATA 0x0f2,2,"UNLOCK {##lock-args}"
DATA 0x0f3,"VIEW ({5}, {4})-({3}, {2}){nularg:1}{nularg:0}"
DATA "VIEW ({5}, {4})-({3}, {2}), {nularg:1}, {0}"
DATA "VIEW ({5}, {4})-({3}, {2}), {1}{nularg:0}"
DATA "VIEW ({5}, {4})-({3}, {2})"
DATA 0x0f4,"VIEW"
DATA 0x0f5,"VIEW PRINT"
DATA 0x0f6,"VIEW PRINT {1} TO {0}"
DATA 0x0f7,"VIEW SCREEN ({5}, {4})-({3}, {2}){nularg:1}{nularg:0}"
DATA "VIEW SCREEN ({5}, {4})-({3}, {2}), {nularg:1}, {0}"
DATA "VIEW SCREEN ({5}, {4})-({3}, {2}), {1}{nularg:0}"
DATA "VIEW SCREEN ({5}, {4})-({3}, {2})"
DATA 0x0f8,"WIDTH {1}{nularg:0}|WIDTH {1}, {0}"
DATA 0x0f9,"WIDTH LPRINT {0}"
DATA 0x0fa,"WIDTH {1}, {0}"
DATA 0x0fb,"WINDOW ({3}, {2})-({1}, {0})"
DATA 0x0fc,"WINDOW"
DATA 0x0fd,"WINDOW SCREEN ({3}, {2})-({1}, {0})"
DATA 0x0fe,"prnmod::=WRITE"
DATA 0x0ff,"prnuse::=USING {0};"
DATA .default expr
DATA 0x100,"{1} + {0}"
DATA 0x101,"{1} AND {0}"
DATA 0x102,"{1} / {0}"
DATA 0x103,"{1} = {0}"
DATA 0x104,"{1} EQV {0}"
DATA 0x105,"ABS({0})"
DATA 0x106,"ASC({0})"
DATA 0x107,"ATN({0})"
DATA 0x108,"C{#type-abbr}({0})"
DATA 0x109,"CHR$({0})"
DATA 0x10a,"COMMAND$"
DATA 0x10b,"COS({0})"
DATA 0x10c,"CSRLIN"
DATA 0x10d,"CVD({0})"
DATA 0x10e,"CVDMBD({0})"
DATA 0x10f,"CVI({0})"
DATA 0x110,"CVL({0})"
DATA 0x111,"CVS({0})"
DATA 0x112,"CVSMBF({0})"
DATA 0x113,"DATE$"
DATA 0x114,"ENVIRON$({0})"
DATA 0x115,"EOF({0})"
DATA 0x116,"ERDEV"
DATA 0x117,"ERDEV$"
DATA 0x118,"ERL"
DATA 0x119,"ERR"
DATA 0x11a,"EXP({0})"
DATA 0x11b,"FILEATTR({1}, {0})"
DATA 0x11c,"FIX({0})"
DATA 0x11d,"FRE({0})"
DATA 0x11e,"FREEFILE"
DATA 0x11f,"HEX$({0})"
DATA 0x120,"INKEY$"
DATA 0x121,"INP({0})"
DATA 0x122,"INPUT$({0})"
DATA 0x123,"INPUT$({1}, {0})"
DATA 0x124,"INSTR({1}, {0})"
DATA 0x125,"INSTR({2}, {1}, {0})"
DATA 0x126,"INT({0})"
DATA 0x127,"IOCTL$({0})"
DATA 0x128,"LBOUND({0})"
DATA 0x129,"LBOUND({1}, {0})"
DATA 0x12a,"LCASE$({0})"
DATA 0x12b,"LTRIM$({0})"
DATA 0x12c,"LEFT$({1}, {0})"
DATA 0x12d,2,"LEN({0})"
DATA 0x12e,"LOC({0})"
DATA 0x12f,"LOF({0})"
DATA 0x130,"LOG({0})"
DATA 0x131,"LPOS({0})"
DATA 0x132,"MID$({1}, {0})"
DATA 0x133,"MID$({2}, {1}, {0})"
DATA 0x134,"MKD$({0})"
DATA 0x135,"MKDMBF$({0})"
DATA 0x136,"MKI$({0})"
DATA 0x137,"MKL$({0})"
DATA 0x138,"MKS$({0})"
DATA 0x139,"MKSMBF({0})"
DATA 0x13a,"OCT$({0})"
DATA 0x13b,"PEEK({0})"
DATA 0x13c,"PEN"
DATA 0x13d,"PLAY"
DATA 0x13e,"PMAP({1}, {0})"
DATA 0x13f,"POINT({0})"
DATA 0x140,"POINT({1}, {0})"
DATA 0x141,"POS({0})"
DATA 0x142,"RIGHT$({1}, {0})"
DATA 0x143,"RND"
DATA 0x144,"RND({0})"
DATA 0x145,"RTRIM$({0})"
DATA 0x146,"SADD({0})"
DATA 0x147,"SCREEN({1}, {0})"
DATA 0x148,"SCREEN({2}, {1}, {0})"
DATA 0x149,"SEEK({0})"
DATA 0x14a,"SETMEM({0})"
DATA 0x14b,"SGN({0})"
DATA 0x14c,"SHELL({0})"
DATA 0x14d,"SIN({0})"
DATA 0x14e,"SPACE$({0})"
DATA 0x14f,"SQR({0})"
DATA 0x150,"STICK({0})"
DATA 0x151,"STR$({0})"
DATA 0x152,"STRIG({0})"
DATA 0x153,"STRING$({1}, {0})"
DATA 0x154,"TAN({0})"
DATA 0x155,"TIME$"
DATA 0x156,"TIMER"
DATA 0x157,"UBOUND({0})"
DATA 0x158,"UBOUND({1}, {0})"
DATA 0x159,"UCASE$({0})"
DATA 0x15a,"VAL({0})"
DATA 0x15b,"VARPTR({0})"
DATA 0x15c,2,"VARPTR$({0})"
DATA 0x15d,"VARSEG({0})"
DATA 0x15e,"{1} >= {0}"
DATA 0x15f,"{1} > {0}"
DATA 0x160,"{1} \ {0}"
DATA 0x161,"{1} IMP {0}"
DATA 0x162,"{1} <= {0}"
DATA 0x163,"{1} < {0}"
DATA 0x164,"{#hprm}"
DATA 0x165,2,"{#int}"
DATA 0x166,4,"{#lng}"
DATA 0x167,2,"{#int&h}"
DATA 0x168,4,"{#lng&h}"
DATA 0x169,2,"{#int&o}"
DATA 0x16a,4,"{#lng&o}"
DATA 0x16b,4,"{#sng}"
DATA 0x16c,8,"{#dbl}"
DATA 0x16d,*,"{#qstr}"
DATA 0x16e,"({0})"
DATA 0x16f,"{1} MOD {0}"
DATA 0x170,"{1} * {0}"
DATA 0x171,"{1} <> {0}"
DATA 0x172,"{#nul}"
DATA 0x173,"nularg::={#nul}"
DATA 0x174,"NOT {0}"
DATA 0x175,"{1} OR {0}"
DATA 0x176,"{1} ^ {0}"
DATA 0x177,"{1} - {0}"
DATA 0x178,"-{0}"
DATA 0x179,"{1} XOR {0}"
DATA .default
DATA 0x17a,"UEVENT"
DATA 0x17b,"SLEEP {0}"
DATA 0x17c,6,"astype::={#tabi:4}AS STRING * {#int:2}"
DATA 0x17d,2,"decl::=DIM {declmod:0}|DIM"
DATA .
REM $STATIC
'
' This subroutine is called whenever a program line has been decoded.
'
SUB AOutput (ProgramLine AS STRING)
STATIC OutputLines
OutputLines = OutputLines + 1
IF LEN(OutputContents$) THEN
OutputContents$ = OutputContents$ + CHR$(10) + ProgramLine
ELSE
OutputContents$ = ProgramLine
END IF
END SUB
SUB DbgOutput (DbgTxt AS STRING)
EXIT SUB
PRINT #5, DbgTxt
END SUB
FUNCTION DbgPlainText$ (Txt2$)
Txt$ = Txt2$
DO
Marker = INSTR(Txt$, MKL$(0))
IF Marker = 0 THEN EXIT DO
TagTxtLen = CVI(MID$(Txt$, Marker + 4, 2))
TagParam = CVI(MID$(Txt$, Marker + 6, 2))
TagTxt$ = MID$(Txt$, Marker + 8, TagTxtLen)
TagParam$ = ITOA(TagParam)
IF TagParam > 0 THEN TagParam$ = "+" + TagParam$
TagParam$ = "$" + TagParam$
IF TagTxt$ <> "" THEN TagParam$ = TagTxt$ + ":" + TagParam$
Txt$ = LEFT$(Txt$, Marker - 1) + "{" + TagParam$ + "}" + MID$(Txt$, Marker + 8 + TagTxtLen)
LOOP
DO
Marker = INSTR(Txt$, CHR$(&HD))
IF Marker = 0 THEN EXIT DO
IF CVI(MID$(Txt$, Marker, 2)) = &HD THEN
Txt$ = LEFT$(Txt$, Marker - 1) + "<22>newline<6E>" + MID$(Txt$, Marker + 2)
ELSEIF CVI(MID$(Txt$, Marker, 2)) = &H10D THEN
Txt$ = LEFT$(Txt$, Marker - 1) + "<22>indent<6E>" + MID$(Txt$, Marker + 4)
ELSE
Txt$ = LEFT$(Txt$, Marker - 1) + "<22>rle<6C>" + MID$(Txt$, Marker + 3)
END IF
LOOP
DbgPlainText$ = Txt$
END FUNCTION
'
' Iterates through the various rules for a token contained in the ParseRules
' array and stops when one of them works.
'
SUB DefaultParseRule
DIM ParseRule AS STRING
IF PCODE < LBOUND(ParseRules) OR PCODE > UBOUND(ParseRules) THEN EXIT SUB
ParseRule = ParseRules(PCODE)
IF ParseRule = "" THEN EXIT SUB
DbgOutput ""
DbgOutput "PCODE = 0x" + HEX$(PCODE)
DbgOutput "HPARAM = 0x" + HEX$(HPARAM)
DbgOutput ""
'DumpStack
FOR RuleBegin = 3 TO LEN(ParseRule) STEP 4
RuleLn = CVI(MID$(ParseRule, RuleBegin + 0, 2))
RuleID = CVI(MID$(ParseRule, RuleBegin + 2, 2))
RuleTxt$ = MID$(ParseRule, RuleBegin + 4, RuleLn)
IF ExecuteParseRule(RuleID, RuleTxt$) THEN EXIT FOR
RuleBegin = RuleBegin + RuleLn
NEXT RuleBegin
END SUB
'
' Returns the string of the first rule in a compound|parse|rule, and removes
' it from the input string.
'
' If the rule does not have a rule id (ident::=), DefaultRuleID is assigned.
'
FUNCTION DelimitParseRule$ (ParseRule AS STRING, DefaultRuleID AS STRING)
DIM FirstRule AS STRING
'----------------------------------------------------------------------------
' Locate the first instance of the rule delimiter "|" that does not occur
' inside a rule {tag}
'----------------------------------------------------------------------------
RuleOffset = 1
RuleEnd = LEN(ParseRule) + 1
DO
BraceOffset = INSTR(RuleOffset, ParseRule, "{")
IF BraceOffset = 0 THEN BraceOffset = RuleEnd
PipeOffset = INSTR(RuleOffset, ParseRule, "|")
RuleOffset = INSTR(BraceOffset, ParseRule, "}")
IF RuleOffset = 0 THEN RuleOffset = RuleEnd
LOOP UNTIL PipeOffset < BraceOffset
IF PipeOffset = 0 THEN PipeOffset = RuleEnd
'----------------------------------------------------------------------------
' Extract the first rule and return if there is nothing left.
'----------------------------------------------------------------------------
FirstRule = LEFT$(ParseRule, PipeOffset - 1)
ParseRule = MID$(ParseRule, PipeOffset + 1)
'----------------------------------------------------------------------------
' If the first rule has a symbol on the left-hand side and the next rule
' does not, the next rule inherits the symbol.
'----------------------------------------------------------------------------
RuleLHS$ = GetParseRuleLHS(FirstRule)
IF RuleLHS$ = "" AND DefaultRuleID <> "" THEN
RuleLHS$ = DefaultRuleID
FirstRule = DefaultRuleID + "::=" + FirstRule
END IF
DelimitParseRule = FirstRule
IF ParseRule = "" THEN EXIT FUNCTION
IF RuleLHS$ <> "" AND GetParseRuleLHS(ParseRule) = "" THEN
ParseRule = RuleLHS$ + "::=" + ParseRule
END IF
END FUNCTION
'
' For debugging only
'
SUB DumpStack
PRINT #5, "The stack has"; SP; "entries"
FOR i = 1 TO SP
ID = CVI(LEFT$(STACK(i), 2))
Txt$ = MID$(STACK(i), 3)
DO
Marker = INSTR(Txt$, CHR$(&HD))
IF Marker = 0 THEN EXIT DO
IF CVI(MID$(Txt$, Marker, 2)) = &HD THEN
Txt$ = LEFT$(Txt$, Marker - 1) + "<22>newline<6E>" + MID$(Txt$, Marker + 2)
ELSEIF CVI(MID$(Txt$, Marker, 2)) = &H10D THEN
Txt$ = LEFT$(Txt$, Marker - 1) + "<22>indent<6E>" + MID$(Txt$, Marker + 4)
ELSE
Txt$ = LEFT$(Txt$, Marker - 1) + "<22>rle<6C>" + MID$(Txt$, Marker + 3)
END IF
LOOP
PRINT #5, ITOA$(i); ": 0x"; HEX$(ID),
TRIM = 76 - POS(0) - LEN(Txt$)
IF TRIM < 0 THEN PRINT #5, LEFT$(Txt$, LEN(Txt$) + TRIM); " ..." ELSE PRINT #5, Txt$
'80-60-19=1
NEXT i
END SUB
FUNCTION ExecuteParseRule% (RuleID AS INTEGER, ParseRule AS STRING)
DIM RuleTxt AS STRING
DIM TagTxt AS STRING
DIM OutTxt AS STRING
RuleOffset = 1
'
' NOTE: Since the stack is flushed immediately upon seeing a leading period,
' rules should not have non-flushing alternatives.
'
IF LEFT$(ParseRule, 1) = "." THEN
FlushStack
RuleOffset = 2
END IF
InitialSP = SP
FinalSP = SP
RuleTxt = ParseRule
DbgOutput "Trying rule: " + Quote(ParseRule)
DO
DbgOutput "Rule: " + ParseRule
DbgOutput "Output: " + OutTxt
TagBegin = INSTR(RuleOffset, RuleTxt, "{")
IF TagBegin = 0 THEN TagBegin = LEN(RuleTxt) + 1
TagEnd = INSTR(TagBegin, RuleTxt, "}") + 1
OutTxt = OutTxt + MID$(RuleTxt, RuleOffset, TagBegin - RuleOffset)
IF TagEnd <= TagBegin THEN EXIT DO
TagTxt = MID$(RuleTxt, TagBegin + 1, TagEnd - TagBegin - 2)
SELECT CASE TokenizeTag(TagTxt, TagParam)
'------------------------------------------------------------------------
' If a relative stack tag is used, we will need to wait until all the
' absolute tags have been processed before we can calculate the tag
' offset, so we insert a marker into OutTxt.
'------------------------------------------------------------------------
CASE TagType.StackREL
OutTxt = OutTxt + MKL$(0) + MKI$(LEN(TagTxt)) + MKI$(TagParam) + TagTxt
RuleOffset = TagEnd
CASE TagType.StackABS
IF NOT ValidateStackTag(RuleID, TagTxt, TagParam) THEN
ExecuteParseRule = 0
DbgOutput "Rule REJECTED!"
EXIT FUNCTION
ELSE
IF OffsetSP < SP THEN OutTxt = OutTxt + MID$(STACK(SP - TagParam), 3)
IF SP - TagParam - 1 < FinalSP THEN FinalSP = SP - TagParam - 1
END IF
RuleOffset = TagEnd
CASE TagType.Recursive
RuleTxt = LEFT$(RuleTxt, TagBegin - 1) + GetTaggedItem(TagTxt, TagParam) + MID$(RuleTxt, TagEnd)
RuleOffset = TagBegin
CASE TagType.TokenData
OutTxt = OutTxt + GetTaggedItem(TagTxt, TagParam)
RuleOffset = TagEnd
END SELECT
LOOP WHILE RuleOffset <= LEN(RuleTxt)
DbgOutput "Rule: " + ParseRule
DbgOutput "Output: " + OutTxt
SP = FinalSP
DO
Marker = INSTR(OutTxt, MKL$(0))
IF Marker = 0 THEN EXIT DO
TagTxtLen = CVI(MID$(OutTxt, Marker + 4, 2))
TagParam = CVI(MID$(OutTxt, Marker + 6, 2))
TagTxt = MID$(OutTxt, Marker + 8, TagTxtLen)
IF NOT (ValidateStackTag(RuleID, TagTxt, TagParam)) THEN
SP = InitialSP
ExecuteParseRule = 0
DbgOutput "Rule REJECTED!"
EXIT FUNCTION
END IF
OutTxt = LEFT$(OutTxt, Marker - 1) + MID$(STACK(SP - TagParam), 3) + MID$(OutTxt, Marker + 8 + TagTxtLen)
IF SP - TagParam - 1 < FinalSP THEN FinalSP = SP - TagParam - 1
LOOP
FOR SP = InitialSP TO FinalSP + 1 STEP -1: STACK(SP) = "": NEXT SP
SP = FinalSP
PUSH RuleID, OutTxt
ExecuteParseRule = -1
DbgOutput "Rule ACCEPTED!"
'PCODE = RuleID
END FUNCTION
FUNCTION ExtractProgramLine% (ProgramLine AS STRING)
END FUNCTION
'
' Generates a /blockname/ as used in COMMON statements, using the ID at
' CODE(DP)
'
FUNCTION FetchBlockName$ (DP AS INTEGER)
ID = FetchINT(DP)
IF ID <> -1 THEN x$ = " /" + GetID(ID) + "/" ELSE x$ = ""
END FUNCTION
'
' Reads a null-terminate string. These are only found in DATA statements
' and the null always seems to be at the end of the string anyway, but we
' will process it properly to be sure.
'
FUNCTION FetchCSTR$ (DP AS INTEGER)
CSTR$ = FetchRAW(DP)
null = INSTR(CSTR$, CHR$(0))
IF null THEN CSTR$ = LEFT$(CSTR$, null - 1)
FetchCSTR$ = CSTR$
END FUNCTION
'
' Fetches an identifier from the current TOKEN data by performing a symbol
' table lookup on the word at the specified offset.
'
FUNCTION FetchID$ (Offset AS INTEGER)
FetchID$ = ""
IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN EXIT FUNCTION
FetchID$ = GetID(CVI(MID$(TOKEN, Offset + 3, 2)))
END FUNCTION
FUNCTION FetchIDList$ (DP AS INTEGER)
TkLen = LEN(TOKEN)
IF DP < 0 OR DP > TkLen - 2 THEN EXIT FUNCTION
FOR i = DP + 3 TO TkLen - 1 STEP 2
ID$ = GetID(CVI(MID$(TOKEN, i, 2)))
IF IdList$ <> "" THEN IdList$ = IdList$ + ", "
IdList$ = IdList$ + ID$
NEXT i
FetchIDList = IdList$
END FUNCTION
'
' Returns the integer at the specified zero-based offset from the start
' of the token data.
'
FUNCTION FetchINT% (Offset AS INTEGER)
FetchINT = -1
IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN EXIT FUNCTION
FetchINT = CVI(MID$(TOKEN, Offset + 3, 2))
END FUNCTION
'
' Returns the integer at the specified zero-based offset from the start
' of the token data as a LONG value.
'
FUNCTION FetchINTASLONG& (Offset AS INTEGER)
FetchINTASLONG = -1
IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN EXIT FUNCTION
FetchINTASLONG = CVI(MID$(TOKEN, Offset + 3, 2)) AND &HFFFF&
END FUNCTION
'
' Reads a literal 64-bit float from the p-code and returns its string
' representation. Using the "{dbl}" tag in the SHIFT procedure is a more
' convienient method to extract literals.
'
' The IP is passed by reference, and will be incremented to the code
' following the literal. There is no radix option for floating point values.
'
FUNCTION FetchLiteralDBL$ (DP)
IF DP > UBOUND(CODE) THEN
FetchLiteralDBL$ = "0#"
EXIT FUNCTION
END IF
Value# = CVD(MID$(TOKEN, DP + 3, 8))
Txt$ = LTRIM$(STR$(Value#))
' If the single and double precision representations are equal, we will
' insert a # to indicate double precision.
IF Value# = CSNG(Value#) THEN Txt$ = Txt$ + "#"
FetchLiteralDBL$ = Txt$
END FUNCTION
'
' Reads a literal 16-bit integer from the code and returns its string
' representation. Using the "{int}" tag in ExecuteParseRule is a more
' convienient method to extract literals.
'
' The Radix parameter may be 8, 10 or 16 to produce
' the desired number format, or use the "{int&o}" and "{int&h}" tags.
'
FUNCTION FetchLiteralINT$ (Offset AS INTEGER, Radix AS INTEGER)
DIM Value AS INTEGER
IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN
FetchLiteralINT$ = "0"
EXIT FUNCTION
END IF
Value = CVI(MID$(TOKEN, Offset + 3, 2))
SELECT CASE Radix
CASE 8: Txt$ = "&O" + OCT$(Value)
CASE 10: Txt$ = ITOA$(Value)
CASE 16: Txt$ = "&H" + HEX$(Value)
CASE ELSE: Txt$ = "[bad radix]"
END SELECT
FetchLiteralINT$ = Txt$
END FUNCTION
'
' Reads a literal 32-bit integer from the code and returns its string
' representation. Using the "{lng}" tag in ExecuteParseRule is a more
' convienient method to extract literals.
'
' The Radix parameter may be 8, 10 or 16 to produce the desired number
' format, or use the "{lng&o}" and "{lng&h}" tags.
'
FUNCTION FetchLiteralLNG$ (Offset AS INTEGER, Radix AS INTEGER)
DIM Value AS LONG
IF Offset < 0 OR Offset > LEN(TOKEN) - 6 THEN
FetchLiteralLNG$ = "0"
EXIT FUNCTION
END IF
Value = CVL(MID$(TOKEN, Offset + 3, 4))
SELECT CASE Radix
CASE 8: Txt$ = "&O" + OCT$(Value)
CASE 10: Txt$ = LTOA$(Value)
CASE 16: Txt$ = "&H" + HEX$(Value)
CASE ELSE: Txt$ = "[bad radix]"
END SELECT
IF Value < 65536 THEN Txt$ = Txt$ + "&"
FetchLiteralLNG$ = Txt$
END FUNCTION
'
' Reads a literal 32-bit float from the p-code and returns its string
' representation. Using the "{sng}" tag in the SHIFT procedure is a more
' convienient method to extract literals.
'
' The IP is passed by reference, and will be incremented to the code
' following the literal. There is no radix option for floating point values.
'
FUNCTION FetchLiteralSNG$ (DP)
IF OffsetIP > UBOUND(CODE) THEN
FetchLiteralSNG$ = "0"
EXIT FUNCTION
END IF
Value! = CVS(MID$(TOKEN, DP + 3, 4))
Txt$ = LTRIM$(STR$(Value!))
FetchLiteralSNG$ = Txt$
END FUNCTION
FUNCTION FetchLNG& (Offset AS INTEGER)
FetchLNG = -1
IF Offset < 0 OR Offset > LEN(TOKEN) - 6 THEN EXIT FUNCTION
FetchLNG = CVL(MID$(TOKEN, Offset + 3, 4))
END FUNCTION
FUNCTION FetchRAW$ (Offset AS INTEGER)
IF Offset < 0 OR Offset > LEN(TOKEN) - 2 THEN EXIT FUNCTION
FetchRAW$ = MID$(TOKEN, 3 + Offset)
END FUNCTION
FUNCTION FindRuleDelimiter% (ParseRule AS STRING)
RuleOffset = 1
RuleEnd = LEN(ParseRule) + 1
DO WHILE RuleOffset < RuleEnd
BraceOffset = INSTR(RuleOffset, ParseRule, "{")
PipeOffset = INSTR(RuleOffset, ParseRule, "|")
IF BraceOffset = 0 OR PipeOffset <= BraceOffset THEN EXIT DO
RuleOffset = INSTR(BraceOffset + 1, ParseRule, "}")
IF RuleOffset = 1 THEN EXIT DO
LOOP
FindRuleDelimiter = PipeOffset
END FUNCTION
'
' Flushes all stack entries to STACK(0), ready for final processing into
' a program line.
'
SUB FlushStack
FOR i = 1 TO SP
STACK(0) = STACK(0) + MID$(STACK(i), 3)
STACK(i) = ""
NEXT i
SP = 0
END SUB
'
' Returns an integer identifier for a parse rule symbol
'
FUNCTION GetHashedSymbol (ParseRuleSymbol AS STRING)
DIM LookupSymbol AS STRING
SymbolID$ = LTRIM$(RTRIM$(ParseRuleSymbol))
'----------------------------------------------------------------------------
' Parse rule symbols my be literal integers
'----------------------------------------------------------------------------
IF StringToINT(SymbolID$, SymbolID%) THEN
GetHashedSymbol% = SymbolID%
EXIT FUNCTION
END IF
Hash = HashPJW(SymbolID$)
LookupSymbol = "[" + SymbolID$ + "]"
SymbolOffset = INSTR(SymbolHashTable(Hash), LookupSymbol)
IF SymbolOffset = 0 THEN
SymbolID% = SymbolHashEntries
SymbolID% = SymbolID% + UBOUND(ParseRules) + 1
SymbolID$ = RIGHT$(SymbolHashTable(Hash), 2)
IF SymbolID$ <> "" THEN SymbolID% = CVI(SymbolID$) + 1
SymbolID$ = MKI$(SymbolID%)
SymbolHashTable(Hash) = SymbolHashTable(Hash) + LookupSymbol + SymbolID$
SymbolHashEntries = SymbolHashEntries + 1
ELSE
SymbolOffset = SymbolOffset + LEN(LookupSymbol)
SymbolID$ = MID$(SymbolHashTable(Hash), SymbolOffset, 2)
SymbolID% = CVI(SymbolID$)
END IF
GetHashedSymbol% = SymbolID% '+ UBOUND(ParseRules) + 1
END FUNCTION
'
' Reads an identifier from the symbol table data stored in the SYMTBL
' array.
'
FUNCTION GetID$ (SymTblOffset AS INTEGER)
'----------------------------------------------------------------------------
' Convert offset to LONG to we can read above 32767
'----------------------------------------------------------------------------
SymTblOfs& = SymTblOffset AND &HFFFF&
'----------------------------------------------------------------------------
' offset FFFF is used as a shortcut for "0" in statements such as
' ON ERROR GOTO 0
'----------------------------------------------------------------------------
IF SymTblOfs& = &HFFFF& THEN
GetID$ = "0"
EXIT FUNCTION
END IF
'----------------------------------------------------------------------------
' Make sure we can at least read the first 4 bytes
'----------------------------------------------------------------------------
IF SymTblOfs& \ 2 > UBOUND(SYMTBL) - 2 THEN
GetID$ = "<22>QB45BIN:SymbolTableError<6F>"
EXIT FUNCTION
END IF
DEF SEG = VARSEG(SYMTBL(1))
Offset = VARPTR(SYMTBL(1))
Symbol& = (Offset AND &HFFFF&) + SymTblOfs&
SymbolFlags = PEEK(Symbol& + 2)
IF SymbolFlags AND 2 THEN
' Short line numbers are stored as integers.
NumericID& = PEEK(Symbol& + 4) OR PEEK(Symbol& + 5) * &H100&
GetID$ = LTRIM$(STR$(NumericID&))
ELSE
' Identifier is a text string - extract it. Note the string may be
' a line number.
Length = PEEK(Symbol& + 3)
IF SymTblOfs& \ 2 > UBOUND(SYMTBL) - (Length + 1) \ 2 THEN
GetID$ = "SymbolTableError"
EXIT FUNCTION
END IF
ID$ = STRING$(Length, CHR$(0))
FOR i = 1 TO Length
MID$(ID$, i, 1) = CHR$(PEEK(Symbol& + 3 + i))
NEXT i
GetID$ = ID$
END IF
END FUNCTION
'
' Removes the parse rule id::= from a string and returns its numeric ID.
'
FUNCTION GetParseRuleID% (ParseRule AS STRING, TokenID AS INTEGER)
'----------------------------------------------------------------------------
' The default rule ID is always the PCODE
'----------------------------------------------------------------------------
FOR i = 1 TO LEN(ParseRule)
IF INSTR("{}|", MID$(ParseRule, i, 1)) THEN EXIT FOR
IF MID$(ParseRule, i, 3) = "::=" THEN
GetParseRuleID = SetHashedSymbol(LEFT$(ParseRule, i - 1), TokenID)
ParseRule = MID$(ParseRule, i + 3)
EXIT FUNCTION
END IF
NEXT i
GetParseRuleID = -1
END FUNCTION
FUNCTION GetParseRuleLHS$ (ParseRule AS STRING)
FOR i = 1 TO LEN(ParseRule)
IF INSTR("{}|", MID$(ParseRule, i, 1)) THEN EXIT FOR
IF MID$(ParseRule, i, 3) = "::=" THEN
GetParseRuleLHS = LEFT$(ParseRule, i - 1)
EXIT FUNCTION
END IF
NEXT i
END FUNCTION
FUNCTION GetTaggedItem$ (TagTxt AS STRING, DP AS INTEGER)
DIM SubstTxt AS STRING
SELECT CASE LCASE$(TagTxt)
CASE "blockname": SubstTxt = FetchBlockName(DP)
CASE "circle-args": SubstTxt = SubstTagCIRCLE
CASE "input-args": SubstTxt = SubstTagINPUT
CASE "line-args": SubstTxt = SubstTagLINE
CASE "lock-args": SubstTxt = SubstTagLOCK
CASE "open-args": SubstTxt = SubstTagOPEN
CASE "action-verb": SubstTxt = SubstTagVERB
CASE "keymode": SubstTxt = SubstTagKEY
CASE "type-abbr": SubstTxt = GetTypeAbbr(HPARAM)
CASE "call": SubstTxt = ParseCALL(0)
CASE "call()": SubstTxt = ParseCALL(-1)
CASE "defxxx": SubstTxt = SubstTagDEFxxx(QBBinDefType())
CASE "newline": SubstTxt = MKI$(&H10D)
CASE "newline-include": SubstTxt = MKI$(&H20D)
CASE "tabh": SubstTxt = MKI$(&HD) + MKI$(HPARAM)
CASE "tabi": SubstTxt = MKI$(&HD) + MKI$(FetchINT(DP))
CASE "indent": SubstTxt = SPACE$(FetchINT(DP) AND &HFFFF&)
CASE "type": SubstTxt = GetTypeName$(FetchINT(DP))
CASE "id": SubstTxt = GetID(FetchINT(DP))
CASE "id+": SubstTxt = GetID(FetchINT(DP)) + GetTypeSuffix(HPARAM)
CASE "id-list": SubstTxt = FetchIDList(DP)
CASE "id(decl)": SubstTxt = ParseArrayDecl
CASE "id(expr)": SubstTxt = ParseArrayExpr
CASE "hprm": SubstTxt = ITOA$(HPARAM)
CASE "int": SubstTxt = FetchLiteralINT(DP, 10)
CASE "int&h": SubstTxt = FetchLiteralINT(DP, 16)
CASE "int&o": SubstTxt = FetchLiteralINT(DP, 8)
CASE "label": SubstTxt = FetchID(DP): IF IsLineNumber(SubstTxt) THEN SubstTxt = SubstTxt + " " ELSE SubstTxt = SubstTxt + ":"
CASE "lng": SubstTxt = FetchLiteralLNG(DP, 10)
CASE "lng&h": SubstTxt = FetchLiteralLNG(DP, 16)
CASE "lng&o": SubstTxt = FetchLiteralLNG(DP, 8)
CASE "nul": SubstTxt = ""
CASE "sng": SubstTxt = FetchLiteralSNG(DP)
CASE "dbl": SubstTxt = FetchLiteralDBL(DP)
CASE "qstr": SubstTxt = Quote(FetchRAW(DP))
CASE "cstr": SubstTxt = FetchCSTR(DP)
CASE "raw": SubstTxt = FetchRAW(DP)
CASE "varargs": SubstTxt = ParseVarArgs
CASE "optargs":
CASE "procdecl": SubstTxt = ParseProcDecl$(DP, 0)
CASE "procdecl()": SubstTxt = ParseProcDecl$(DP, -1)
CASE "proctype": SubstTxt = QBBinProcedureType
CASE "thaddr": SanityCheck DP
CASE ELSE:
SubstTxt = "<22>QB45BIN:bad tag<61>"
END SELECT
GetTaggedItem$ = SubstTxt
END FUNCTION
FUNCTION GetTotalLines%
DIM TotalLines AS LONG
DIM IncludeLines AS LONG
TotalLines = 0
IncludeLines = 0
FTell& = LOC(QBBinFile) + 1
GET #QBBinFile, 27, SymTblLen%
ModuleLOC& = LOC(QBBinFile) + (SymTblLen% AND &HFFFF&) + 1
SEEK #QBBinFile, ModuleLOC&
DO
GET #QBBinFile, , ModuleLen%
SEEK #QBBinFile, LOC(QBBinFile) + (ModuleLen% AND &HFFFF&) + 9
GET #QBBinFile, , NumTotLines%
GET #QBBinFile, , NumIncLines%
TotalLines = TotalLines + (NumTotLines% AND &HFFFF&)
IncludeLines = IncludeLines + (NumIncLines% AND &HFFFF&)
SEEK #QBBinFile, LOC(QBBinFile) + 5
Byte$ = CHR$(0)
GET #QBBinFile, , Byte$
IF EOF(QBBinFile) THEN EXIT DO
ProcedureCOUNT = ProcedureCOUNT + 1
GET #QBBinFile, , NameLen%
SEEK #QBBinFile, LOC(QBBinFile) + (NameLen% AND &HFFFF&) + 4
LOOP
REDIM ProcedureNAME(1 TO ProcedureCOUNT + 1) AS STRING
REDIM ProcedureLOC(1 TO ProcedureCOUNT + 1) AS LONG
SEEK #QBBinFile, ModuleLOC&
FOR i = 1 TO ProcedureCOUNT
GET #QBBinFile, , ModuleLen%
ProcedureLOC(i) = LOC(QBBinFile) + (ModuleLen% AND &HFFFF&) + 17
SEEK #QBBinFile, ProcedureLOC(i) + 1
GET #QBBinFile, , ProcedureNameLEN%
ProcedureNAME(i) = STRING$(ProcedureNameLEN%, 0)
GET #QBBinFile, , ProcedureNAME(i)
ProcedureNAME(i) = UCASE$(ProcedureNAME(i))
'------------------------------------------------------------------------
' Incremental bubble sort of procedure names
'------------------------------------------------------------------------
IF QBBinOption.SortProceduresAZ THEN
FOR j = i - 1 TO 1 STEP -1
IF ProcedureNAME(j + 1) > ProcedureNAME(j) THEN EXIT FOR
SWAP ProcedureNAME(j + 1), ProcedureNAME(j)
SWAP ProcedureLOC(j + 1), ProcedureLOC(j)
NEXT j
END IF
SEEK #QBBinFile, LOC(QBBinFile) + 4
NEXT i
FOR i = 1 TO ProcedureCOUNT
'PRINT ProcedureNAME(i)
QBBinProcedureIndex = QBBinProcedureIndex + MKL$(ProcedureLOC(i))
NEXT i
ERASE ProcedureNAME, ProcedureLOC
SEEK #QBBinFile, FTell&
IF QBBinOption.OmitIncludedLines THEN
GetTotalLines = TotalLines - IncludedLines
ELSE
GetTotalLines = TotalLines
END IF
END FUNCTION
'
' Returns the abbreviated name for a built-in type (ie: LNG or DBL).
'
FUNCTION GetTypeAbbr$ (TypeID AS INTEGER)
GetTypeAbbr$ = TypeSpecifiers(LIMIT(TypeID, 0, 5), 2)
END FUNCTION
FUNCTION GetTypeName$ (TypeID AS INTEGER)
LTypeID& = TypeID AND &HFFFF&
IF LTypeID& > 5 THEN
GetTypeName$ = GetID$(TypeID) ' User-define type
ELSE
GetTypeName$ = TypeSpecifiers(LTypeID&, 1)
END IF
END FUNCTION
FUNCTION GetTypeSuffix$ (TypeID AS INTEGER)
GetTypeSuffix$ = TypeSpecifiers(LIMIT(TypeID, 0, 5), 3)
END FUNCTION
'
' Implementation of PJW hash, written to avoid 32-bit overflow.
'
FUNCTION HashPJW% (Identifier AS STRING)
DIM h AS LONG, g AS LONG, k AS LONG
FOR i = 1 TO LEN(Identifier)
k = ASC(MID$(Identifier, i, 1))
h = h + (k \ 16)
g = (h AND &HF000000) \ 2 ^ 20
h = (h AND &HFFFFFF) * 16 + (k AND 15)
IF g THEN h = h XOR (g \ 2 ^ 20)
NEXT i
HashPJW% = h MOD SymbolHashBuckets
END FUNCTION
FUNCTION IsLineNumber (ID AS STRING)
Ch$ = LEFT$(ID, 1)
IF Ch$ = "" THEN EXIT FUNCTION
IF ASC(Ch$) >= 48 AND ASC(Ch$) < 57 THEN IsLineNumber = -1
END FUNCTION
FUNCTION ITOA$ (Value AS INTEGER)
ITOA$ = LTRIM$(RTRIM$(STR$(Value)))
END FUNCTION
FUNCTION LIMIT (x, xMin, xMax)
IF x < xMin THEN
LIMIT = xMin
ELSEIF x > xMax THEN
LIMIT = xMax
ELSE
LIMIT = x
END IF
END FUNCTION
FUNCTION LoadMainModule
'----------------------------------------------------------------------------
' Read module size and convert to long to lose sign bit. Note that modules
' should always be a multiple of two in size since all the tokens are 16
' bits.
'----------------------------------------------------------------------------
IF EOF(QBBinFile) THEN EXIT FUNCTION
GET #QBBinFile, , szModule%
szModule& = (szModule% AND &HFFFF&)
szModule% = (szModule& + 1) \ 2
REDIM CODE(1 TO szModule%) AS INTEGER
ReadToArrayINT QBBinFile, CODE(), szModule&
'----------------------------------------------------------------------------
' There is always 16 bytes of data after a code block
'----------------------------------------------------------------------------
DIM Footer AS STRING * 16
GET #QBBinFile, , Footer
IF EOF(QBBinFile) THEN
QBBinCloseFile
EXIT FUNCTION
END IF
LoadMainModule = -1
IP = LBOUND(CODE)
END FUNCTION
FUNCTION LoadNextProcedure
IF QBBinProcedureIndex = "" THEN
QBBinCloseFile
EXIT FUNCTION
END IF
ProcedureLOC& = CVL(LEFT$(QBBinProcedureIndex, 4))
QBBinProcedureIndex = MID$(QBBinProcedureIndex, 5)
SEEK #QBBinFile, ProcedureLOC&
DIM Junk AS STRING
Junk = CHR$(0)
GET #QBBinFile, , Junk
IF EOF(QBBinFile) THEN
QBBinCloseFile
EXIT FUNCTION
END IF
GET #QBBinFile, , ProcNameLen%
QBBinProcedureName = STRING$(ProcNameLen% AND &HFFFF&, 0)
GET #QBBinFile, , QBBinProcedureName
Junk = STRING$(3, 0)
GET #QBBinFile, , Junk
GET #QBBinFile, , ProcCodeLen%
ReadToArrayINT QBBinFile, CODE(), ProcCodeLen% AND &HFFFF&
DIM Footer AS STRING * 16
GET #QBBinFile, , Footer
LoadNextProcedure = -1
IP = LBOUND(CODE)
END FUNCTION
SUB LoadParseRules
DIM ParseRule AS STRING
TokenLBound = &H7FFF
TokenUBound = 0
TokenLength = 0
'----------------------------------------------------------------------------
' Clear the symbol hash table
'----------------------------------------------------------------------------
FOR i = 0 TO SymbolHashBuckets - 1: SymbolHashTable(i) = "": NEXT i
SymbolHashEntries = 0
'----------------------------------------------------------------------------
' PASS 1: Enumerate all tokens
'----------------------------------------------------------------------------
RestoreParseRules
DO WHILE ReadParseRule(TokenPCODE, TokenLength, ParseRule)
TokenLBound = MIN(TokenPCODE, TokenLBound)
TokenUBound = MAX(TokenPCODE, TokenLBound)
LOOP
REDIM ParseRules(TokenLBound TO TokenUBound) AS STRING
'----------------------------------------------------------------------------
' PASS 2: Generate token strings
'----------------------------------------------------------------------------
RestoreParseRules
DO WHILE ReadParseRule(TokenPCODE, TokenLength, ParseRule)
'------------------------------------------------------------------------
' If this is the first rule for this PCODE, then we'll write the
' length of the token data as the first word.
'------------------------------------------------------------------------
IF ParseRules(TokenPCODE) = "" THEN
ParseRules(TokenPCODE) = MKI$(TokenLength)
END IF
RuleID = GetParseRuleID(ParseRule, TokenPCODE)
IF RuleID = -1 THEN RuleID = TokenPCODE
ParseRule = MKI$(LEN(ParseRule)) + MKI$(RuleID) + ParseRule
ParseRules(TokenPCODE) = ParseRules(TokenPCODE) + ParseRule
LOOP
QBBinTok.SUBDEF = GetHashedSymbol("subdef")
QBBinTok.FUNCDEF = GetHashedSymbol("funcdef")
QBBinTok.DEFTYPE = GetHashedSymbol("deftype")
END SUB
'
' Returns the token id of the next unprocessed token without modifying IP.
' Neccessary for REDIM, which causes an array expression to behave like
' an array declaration, for reasons best known to the QB45 dev team.
'
FUNCTION LookAhead
IF IP < LBOUND(CODE) OR IP > UBOUND(CODE) THEN
LookAhead = -1
ELSE
LookAhead = CODE(IP) AND &H3FF
END IF
END FUNCTION
FUNCTION LTOA$ (Value AS LONG)
LTOA$ = LTRIM$(RTRIM$(STR$(Value)))
END FUNCTION
FUNCTION MAX% (x AS INTEGER, Y AS INTEGER)
IF x > Y THEN MAX = x ELSE MAX = Y
END FUNCTION
FUNCTION MIN% (x AS INTEGER, Y AS INTEGER)
IF x < Y THEN MIN = x ELSE MIN = Y
END FUNCTION
FUNCTION ParseArrayDecl$
STATIC RuleIDLoaded AS INTEGER
STATIC RuleAsTypeID AS INTEGER
STATIC RuleDeclID AS INTEGER
STATIC RuleDeclsID AS INTEGER
IF NOT RuleIDLoaded THEN
RuleAsTypeID = GetHashedSymbol("astype")
RuleDeclID = GetHashedSymbol("decl")
RuleDeclsID = GetHashedSymbol("decls")
END IF
nElmts = FetchINT(0)
ID$ = FetchID(2) + GetTypeSuffix(HPARAM)
IF StackPeek(0) = RuleAsTypeID THEN
ArgC = 1
AsType$ = "{0}"
END IF
WHILE nElmts > 0
nElmts = nElmts - 1
Indices$ = STAG(ArgC) + Indices$
ArgC = ArgC + 1
IF nElmts AND 1 THEN
IF StackPeek(ArgC) <> &H18 THEN Indices$ = " TO " + Indices$
ELSE
IF nElmts THEN Indices$ = ", " + Indices$
END IF
WEND
IF Indices$ <> "" THEN Indices$ = "(" + Indices$ + ")"
IF StackPeek(ArgC) = RuleDeclsID THEN
ParseArrayDecl$ = STAG(ArgC) + ", " + ID$ + Indices$ + AsType$
ELSEIF StackPeek(ArgC) = RuleDeclID THEN
ParseArrayDecl$ = STAG(ArgC) + " " + ID$ + Indices$ + AsType$
ELSE
ParseArrayDecl$ = ID$ + Indices$ + AsType$
END IF
END FUNCTION
'
' Generates a parse rule for an array expression.
'
FUNCTION ParseArrayExpr$
IF LookAhead = 28 THEN
ParseArrayExpr = ParseArrayDecl
EXIT FUNCTION
END IF
'IF PCODE = 15 THEN ArgC = 1
nElmts = FetchINT(0)
ID$ = FetchID(2) + GetTypeSuffix(HPARAM)
IF NOT nElmts AND &H8000 THEN
FOR i = nElmts - 1 TO 0 STEP -1
IF i THEN
Indices$ = ", " + STAG(ArgC) + Indices$
ELSE
Indices$ = STAG(ArgC) + Indices$
END IF
ArgC = ArgC + 1
NEXT i
Indices$ = "(" + Indices$ + ")"
END IF
ParseArrayExpr = ID$ + Indices$
END FUNCTION
'
' Generates parse rule fragment for a procedure call
'
FUNCTION ParseCALL$ (Parenthesis AS INTEGER)
ArgC = FetchINT(0)
FOR ArgI = 0 TO ArgC - 1
IF ArgI THEN
ArgV$ = STAG(ArgI) + ", " + ArgV$
ELSE
ArgV$ = STAG(ArgI) + ArgV$
END IF
NEXT ArgI
IF ArgC > 0 THEN
IF Parenthesis THEN ArgV$ = "(" + ArgV$ + ")" ELSE ArgV$ = " " + ArgV$
END IF
ParseCALL$ = ArgV$
END FUNCTION
'
' This helper function parses a SUB or FUNCTION declaration, or a
' SUB/FUNCTION/DEF FN definition.
'
FUNCTION ParseProcDecl$ (DP AS INTEGER, Parenthesis AS INTEGER)
DIM Flags AS LONG
DIM ArgC AS LONG
CONST fCDECL = &H8000
CONST fALIAS = &H400
ID$ = GetID(FetchINT(DP + 0))
Flags = FetchINTASLONG(DP + 2)
ArgC = FetchINTASLONG(DP + 4)
LenALIAS = Flags \ &H400 AND &H1F
IF Flags AND &H80 THEN TS$ = GetTypeSuffix(Flags AND 7)
Arguments$ = ""
ProcType = (Flags AND &H300) \ 256
SELECT CASE ProcType
CASE 1: ID$ = "SUB " + ID$ + TS$: QBBinProcedureType = "SUB"
CASE 2: ID$ = "FUNCTION " + ID$ + TS$: QBBinProcedureType = "FUNCTION"
CASE 3: ID$ = "DEF " + ID$ + TS$: QBBinProcedureType = "DEF"
END SELECT
'
' Process arguments list
'
FOR ArgI = 1 TO ArgC
ArgName$ = GetID(FetchINT(DP + ArgI * 6 + 0))
ArgFlags = FetchINT(DP + ArgI * 6 + 2)
ArgType = FetchINT(DP + ArgI * 6 + 4)
'------------------------------------------------------------------------
' Process special argument flags. Not all of these can be combined,
' but we'll just assume the file contains a valid combination.
'------------------------------------------------------------------------
IF ArgFlags AND &H200 THEN ArgName$ = ArgName$ + GetTypeSuffix(ArgType)
IF ArgFlags AND &H400 THEN ArgName$ = ArgName$ + "()"
IF ArgFlags AND &H800 THEN ArgName$ = "SEG " + ArgName$
IF ArgFlags AND &H1000 THEN ArgName$ = "BYVAL " + ArgName$
IF ArgFlags AND &H2000 THEN ArgName$ = ArgName$ + " AS " + GetTypeName(ArgType)
IF ArgI = 1 THEN
Arguments$ = ArgName$
ELSE
Arguments$ = Arguments$ + ", " + ArgName$
END IF
NEXT ArgI
IF Parenthesis OR Arguments$ <> "" THEN Arguments$ = " (" + Arguments$ + ")"
'
' Process CDECL and ALIAS modifiers
'
IF Flags AND fCDECL THEN ID$ = ID$ + " CDECL"
AliasName$ = LEFT$(FetchRAW(DP + ArgI * 6), LenALIAS)
IF LenALIAS THEN ID$ = ID$ + " ALIAS " + AliasName$
ParseProcDecl$ = ID$ + Arguments$
END FUNCTION
'
'
'
FUNCTION ParseVarArgs$
ArgC = FetchINT(0)
STATIC NULARG
IF NULARG = 0 THEN NULARG = GetHashedSymbol("nularg")
FOR ArgI = 0 TO ArgC - 1
IF StackPeek(ArgI) <> NULARG THEN ArgV$ = ", " + ArgV$
ArgV$ = STAG(ArgI) + ArgV$
NEXT ArgI
'----------------------------------------------------------------------------
' Trim trailing commas
'----------------------------------------------------------------------------
FOR i = LEN(ArgV$) TO 1 STEP -1
Ch$ = MID$(ArgV$, i, 1)
IF Ch$ <> " " AND Ch$ <> "," THEN EXIT FOR
NEXT i
ArgV$ = LEFT$(ArgV$, i)
IF ArgV$ <> "" THEN ArgV$ = " " + ArgV$
ParseVarArgs$ = ArgV$
END FUNCTION
FUNCTION POP$
IF SP = LBOUND(STACK) THEN EXIT FUNCTION
POP$ = MID$(STACK(SP), 3)
SP = SP - 1
END FUNCTION
'
' The following special codes may be embedded in a string:
'
' 0xccnn0D - RLE encoding (used by QB45 comments)
' 0xnnnn000D - Indentation marker
' 0x101D - NEWLINE marker 1
' 0x201D - NEWLINE marker 2
'
SUB PostProcess
DIM OutText AS STRING
DIM OutTxt AS STRING
DIM Marker AS LONG
DIM LineColumn AS LONG
DIM OffsetFromNewline AS LONG
DIM TextBegin AS LONG
TextBegin = 1
DO
'------------------------------------------------------------------------
' Look for special symbol marker
'------------------------------------------------------------------------
Marker = INSTR(TextBegin, STACK(0), CHR$(&HD))
IF Marker = 0 THEN Marker = LEN(STACK(0)) + 1
'------------------------------------------------------------------------
' Copy leading text to output string
'------------------------------------------------------------------------
OutTxt = OutTxt + MID$(STACK(0), TextBegin, Marker - TextBegin)
IF Marker > LEN(STACK(0)) THEN
TextBegin = Marker
EXIT DO
END IF
OffsetFromNewline = OffsetFromNewline + Marker - TextBegin
SELECT CASE MID$(STACK(0), Marker + 1, 1)
CASE CHR$(0):
'----------------------------------------------------------------
' Indentation
'----------------------------------------------------------------
RunLn& = CVI(MID$(STACK(0), Marker + 2)) AND &HFFFF&
RunLn& = RunLn& - CLNG(OffsetFromNewline)
IF (RunLn& < 0) THEN RunLn& = 1
OffsetFromNewline = OffsetFromNewline + RunLn&
OutTxt = OutTxt + SPACE$(RunLn&)
TextBegin = Marker + 4
CASE CHR$(1):
'----------------------------------------------------------------
' Newline
'----------------------------------------------------------------
IF FlushToOutput THEN EXIT DO
DiscardLine = 0
FlushToOutput = -1
OffsetFromNewline = 0
TextBegin = Marker + 2
CASE CHR$(2):
'----------------------------------------------------------------
' Newline - $INCLUDEd file
'----------------------------------------------------------------
DiscardLine = QBBinOption.OmitIncludedLines
FlushToOutput = -1
OffsetFromNewline = 0
TextBegin = Marker + 2
CASE ELSE:
'----------------------------------------------------------------
' RLE encoded comment
'----------------------------------------------------------------
RunLn& = ASC(MID$(STACK(0), Marker + 1))
RunCh$ = MID$(STACK(0), Marker + 2)
OutTxt = OutTxt + STRING$(RunLn&, RunCh$)
OffsetFromNewline = OffsetFromNewline + RunLn&
TextBegin = Marker + 3
END SELECT
LOOP
IF FlushToOutput THEN
IF OutTxt <> SPACE$(LEN(OutTxt)) THEN OutTxt = RTRIM$(OutTxt)
QBBinProgramLine = OutTxt
QBBinLineReady = NOT DiscardLine
OutTxt = ""
END IF
STACK(0) = OutTxt + MID$(STACK(0), Marker)
END SUB
SUB ProcessProcDefType
' Procedure DEFTYPE defaults to SINGLE
DIM ProcDefType(1 TO 26) AS INTEGER
DIM OutTxt AS STRING
FOR i = 1 TO 26: ProcDefType(i) = 3: NEXT i
DO WHILE LookAhead = 0
IF NOT ReadToken THEN EXIT SUB
IF LookAhead <> QBBinTok.DEFTYPE THEN
IP = IP - 1
EXIT DO
END IF
IF NOT ReadToken THEN EXIT DO
UnwantedReturnValue$ = SubstTagDEFxxx(ProcDefType())
LOOP
'FOR i = 1 TO 26: PRINT GetTypeSuffix(ProcDefType(i)); : NEXT i: PRINT
'PRINT QBBinProcedureName
FOR i = 1 TO 5
'IF i = 3 THEN i = i + 1
AnythingOutput = 0
InitialLetter = 0
OutTxt = ""
FOR j = 1 TO 27
BITSET = 0
IF j < 27 THEN
BITSET = ProcDefType(j) = i
BITSET = BITSET AND QBBinDefType(j) <> i
END IF
IF BITSET AND InitialLetter = 0 THEN
InitialLetter = j + 64
ELSEIF InitialLetter AND NOT BITSET THEN
IF AnythingOutput THEN OutTxt = OutTxt + ", "
OutTxt = OutTxt + CHR$(InitialLetter)
Range = j + 64 - InitialLetter
IF Range > 1 THEN OutTxt = OutTxt + "-" + CHR$(j + 63)
AnythingOutput = -1
InitialLetter = 0
END IF
NEXT j
IF AnythingOutput THEN
PUSH 0, MKI$(&H10D)
PUSH QBBinTok.DEFTYPE, "DEF" + GetTypeAbbr(i) + " " + OutTxt
FlushStack
END IF
NEXT i
FOR i = 1 TO 26: QBBinDefType(i) = ProcDefType(i): NEXT i
END SUB
FUNCTION ProcessToken
ProcessToken = 0
IF NOT ReadToken THEN EXIT FUNCTION
IF PCODE = 8 THEN EXIT FUNCTION
ProcessToken = -1
DefaultParseRule
END FUNCTION
SUB PUSH (ID AS INTEGER, Txt AS STRING)
IF SP = UBOUND(STACK) THEN EXIT SUB
SP = SP + 1
STACK(SP) = MKI$(ID) + Txt
END SUB
SUB QBBinCloseFile
CLOSE #QBBinFile
QBBinFile = 0
QBBinEOF = -1
END SUB
DEFSNG A-Z
FUNCTION QBBinGetFileType
END FUNCTION
DEFINT A-Z
'
FUNCTION QBBinGetProcName$
END FUNCTION
SUB QBBinOpenFile (FileName AS STRING)
QBBinFile = FREEFILE
QBBinEOF = 0
OPEN FileName FOR BINARY AS #QBBinFile
GET #QBBinFile, , Magic%
GET #QBBinFile, , Version%
'----------------------------------------------------------------------------
' Only QB45 is currently supported
'----------------------------------------------------------------------------
IF (Magic% <> &HFC) OR (Version% <> 1) THEN
RESET
PRINT "ERROR: The file you provided does not have a valid QB45 header."
SYSTEM 1
END IF
' Don't delete this - alpha sorter needs it!
x = GetTotalLines
'----------------------------------------------------------------------------
' Read symbol table size and convert to long to lose sign bit
'----------------------------------------------------------------------------
GET #QBBinFile, 27, szSymTbl%
szSymTbl& = szSymTbl% AND &HFFFF&
'----------------------------------------------------------------------------
' Load symbol table to memory and return file number
'----------------------------------------------------------------------------
REDIM SYMTBL(1 TO (szSymTbl& + 1) \ 2) AS INTEGER
ReadToArrayINT QBBinFile, SYMTBL(), szSymTbl&
IF NOT LoadMainModule THEN EXIT SUB
'----------------------------------------------------------------------------
' If main module is empty, look for non-empty procedure
'----------------------------------------------------------------------------
WHILE CODE(IP) = 8
IF NOT LoadNextProcedure THEN EXIT SUB
WEND
END SUB
FUNCTION QBBinReadLine$ (Meta AS LONG)
STATIC NewProc
Meta = 0
PostProcess
IF QBBinLineReady THEN
QBBinReadLine = QBBinProgramLine
QBBinLineReady = 0
QBBinProgramLine = ""
EXIT FUNCTION
END IF
IF QBBinEOF THEN EXIT FUNCTION
DO
IF NoMoreTokens THEN
QBBinCloseFile
EXIT FUNCTION
END IF
IF NOT ReadToken THEN EXIT FUNCTION
DefaultParseRule
'------------------------------------------------------------------------
' Trap some special tokens
'------------------------------------------------------------------------
SELECT CASE PCODE
'------------------------------------------------------------------------
' Token 0x008 appears at the end of the code (before the watch list)
'------------------------------------------------------------------------
CASE 8:
IF NOT LoadNextProcedure THEN
NoMoreTokens = -1
ELSE
PUSH 0, MKI$(&H10D) ' Force blank line before SUB/FUNCTION
ProcessProcDefType
NewProc = -1
'ProcessProcDefType
END IF
'END SELECT
'SELECT CASE StackPeek(0)
CASE QBBinTok.SUBDEF: Meta = QBBinMeta.SUB
CASE QBBinTok.FUNCDEF: Meta = QBBinMeta.FUNCTION
END SELECT
PostProcess
LOOP WHILE NOT QBBinLineReady
QBBinReadLine = QBBinProgramLine
QBBinLineReady = 0
QBBinProgramLine = ""
END FUNCTION
SUB QBBinSetOption (OptionName AS STRING, OptionValue AS INTEGER)
END SUB
FUNCTION Quote$ (Txt AS STRING)
Quote$ = CHR$(34) + Txt + CHR$(34)
END FUNCTION
FUNCTION ReadKey$
DO: LOOP WHILE INKEY$ <> ""
DO: Key$ = INKEY$: LOOP WHILE Key$ = ""
ReadKey = UCASE$(Key$)
END FUNCTION
FUNCTION ReadParseRule (TokenID AS INTEGER, OpLen AS INTEGER, ParseRule AS STRING)
'------------------------------------------------------------------------
' Ugh... static. I'm being lazy.
'------------------------------------------------------------------------
STATIC RuleItem AS STRING
STATIC DefaultRuleID AS STRING
'------------------------------------------------------------------------
' If RuleItem isn't empty, extract the next rule.
'------------------------------------------------------------------------
IF RuleItem <> "" THEN
ParseRule = DelimitParseRule(RuleItem, DefaultRuleID)
ReadParseRule = -1
EXIT FUNCTION
END IF
ReadParseRule = 0
READ RuleItem
'------------------------------------------------------------------------
' Loop until we have something which isn't the .default directive
'------------------------------------------------------------------------
WHILE MID$(RuleItem, 1, 8) = ".default"
DefaultRuleID = LTRIM$(RTRIM$(MID$(RuleItem, 9)))
READ RuleItem
WEND
'------------------------------------------------------------------------
' The rule list is terminated by a period.
'------------------------------------------------------------------------
IF RuleItem = "." THEN
RuleItem = ""
DefaultRuleID = ""
EXIT FUNCTION
END IF
'------------------------------------------------------------------------
' If RuleItem is a number, then assume it is the start of a new token.
' Otherwise, we assume it is an additional rule of the previous token.
'------------------------------------------------------------------------
IF (StringToINT(RuleItem, TokenID)) THEN
READ RuleItem
'--------------------------------------------------------------------
' If the token length is not omitted, then we need to read again
' to fetch the token parse rule. Also, an asterisk may be used to
' represent a variable length token, so we need to check for that.
'--------------------------------------------------------------------
IF StringToINT(RuleItem, OpLen) THEN
READ RuleItem
ELSEIF RuleItem$ = "*" THEN
OpLen = -1
READ RuleItem
ELSE
OpLen = 0
END IF
END IF
'------------------------------------------------------------------------
' Extract rule and return
'------------------------------------------------------------------------
ParseRule = DelimitParseRule(RuleItem, DefaultRuleID)
ReadParseRule = -1
END FUNCTION
SUB ReadToArrayINT (FileNumber AS INTEGER, Array() AS INTEGER, ByteCount AS LONG)
CONST BlockReadSize = 1024 ' must be a multiple of 2
IF BlockReadSize AND 1 THEN PRINT "BlockReadSize error.": SYSTEM 1 'ERROR 255
DIM i AS LONG
DIM BytesToRead AS LONG
'----------------------------------------------------------------------------
' REDIM the array if necessary, but keep the lower bound in place
'----------------------------------------------------------------------------
IF (UBOUND(Array) - LBOUND(Array)) * 2 < ByteCount THEN
REDIM Array(LBOUND(Array) TO LBOUND(Array) + (ByteCount + 1) \ 2) AS INTEGER
END IF
FOR i = 0 TO ByteCount - 1 STEP BlockReadSize
BytesToRead = ByteCount - i
IF BytesToRead > BlockReadSize THEN BytesToRead = BlockReadSize
Buffer$ = STRING$(BytesToRead, 0)
GET FileNumber, , Buffer$
'------------------------------------------------------------------------
' Copy data from string to integer array (even number of bytes only)
'------------------------------------------------------------------------
FOR j = 1 TO BytesToRead - 1 STEP 2
Index = LBOUND(Array) + i \ 2 + j \ 2
Array(Index) = CVI(MID$(Buffer$, j, 2))
NEXT j
'------------------------------------------------------------------------
' The final block may have had an odd number of bytes
'------------------------------------------------------------------------
IF BytesToRead AND 1 THEN
Index = LBOUND(Array) + i \ 2 + j \ 2
Array(Index) = ASC(RIGHT$(Buffer$, 1))
END IF
NEXT i
END SUB
'
' Reads a token into the globals PCODE and HPARAM. IP is updated to point
' To the next token, and DP points to the start of the token data.
'
FUNCTION ReadToken
DIM TokLen AS LONG
ReadToken = 0
IF IP < LBOUND(CODE) OR IP > UBOUND(CODE) THEN EXIT FUNCTION
'----------------------------------------------------------------------------
' Fetch basic token information
'----------------------------------------------------------------------------
TOKEN = MKI$(CODE(IP))
PCODE = CODE(IP) AND &H3FF
HPARAM = (CODE(IP) AND &HFC00&) \ 1024
ReadToken = -1
'----------------------------------------------------------------------------
' If the token is outside the known token range, we have a problem.
'----------------------------------------------------------------------------
IF PCODE < LBOUND(ParseRules) OR PCODE > UBOUND(ParseRules) THEN
IP = IP + 1
'PRINT "Bad token found.": SYSTEM 1 'ERROR QBErrBadToken
PCODE = 0: HPARAM = 0: TOKEN = MKI$(0)
EXIT FUNCTION
END IF
'----------------------------------------------------------------------------
' If the token has no information in the parse rules, then we clearly don't
' understand what it does, so increment IP and return. We will try to
' soldier on and parse the rest of the file
'----------------------------------------------------------------------------
IF ParseRules(PCODE) = "" THEN
AOutput "REM <20>QB45BIN<49> Unkown token - " + HEX$(PCODE)
IP = IP + 1
EXIT FUNCTION
END IF
'----------------------------------------------------------------------------
' Fetch the token data length from the parse rules to determine if the token
' is fixed or variable length
'----------------------------------------------------------------------------
IF PCODE >= LBOUND(ParseRules) AND PCODE <= UBOUND(ParseRules) THEN
IF LEN(ParseRules(PCODE)) > 2 THEN
TokLen = CVI(LEFT$(ParseRules(PCODE), 2)) AND &HFFFF&
END IF
END IF
'----------------------------------------------------------------------------
' If the token is variable length it will be followed by the size word, so
' read it now.
'----------------------------------------------------------------------------
IF TokLen = &HFFFF& THEN
IP = IP + 1
TokLen = CODE(IP) AND &HFFFF&
END IF
'----------------------------------------------------------------------------
' Read the token data into the TOKEN string. Note that due to a bug in QB64,
' we can not use IP as the control variable.
'----------------------------------------------------------------------------
FOR DP = IP + 1 TO IP + (TokLen + 1) \ 2
TOKEN = TOKEN + MKI$(CODE(DP))
NEXT DP
IP = DP
TOKEN = LEFT$(TOKEN, TokLen + 2)
END FUNCTION
SUB RestoreParseRules
'
' This is so I can change parse rules later if I add QB40 support.
'
RESTORE QB45TOKENS
END SUB
SUB SanityCheck (DP AS INTEGER)
DIM ThAddr AS LONG
ThAddr = FetchINTASLONG(DP)
IF ThAddr = &HFFFF& THEN EXIT SUB
ThAddr = ThAddr \ 2 - 1
IF ThAddr >= LBOUND(CODE) AND ThAddr <= UBOUND(CODE) - 1 THEN
IF (CODE(LBOUND(CODE) + ThAddr) AND &H1FF) = PCODE THEN EXIT SUB
END IF
'ERROR QBBinErrInsane
END SUB
FUNCTION SetHashedSymbol% (ParseRuleSymbol AS STRING, SymbolID AS INTEGER)
DIM LookupSymbol AS STRING
SymbolName$ = LTRIM$(RTRIM$(ParseRuleSymbol))
'----------------------------------------------------------------------------
' Parse rule symbols my be literal integers
'----------------------------------------------------------------------------
IF StringToINT(SymbolName$, SymbolID%) THEN EXIT FUNCTION
Hash = HashPJW(SymbolName$)
LookupSymbol = "[" + SymbolName$ + "]"
SymbolOffset = INSTR(SymbolHashTable(Hash), LookupSymbol)
IF SymbolOffset = 0 THEN
SymbolHashTable(Hash) = SymbolHashTable(Hash) + LookupSymbol + MKI$(SymbolID)
SetHashedSymbol = SymbolID
ELSE
SymbolOffset = SymbolOffset + LEN(LookupSymbol)
ID$ = MID$(SymbolHashTable(Hash), SymbolOffset, 2)
SetHashedSymbol = CVI(ID$)
END IF
'GetHashedSymbol% = SymbolID% + UBOUND(ParseRules) + 1
END FUNCTION
'
' Peeks at the ID of a stack item
'
FUNCTION StackPeek (OffsetSP)
StackPeek = -1
IF OffsetSP < 0 OR OffsetSP >= SP THEN EXIT FUNCTION
StackPeek = CVI(LEFT$(STACK(SP - OffsetSP), 2))
END FUNCTION
'
' STAG is a shortcut function for creating numeric stack tags dynamically
' such as {1}.
'
FUNCTION STAG$ (n)
STAG$ = "{" + LTRIM$(RTRIM$(STR$(n))) + "}"
END FUNCTION
'
' Parses a STRING into an INTEGER, returning 0 if the string contained
' any invalid characters (not including leading and trailing whitespace).
' Only positive integers are recognised (no negative numbers!).
'
' The actual numeric value is returned in OutVal
'
FUNCTION StringToINT (Txt AS STRING, OutVal AS INTEGER)
x$ = UCASE$(LTRIM$(RTRIM$(Txt)))
SignCharacter$ = LEFT$(x$, 1)
SignMultiplier = 1
IF (SignCharacter$ = "+" OR SignCharacter$ = "-") THEN
SignMultiplier = 45 - ASC(SignCharacter$)
x$ = MID$(x$, 2)
END IF
FoundBadDigit = LEN(x$) = 0
SELECT CASE LEFT$(x$, 2)
CASE "&H", "0X": nBase = 16: FirstDigitPos = 3
CASE "&O": nBase = 8: FirstDigitPos = 3
CASE ELSE: nBase = 10: FirstDigitPos = 1
END SELECT
IF nBase THEN
FOR i = FirstDigitPos TO LEN(x$)
Digit = ASC(MID$(x$, i, 1)) - 48
IF Digit > 16 THEN Digit = Digit - 7
IF Digit < 0 OR Digit >= nBase THEN FoundBadDigit = -1
IF NOT FoundBadDigit THEN
Value = Value * nBase
Value = Value + Digit
END IF
NEXT i
END IF
StringToINT = NOT FoundBadDigit
IF NOT FoundBadDigit THEN OutVal = Value * SignMultiplier
END FUNCTION
FUNCTION SubstTagCIRCLE$
DIM ParseRule AS STRING
ParseRule = "{?}, {?}, {?}, {?}, {?}, {?}"
ArgC = 0
ArgI = 0
'
' The last 3 arguments are optional.
'
FOR i = 0 TO 2
IF StackPeek(ArgC) = &H7E + i THEN
IF ArgI = 0 THEN ArgI = 28 - i * 5
MID$(ParseRule, 27 - i * 5, 1) = CHR$(ArgC + 48)
ArgC = ArgC + 1
END IF
NEXT i
' PCODE 0x09f means no colour argument
IF PCODE <> &H9F THEN
IF ArgI = 0 THEN ArgI = 13
MID$(ParseRule, 12, 1) = CHR$(ArgC + 48)
ArgC = ArgC + 1
END IF
' The last 3 arguments are required
IF ArgI = 0 THEN ArgI = 8
MID$(ParseRule, 7, 1) = CHR$(ArgC + 48): ArgC = ArgC + 1
MID$(ParseRule, 2, 1) = CHR$(ArgC + 48): ArgC = ArgC + 1
' Remove unused arguments
ParseRule = LEFT$(ParseRule, ArgI)
DO
ArgI = INSTR(ParseRule, "?")
IF ArgI <= 1 THEN EXIT DO
ParseRule = LEFT$(ParseRule, ArgI - 2) + MID$(ParseRule, ArgI + 2)
LOOP
SubstTagCIRCLE = ParseRule
END FUNCTION
'
' 0x01B : DEF(INT|LNG|SNG|DBL|STR) letterrange
'
' The DEFxxx token is followed by 6 bytes of data. The first two bytes give
' the absolute offset in the p-code to the correspdoning bytes of the next
' DEFxxx statement (!), or 0xFFFF if there are no more DEFxxx statements.
'
' Naturally, we can ignore these two bytes.
'
' The next 4 bytes form a 32-bit integer. The low 3 bits give the data-type
' for the DEFxxx. The upper 26 bits represent each letter or the alphabet,
' with A occupying the highest bit, and Z the lowest.
'
FUNCTION SubstTagDEFxxx$ (DefTypeArray() AS INTEGER)
DIM AlphaMask AS LONG
DIM OutTxt AS STRING
AlphaMask = FetchLNG(2)
DefType = LIMIT(AlphaMask AND 7, 0, 5)
OutTxt = "DEF" + GetTypeAbbr(DefType) + " "
' Shift the mask right once to avoid overflow problems.
AlphaMask = AlphaMask \ 2
InitialLetter = 0
AnythingOutput = 0
' We will loop one extra time to avoid code redendancy after the loop to
' clean up the Z. To ensure everything works out, we just need to make
' sure the bit after the Z is clear. We also need to clear the high 2 bits
' every time to avoid overflow ploblems.
FOR i = 0 TO 26
' Get the next bit and shift the mask
BITSET = (AlphaMask AND &H40000000) <> 0
AlphaMask = AlphaMask AND &H3FFFFFE0
AlphaMask = AlphaMask * 2
'------------------------------------------------------------------------
' Update current DEFtype state
'------------------------------------------------------------------------
IF i < 26 AND BITSET THEN DefTypeArray(i + 1) = DefType
IF BITSET AND InitialLetter = 0 THEN
InitialLetter = i + 65
ELSEIF InitialLetter AND NOT BITSET THEN
IF AnythingOutput THEN OutTxt = OutTxt + ", "
OutTxt = OutTxt + CHR$(InitialLetter)
Range = i + 65 - InitialLetter
IF Range > 1 THEN OutTxt = OutTxt + "-" + CHR$(i + 64)
AnythingOutput = -1
InitialLetter = 0
END IF
NEXT i
SubstTagDEFxxx$ = OutTxt
END FUNCTION
FUNCTION SubstTagINPUT$
CONST fPrompt = &H4
CONST fSemiColon = &H2
CONST fComma = &H1
Flags = ASC(MID$(TOKEN, 3, 1))
IF Flags AND fSemiColon THEN OutTxt$ = "; "
IF Flags AND fPrompt THEN
Tail = 59 - (((Flags AND fComma) = 1) AND 15)
OutTxt$ = OutTxt$ + "{$+0}" + CHR$(Tail)
END IF
SubstTagINPUT = OutTxt$
END FUNCTION
FUNCTION SubstTagKEY$
SELECT CASE CVI(MID$(TOKEN, 3, 2))
CASE 1: SubstTagKEY$ = "ON"
CASE 2: SubstTagKEY$ = "LIST"
CASE ELSE: SubstTagKEY$ = "OFF"
END SELECT
END FUNCTION
FUNCTION SubstTagLINE$
LineForm = PCODE - &HBB
SELECT CASE FetchINT(0) AND 3
CASE 1: BF$ = "B"
CASE 2: BF$ = "BF"
CASE ELSE: BF$ = ""
END SELECT
' 0x0bb : LINE x-x, ,[b[f]]
' 0x0bc : LINE x-x,n,[b[f]]
' 0x0bd : LINE x-x,n,[b[f]],n
' 0x0be : LINE x-x, ,[b[f]],n
IF BF$ <> "" THEN
SELECT CASE LineForm
CASE 0: Rule$ = "{0}, , " + BF$
CASE 1: Rule$ = "{1}, {0}, " + BF$
CASE 2: Rule$ = "{2}, {1}, " + BF$ + ", {0}"
CASE 3: Rule$ = "{1}, , " + BF$ + ", {0}"
END SELECT
ELSE
SELECT CASE LineForm
CASE 0: Rule$ = "{0}"
CASE 1: Rule$ = "{1}, {0}"
CASE 2: Rule$ = "{2}, {1}, , {0}"
CASE 3: Rule$ = "{1}, , , {0}"
END SELECT
END IF
SubstTagLINE = Rule$
END FUNCTION
FUNCTION SubstTagLOCK$
DIM Flags AS LONG
Flags = FetchINTASLONG(0) AND &HFFFF&
IF (Flags AND 2) = 0 THEN
SubstTagLOCK$ = "{0}"
ELSE
' check high 2 bits
SELECT CASE Flags \ &H4000
CASE 0: SubstTagLOCK$ = "{2}, {1} TO {0}"
CASE 1: SubstTagLOCK$ = "{2}, TO {0}"
CASE 2: SubstTagLOCK$ = "{1}, {0}"
END SELECT
END IF
END FUNCTION
FUNCTION SubstTagOPEN$
DIM ModeFlags AS LONG
DIM ForMode AS STRING
DIM AccessMode AS STRING
DIM LockMode AS STRING
DIM OutTxt AS STRING
ModeFlags = FetchINT(0) AND &HFFFF&
SELECT CASE ModeFlags AND &H3F
CASE &H1: ForMode = "FOR INPUT"
CASE &H2: ForMode = "FOR OUTPUT"
CASE &H4: ForMode = "FOR RANDOM"
CASE &H8: ForMode = "FOR APPEND"
CASE &H20: ForMode = "FOR BINARY"
END SELECT
SELECT CASE ModeFlags \ 256 AND 3
CASE 1: AccessMode = "ACCESS READ"
CASE 2: AccessMode = "ACCESS WRITE"
CASE 3: AccessMode = "ACCESS READ WRITE"
END SELECT
SELECT CASE ModeFlags \ &H1000 AND &H7
CASE 1: LockMode = "LOCK READ WRITE"
CASE 2: LockMode = "LOCK WRITE"
CASE 3: LockMode = "LOCK READ"
CASE 4: LockMode = "SHARED"
END SELECT
OutTxt = ForMode
IF (OutTxt <> "" AND AccessMode <> "") THEN OutTxt = OutTxt + " "
OutTxt = OutTxt + AccessMode
IF (OutTxt <> "" AND LockMode <> "") THEN OutTxt = OutTxt + " "
OutTxt = OutTxt + LockMode
SubstTagOPEN = OutTxt
END FUNCTION
FUNCTION SubstTagVERB$
Verbs$ = "0OR|1AND|2PRESET|3PSET|4XOR|"
VerbBegin = INSTR(Verbs$, CHR$(48 + LIMIT(FetchINT(0), 0, 4))) + 1
VerbEnd = INSTR(VerbBegin, Verbs$, "|")
SubstTagVERB$ = MID$(Verbs$, VerbBegin, VerbEnd - VerbBegin)
END FUNCTION
'
' Splits a {ruletag} into it's constituent components.
'
FUNCTION TokenizeTag (TagTxt AS STRING, TagParam AS INTEGER)
DIM ParamTxt AS STRING
Delimiter = INSTR(TagTxt, ":")
ParamTxt = LTRIM$(MID$(TagTxt, Delimiter + 1))
IF LEFT$(ParamTxt, 1) = "$" THEN
TokenizeTag = TagType.StackREL
IF NOT StringToINT(MID$(ParamTxt, 2), TagParam) THEN
Delimiter = LEN(TagTxt) + 1
TagParam = 0
END IF
ELSE
TokenizeTag = TagType.StackABS
IF NOT StringToINT(MID$(ParamTxt, 1), TagParam) THEN
Delimiter = LEN(TagTxt) + 1
TagParam = 0
END IF
END IF
IF Delimiter THEN Delimiter = Delimiter - 1
TagTxt = LTRIM$(RTRIM$(LEFT$(TagTxt, Delimiter)))
IF LEFT$(TagTxt, 2) = "##" THEN
TokenizeTag = TagType.Recursive
TagTxt = MID$(TagTxt, 3)
ELSEIF LEFT$(TagTxt, 1) = "#" THEN
TokenizeTag = TagType.TokenData
TagTxt = MID$(TagTxt, 2)
END IF
END FUNCTION
FUNCTION ValidateStackTag (RuleID AS INTEGER, TagTxt AS STRING, OffsetSP AS INTEGER)
DIM RuleSymbol AS STRING
'------------------------------------------------------------------------
' If the specified stack offset is invalid, only the null tag will do.
'------------------------------------------------------------------------
IF (OffsetSP < 0 OR OffsetSP >= SP) THEN
ValidateStackTag = (TagTxt = "")
EXIT FUNCTION
END IF
TagLen = LEN(TagTxt)
TagOffset = 1
DO WHILE TagOffset <= TagLen
Delimiter = INSTR(TagOffset, TagTxt, "|")
IF Delimiter = 0 THEN Delimiter = TagLen + 1
RuleSymbol = MID$(TagTxt, TagOffset, Delimiter - TagOffset)
RuleSymbol = LTRIM$(RTRIM$(RuleSymbol))
IF NOT StringToINT(RuleSymbol, RuleSymbolID) THEN
RuleSymbolID = GetHashedSymbol(RuleSymbol)
END IF
IF RuleSymbol = "*" THEN EXIT DO
IF RuleSymbol = "self" THEN RuleSymbolID = RuleID
IF StackPeek(OffsetSP) = RuleSymbolID THEN EXIT DO
TagOffset = Delimiter + 1
LOOP
ValidateStackTag = NOT (TagLen AND TagOffset > TagLen)
IF TagLen AND TagOffset > TagLen THEN
ValidateStackTag = 0
ELSE
ValidateStackTag = -1
END IF
END FUNCTION