Author Topic: Extended syntax highlighting added  (Read 6025 times)

0 Members and 1 Guest are viewing this topic.

Offline odin

  • Administrator
  • Newbie
  • Posts: 92
  • I am.
    • View Profile
Extended syntax highlighting added
« on: April 27, 2019, 08:01:37 pm »
To post code on this forum, one should use the [code] tag:

    [code]
    PRINT "my code"
    [/code]


Which results in:
Code: [Select]
PRINT "my code"
Beginning today we have added extended syntax highlighting, which can be used by modifying the code tag as follows:

    [code=qb64]
    PRINT "my code"
    [/code]


Which results in:
Code: QB64: [Select]
  1. PRINT "my code"

That gives your code syntax highlighting, line numbers and the keywords will even link to the wiki, so they can be clicked for reference.

The only drawback is that there's no easy way to "[Select]" code. No more drawbacks. See below.

Example of code box with a larger piece of code:

Code: QB64: [Select]
  1. 'QB45BIN.BAS - written by qarnos
  2. 'Used by permission: http://www.[abandoned, outdated and now likely malicious qb64 dot net website - don’t go there]/forum/index.php?topic=1771.msg16215#msg16215
  3. 'Command line interface adapted by FellippeHeitor
  4.  
  5.  
  6. DEFINT A-Z
  7. '----------------------------------------------------------------------------
  8. ' Used for sorting alphabetically.
  9. '----------------------------------------------------------------------------
  10. DIM SHARED QBBinProcedureIndex AS STRING
  11.  
  12. '----------------------------------------------------------------------------
  13. ' Internal constants used by parse rule decoder
  14. '----------------------------------------------------------------------------
  15. CONST TagType.Recursive = 1
  16. CONST TagType.TokenData = 2
  17. CONST TagType.StackABS = 3
  18. CONST TagType.StackREL = 4
  19.  
  20. '----------------------------------------------------------------------------
  21. ' Constants returned by the Meta field of QBBinReadLine. I will probably
  22. ' use the high 16-bits for flags, so best to mask them out for now.
  23. '----------------------------------------------------------------------------
  24. CONST QBBinMeta.SUB = 1
  25. CONST QBBinMeta.FUNCTION = 2
  26.  
  27. '----------------------------------------------------------------------------
  28. ' Not yet used since it only supports QB45 atm.
  29. '----------------------------------------------------------------------------
  30. CONST QBBinFileMode.QB45 = 1
  31.  
  32. '----------------------------------------------------------------------------
  33. ' Option variable declarations
  34. '----------------------------------------------------------------------------
  35. DIM SHARED QBBinOption.OmitIncludedLines AS INTEGER
  36. DIM SHARED QBBinOption.SortProceduresAZ AS INTEGER
  37.  
  38. '----------------------------------------------------------------------------
  39. ' Option variable initialisation
  40. '----------------------------------------------------------------------------
  41. QBBinOption.OmitIncludedLines = -1
  42. QBBinOption.SortProceduresAZ = -1
  43.  
  44. '----------------------------------------------------------------------------
  45. ' Errors only half-implemented so far.
  46. '----------------------------------------------------------------------------
  47. CONST QBErrBadFormat = 255
  48. CONST QBErrBadToken = 254
  49. CONST QBErrInsane = 253
  50.  
  51. '----------------------------------------------------------------------------
  52. ' You may use QBBinEOF, for now, to determine when EOF has been reached.
  53. ' QBBinDefType contains the current DEFxxx setting for each letter of the
  54. ' alphabet (1 = INT, 2 = LNG, 3 = SNG, 4 = DBL, 5 = STR).
  55. '----------------------------------------------------------------------------
  56. DIM SHARED QBBinDefType(1 TO 26) AS INTEGER
  57. DIM SHARED QBBinLineReady AS INTEGER ' get rid of this
  58. DIM SHARED QBBinProgramLine AS STRING ' and this
  59. DIM SHARED QBBinFile AS INTEGER
  60. DIM SHARED QBBinEOF AS INTEGER
  61.  
  62. '----------------------------------------------------------------------------
  63. ' A hash table is used for symbols defined in the parse rules. There aren't
  64. ' many of them, so a small table will do.
  65. '----------------------------------------------------------------------------
  66. CONST SymbolHashBuckets = 43
  67. DIM SHARED SymbolHashTable(0 TO SymbolHashBuckets - 1) AS STRING
  68. DIM SHARED SymbolHashEntries AS INTEGER
  69.  
  70. '----------------------------------------------------------------------------
  71. ' Not worth commenting on... oops.
  72. '----------------------------------------------------------------------------
  73. DIM SHARED TypeSpecifiers(0 TO 5, 1 TO 3) AS STRING
  74. DIM SHARED ParseRules(0) AS STRING
  75.  
  76. '----------------------------------------------------------------------------
  77. ' We don't need a very big stack. I haven't seen it go beyond 8 or 9 entries
  78. ' so 255 is plenty. Also, STACK(0) is a special entry. IF SP = 0 then there
  79. ' is nothing on the stack.
  80. '----------------------------------------------------------------------------
  81. DIM SHARED STACK(0 TO 255) AS STRING
  82.  
  83. '----------------------------------------------------------------------------
  84. ' Define global symbol table, code space and instruction pointer
  85. '----------------------------------------------------------------------------
  86. DIM SHARED SYMTBL(0) AS INTEGER
  87.  
  88. '----------------------------------------------------------------------------
  89. ' PCODE always contains the ID of the current token (the low 10 bits of the
  90. '       input word.
  91. '
  92. ' HPARAM contains the high 6 bits of the input word and is used by some
  93. '        tokens. IE: Identifiers use it for the type suffix and integers
  94. '        smaller than 10 are encoded this way.
  95. '
  96. ' TOKEN is a string containing the binary data for the current token (PCODE
  97. '       and HPARAM in the first word, the rest of the data follows). All the
  98. '       FetchXXX functions work on this variable
  99. '----------------------------------------------------------------------------
  100.  
  101. '----------------------------------------------------------------------------
  102. ' LastProcType is just a hack to keep track of the current SUB or FUNCTION
  103. ' status since END SUB and END FUNCTION share the same token.
  104. '----------------------------------------------------------------------------
  105. DIM SHARED LastProcType AS STRING ' Current procedure type
  106. DIM SHARED QBTxtFile AS INTEGER
  107.  
  108. '----------------------------------------------------------------------------
  109. ' These variables contain the current prodecure name and type the parser
  110. ' is decoding.
  111. '
  112. ' QBBinProcedureType = MAIN | SUB | FUNCTON | DEF
  113. '----------------------------------------------------------------------------
  114. DIM SHARED QBBinProcedureName AS STRING
  115. DIM SHARED QBBinProcedureType AS STRING
  116.  
  117.  
  118. '----------------------------------------------------------------------------
  119. ' Variables used to store common token codes referenced in the code. Faster
  120. ' than doing GetHashedSymbol("tokenname") every time, and flexible since the
  121. ' QB40 token codes are different from QB45.
  122. '----------------------------------------------------------------------------
  123. DIM SHARED QBBinTok.SUBDEF AS INTEGER
  124. DIM SHARED QBBinTok.FUNCDEF AS INTEGER
  125. DIM SHARED QBBinTok.DEFTYPE AS INTEGER
  126.  
  127. DIM SHARED OutputContents$
  128.  
  129.  
  130. '----------------------------------------------------------------------------
  131. ' Initialisation will eventually be automatic in QBBinOpenFile
  132. '----------------------------------------------------------------------------
  133. RESTORE TSPECS
  134. FOR i = 0 TO 17: READ TypeSpecifiers(i \ 3, i MOD 3 + 1): NEXT i
  135.  
  136. '----------------------------------------------------------------------------
  137. ' Get file names, etc.
  138. '----------------------------------------------------------------------------
  139. 'ON ERROR GOTO ErrorHandler
  140.  
  141. GetInputFileName:
  142.  
  143.     PRINT "QB45BIN"
  144.     PRINT
  145.     PRINT "Conversion utility from QuickBASIC 4.5 binary to plain text."
  146.     PRINT "by qarnos"
  147.     PRINT
  148.     PRINT "    Syntax: QB45BIN <source.bas> [-o output.bas]"
  149.     PRINT
  150.     PRINT "If no output is specified, a backup file is saved and the original"
  151.     PRINT "file is overwritten."
  152.     PRINT
  153.     SYSTEM 1
  154.  
  155.     IF INSTR(InputFile$, ".") = 0 THEN InputFile$ = InputFile$ + ".BAS"
  156.  
  157.     PRINT "File not found: "; COMMAND$(1)
  158.     SYSTEM 1
  159.     InputFile$ = COMMAND$(1)
  160.  
  161. IF LCASE$(COMMAND$(2)) = "-o" THEN
  162.     IF LEN(COMMAND$(3)) THEN
  163.         OutputFile$ = COMMAND$(3)
  164.     END IF
  165.  
  166. IF OutputFile$ = "" THEN
  167.     IF INSTR(InputFile$, "\") > 0 OR INSTR(InputFile$, "/") > 0 THEN
  168.         FOR i = LEN(InputFile$) TO 1 STEP -1
  169.             IF MID$(InputFile$, i, 1) = "/" OR MID$(InputFile$, i, 1) = "\" THEN
  170.                 path$ = LEFT$(InputFile$, i)
  171.                 InputFile$ = MID$(InputFile$, i + 1)
  172.                 EXIT FOR
  173.             END IF
  174.         NEXT
  175.     END IF
  176.     OutputFile$ = path$ + InputFile$ + ".converted.bas"
  177.  
  178. PRINT UCASE$(InputFile$)
  179.  
  180. PRINT "Loading parse rules... ";
  181. LoadParseRules
  182. PRINT "Done!": PRINT
  183.  
  184. QBBinOpenFile path$ + InputFile$
  185.  
  186. '---------------------------------------------------------------------------
  187. ' The main loop is pretty straight-forward these days.
  188. '---------------------------------------------------------------------------
  189. StartProcessing! = TIMER
  190. DO WHILE NOT QBBinEOF
  191.  
  192.     ProgramLine$ = QBBinReadLine$(Meta&)
  193.  
  194.     '-----------------------------------------------------------------------
  195.     ' Just an example of meta-data usage. Pretty limited at the moment,
  196.     ' but could be helpful to QB64 IDE when building SUB/FUNCTION list.
  197.     '-----------------------------------------------------------------------
  198.     'IF Meta& = QBBinMeta.SUB THEN PRINT "----- SUBROUTINE -----"
  199.     'IF Meta& = QBBinMeta.FUNCTION THEN PRINT "----- FUNCTION -----"
  200.  
  201.     '-----------------------------------------------------------------------
  202.     ' AOutput has become a pretty-print function. All program lines are now
  203.     ' retrieved by calling QBBinReadLine.
  204.     '-----------------------------------------------------------------------
  205.     AOutput ProgramLine$
  206.  
  207.     'Quit after a number of seconds - likely an invalid file causing an endless loop
  208.     CONST TIMEOUT = 30
  209.     IF StartProcessing! > TIMER THEN StartProcessing! = StartProcessing! - 86400
  210.     IF TIMER - StartProcessing! > TIMEOUT THEN PRINT "Conversion failed.": SYSTEM 1
  211.  
  212.  
  213. 'If we've made it this far, output the resulting file:
  214. QBTxtFile = FREEFILE
  215. OPEN OutputFile$ FOR BINARY AS #QBTxtFile
  216. PUT #QBTxtFile, 1, OutputContents$
  217. CLOSE #QBTxtFile
  218.  
  219.  
  220. PRINT "Finished!"
  221.  
  222.  
  223. TSPECS:
  224. DATA ANY,,
  225. DATA LONG,LNG,&
  226. DATA SINGLE,SNG,!
  227. DATA DOUBLE,DBL,#
  228. DATA STRING,STR,$
  229.  
  230.  
  231. QB45TOKENS:
  232. '
  233. ' Most of the tokens for QB45 are defined here, along with the length of the
  234. ' token (or '*' for variable length) and some parse rules.
  235. '
  236. ' The first column determined the PCODE (the low 10 bits of the token)
  237. ' which the rule responds to. This is followed by the length of the token
  238. ' *data*, which may be omitted if the token has no data, or an asterisk to
  239. ' indicate a variable length token. Variable length tokens are always
  240. ' followed by a word indicating the length of the token.
  241. '
  242. ' The final column is the parse rule itself. A token may have multiple
  243. ' parse rules. Multiple parse rules may be specified on a seperate line
  244. ' (without a PCODE or LENGTH field), or seperated by a pipe ('|') symbol.
  245. '
  246. ' There is one important difference between the two methods. Some rules
  247. ' define a symbol which can be used to reference the rule, such as:
  248. '
  249. '   declmod::=SHARED
  250. '
  251. ' If a pipe symbol is used, the next rule will inherit the "declmod" (or
  252. ' whatever symbol), unless it exlicitly defines it's own. Rules defined
  253. ' on seperate lines use the default symbol which, initially, is nothing, but
  254. ' may be overridden using the ".default" directive. This is only really used
  255. ' in the second half of the rule list, where almost every token is an
  256. ' expression ('expr').
  257. '
  258. ' Rules are matched on a first-come first-served basis. The first rule which
  259. ' can be successfully applied (see below) is accepted.
  260. '
  261. ' The rules can have {tags} embedded in them. There are basically two types
  262. ' of tags - stack and data/format tags. I will discuss them briefly here:
  263. '
  264. ' STACK tags can take these basic forms:
  265. '
  266. '  {1}
  267. '  {*:1}
  268. '  {rulename:1}
  269. '  {$+1}
  270. '  {$-1}
  271. '  {rulename:$+1}
  272. '
  273. ' The first type will be substituded for the text located 1 item from the
  274. ' top of the parse stack. If the stack isn't that deep, it will be replaced
  275. ' with the null string.
  276. '
  277. ' The second type is just like the first, except the rule will be rejected
  278. ' if the stack item doesn't exist.
  279. '
  280. ' The third type will only accept a rule if the stack item at the specified
  281. ' offset is of the correct rule type. So {declmod:1} will reject the rule
  282. ' if the stack entry at offset 1 is not a "declemod". There is also a special
  283. ' rule name, "self", which always refers to the current rule.
  284. '
  285. ' The final three forms, use the '$' symbol. This symbol refers to a
  286. ' "relative" stack offset - an offset from the deepest stack item referenced
  287. ' in a normal tag. This is really a bit of a hack, due to me trying to avoid
  288. ' writing a full LALR(1) parser! This feature is rarely used.
  289. '
  290. ' DATA/FORMAT tags
  291. '
  292. ' Data and format tags being with a '#', such as {#id:2}. These tags are used
  293. ' either to interpret data from the token or to generate a dynamic parse
  294. ' rule (another hack).
  295. '
  296. ' In the case of data tokens, the number refers to the offset into the token
  297. ' data on which the tag is to work.
  298. '
  299. ' Format tokens usually have two '#' symbols, such as {##id(decl)}. The
  300. ' extra '#' causes the parser to re-scan the tag for other tags once it
  301. ' has been subsituted, allowing these tags to generate stack tags which can
  302. ' then be parsed.
  303. '
  304. ' See the function GetTaggedItem for a list of tag names which can be used.
  305. '
  306. '
  307. '
  308.  
  309.  
  310. REM     Token   Length  Rule(s)
  311. REM     -------+-------+-------
  312.  
  313. DATA 0x000,"newline::=.{#newline}{#tabh}"
  314. DATA 0x001,2,"newline::=.{#newline}{#tabi}"
  315. DATA 0x002,2,"newline::=.{#newline-include}"
  316. DATA 0x003,4,"newline::=.{#newline-include}{#indent:2} "
  317. DATA 0x004,4,".{#newline}{#thaddr:0}{#label:2}"
  318. DATA 0x005,6,".{#newline}{#thaddr:0}{#label:2} {#indent:4}"
  319. DATA 0x006,": "
  320. DATA 0x007,2,":{#tabi}"
  321.  
  322. '----------------------------------------------------------------------------
  323. ' 0x008 = End of procedure/module code (watch list follows)
  324. ' 0x009 = End of watch list
  325. '----------------------------------------------------------------------------
  326. DATA 0x008,"."
  327. DATA 0x009,
  328.  
  329. DATA 0x00a,*,"{#raw:2}"
  330. DATA 0x00b,2,"expr::={#id+}"
  331. DATA 0x00c,2,"consts::={const:1} {#id+} = {0}"
  332. DATA "consts::={consts:1}, {#id+} = {0}"
  333. DATA "{#id+} = {0}"
  334. DATA 0x00d,2,"decls::={decls:1}, {#id+:0} {astype:0}"
  335. DATA "decls::={decls:0}, {#id+:0}"
  336. DATA "decls::={decl:1} {#id+:0} {astype:0}"
  337. DATA "decls::={decl:0} {#id+:0}"
  338. DATA "{#id+:0} {astype:0}"
  339. DATA "{#id+:0}"
  340. DATA 0x00e,4,"expr::={##id(expr)}"
  341. DATA 0x00f,4,"{##id(expr)} = {$+0}"
  342. DATA 0x010,4,"decls::={##id(decl)}"
  343. DATA 0x011,2,"expr::={0}.{#id}"
  344. DATA 0x012,2,"{0}.{#id} = {1}"
  345.  
  346. ' 0x015 = AS USERTYPE
  347. ' 0x016 = AS BUILTINTYPE?
  348. DATA 0x015,4,"astype::={#tabi:2}AS {#type:0}"
  349. DATA 0x016,4,"astype::={#tabi:2}AS {#type:0}"
  350.  
  351. ' 0x017 - used for unkown type assignments?
  352. DATA 0x017,0,""
  353.  
  354. DATA 0x018,""
  355.  
  356. '----------------------------------------------------------------------------
  357. ' 0x019 = user-type field declaration.
  358. '----------------------------------------------------------------------------
  359. DATA 0x019,2,"{#id}"
  360. DATA 0x01a,"declmod::=SHARED"
  361. DATA 0x01b,6,"deftype::={#thaddr:0}{#DEFxxx}"
  362. DATA 0x01c,"{self:1}, {0}|REDIM {declmod:1} {0}|REDIM {0}"
  363. DATA 0x01d,2,"END TYPE"
  364. DATA 0x01e,2,"decl::=SHARED"
  365. DATA 0x01f,2,"decl::=STATIC"
  366. DATA 0x020,4,"TYPE {#id:2}"
  367. DATA 0x021,*,"$STATIC{#raw}"
  368. DATA 0x022,*,"$DYNAMIC{#raw}"
  369. DATA 0x023,"const::=CONST"
  370.  
  371. '----------------------------------------------------------------------------
  372. ' 0x024 = IDE breakpoint
  373. '----------------------------------------------------------------------------
  374. DATA 0x024,
  375.  
  376. DATA 0x025,"BYVAL {0}"
  377. DATA 0x026,*,"{deffn:1} = {0}"
  378. DATA 0x027,"COM({0})"
  379. DATA 0x028,2,"ON {0} GOSUB {#id}"
  380. DATA 0x029,"KEY({0})"
  381. DATA 0x02a,"{0} OFF"
  382. DATA 0x02b,"{0} ON"
  383. DATA 0x02c,"{0} STOP"
  384. DATA 0x02d,"PEN"
  385. DATA 0x02e,"PLAY"
  386. DATA 0x02f,"PLAY({0})"
  387. DATA 0x030,"SIGNAL({0})"
  388. DATA 0x031,"STRIG({0})"
  389. DATA 0x032,"TIMER"
  390. DATA 0x033,"TIMER({0})"
  391.  
  392. '----------------------------------------------------------------------------
  393. ' Labels used in $INCLUDEd lines
  394. '----------------------------------------------------------------------------
  395. DATA 0x034,4,"newline::={#thaddr:0}{#label:2} "
  396. DATA 0x035,6,"newline::={#thaddr:0}{#label:2} {#indent:4}"
  397.  
  398. DATA 0x037,4,"CALL {#id:2}{##call()}"
  399. DATA 0x038,4,"{#id:2}{##call}"
  400. DATA 0x039,4,"CALLS {#id:2}{##call()}"
  401. DATA 0x03a,"CASE ELSE"
  402. DATA 0x03b,"case::={case:1}, {0}|CASE {0}"
  403. DATA 0x03c,"case::={case:2}, {1} TO {0}|CASE {1} TO {0}"
  404. DATA 0x03d,"case::={case:1}, IS = {0}|CASE IS = {0}"
  405. DATA 0x03e,"case::={case:1}, IS < {0}|CASE IS < {0}"
  406. DATA 0x03f,"case::={case:1}, IS > {0}|CASE IS > {0}"
  407. DATA 0x040,"case::={case:1}, IS <= {0}|CASE IS <= {0}"
  408. DATA 0x041,"case::={case:1}, IS >= {0}|CASE IS >= {0}"
  409. DATA 0x042,"case::={case:1}, IS <> {0}|CASE IS <> {0}"
  410.  
  411. DATA 0x043,"ON"
  412. DATA 0x044,*,"DECLARE {#procdecl()}"
  413. DATA 0x045,*,"deffn::={#procdecl:2}"
  414. DATA 0x046,"DO"
  415. DATA 0x047,"DO UNTIL {0}"
  416. DATA 0x048,2,"DO WHILE {0}"
  417. DATA 0x049,2,"{newline:0}ELSE| ELSE "
  418.  
  419. ' 0x04a = implicit GOTO linenumber used in 0x04c ELSE
  420. DATA 0x04a,2,"{#id}"
  421. DATA 0x04c," ELSE "
  422.  
  423. DATA 0x04d,2,"ELSEIF {0} THEN"
  424. DATA 0x04e,"END"
  425. DATA 0x04f,*,"END DEF"
  426. DATA 0x050,"END IF"
  427. DATA 0x051,"END {#proctype}"
  428. DATA 0x052,"END SELECT"
  429. DATA 0x053,2,"EXIT DO"
  430. DATA 0x054,2,"EXIT FOR"
  431. DATA 0x055,2,"EXIT {#proctype}"
  432. DATA 0x056,4,"FOR {2} = {1} TO {0}"
  433. DATA 0x057,4,"FOR {3} = {2} TO {1} STEP {0}"
  434. DATA 0x058,*,"funcdef::={#procdecl}"
  435. DATA 0x059,2,"GOSUB {#id}"
  436. '       0x05a   2,      "GOSUB {#id}"
  437. DATA 0x05b,2,"GOTO {#id}"
  438. '       0x05c   2,      "GOTO {#id}"
  439. DATA 0x05d,2,"IF {0} THEN "
  440. DATA 0x05e,2,"IF {0} THEN {#id}"
  441. '       0x05f,  2,      "IF {0} THEN "
  442. DATA 0x060,2,"IF {0} GOTO {#id}"
  443. DATA 0x061,2,"IF {0} THEN"
  444. DATA 0x062,2,"LOOP"
  445. DATA 0x063,2,"LOOP UNTIL {0}"
  446. DATA 0x064,2,"LOOP WHILE {0}"
  447. DATA 0x065,4,"NEXT"
  448. DATA 0x066,4,"{self:1}, {0}|NEXT {0}"
  449. DATA 0x067,2,"ON ERROR GOTO {#id}"
  450. DATA 0x068,*,"ON {0} GOSUB {#id-list}"
  451. DATA 0x069,*,"ON {0} GOTO {#id-list}"
  452. DATA 0x06a,"RESTORE"
  453. DATA 0x06b,2,"RESTORE {#id}"
  454. DATA 0x06c,"RESUME"
  455. DATA 0x06d,2,"RESUME {#id}"
  456. DATA 0x06e,"RESUME NEXT"
  457. DATA 0x06f,"RETURN"
  458. DATA 0x070,2,"RETURN {#id}"
  459. DATA 0x071,"RUN {0}"
  460. DATA 0x072,2,"RUN {#id}"
  461. DATA 0x073,"RUN"
  462. DATA 0x074,2,"SELECT CASE {0}"
  463. DATA 0x075,2,"STOP"
  464. DATA 0x076,*,"subdef::={#procdecl}"
  465. DATA 0x077,"WAIT {1}, {0}"
  466. DATA 0x078,"WAIT {2}, {1}, {0}"
  467. DATA 0x079,2,"WEND"
  468. DATA 0x07a,2,"WHILE {0}"
  469.  
  470. '----------------------------------------------------------------------------
  471. ' 0x07b used in IDE watch mode. Probably 0x07c, too.
  472. '----------------------------------------------------------------------------
  473. DATA 0x07b,
  474. DATA 0x07c,
  475.  
  476. DATA 0x07d,"prnmod::={prnmod:1} {0},|PRINT {0},"
  477.  
  478. '----------------------------------------------------------------------------
  479. ' 3 dummy tokens used in LINE statements
  480. '----------------------------------------------------------------------------
  481. DATA 0x07e,"{0}"
  482. DATA 0x07f,"{0}"
  483. DATA 0x080,"{0}"
  484.  
  485. '----------------------------------------------------------------------------
  486. ' graphics co-ordinates
  487. '----------------------------------------------------------------------------
  488. DATA 0x081,"1st-coord::=({1}, {0})"
  489. DATA 0x082,"1st-coord::=STEP({1}, {0})"
  490. DATA 0x083,"{1st-coord:2}-({1}, {0})|({1}, {0})"
  491. DATA 0x084,"{1st-coord:2}-STEP({1}, {0})|-STEP({1}, {0})"
  492.  
  493. DATA 0x085,"FIELD {0}"
  494. DATA 0x086,", {1} AS {0}"
  495. DATA 0x087,"finput::=INPUT {0},"
  496. DATA 0x088,"{input:1} {inputs:0}"
  497. DATA 0x089,*,"input::=INPUT {##input-args}"
  498. DATA 0x08a,"#{0}"
  499.  
  500. '----------------------------------------------------------------------------
  501. ' These two consume data, but I have no idea what they do. I haven't seen
  502. ' one in the wild.
  503. '----------------------------------------------------------------------------
  504. DATA 0x08c,2,""
  505. '       0x08d,  4,      ""
  506.  
  507. '----------------------------------------------------------------------------
  508. ' Most of the PRINT stuff is here. The rules are pretty finicky. These
  509. ' sequences also apply to LPRINT and WRITE.
  510. '----------------------------------------------------------------------------
  511. DATA 0x08f,"prnsmc::={self|prncma|prnsrl:1} SPC({0});"
  512. DATA "prnsmc::=SPC({0});"
  513. DATA 0x090,"prnsmc::={self|prncma|prnsrl:1} TAB({0});"
  514. DATA "prnsmc::=TAB({0});"
  515.  
  516. DATA 0x091,"prncma::={self|prnsmc|prnsrl:0} ,|,"
  517.  
  518. DATA 0x092,"prnsmc::={self:0}|{prncma|prnsrl:0} ;|;"
  519.  
  520. DATA 0x093,"{prnmod:2} {prnuse:1} {prnsrl|prnsmc|prncma:0}"
  521. DATA "{prnmod:1} {prnsrl|prnsmc|prncma:0}"
  522. DATA "{prnmod:1} {prnuse:0}"
  523. DATA "{prnmod:1}"
  524. DATA "PRINT {prnuse:1} {prnsrl|prnsmc|prncma:0}"
  525. DATA "PRINT {prnsrl|prnsmc|prncma:0}"
  526. DATA "PRINT {prnuse:0}"
  527. DATA "PRINT"
  528.  
  529. DATA 0x094,"prnsrl::={prncma|prnsmc|self:1} {expr:0},|{expr:0},"
  530. DATA 0x095,"prnsrl::={prncma|prnsmc|self:1} {expr:0};|{expr:0};"
  531.  
  532. DATA 0x096,"{prnmod:3} {prnuse:2} {prnsmc|prncma|prnsrl:1} {expr:0}"
  533. DATA "{prnmod:2} {prnsmc|prncma|prnsrl:1} {expr:0}"
  534. DATA "{prnmod:1} {prnsmc|prncma|prnsrl|expr:0}"
  535. DATA "PRINT {prnuse:2} {prnsmc|prncma|prnsrl:1} {expr:0}"
  536. DATA "PRINT {prnsmc|prncma|prnsrl:1} {expr:0}"
  537. DATA "PRINT {prnsmc|prncma|prnsrl|expr:0}"
  538.  
  539.  
  540. DATA 0x097,*,"{#tabi:0}'{#raw:2}"
  541. '       0x098           nothing?
  542. DATA 0x099,*,"$INCLUDE: '{#raw:0}"
  543. DATA 0x09a,"BEEP"
  544. DATA 0x09b,"BLOAD {0}"
  545. DATA 0x09c,"BLOAD {1}, {0}"
  546. DATA 0x09d,"BSAVE {2}, {1}, {0}"
  547. DATA 0x09e,"CHDIR {0}"
  548. DATA 0x09f,"CIRCLE {##circle-args}"
  549. DATA 0x0a0,"CIRCLE {##circle-args}"
  550. DATA 0x0a1,2,"CLEAR{##varargs}"
  551. DATA 0x0a2,2,"CLOSE{##varargs}"
  552. DATA 0x0a3,"CLS {expr:0}|CLS"
  553. DATA 0x0a4,2,"COLOR{##varargs}"
  554.  
  555. DATA 0x0a5,4,"decl::=COMMON {declmod:0}{#blockname:2}"
  556. DATA "decl::=COMMON{#blockname:2}"
  557.  
  558. DATA 0x0a6,*,"DATA{#cstr:2}"
  559. DATA 0x0a7,"DATE$ = {0}"
  560. DATA 0x0a8,"DEF SEG"
  561. DATA 0x0a9,"DEF SEG = {0}"
  562.  
  563. DATA 0x0aa,"DRAW {0}"
  564. DATA 0x0ab,"ENVIRON {0}"
  565. DATA 0x0ac,2,"ERASE{##varargs}"
  566. DATA 0x0ad,"ERROR {0}"
  567. DATA 0x0ae,"FILES"
  568. DATA 0x0af,"FILES {0}"
  569.  
  570. DATA 0x0b0,"GET {0}"
  571. DATA 0x0b1,"GET {1}, {0}"
  572. DATA 0x0b2,2,"GET {1}, , {0}"
  573. DATA 0x0b3,2,"GET {2}, {1}, {0}"
  574. DATA 0x0b4,"GET {1}, {0}"
  575. DATA 0x0b5,2,"PUT {1}, {0}, {#action-verb}"
  576.  
  577.  
  578. DATA 0x0b6,"inputs::={inputs:1}, {0}|{0}"
  579. DATA 0x0b7,"IOCTL {1}, {0}"
  580. DATA 0x0b8,2,"KEY {#keymode}"
  581. DATA 0x0b9,"KEY {1}, {0}"
  582. DATA 0x0ba,"KILL {0}"
  583. DATA 0x0bb,2,"LINE {##line-args}"
  584. DATA 0x0bc,2,"LINE {##line-args}"
  585. DATA 0x0bd,2,"LINE {##line-args}"
  586. DATA 0x0be,2,"LINE {##line-args}"
  587. DATA 0x0bf,"LET "
  588.  
  589. DATA 0x0c0,2,"input::=LINE {finput:1} {0}"
  590. DATA "input::=LINE INPUT {##input-args} {0}"
  591.  
  592. DATA 0x0c1,2,"LOCATE{##varargs}"
  593. DATA 0x0c2,2,"LOCK {##lock-args}"
  594. DATA 0x0c3,"prnmod::=LPRINT"
  595. DATA 0x0c4,"LSET {0} = {1}"
  596. DATA 0x0c5,"MID$({0}, {2}) = {1}"
  597. DATA 0x0c6,"MID$({0}, {3}, {2}) = {1}"
  598. DATA 0x0c7,"MKDIR {0}"
  599. DATA 0x0c8,"NAME {1} AS {0}"
  600.  
  601. DATA 0x0c9,2,"OPEN {1} {#open-args} AS {0}"
  602. DATA 0x0ca,2,"OPEN {2} {#open-args} AS {1} LEN = {0}"
  603. DATA 0x0cb,"OPEN {2}, {1}, {0}"
  604. DATA 0x0cc,"OPEN {3}, {2}, {1}, {0}"
  605. DATA 0x0cd,"OPTION BASE 0"
  606. DATA 0x0ce,"OPTION BASE 1"
  607. DATA 0x0cf,"OUT {1}, {0}"
  608.  
  609.  
  610.  
  611. DATA 0x0d0,"PAINT {2}{nularg:1}{nularg:0}"
  612. DATA "PAINT {2}, {nularg:1}, {0}"
  613. DATA "PAINT {2}, {1}{nularg:0}"
  614. DATA "PAINT {2}, {1}, {0}"
  615. DATA 0x0d1,"PAINT {3}, {2}, {1}, {0}"
  616. DATA 0x0d2,"PALETTE"
  617. DATA 0x0d3,"PALETTE {1}, {0}"
  618. DATA 0x0d4,"PALETTE {0}"
  619. DATA 0x0d5,"PCOPY {1}, {0}"
  620. DATA 0x0d6,"PLAY {0}"
  621.  
  622. DATA 0x0d7,"POKE {1}, {0}"
  623. DATA 0x0d8,"PRESET {0}"
  624. DATA 0x0d9,"PRESET {0}, {1}"
  625. DATA 0x0da,"PSET {0}"
  626. DATA 0x0db,"PSET {1}, {0}"
  627. DATA 0x0dd,"PUT {1}, {0}"
  628. DATA 0x0de,2,"PUT {1}, , {0}"
  629. DATA 0x0df,2,"PUT {2}, {1}, {0}"
  630.  
  631. DATA 0x0e0,"RANDOMIZE"
  632. DATA 0x0e1,"RANDOMIZE {0}"
  633. DATA 0x0e2,"{self:1}, {0}|READ {0}"
  634. DATA 0x0e3,*,"REM{#raw}"
  635. DATA 0x0e4,"RESET"
  636. DATA 0x0e5,"RMDIR {0}"
  637. DATA 0x0e6,"RSET {0} = {1}"
  638.  
  639. DATA 0x0e7,2,"SCREEN{##varargs}"
  640. DATA 0x0e8,"SEEK {1}, {0}"
  641. DATA 0x0e9,"SHELL"
  642. DATA 0x0ea,"SHELL {0}"
  643. DATA 0x0eb,"SLEEP"
  644. DATA 0x0ec,"SOUND {1}, {0}"
  645. DATA 0x0ed,2,"SWAP {1}, {0}"
  646. DATA 0x0ee,"SYSTEM"
  647. DATA 0x0ef,"TIME$ = {0}"
  648. DATA 0x0f0,"TROFF"
  649. DATA 0x0f1,"TRON"
  650. DATA 0x0f2,2,"UNLOCK {##lock-args}"
  651. DATA 0x0f3,"VIEW ({5}, {4})-({3}, {2}){nularg:1}{nularg:0}"
  652. DATA "VIEW ({5}, {4})-({3}, {2}), {nularg:1}, {0}"
  653. DATA "VIEW ({5}, {4})-({3}, {2}), {1}{nularg:0}"
  654. DATA "VIEW ({5}, {4})-({3}, {2})"
  655. DATA 0x0f4,"VIEW"
  656.  
  657. DATA 0x0f5,"VIEW PRINT"
  658. DATA 0x0f6,"VIEW PRINT {1} TO {0}"
  659.  
  660. DATA 0x0f7,"VIEW SCREEN ({5}, {4})-({3}, {2}){nularg:1}{nularg:0}"
  661. DATA "VIEW SCREEN ({5}, {4})-({3}, {2}), {nularg:1}, {0}"
  662. DATA "VIEW SCREEN ({5}, {4})-({3}, {2}), {1}{nularg:0}"
  663. DATA "VIEW SCREEN ({5}, {4})-({3}, {2})"
  664. DATA 0x0f8,"WIDTH {1}{nularg:0}|WIDTH {1}, {0}"
  665. DATA 0x0f9,"WIDTH LPRINT {0}"
  666. DATA 0x0fa,"WIDTH {1}, {0}"
  667. DATA 0x0fb,"WINDOW ({3}, {2})-({1}, {0})"
  668. DATA 0x0fc,"WINDOW"
  669. DATA 0x0fd,"WINDOW SCREEN ({3}, {2})-({1}, {0})"
  670. DATA 0x0fe,"prnmod::=WRITE"
  671. DATA 0x0ff,"prnuse::=USING {0};"
  672.  
  673. DATA .default expr
  674.  
  675. DATA 0x100,"{1} + {0}"
  676. DATA 0x101,"{1} AND {0}"
  677. DATA 0x102,"{1} / {0}"
  678. DATA 0x103,"{1} = {0}"
  679. DATA 0x104,"{1} EQV {0}"
  680. DATA 0x105,"ABS({0})"
  681. DATA 0x106,"ASC({0})"
  682. DATA 0x107,"ATN({0})"
  683. DATA 0x108,"C{#type-abbr}({0})"
  684. DATA 0x109,"CHR$({0})"
  685. DATA 0x10a,"COMMAND$"
  686. DATA 0x10b,"COS({0})"
  687. DATA 0x10c,"CSRLIN"
  688. DATA 0x10d,"CVD({0})"
  689. DATA 0x10e,"CVDMBD({0})"
  690. DATA 0x10f,"CVI({0})"
  691. DATA 0x110,"CVL({0})"
  692. DATA 0x111,"CVS({0})"
  693. DATA 0x112,"CVSMBF({0})"
  694. DATA 0x113,"DATE$"
  695. DATA 0x114,"ENVIRON$({0})"
  696. DATA 0x115,"EOF({0})"
  697. DATA 0x116,"ERDEV"
  698. DATA 0x117,"ERDEV$"
  699. DATA 0x118,"ERL"
  700. DATA 0x119,"ERR"
  701. DATA 0x11a,"EXP({0})"
  702. DATA 0x11b,"FILEATTR({1}, {0})"
  703. DATA 0x11c,"FIX({0})"
  704. DATA 0x11d,"FRE({0})"
  705. DATA 0x11e,"FREEFILE"
  706. DATA 0x11f,"HEX$({0})"
  707. DATA 0x120,"INKEY$"
  708. DATA 0x121,"INP({0})"
  709. DATA 0x122,"INPUT$({0})"
  710. DATA 0x123,"INPUT$({1}, {0})"
  711. DATA 0x124,"INSTR({1}, {0})"
  712. DATA 0x125,"INSTR({2}, {1}, {0})"
  713. DATA 0x126,"INT({0})"
  714. DATA 0x127,"IOCTL$({0})"
  715. DATA 0x128,"LBOUND({0})"
  716. DATA 0x129,"LBOUND({1}, {0})"
  717. DATA 0x12a,"LCASE$({0})"
  718. DATA 0x12b,"LTRIM$({0})"
  719. DATA 0x12c,"LEFT$({1}, {0})"
  720. DATA 0x12d,2,"LEN({0})"
  721. DATA 0x12e,"LOC({0})"
  722. DATA 0x12f,"LOF({0})"
  723. DATA 0x130,"LOG({0})"
  724. DATA 0x131,"LPOS({0})"
  725. DATA 0x132,"MID$({1}, {0})"
  726. DATA 0x133,"MID$({2}, {1}, {0})"
  727. DATA 0x134,"MKD$({0})"
  728. DATA 0x135,"MKDMBF$({0})"
  729. DATA 0x136,"MKI$({0})"
  730. DATA 0x137,"MKL$({0})"
  731. DATA 0x138,"MKS$({0})"
  732. DATA 0x139,"MKSMBF({0})"
  733. DATA 0x13a,"OCT$({0})"
  734. DATA 0x13b,"PEEK({0})"
  735. DATA 0x13c,"PEN"
  736. DATA 0x13d,"PLAY"
  737. DATA 0x13e,"PMAP({1}, {0})"
  738. DATA 0x13f,"POINT({0})"
  739. DATA 0x140,"POINT({1}, {0})"
  740. DATA 0x141,"POS({0})"
  741. DATA 0x142,"RIGHT$({1}, {0})"
  742. DATA 0x143,"RND"
  743. DATA 0x144,"RND({0})"
  744. DATA 0x145,"RTRIM$({0})"
  745. DATA 0x146,"SADD({0})"
  746. DATA 0x147,"SCREEN({1}, {0})"
  747. DATA 0x148,"SCREEN({2}, {1}, {0})"
  748. DATA 0x149,"SEEK({0})"
  749. DATA 0x14a,"SETMEM({0})"
  750. DATA 0x14b,"SGN({0})"
  751. DATA 0x14c,"SHELL({0})"
  752. DATA 0x14d,"SIN({0})"
  753. DATA 0x14e,"SPACE$({0})"
  754. DATA 0x14f,"SQR({0})"
  755. DATA 0x150,"STICK({0})"
  756. DATA 0x151,"STR$({0})"
  757. DATA 0x152,"STRIG({0})"
  758. DATA 0x153,"STRING$({1}, {0})"
  759. DATA 0x154,"TAN({0})"
  760. DATA 0x155,"TIME$"
  761. DATA 0x156,"TIMER"
  762. DATA 0x157,"UBOUND({0})"
  763. DATA 0x158,"UBOUND({1}, {0})"
  764. DATA 0x159,"UCASE$({0})"
  765. DATA 0x15a,"VAL({0})"
  766. DATA 0x15b,"VARPTR({0})"
  767. DATA 0x15c,2,"VARPTR$({0})"
  768. DATA 0x15d,"VARSEG({0})"
  769. DATA 0x15e,"{1} >= {0}"
  770. DATA 0x15f,"{1} > {0}"
  771. DATA 0x160,"{1} \ {0}"
  772. DATA 0x161,"{1} IMP {0}"
  773. DATA 0x162,"{1} <= {0}"
  774. DATA 0x163,"{1} < {0}"
  775. DATA 0x164,"{#hprm}"
  776. DATA 0x165,2,"{#int}"
  777. DATA 0x166,4,"{#lng}"
  778. DATA 0x167,2,"{#int&h}"
  779. DATA 0x168,4,"{#lng&h}"
  780. DATA 0x169,2,"{#int&o}"
  781. DATA 0x16a,4,"{#lng&o}"
  782. DATA 0x16b,4,"{#sng}"
  783. DATA 0x16c,8,"{#dbl}"
  784. DATA 0x16d,*,"{#qstr}"
  785. DATA 0x16e,"({0})"
  786. DATA 0x16f,"{1} MOD {0}"
  787. DATA 0x170,"{1} * {0}"
  788. DATA 0x171,"{1} <> {0}"
  789. DATA 0x172,"{#nul}"
  790. DATA 0x173,"nularg::={#nul}"
  791. DATA 0x174,"NOT {0}"
  792. DATA 0x175,"{1} OR {0}"
  793. DATA 0x176,"{1} ^ {0}"
  794. DATA 0x177,"{1} - {0}"
  795. DATA 0x178,"-{0}"
  796. DATA 0x179,"{1} XOR {0}"
  797.  
  798. DATA .default
  799.  
  800. DATA 0x17a,"UEVENT"
  801. DATA 0x17b,"SLEEP {0}"
  802. DATA 0x17c,6,"astype::={#tabi:4}AS STRING * {#int:2}"
  803. DATA 0x17d,2,"decl::=DIM {declmod:0}|DIM"
  804.  
  805.  
  806. '
  807. ' This subroutine is called whenever a program line has been decoded.
  808. '
  809. SUB AOutput (ProgramLine AS STRING)
  810.  
  811.     STATIC OutputLines
  812.  
  813.     OutputLines = OutputLines + 1
  814.  
  815.     IF LEN(OutputContents$) THEN
  816.         OutputContents$ = OutputContents$ + CHR$(10) + ProgramLine
  817.     ELSE
  818.         OutputContents$ = ProgramLine
  819.     END IF
  820.  
  821.  
  822. SUB DbgOutput (DbgTxt AS STRING)
  823.  
  824.     EXIT SUB
  825.  
  826.     PRINT #5, DbgTxt
  827.  
  828.  
  829. FUNCTION DbgPlainText$ (Txt2$)
  830.  
  831.     Txt$ = Txt2$
  832.  
  833.     DO
  834.         Marker = INSTR(Txt$, MKL$(0))
  835.         IF Marker = 0 THEN EXIT DO
  836.  
  837.         TagTxtLen = CVI(MID$(Txt$, Marker + 4, 2))
  838.         TagParam = CVI(MID$(Txt$, Marker + 6, 2))
  839.         TagTxt$ = MID$(Txt$, Marker + 8, TagTxtLen)
  840.  
  841.         TagParam$ = ITOA(TagParam)
  842.         IF TagParam > 0 THEN TagParam$ = "+" + TagParam$
  843.         TagParam$ = "$" + TagParam$
  844.         IF TagTxt$ <> "" THEN TagParam$ = TagTxt$ + ":" + TagParam$
  845.  
  846.  
  847.         Txt$ = LEFT$(Txt$, Marker - 1) + "{" + TagParam$ + "}" + MID$(Txt$, Marker + 8 + TagTxtLen)
  848.  
  849.     LOOP
  850.  
  851.  
  852.     DO
  853.         Marker = INSTR(Txt$, CHR$(&HD))
  854.         IF Marker = 0 THEN EXIT DO
  855.  
  856.         IF CVI(MID$(Txt$, Marker, 2)) = &HD THEN
  857.             Txt$ = LEFT$(Txt$, Marker - 1) + "®newlineÂŻ" + MID$(Txt$, Marker + 2)
  858.         ELSEIF CVI(MID$(Txt$, Marker, 2)) = &H10D THEN
  859.             Txt$ = LEFT$(Txt$, Marker - 1) + "®indentÂŻ" + MID$(Txt$, Marker + 4)
  860.         ELSE
  861.             Txt$ = LEFT$(Txt$, Marker - 1) + "®rleÂŻ" + MID$(Txt$, Marker + 3)
  862.         END IF
  863.  
  864.     LOOP
  865.  
  866.     DbgPlainText$ = Txt$
  867.  
  868.  
  869. '
  870. ' Iterates through the various rules for a token contained in the ParseRules
  871. ' array and stops when one of them works.
  872. '
  873. SUB DefaultParseRule
  874.  
  875.     DIM ParseRule AS STRING
  876.  
  877.     IF PCODE < LBOUND(ParseRules) OR PCODE > UBOUND(ParseRules) THEN EXIT SUB
  878.     ParseRule = ParseRules(PCODE)
  879.  
  880.     IF ParseRule = "" THEN EXIT SUB
  881.  
  882.     DbgOutput ""
  883.     DbgOutput "PCODE = 0x" + HEX$(PCODE)
  884.     DbgOutput "HPARAM = 0x" + HEX$(HPARAM)
  885.     DbgOutput ""
  886.     'DumpStack
  887.  
  888.     FOR RuleBegin = 3 TO LEN(ParseRule) STEP 4
  889.  
  890.         RuleLn = CVI(MID$(ParseRule, RuleBegin + 0, 2))
  891.         RuleID = CVI(MID$(ParseRule, RuleBegin + 2, 2))
  892.  
  893.         RuleTxt$ = MID$(ParseRule, RuleBegin + 4, RuleLn)
  894.  
  895.         IF ExecuteParseRule(RuleID, RuleTxt$) THEN EXIT FOR
  896.  
  897.         RuleBegin = RuleBegin + RuleLn
  898.  
  899.     NEXT RuleBegin
  900.  
  901.  
  902. '
  903. ' Returns the string of the first rule in a compound|parse|rule, and removes
  904. ' it from the input string.
  905. '
  906. ' If the rule does not have a rule id (ident::=), DefaultRuleID is assigned.
  907. '
  908. FUNCTION DelimitParseRule$ (ParseRule AS STRING, DefaultRuleID AS STRING)
  909.  
  910.     DIM FirstRule AS STRING
  911.  
  912.     '----------------------------------------------------------------------------
  913.     ' Locate the first instance of the rule delimiter "|" that does not occur
  914.     ' inside a rule {tag}
  915.     '----------------------------------------------------------------------------
  916.     RuleOffset = 1
  917.     RuleEnd = LEN(ParseRule) + 1
  918.  
  919.     DO
  920.    
  921.         BraceOffset = INSTR(RuleOffset, ParseRule, "{")
  922.         IF BraceOffset = 0 THEN BraceOffset = RuleEnd
  923.  
  924.         PipeOffset = INSTR(RuleOffset, ParseRule, "|")
  925.  
  926.         RuleOffset = INSTR(BraceOffset, ParseRule, "}")
  927.         IF RuleOffset = 0 THEN RuleOffset = RuleEnd
  928.  
  929.     LOOP UNTIL PipeOffset < BraceOffset
  930.  
  931.     IF PipeOffset = 0 THEN PipeOffset = RuleEnd
  932.  
  933.  
  934.     '----------------------------------------------------------------------------
  935.     ' Extract the first rule and return if there is nothing left.
  936.     '----------------------------------------------------------------------------
  937.     FirstRule = LEFT$(ParseRule, PipeOffset - 1)
  938.     ParseRule = MID$(ParseRule, PipeOffset + 1)
  939.  
  940.  
  941.     '----------------------------------------------------------------------------
  942.     ' If the first rule has a symbol on the left-hand side and the next rule
  943.     ' does not, the next rule inherits the symbol.
  944.     '----------------------------------------------------------------------------
  945.     RuleLHS$ = GetParseRuleLHS(FirstRule)
  946.  
  947.     IF RuleLHS$ = "" AND DefaultRuleID <> "" THEN
  948.         RuleLHS$ = DefaultRuleID
  949.         FirstRule = DefaultRuleID + "::=" + FirstRule
  950.     END IF
  951.  
  952.     DelimitParseRule = FirstRule
  953.     IF ParseRule = "" THEN EXIT FUNCTION
  954.  
  955.     IF RuleLHS$ <> "" AND GetParseRuleLHS(ParseRule) = "" THEN
  956.         ParseRule = RuleLHS$ + "::=" + ParseRule
  957.     END IF
  958.  
  959.  
  960. '
  961. ' For debugging only
  962. '
  963. SUB DumpStack
  964.  
  965.     PRINT #5, "The stack has"; SP; "entries"
  966.  
  967.     FOR i = 1 TO SP
  968.         ID = CVI(LEFT$(STACK(i), 2))
  969.         Txt$ = MID$(STACK(i), 3)
  970.  
  971.  
  972.         DO
  973.             Marker = INSTR(Txt$, CHR$(&HD))
  974.             IF Marker = 0 THEN EXIT DO
  975.  
  976.             IF CVI(MID$(Txt$, Marker, 2)) = &HD THEN
  977.                 Txt$ = LEFT$(Txt$, Marker - 1) + "®newlineÂŻ" + MID$(Txt$, Marker + 2)
  978.             ELSEIF CVI(MID$(Txt$, Marker, 2)) = &H10D THEN
  979.                 Txt$ = LEFT$(Txt$, Marker - 1) + "®indentÂŻ" + MID$(Txt$, Marker + 4)
  980.             ELSE
  981.                 Txt$ = LEFT$(Txt$, Marker - 1) + "®rleÂŻ" + MID$(Txt$, Marker + 3)
  982.             END IF
  983.  
  984.         LOOP
  985.  
  986.         PRINT #5, ITOA$(i); ": 0x"; HEX$(ID),
  987.  
  988.         TRIM = 76 - POS(0) - LEN(Txt$)
  989.         IF TRIM < 0 THEN PRINT #5, LEFT$(Txt$, LEN(Txt$) + TRIM); " ..." ELSE PRINT #5, Txt$
  990.         '80-60-19=1
  991.  
  992.  
  993.  
  994.     NEXT i
  995.  
  996. FUNCTION ExecuteParseRule% (RuleID AS INTEGER, ParseRule AS STRING)
  997.  
  998.     DIM RuleTxt AS STRING
  999.     DIM TagTxt AS STRING
  1000.     DIM OutTxt AS STRING
  1001.  
  1002.     RuleOffset = 1
  1003.  
  1004.     '
  1005.     ' NOTE: Since the stack is flushed immediately upon seeing a leading period,
  1006.     ' rules should not have non-flushing alternatives.
  1007.     '
  1008.     IF LEFT$(ParseRule, 1) = "." THEN
  1009.         FlushStack
  1010.         RuleOffset = 2
  1011.     END IF
  1012.  
  1013.     InitialSP = SP
  1014.     FinalSP = SP
  1015.     RuleTxt = ParseRule
  1016.  
  1017.     DbgOutput "Trying rule: " + Quote(ParseRule)
  1018.  
  1019.     DO
  1020.  
  1021.         DbgOutput "Rule: " + ParseRule
  1022.         DbgOutput "Output: " + OutTxt
  1023.  
  1024.         TagBegin = INSTR(RuleOffset, RuleTxt, "{")
  1025.         IF TagBegin = 0 THEN TagBegin = LEN(RuleTxt) + 1
  1026.  
  1027.         TagEnd = INSTR(TagBegin, RuleTxt, "}") + 1
  1028.  
  1029.         OutTxt = OutTxt + MID$(RuleTxt, RuleOffset, TagBegin - RuleOffset)
  1030.  
  1031.         IF TagEnd <= TagBegin THEN EXIT DO
  1032.  
  1033.         TagTxt = MID$(RuleTxt, TagBegin + 1, TagEnd - TagBegin - 2)
  1034.  
  1035.         SELECT CASE TokenizeTag(TagTxt, TagParam)
  1036.  
  1037.             '------------------------------------------------------------------------
  1038.             ' If a relative stack tag is used, we will need to wait until all the
  1039.             ' absolute tags have been processed before we can calculate the tag
  1040.             ' offset, so we insert a marker into OutTxt.
  1041.             '------------------------------------------------------------------------
  1042.             CASE TagType.StackREL
  1043.                 OutTxt = OutTxt + MKL$(0) + MKI$(LEN(TagTxt)) + MKI$(TagParam) + TagTxt
  1044.                 RuleOffset = TagEnd
  1045.  
  1046.  
  1047.             CASE TagType.StackABS
  1048.  
  1049.                 IF NOT ValidateStackTag(RuleID, TagTxt, TagParam) THEN
  1050.                     ExecuteParseRule = 0
  1051.                     DbgOutput "Rule REJECTED!"
  1052.                     EXIT FUNCTION
  1053.                 ELSE
  1054.                     IF OffsetSP < SP THEN OutTxt = OutTxt + MID$(STACK(SP - TagParam), 3)
  1055.                     IF SP - TagParam - 1 < FinalSP THEN FinalSP = SP - TagParam - 1
  1056.                 END IF
  1057.  
  1058.                 RuleOffset = TagEnd
  1059.  
  1060.  
  1061.             CASE TagType.Recursive
  1062.                 RuleTxt = LEFT$(RuleTxt, TagBegin - 1) + GetTaggedItem(TagTxt, TagParam) + MID$(RuleTxt, TagEnd)
  1063.                 RuleOffset = TagBegin
  1064.  
  1065.  
  1066.             CASE TagType.TokenData
  1067.                 OutTxt = OutTxt + GetTaggedItem(TagTxt, TagParam)
  1068.                 RuleOffset = TagEnd
  1069.  
  1070.  
  1071.         END SELECT
  1072.  
  1073.  
  1074.  
  1075.  
  1076.     LOOP WHILE RuleOffset <= LEN(RuleTxt)
  1077.  
  1078.     DbgOutput "Rule: " + ParseRule
  1079.     DbgOutput "Output: " + OutTxt
  1080.  
  1081.     SP = FinalSP
  1082.  
  1083.     DO
  1084.         Marker = INSTR(OutTxt, MKL$(0))
  1085.         IF Marker = 0 THEN EXIT DO
  1086.  
  1087.         TagTxtLen = CVI(MID$(OutTxt, Marker + 4, 2))
  1088.         TagParam = CVI(MID$(OutTxt, Marker + 6, 2))
  1089.         TagTxt = MID$(OutTxt, Marker + 8, TagTxtLen)
  1090.  
  1091.         IF NOT (ValidateStackTag(RuleID, TagTxt, TagParam)) THEN
  1092.             SP = InitialSP
  1093.             ExecuteParseRule = 0
  1094.             DbgOutput "Rule REJECTED!"
  1095.             EXIT FUNCTION
  1096.         END IF
  1097.  
  1098.         OutTxt = LEFT$(OutTxt, Marker - 1) + MID$(STACK(SP - TagParam), 3) + MID$(OutTxt, Marker + 8 + TagTxtLen)
  1099.         IF SP - TagParam - 1 < FinalSP THEN FinalSP = SP - TagParam - 1
  1100.     LOOP
  1101.  
  1102.     FOR SP = InitialSP TO FinalSP + 1 STEP -1: STACK(SP) = "": NEXT SP
  1103.     SP = FinalSP
  1104.  
  1105.     PUSH RuleID, OutTxt
  1106.     ExecuteParseRule = -1
  1107.  
  1108.     DbgOutput "Rule ACCEPTED!"
  1109.  
  1110.     'PCODE = RuleID
  1111.  
  1112.  
  1113. FUNCTION ExtractProgramLine% (ProgramLine AS STRING)
  1114.  
  1115.  
  1116. '
  1117. ' Generates a /blockname/ as used in COMMON statements, using the ID at
  1118. ' CODE(DP)
  1119. '
  1120. FUNCTION FetchBlockName$ (DP AS INTEGER)
  1121.  
  1122.     ID = FetchINT(DP)
  1123.     IF ID <> -1 THEN x$ = " /" + GetID(ID) + "/" ELSE x$ = ""
  1124.  
  1125.  
  1126. '
  1127. ' Reads a null-terminate string. These are only found in DATA statements
  1128. ' and the null always seems to be at the end of the string anyway, but we
  1129. ' will process it properly to be sure.
  1130. '
  1131. FUNCTION FetchCSTR$ (DP AS INTEGER)
  1132.  
  1133.     CSTR$ = FetchRAW(DP)
  1134.  
  1135.     null = INSTR(CSTR$, CHR$(0))
  1136.  
  1137.     IF null THEN CSTR$ = LEFT$(CSTR$, null - 1)
  1138.  
  1139.     FetchCSTR$ = CSTR$
  1140.  
  1141.  
  1142. '
  1143. ' Fetches an identifier from the current TOKEN data by performing a symbol
  1144. ' table lookup on the word at the specified offset.
  1145. '
  1146. FUNCTION FetchID$ (Offset AS INTEGER)
  1147.  
  1148.     FetchID$ = ""
  1149.  
  1150.     IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN EXIT FUNCTION
  1151.  
  1152.     FetchID$ = GetID(CVI(MID$(TOKEN, Offset + 3, 2)))
  1153.  
  1154.  
  1155. FUNCTION FetchIDList$ (DP AS INTEGER)
  1156.  
  1157.  
  1158.     TkLen = LEN(TOKEN)
  1159.     IF DP < 0 OR DP > TkLen - 2 THEN EXIT FUNCTION
  1160.  
  1161.     FOR i = DP + 3 TO TkLen - 1 STEP 2
  1162.  
  1163.         ID$ = GetID(CVI(MID$(TOKEN, i, 2)))
  1164.  
  1165.         IF IdList$ <> "" THEN IdList$ = IdList$ + ", "
  1166.         IdList$ = IdList$ + ID$
  1167.  
  1168.     NEXT i
  1169.  
  1170.     FetchIDList = IdList$
  1171.  
  1172.  
  1173. '
  1174. ' Returns the integer at the specified zero-based offset from the start
  1175. ' of the token data.
  1176. '
  1177. FUNCTION FetchINT% (Offset AS INTEGER)
  1178.  
  1179.     FetchINT = -1
  1180.  
  1181.     IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN EXIT FUNCTION
  1182.  
  1183.     FetchINT = CVI(MID$(TOKEN, Offset + 3, 2))
  1184.  
  1185.  
  1186. '
  1187. ' Returns the integer at the specified zero-based offset from the start
  1188. ' of the token data as a LONG value.
  1189. '
  1190. FUNCTION FetchINTASLONG& (Offset AS INTEGER)
  1191.  
  1192.     FetchINTASLONG = -1
  1193.  
  1194.     IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN EXIT FUNCTION
  1195.  
  1196.     FetchINTASLONG = CVI(MID$(TOKEN, Offset + 3, 2)) AND &HFFFF&
  1197.  
  1198.  
  1199. '
  1200. ' Reads a literal 64-bit float from the p-code and returns its string
  1201. ' representation. Using the "{dbl}" tag in the SHIFT procedure is a more
  1202. ' convienient method to extract literals.
  1203. '
  1204. ' The IP is passed by reference, and will be incremented to the code
  1205. ' following the literal. There is no radix option for floating point values.
  1206. '
  1207. FUNCTION FetchLiteralDBL$ (DP)
  1208.  
  1209.     IF DP > UBOUND(CODE) THEN
  1210.         FetchLiteralDBL$ = "0#"
  1211.         EXIT FUNCTION
  1212.     END IF
  1213.  
  1214.     Value# = CVD(MID$(TOKEN, DP + 3, 8))
  1215.     Txt$ = LTRIM$(STR$(Value#))
  1216.  
  1217.  
  1218.     ' If the single and double precision representations are equal, we will
  1219.     ' insert a # to indicate double precision.
  1220.  
  1221.     IF Value# = CSNG(Value#) THEN Txt$ = Txt$ + "#"
  1222.  
  1223.     FetchLiteralDBL$ = Txt$
  1224.  
  1225.  
  1226. '
  1227. ' Reads a literal 16-bit integer from the code and returns its string
  1228. ' representation. Using the "{int}" tag in ExecuteParseRule is a more
  1229. ' convienient method to extract literals.
  1230. '
  1231. ' The Radix parameter may be 8, 10 or 16 to produce
  1232. ' the desired number format, or use the "{int&o}" and "{int&h}" tags.
  1233. '
  1234. FUNCTION FetchLiteralINT$ (Offset AS INTEGER, Radix AS INTEGER)
  1235.  
  1236.     DIM Value AS INTEGER
  1237.  
  1238.     IF Offset < 0 OR Offset > LEN(TOKEN) - 4 THEN
  1239.         FetchLiteralINT$ = "0"
  1240.         EXIT FUNCTION
  1241.     END IF
  1242.  
  1243.     Value = CVI(MID$(TOKEN, Offset + 3, 2))
  1244.  
  1245.     SELECT CASE Radix
  1246.  
  1247.         CASE 8: Txt$ = "&O" + OCT$(Value)
  1248.         CASE 10: Txt$ = ITOA$(Value)
  1249.         CASE 16: Txt$ = "&H" + HEX$(Value)
  1250.  
  1251.         CASE ELSE: Txt$ = "[bad radix]"
  1252.  
  1253.     END SELECT
  1254.  
  1255.     FetchLiteralINT$ = Txt$
  1256.  
  1257.  
  1258. '
  1259. ' Reads a literal 32-bit integer from the code and returns its string
  1260. ' representation. Using the "{lng}" tag in ExecuteParseRule is a more
  1261. ' convienient method to extract literals.
  1262. '
  1263. ' The Radix parameter may be 8, 10 or 16 to produce the desired number
  1264. ' format, or use the "{lng&o}" and "{lng&h}" tags.
  1265. '
  1266. FUNCTION FetchLiteralLNG$ (Offset AS INTEGER, Radix AS INTEGER)
  1267.  
  1268.     DIM Value AS LONG
  1269.  
  1270.     IF Offset < 0 OR Offset > LEN(TOKEN) - 6 THEN
  1271.         FetchLiteralLNG$ = "0"
  1272.         EXIT FUNCTION
  1273.     END IF
  1274.  
  1275.     Value = CVL(MID$(TOKEN, Offset + 3, 4))
  1276.  
  1277.     SELECT CASE Radix
  1278.  
  1279.         CASE 8: Txt$ = "&O" + OCT$(Value)
  1280.         CASE 10: Txt$ = LTOA$(Value)
  1281.         CASE 16: Txt$ = "&H" + HEX$(Value)
  1282.  
  1283.         CASE ELSE: Txt$ = "[bad radix]"
  1284.  
  1285.     END SELECT
  1286.  
  1287.     IF Value < 65536 THEN Txt$ = Txt$ + "&"
  1288.  
  1289.     FetchLiteralLNG$ = Txt$
  1290.  
  1291.  
  1292. '
  1293. ' Reads a literal 32-bit float from the p-code and returns its string
  1294. ' representation. Using the "{sng}" tag in the SHIFT procedure is a more
  1295. ' convienient method to extract literals.
  1296. '
  1297. ' The IP is passed by reference, and will be incremented to the code
  1298. ' following the literal. There is no radix option for floating point values.
  1299. '
  1300. FUNCTION FetchLiteralSNG$ (DP)
  1301.  
  1302.     IF OffsetIP > UBOUND(CODE) THEN
  1303.         FetchLiteralSNG$ = "0"
  1304.         EXIT FUNCTION
  1305.     END IF
  1306.  
  1307.     Value! = CVS(MID$(TOKEN, DP + 3, 4))
  1308.  
  1309.     Txt$ = LTRIM$(STR$(Value!))
  1310.  
  1311.     FetchLiteralSNG$ = Txt$
  1312.  
  1313.  
  1314. FUNCTION FetchLNG& (Offset AS INTEGER)
  1315.  
  1316.     FetchLNG = -1
  1317.  
  1318.     IF Offset < 0 OR Offset > LEN(TOKEN) - 6 THEN EXIT FUNCTION
  1319.  
  1320.     FetchLNG = CVL(MID$(TOKEN, Offset + 3, 4))
  1321.  
  1322.  
  1323. FUNCTION FetchRAW$ (Offset AS INTEGER)
  1324.  
  1325.     IF Offset < 0 OR Offset > LEN(TOKEN) - 2 THEN EXIT FUNCTION
  1326.  
  1327.     FetchRAW$ = MID$(TOKEN, 3 + Offset)
  1328.  
  1329.  
  1330. FUNCTION FindRuleDelimiter% (ParseRule AS STRING)
  1331.  
  1332.     RuleOffset = 1
  1333.     RuleEnd = LEN(ParseRule) + 1
  1334.  
  1335.     DO WHILE RuleOffset < RuleEnd
  1336.    
  1337.         BraceOffset = INSTR(RuleOffset, ParseRule, "{")
  1338.         PipeOffset = INSTR(RuleOffset, ParseRule, "|")
  1339.    
  1340.         IF BraceOffset = 0 OR PipeOffset <= BraceOffset THEN EXIT DO
  1341.  
  1342.         RuleOffset = INSTR(BraceOffset + 1, ParseRule, "}")
  1343.         IF RuleOffset = 1 THEN EXIT DO
  1344.  
  1345.     LOOP
  1346.  
  1347.     FindRuleDelimiter = PipeOffset
  1348.  
  1349.  
  1350. '
  1351. ' Flushes all stack entries to STACK(0), ready for final processing into
  1352. ' a program line.
  1353. '
  1354. SUB FlushStack
  1355.  
  1356.     FOR i = 1 TO SP
  1357.         STACK(0) = STACK(0) + MID$(STACK(i), 3)
  1358.         STACK(i) = ""
  1359.     NEXT i
  1360.  
  1361.     SP = 0
  1362.  
  1363.  
  1364. '
  1365. ' Returns an integer identifier for a parse rule symbol
  1366. '
  1367. FUNCTION GetHashedSymbol (ParseRuleSymbol AS STRING)
  1368.     DIM LookupSymbol AS STRING
  1369.  
  1370.     SymbolID$ = LTRIM$(RTRIM$(ParseRuleSymbol))
  1371.  
  1372.     '----------------------------------------------------------------------------
  1373.     ' Parse rule symbols my be literal integers
  1374.     '----------------------------------------------------------------------------
  1375.     IF StringToINT(SymbolID$, SymbolID%) THEN
  1376.         GetHashedSymbol% = SymbolID%
  1377.         EXIT FUNCTION
  1378.     END IF
  1379.    
  1380.     Hash = HashPJW(SymbolID$)
  1381.  
  1382.     LookupSymbol = "[" + SymbolID$ + "]"
  1383.  
  1384.     SymbolOffset = INSTR(SymbolHashTable(Hash), LookupSymbol)
  1385.  
  1386.     IF SymbolOffset = 0 THEN
  1387.  
  1388.         SymbolID% = SymbolHashEntries
  1389.         SymbolID% = SymbolID% + UBOUND(ParseRules) + 1
  1390.         SymbolID$ = RIGHT$(SymbolHashTable(Hash), 2)
  1391.         IF SymbolID$ <> "" THEN SymbolID% = CVI(SymbolID$) + 1
  1392.  
  1393.         SymbolID$ = MKI$(SymbolID%)
  1394.  
  1395.         SymbolHashTable(Hash) = SymbolHashTable(Hash) + LookupSymbol + SymbolID$
  1396.  
  1397.         SymbolHashEntries = SymbolHashEntries + 1
  1398.  
  1399.     ELSE
  1400.  
  1401.         SymbolOffset = SymbolOffset + LEN(LookupSymbol)
  1402.  
  1403.         SymbolID$ = MID$(SymbolHashTable(Hash), SymbolOffset, 2)
  1404.         SymbolID% = CVI(SymbolID$)
  1405.  
  1406.     END IF
  1407.  
  1408.     GetHashedSymbol% = SymbolID% '+ UBOUND(ParseRules) + 1
  1409.  
  1410.  
  1411.  
  1412. '
  1413. ' Reads an identifier from the symbol table data stored in the SYMTBL
  1414. ' array.
  1415. '
  1416. FUNCTION GetID$ (SymTblOffset AS INTEGER)
  1417.  
  1418.     '----------------------------------------------------------------------------
  1419.     ' Convert offset to LONG to we can read above 32767
  1420.     '----------------------------------------------------------------------------
  1421.     SymTblOfs& = SymTblOffset AND &HFFFF&
  1422.  
  1423.     '----------------------------------------------------------------------------
  1424.     ' offset FFFF is used as a shortcut for "0" in statements such as
  1425.     ' ON ERROR GOTO 0
  1426.     '----------------------------------------------------------------------------
  1427.     IF SymTblOfs& = &HFFFF& THEN
  1428.         GetID$ = "0"
  1429.         EXIT FUNCTION
  1430.     END IF
  1431.  
  1432.  
  1433.     '----------------------------------------------------------------------------
  1434.     ' Make sure we can at least read the first 4 bytes
  1435.     '----------------------------------------------------------------------------
  1436.     IF SymTblOfs& \ 2 > UBOUND(SYMTBL) - 2 THEN
  1437.         GetID$ = "®QB45BIN:SymbolTableErrorÂŻ"
  1438.         EXIT FUNCTION
  1439.     END IF
  1440.  
  1441.     DEF SEG = VARSEG(SYMTBL(1))
  1442.     Offset = VARPTR(SYMTBL(1))
  1443.  
  1444.     Symbol& = (Offset AND &HFFFF&) + SymTblOfs&
  1445.  
  1446.     SymbolFlags = PEEK(Symbol& + 2)
  1447.  
  1448.     IF SymbolFlags AND 2 THEN
  1449.  
  1450.         ' Short line numbers are stored as integers.
  1451.  
  1452.         NumericID& = PEEK(Symbol& + 4) OR PEEK(Symbol& + 5) * &H100&
  1453.         GetID$ = LTRIM$(STR$(NumericID&))
  1454.     ELSE
  1455.  
  1456.         ' Identifier is a text string - extract it. Note the string may be
  1457.         ' a line number.
  1458.  
  1459.         Length = PEEK(Symbol& + 3)
  1460.  
  1461.         IF SymTblOfs& \ 2 > UBOUND(SYMTBL) - (Length + 1) \ 2 THEN
  1462.             GetID$ = "SymbolTableError"
  1463.             EXIT FUNCTION
  1464.         END IF
  1465.  
  1466.         ID$ = STRING$(Length, CHR$(0))
  1467.         FOR i = 1 TO Length
  1468.             MID$(ID$, i, 1) = CHR$(PEEK(Symbol& + 3 + i))
  1469.         NEXT i
  1470.  
  1471.         GetID$ = ID$
  1472.     END IF
  1473.  
  1474.  
  1475.  
  1476. '
  1477. ' Removes the parse rule id::= from a string and returns its numeric ID.
  1478. '
  1479. FUNCTION GetParseRuleID% (ParseRule AS STRING, TokenID AS INTEGER)
  1480.  
  1481.     '----------------------------------------------------------------------------
  1482.     ' The default rule ID is always the PCODE
  1483.     '----------------------------------------------------------------------------
  1484.  
  1485.     FOR i = 1 TO LEN(ParseRule)
  1486.  
  1487.         IF INSTR("{}|", MID$(ParseRule, i, 1)) THEN EXIT FOR
  1488.  
  1489.         IF MID$(ParseRule, i, 3) = "::=" THEN
  1490.             GetParseRuleID = SetHashedSymbol(LEFT$(ParseRule, i - 1), TokenID)
  1491.             ParseRule = MID$(ParseRule, i + 3)
  1492.             EXIT FUNCTION
  1493.         END IF
  1494.  
  1495.     NEXT i
  1496.  
  1497.     GetParseRuleID = -1
  1498.  
  1499.  
  1500. FUNCTION GetParseRuleLHS$ (ParseRule AS STRING)
  1501.  
  1502.     FOR i = 1 TO LEN(ParseRule)
  1503.  
  1504.         IF INSTR("{}|", MID$(ParseRule, i, 1)) THEN EXIT FOR
  1505.  
  1506.         IF MID$(ParseRule, i, 3) = "::=" THEN
  1507.             GetParseRuleLHS = LEFT$(ParseRule, i - 1)
  1508.             EXIT FUNCTION
  1509.         END IF
  1510.  
  1511.     NEXT i
  1512.  
  1513.  
  1514. FUNCTION GetTaggedItem$ (TagTxt AS STRING, DP AS INTEGER)
  1515.  
  1516.     DIM SubstTxt AS STRING
  1517.  
  1518.     SELECT CASE LCASE$(TagTxt)
  1519.      
  1520.         CASE "blockname": SubstTxt = FetchBlockName(DP)
  1521.         CASE "circle-args": SubstTxt = SubstTagCIRCLE
  1522.         CASE "input-args": SubstTxt = SubstTagINPUT
  1523.         CASE "line-args": SubstTxt = SubstTagLINE
  1524.         CASE "lock-args": SubstTxt = SubstTagLOCK
  1525.  
  1526.         CASE "open-args": SubstTxt = SubstTagOPEN
  1527.         CASE "action-verb": SubstTxt = SubstTagVERB
  1528.         CASE "keymode": SubstTxt = SubstTagKEY
  1529.         CASE "type-abbr": SubstTxt = GetTypeAbbr(HPARAM)
  1530.  
  1531.         CASE "call": SubstTxt = ParseCALL(0)
  1532.         CASE "call()": SubstTxt = ParseCALL(-1)
  1533.  
  1534.         CASE "defxxx": SubstTxt = SubstTagDEFxxx(QBBinDefType())
  1535.         CASE "newline": SubstTxt = MKI$(&H10D)
  1536.  
  1537.         CASE "newline-include": SubstTxt = MKI$(&H20D)
  1538.  
  1539.         CASE "tabh": SubstTxt = MKI$(&HD) + MKI$(HPARAM)
  1540.         CASE "tabi": SubstTxt = MKI$(&HD) + MKI$(FetchINT(DP))
  1541.         CASE "indent": SubstTxt = SPACE$(FetchINT(DP) AND &HFFFF&)
  1542.         CASE "type": SubstTxt = GetTypeName$(FetchINT(DP))
  1543.         CASE "id": SubstTxt = GetID(FetchINT(DP))
  1544.         CASE "id+": SubstTxt = GetID(FetchINT(DP)) + GetTypeSuffix(HPARAM)
  1545.         CASE "id-list": SubstTxt = FetchIDList(DP)
  1546.         CASE "id(decl)": SubstTxt = ParseArrayDecl
  1547.         CASE "id(expr)": SubstTxt = ParseArrayExpr
  1548.  
  1549.         CASE "hprm": SubstTxt = ITOA$(HPARAM)
  1550.         CASE "int": SubstTxt = FetchLiteralINT(DP, 10)
  1551.         CASE "int&h": SubstTxt = FetchLiteralINT(DP, 16)
  1552.         CASE "int&o": SubstTxt = FetchLiteralINT(DP, 8)
  1553.         CASE "label": SubstTxt = FetchID(DP): IF NOT IsLineNumber(SubstTxt) THEN SubstTxt = SubstTxt + ":"
  1554.  
  1555.         CASE "lng": SubstTxt = FetchLiteralLNG(DP, 10)
  1556.         CASE "lng&h": SubstTxt = FetchLiteralLNG(DP, 16)
  1557.         CASE "lng&o": SubstTxt = FetchLiteralLNG(DP, 8)
  1558.         CASE "nul": SubstTxt = ""
  1559.         CASE "sng": SubstTxt = FetchLiteralSNG(DP)
  1560.         CASE "dbl": SubstTxt = FetchLiteralDBL(DP)
  1561.         CASE "qstr": SubstTxt = Quote(FetchRAW(DP))
  1562.         CASE "cstr": SubstTxt = FetchCSTR(DP)
  1563.         CASE "raw": SubstTxt = FetchRAW(DP)
  1564.         CASE "varargs": SubstTxt = ParseVarArgs
  1565.         CASE "optargs":
  1566.         CASE "procdecl": SubstTxt = ParseProcDecl$(DP, 0)
  1567.         CASE "procdecl()": SubstTxt = ParseProcDecl$(DP, -1)
  1568.         CASE "proctype": SubstTxt = QBBinProcedureType
  1569.  
  1570.         CASE "thaddr": SanityCheck DP
  1571.  
  1572.         CASE ELSE:
  1573.             SubstTxt = "®QB45BIN:bad tagÂŻ"
  1574.     END SELECT
  1575.  
  1576.     GetTaggedItem$ = SubstTxt
  1577.  
  1578.  
  1579. FUNCTION GetTotalLines%
  1580.  
  1581.     DIM TotalLines AS LONG
  1582.     DIM IncludeLines AS LONG
  1583.  
  1584.     TotalLines = 0
  1585.     IncludeLines = 0
  1586.  
  1587.     FTell& = LOC(QBBinFile) + 1
  1588.  
  1589.     GET #QBBinFile, 27, SymTblLen%
  1590.     ModuleLOC& = LOC(QBBinFile) + (SymTblLen% AND &HFFFF&) + 1
  1591.  
  1592.     SEEK #QBBinFile, ModuleLOC&
  1593.  
  1594.     DO
  1595.         GET #QBBinFile, , ModuleLen%
  1596.         SEEK #QBBinFile, LOC(QBBinFile) + (ModuleLen% AND &HFFFF&) + 9
  1597.  
  1598.         GET #QBBinFile, , NumTotLines%
  1599.         GET #QBBinFile, , NumIncLines%
  1600.  
  1601.         TotalLines = TotalLines + (NumTotLines% AND &HFFFF&)
  1602.         IncludeLines = IncludeLines + (NumIncLines% AND &HFFFF&)
  1603.  
  1604.  
  1605.         SEEK #QBBinFile, LOC(QBBinFile) + 5
  1606.         Byte$ = CHR$(0)
  1607.         GET #QBBinFile, , Byte$
  1608.  
  1609.         IF EOF(QBBinFile) THEN EXIT DO
  1610.  
  1611.         ProcedureCOUNT = ProcedureCOUNT + 1
  1612.  
  1613.         GET #QBBinFile, , NameLen%
  1614.         SEEK #QBBinFile, LOC(QBBinFile) + (NameLen% AND &HFFFF&) + 4
  1615.  
  1616.     LOOP
  1617.  
  1618.     REDIM ProcedureNAME(1 TO ProcedureCOUNT + 1) AS STRING
  1619.     REDIM ProcedureLOC(1 TO ProcedureCOUNT + 1) AS LONG
  1620.  
  1621.     SEEK #QBBinFile, ModuleLOC&
  1622.  
  1623.     FOR i = 1 TO ProcedureCOUNT
  1624.  
  1625.         GET #QBBinFile, , ModuleLen%
  1626.  
  1627.         ProcedureLOC(i) = LOC(QBBinFile) + (ModuleLen% AND &HFFFF&) + 17
  1628.         SEEK #QBBinFile, ProcedureLOC(i) + 1
  1629.  
  1630.         GET #QBBinFile, , ProcedureNameLEN%
  1631.         ProcedureNAME(i) = STRING$(ProcedureNameLEN%, 0)
  1632.         GET #QBBinFile, , ProcedureNAME(i)
  1633.         ProcedureNAME(i) = UCASE$(ProcedureNAME(i))
  1634.  
  1635.         '------------------------------------------------------------------------
  1636.         ' Incremental bubble sort of procedure names
  1637.         '------------------------------------------------------------------------
  1638.         IF QBBinOption.SortProceduresAZ THEN
  1639.             FOR j = i - 1 TO 1 STEP -1
  1640.                 IF ProcedureNAME(j + 1) > ProcedureNAME(j) THEN EXIT FOR
  1641.                 SWAP ProcedureNAME(j + 1), ProcedureNAME(j)
  1642.                 SWAP ProcedureLOC(j + 1), ProcedureLOC(j)
  1643.             NEXT j
  1644.         END IF
  1645.  
  1646.         SEEK #QBBinFile, LOC(QBBinFile) + 4
  1647.     NEXT i
  1648.  
  1649.     FOR i = 1 TO ProcedureCOUNT
  1650.         'PRINT ProcedureNAME(i)
  1651.         QBBinProcedureIndex = QBBinProcedureIndex + MKL$(ProcedureLOC(i))
  1652.     NEXT i
  1653.  
  1654.     ERASE ProcedureNAME, ProcedureLOC
  1655.  
  1656.     SEEK #QBBinFile, FTell&
  1657.  
  1658.     IF QBBinOption.OmitIncludedLines THEN
  1659.         GetTotalLines = TotalLines - IncludedLines
  1660.     ELSE
  1661.         GetTotalLines = TotalLines
  1662.     END IF
  1663.  
  1664.  
  1665. '
  1666. ' Returns the abbreviated name for a built-in type (ie: LNG or DBL).
  1667. '
  1668. FUNCTION GetTypeAbbr$ (TypeID AS INTEGER)
  1669.  
  1670.     GetTypeAbbr$ = TypeSpecifiers(LIMIT(TypeID, 0, 5), 2)
  1671.  
  1672.  
  1673. FUNCTION GetTypeName$ (TypeID AS INTEGER)
  1674.  
  1675.     LTypeID& = TypeID AND &HFFFF&
  1676.  
  1677.     IF LTypeID& > 5 THEN
  1678.         GetTypeName$ = GetID$(TypeID) ' User-define type
  1679.     ELSE
  1680.         GetTypeName$ = TypeSpecifiers(LTypeID&, 1)
  1681.     END IF
  1682.  
  1683.  
  1684. FUNCTION GetTypeSuffix$ (TypeID AS INTEGER)
  1685.  
  1686.     GetTypeSuffix$ = TypeSpecifiers(LIMIT(TypeID, 0, 5), 3)
  1687.  
  1688.  
  1689. '
  1690. ' Implementation of PJW hash, written to avoid 32-bit overflow.
  1691. '
  1692. FUNCTION HashPJW% (Identifier AS STRING)
  1693.  
  1694.     DIM h AS LONG, g AS LONG, k AS LONG
  1695.  
  1696.  
  1697.     FOR i = 1 TO LEN(Identifier)
  1698.  
  1699.         k = ASC(MID$(Identifier, i, 1))
  1700.  
  1701.         h = h + (k \ 16)
  1702.  
  1703.         g = (h AND &HF000000) \ 2 ^ 20
  1704.  
  1705.         h = (h AND &HFFFFFF) * 16 + (k AND 15)
  1706.  
  1707.         IF g THEN h = h XOR (g \ 2 ^ 20)
  1708.  
  1709.     NEXT i
  1710.  
  1711.     HashPJW% = h MOD SymbolHashBuckets
  1712.  
  1713.  
  1714. FUNCTION IsLineNumber (ID AS STRING)
  1715.  
  1716.     Ch$ = LEFT$(ID, 1)
  1717.     IF Ch$ = "" THEN EXIT FUNCTION
  1718.     IF ASC(Ch$) >= 48 AND ASC(Ch$) < 57 THEN IsLineNumber = -1
  1719.  
  1720.  
  1721. FUNCTION ITOA$ (Value AS INTEGER)
  1722.  
  1723.     ITOA$ = LTRIM$(RTRIM$(STR$(Value)))
  1724.  
  1725.  
  1726. FUNCTION LIMIT (x, xMin, xMax)
  1727.  
  1728.     IF x < xMin THEN
  1729.         LIMIT = xMin
  1730.  
  1731.     ELSEIF x > xMax THEN
  1732.         LIMIT = xMax
  1733.  
  1734.     ELSE
  1735.         LIMIT = x
  1736.     END IF
  1737.  
  1738.  
  1739. FUNCTION LoadMainModule
  1740.  
  1741.     '----------------------------------------------------------------------------
  1742.     ' Read module size and convert to long to lose sign bit. Note that modules
  1743.     ' should always be a multiple of two in size since all the tokens are 16
  1744.     ' bits.
  1745.     '----------------------------------------------------------------------------
  1746.     IF EOF(QBBinFile) THEN EXIT FUNCTION
  1747.  
  1748.     GET #QBBinFile, , szModule%
  1749.     szModule& = (szModule% AND &HFFFF&)
  1750.     szModule% = (szModule& + 1) \ 2
  1751.  
  1752.     REDIM CODE(1 TO szModule%) AS INTEGER
  1753.     ReadToArrayINT QBBinFile, CODE(), szModule&
  1754.  
  1755.     '----------------------------------------------------------------------------
  1756.     ' There is always 16 bytes of data after a code block
  1757.     '----------------------------------------------------------------------------
  1758.     DIM Footer AS STRING * 16
  1759.     GET #QBBinFile, , Footer
  1760.  
  1761.     IF EOF(QBBinFile) THEN
  1762.         QBBinCloseFile
  1763.         EXIT FUNCTION
  1764.     END IF
  1765.  
  1766.     LoadMainModule = -1
  1767.     IP = LBOUND(CODE)
  1768.  
  1769.  
  1770. FUNCTION LoadNextProcedure
  1771.  
  1772.  
  1773.     IF QBBinProcedureIndex = "" THEN
  1774.         QBBinCloseFile
  1775.         EXIT FUNCTION
  1776.     END IF
  1777.  
  1778.    
  1779.    
  1780.     ProcedureLOC& = CVL(LEFT$(QBBinProcedureIndex, 4))
  1781.     QBBinProcedureIndex = MID$(QBBinProcedureIndex, 5)
  1782.     SEEK #QBBinFile, ProcedureLOC&
  1783.  
  1784.     DIM Junk AS STRING
  1785.  
  1786.  
  1787.  
  1788.     Junk = CHR$(0)
  1789.     GET #QBBinFile, , Junk
  1790.  
  1791.     IF EOF(QBBinFile) THEN
  1792.         QBBinCloseFile
  1793.         EXIT FUNCTION
  1794.     END IF
  1795.    
  1796.     GET #QBBinFile, , ProcNameLen%
  1797.  
  1798.     QBBinProcedureName = STRING$(ProcNameLen% AND &HFFFF&, 0)
  1799.     GET #QBBinFile, , QBBinProcedureName
  1800.     Junk = STRING$(3, 0)
  1801.     GET #QBBinFile, , Junk
  1802.  
  1803.     GET #QBBinFile, , ProcCodeLen%
  1804.  
  1805.     ReadToArrayINT QBBinFile, CODE(), ProcCodeLen% AND &HFFFF&
  1806.  
  1807.     DIM Footer AS STRING * 16
  1808.     GET #QBBinFile, , Footer
  1809.  
  1810.     LoadNextProcedure = -1
  1811.     IP = LBOUND(CODE)
  1812.  
  1813.  
  1814. SUB LoadParseRules
  1815.  
  1816.     DIM ParseRule AS STRING
  1817.  
  1818.     TokenLBound = &H7FFF
  1819.     TokenUBound = 0
  1820.     TokenLength = 0
  1821.  
  1822.     '----------------------------------------------------------------------------
  1823.     ' Clear the symbol hash table
  1824.     '----------------------------------------------------------------------------
  1825.     FOR i = 0 TO SymbolHashBuckets - 1: SymbolHashTable(i) = "": NEXT i
  1826.     SymbolHashEntries = 0
  1827.  
  1828.     '----------------------------------------------------------------------------
  1829.     ' PASS 1: Enumerate all tokens
  1830.     '----------------------------------------------------------------------------
  1831.     RestoreParseRules
  1832.  
  1833.     DO WHILE ReadParseRule(TokenPCODE, TokenLength, ParseRule)
  1834.  
  1835.         TokenLBound = MIN(TokenPCODE, TokenLBound)
  1836.         TokenUBound = MAX(TokenPCODE, TokenLBound)
  1837.  
  1838.     LOOP
  1839.  
  1840.     REDIM ParseRules(TokenLBound TO TokenUBound) AS STRING
  1841.  
  1842.  
  1843.     '----------------------------------------------------------------------------
  1844.     ' PASS 2: Generate token strings
  1845.     '----------------------------------------------------------------------------
  1846.     RestoreParseRules
  1847.  
  1848.     DO WHILE ReadParseRule(TokenPCODE, TokenLength, ParseRule)
  1849.  
  1850.         '------------------------------------------------------------------------
  1851.         ' If this is the first rule for this PCODE, then we'll write the
  1852.         ' length of the token data as the first word.
  1853.         '------------------------------------------------------------------------
  1854.         IF ParseRules(TokenPCODE) = "" THEN
  1855.             ParseRules(TokenPCODE) = MKI$(TokenLength)
  1856.         END IF
  1857.  
  1858.         RuleID = GetParseRuleID(ParseRule, TokenPCODE)
  1859.         IF RuleID = -1 THEN RuleID = TokenPCODE
  1860.  
  1861.         ParseRule = MKI$(LEN(ParseRule)) + MKI$(RuleID) + ParseRule
  1862.         ParseRules(TokenPCODE) = ParseRules(TokenPCODE) + ParseRule
  1863.  
  1864.     LOOP
  1865.  
  1866.     QBBinTok.SUBDEF = GetHashedSymbol("subdef")
  1867.     QBBinTok.FUNCDEF = GetHashedSymbol("funcdef")
  1868.     QBBinTok.DEFTYPE = GetHashedSymbol("deftype")
  1869.  
  1870.  
  1871. '
  1872. ' Returns the token id of the next unprocessed token without modifying IP.
  1873. ' Neccessary for REDIM, which causes an array expression to behave like
  1874. ' an array declaration, for reasons best known to the QB45 dev team.
  1875. '
  1876. FUNCTION LookAhead
  1877.  
  1878.  
  1879.     IF IP < LBOUND(CODE) OR IP > UBOUND(CODE) THEN
  1880.         LookAhead = -1
  1881.     ELSE
  1882.         LookAhead = CODE(IP) AND &H3FF
  1883.     END IF
  1884.  
  1885.  
  1886. FUNCTION LTOA$ (Value AS LONG)
  1887.  
  1888.     LTOA$ = LTRIM$(RTRIM$(STR$(Value)))
  1889.  
  1890.  
  1891.  
  1892.     IF x > Y THEN MAX = x ELSE MAX = Y
  1893.  
  1894.  
  1895.  
  1896.     IF x < Y THEN MIN = x ELSE MIN = Y
  1897.  
  1898.  
  1899. FUNCTION ParseArrayDecl$
  1900.  
  1901.     STATIC RuleIDLoaded AS INTEGER
  1902.     STATIC RuleAsTypeID AS INTEGER
  1903.     STATIC RuleDeclID AS INTEGER
  1904.     STATIC RuleDeclsID AS INTEGER
  1905.  
  1906.     IF NOT RuleIDLoaded THEN
  1907.         RuleAsTypeID = GetHashedSymbol("astype")
  1908.         RuleDeclID = GetHashedSymbol("decl")
  1909.         RuleDeclsID = GetHashedSymbol("decls")
  1910.     END IF
  1911.  
  1912.  
  1913.     nElmts = FetchINT(0)
  1914.     ID$ = FetchID(2) + GetTypeSuffix(HPARAM)
  1915.  
  1916.     IF StackPeek(0) = RuleAsTypeID THEN
  1917.         ArgC = 1
  1918.         AsType$ = "{0}"
  1919.     END IF
  1920.  
  1921.     WHILE nElmts > 0
  1922.  
  1923.         nElmts = nElmts - 1
  1924.  
  1925.         Indices$ = STAG(ArgC) + Indices$
  1926.         ArgC = ArgC + 1
  1927.  
  1928.         IF nElmts AND 1 THEN
  1929.             IF StackPeek(ArgC) <> &H18 THEN Indices$ = " TO " + Indices$
  1930.         ELSE
  1931.             IF nElmts THEN Indices$ = ", " + Indices$
  1932.         END IF
  1933.  
  1934.     WEND
  1935.  
  1936.     IF Indices$ <> "" THEN Indices$ = "(" + Indices$ + ")"
  1937.  
  1938.     IF StackPeek(ArgC) = RuleDeclsID THEN
  1939.         ParseArrayDecl$ = STAG(ArgC) + ", " + ID$ + Indices$ + AsType$
  1940.     ELSEIF StackPeek(ArgC) = RuleDeclID THEN
  1941.         ParseArrayDecl$ = STAG(ArgC) + " " + ID$ + Indices$ + AsType$
  1942.     ELSE
  1943.         ParseArrayDecl$ = ID$ + Indices$ + AsType$
  1944.     END IF
  1945.  
  1946.  
  1947. '
  1948. ' Generates a parse rule for an array expression.
  1949. '
  1950. FUNCTION ParseArrayExpr$
  1951.  
  1952.     IF LookAhead = 28 THEN
  1953.         ParseArrayExpr = ParseArrayDecl
  1954.         EXIT FUNCTION
  1955.     END IF
  1956.  
  1957.     'IF PCODE = 15 THEN ArgC = 1
  1958.  
  1959.     nElmts = FetchINT(0)
  1960.     ID$ = FetchID(2) + GetTypeSuffix(HPARAM)
  1961.  
  1962.     IF NOT nElmts AND &H8000 THEN
  1963.  
  1964.         FOR i = nElmts - 1 TO 0 STEP -1
  1965.  
  1966.             IF i THEN
  1967.                 Indices$ = ", " + STAG(ArgC) + Indices$
  1968.             ELSE
  1969.                 Indices$ = STAG(ArgC) + Indices$
  1970.             END IF
  1971.  
  1972.             ArgC = ArgC + 1
  1973.  
  1974.         NEXT i
  1975.  
  1976.         Indices$ = "(" + Indices$ + ")"
  1977.  
  1978.     END IF
  1979.  
  1980.     ParseArrayExpr = ID$ + Indices$
  1981.  
  1982.  
  1983. '
  1984. ' Generates parse rule fragment for a procedure call
  1985. '
  1986. FUNCTION ParseCALL$ (Parenthesis AS INTEGER)
  1987.  
  1988.     ArgC = FetchINT(0)
  1989.  
  1990.     FOR ArgI = 0 TO ArgC - 1
  1991.  
  1992.         IF ArgI THEN
  1993.             ArgV$ = STAG(ArgI) + ", " + ArgV$
  1994.         ELSE
  1995.             ArgV$ = STAG(ArgI) + ArgV$
  1996.         END IF
  1997.  
  1998.     NEXT ArgI
  1999.  
  2000.     IF ArgC > 0 THEN
  2001.         IF Parenthesis THEN ArgV$ = "(" + ArgV$ + ")" ELSE ArgV$ = " " + ArgV$
  2002.     END IF
  2003.  
  2004.     ParseCALL$ = ArgV$
  2005.  
  2006.  
  2007. '
  2008. ' This helper function parses a SUB or FUNCTION declaration, or a
  2009. ' SUB/FUNCTION/DEF FN definition.
  2010. '
  2011. FUNCTION ParseProcDecl$ (DP AS INTEGER, Parenthesis AS INTEGER)
  2012.  
  2013.     DIM Flags AS LONG
  2014.     DIM ArgC AS LONG
  2015.  
  2016.     CONST fCDECL = &H8000
  2017.     CONST fALIAS = &H400
  2018.  
  2019.     ID$ = GetID(FetchINT(DP + 0))
  2020.     Flags = FetchINTASLONG(DP + 2)
  2021.     ArgC = FetchINTASLONG(DP + 4)
  2022.  
  2023.     LenALIAS = Flags \ &H400 AND &H1F
  2024.  
  2025.     IF Flags AND &H80 THEN TS$ = GetTypeSuffix(Flags AND 7)
  2026.     Arguments$ = ""
  2027.  
  2028.     ProcType = (Flags AND &H300) \ 256
  2029.  
  2030.     SELECT CASE ProcType
  2031.         CASE 1: ID$ = "SUB " + ID$ + TS$: QBBinProcedureType = "SUB"
  2032.         CASE 2: ID$ = "FUNCTION " + ID$ + TS$: QBBinProcedureType = "FUNCTION"
  2033.         CASE 3: ID$ = "DEF " + ID$ + TS$: QBBinProcedureType = "DEF"
  2034.     END SELECT
  2035.  
  2036.  
  2037.     '
  2038.     ' Process arguments list
  2039.     '
  2040.     FOR ArgI = 1 TO ArgC
  2041.  
  2042.         ArgName$ = GetID(FetchINT(DP + ArgI * 6 + 0))
  2043.         ArgFlags = FetchINT(DP + ArgI * 6 + 2)
  2044.         ArgType = FetchINT(DP + ArgI * 6 + 4)
  2045.  
  2046.         '------------------------------------------------------------------------
  2047.         ' Process special argument flags. Not all of these can be combined,
  2048.         ' but we'll just assume the file contains a valid combination.
  2049.         '------------------------------------------------------------------------
  2050.         IF ArgFlags AND &H200 THEN ArgName$ = ArgName$ + GetTypeSuffix(ArgType)
  2051.         IF ArgFlags AND &H400 THEN ArgName$ = ArgName$ + "()"
  2052.         IF ArgFlags AND &H800 THEN ArgName$ = "SEG " + ArgName$
  2053.         IF ArgFlags AND &H1000 THEN ArgName$ = "BYVAL " + ArgName$
  2054.         IF ArgFlags AND &H2000 THEN ArgName$ = ArgName$ + " AS " + GetTypeName(ArgType)
  2055.  
  2056.         IF ArgI = 1 THEN
  2057.             Arguments$ = ArgName$
  2058.         ELSE
  2059.             Arguments$ = Arguments$ + ", " + ArgName$
  2060.         END IF
  2061.  
  2062.     NEXT ArgI
  2063.  
  2064.     IF Parenthesis OR Arguments$ <> "" THEN Arguments$ = " (" + Arguments$ + ")"
  2065.  
  2066.  
  2067.     '
  2068.     ' Process CDECL and ALIAS modifiers
  2069.     '
  2070.     IF Flags AND fCDECL THEN ID$ = ID$ + " CDECL"
  2071.  
  2072.     AliasName$ = LEFT$(FetchRAW(DP + ArgI * 6), LenALIAS)
  2073.     IF LenALIAS THEN ID$ = ID$ + " ALIAS " + AliasName$
  2074.  
  2075.     ParseProcDecl$ = ID$ + Arguments$
  2076.  
  2077.  
  2078. '
  2079. '
  2080. '
  2081. FUNCTION ParseVarArgs$
  2082.  
  2083.     ArgC = FetchINT(0)
  2084.  
  2085.     STATIC NULARG
  2086.  
  2087.     IF NULARG = 0 THEN NULARG = GetHashedSymbol("nularg")
  2088.  
  2089.     FOR ArgI = 0 TO ArgC - 1
  2090.  
  2091.         IF StackPeek(ArgI) <> NULARG THEN ArgV$ = ", " + ArgV$
  2092.  
  2093.         ArgV$ = STAG(ArgI) + ArgV$
  2094.  
  2095.     NEXT ArgI
  2096.  
  2097.  
  2098.     '----------------------------------------------------------------------------
  2099.     ' Trim trailing commas
  2100.     '----------------------------------------------------------------------------
  2101.     FOR i = LEN(ArgV$) TO 1 STEP -1
  2102.         Ch$ = MID$(ArgV$, i, 1)
  2103.         IF Ch$ <> " " AND Ch$ <> "," THEN EXIT FOR
  2104.     NEXT i
  2105.  
  2106.     ArgV$ = LEFT$(ArgV$, i)
  2107.  
  2108.     IF ArgV$ <> "" THEN ArgV$ = " " + ArgV$
  2109.  
  2110.     ParseVarArgs$ = ArgV$
  2111.  
  2112.  
  2113.  
  2114.     IF SP = LBOUND(STACK) THEN EXIT FUNCTION
  2115.  
  2116.     POP$ = MID$(STACK(SP), 3)
  2117.     SP = SP - 1
  2118.  
  2119.  
  2120. '
  2121. ' The following special codes may be embedded in a string:
  2122. '
  2123. ' 0xccnn0D      - RLE encoding (used by QB45 comments)
  2124. ' 0xnnnn000D    - Indentation marker
  2125. ' 0x101D        - NEWLINE marker 1
  2126. ' 0x201D        - NEWLINE marker 2
  2127. '
  2128. SUB PostProcess
  2129.  
  2130.     DIM OutText AS STRING
  2131.     DIM OutTxt AS STRING
  2132.     DIM Marker AS LONG
  2133.     DIM LineColumn AS LONG
  2134.     DIM OffsetFromNewline AS LONG
  2135.     DIM TextBegin AS LONG
  2136.  
  2137.     TextBegin = 1
  2138.  
  2139.     DO
  2140.         '------------------------------------------------------------------------
  2141.         ' Look for special symbol marker
  2142.         '------------------------------------------------------------------------
  2143.         Marker = INSTR(TextBegin, STACK(0), CHR$(&HD))
  2144.         IF Marker = 0 THEN Marker = LEN(STACK(0)) + 1
  2145.  
  2146.         '------------------------------------------------------------------------
  2147.         ' Copy leading text to output string
  2148.         '------------------------------------------------------------------------
  2149.         OutTxt = OutTxt + MID$(STACK(0), TextBegin, Marker - TextBegin)
  2150.         IF Marker > LEN(STACK(0)) THEN
  2151.             TextBegin = Marker
  2152.             EXIT DO
  2153.         END IF
  2154.  
  2155.         OffsetFromNewline = OffsetFromNewline + Marker - TextBegin
  2156.  
  2157.         SELECT CASE MID$(STACK(0), Marker + 1, 1)
  2158.        
  2159.             CASE CHR$(0):
  2160.                 '----------------------------------------------------------------
  2161.                 ' Indentation
  2162.                 '----------------------------------------------------------------
  2163.                 RunLn& = CVI(MID$(STACK(0), Marker + 2)) AND &HFFFF&
  2164.                 RunLn& = RunLn& - CLNG(OffsetFromNewline)
  2165.  
  2166.                 IF (RunLn& < 0) THEN RunLn& = 1
  2167.  
  2168.                 OffsetFromNewline = OffsetFromNewline + RunLn&
  2169.                 OutTxt = OutTxt + SPACE$(RunLn&)
  2170.                 TextBegin = Marker + 4
  2171.        
  2172.             CASE CHR$(1):
  2173.                 '----------------------------------------------------------------
  2174.                 ' Newline
  2175.                 '----------------------------------------------------------------
  2176.                 IF FlushToOutput THEN EXIT DO
  2177.                 DiscardLine = 0
  2178.                 FlushToOutput = -1
  2179.                 OffsetFromNewline = 0
  2180.                 TextBegin = Marker + 2
  2181.  
  2182.             CASE CHR$(2):
  2183.                 '----------------------------------------------------------------
  2184.                 ' Newline - $INCLUDEd file
  2185.                 '----------------------------------------------------------------
  2186.                 DiscardLine = QBBinOption.OmitIncludedLines
  2187.                          
  2188.                 FlushToOutput = -1
  2189.                 OffsetFromNewline = 0
  2190.                 TextBegin = Marker + 2
  2191.  
  2192.             CASE ELSE:
  2193.                 '----------------------------------------------------------------
  2194.                 ' RLE encoded comment
  2195.                 '----------------------------------------------------------------
  2196.                 RunLn& = ASC(MID$(STACK(0), Marker + 1))
  2197.                 RunCh$ = MID$(STACK(0), Marker + 2)
  2198.  
  2199.                 OutTxt = OutTxt + STRING$(RunLn&, RunCh$)
  2200.  
  2201.                 OffsetFromNewline = OffsetFromNewline + RunLn&
  2202.                 TextBegin = Marker + 3
  2203.    
  2204.         END SELECT
  2205.  
  2206.     LOOP
  2207.  
  2208.     IF FlushToOutput THEN
  2209.         IF OutTxt <> SPACE$(LEN(OutTxt)) THEN OutTxt = RTRIM$(OutTxt)
  2210.         QBBinProgramLine = OutTxt
  2211.         QBBinLineReady = NOT DiscardLine
  2212.    
  2213.         OutTxt = ""
  2214.     END IF
  2215.  
  2216.     STACK(0) = OutTxt + MID$(STACK(0), Marker)
  2217.  
  2218.  
  2219. SUB ProcessProcDefType
  2220.  
  2221.     ' Procedure DEFTYPE defaults to SINGLE
  2222.  
  2223.     DIM ProcDefType(1 TO 26) AS INTEGER
  2224.     DIM OutTxt AS STRING
  2225.  
  2226.     FOR i = 1 TO 26: ProcDefType(i) = 3: NEXT i
  2227.  
  2228.     DO WHILE LookAhead = 0
  2229.         IF NOT ReadToken THEN EXIT SUB
  2230.  
  2231.         IF LookAhead <> QBBinTok.DEFTYPE THEN
  2232.             IP = IP - 1
  2233.             EXIT DO
  2234.         END IF
  2235.    
  2236.         IF NOT ReadToken THEN EXIT DO
  2237.  
  2238.         UnwantedReturnValue$ = SubstTagDEFxxx(ProcDefType())
  2239.    
  2240.     LOOP
  2241.  
  2242.     'FOR i = 1 TO 26: PRINT GetTypeSuffix(ProcDefType(i)); : NEXT i: PRINT
  2243.  
  2244.     'PRINT QBBinProcedureName
  2245.  
  2246.     FOR i = 1 TO 5
  2247.    
  2248.         'IF i = 3 THEN i = i + 1
  2249.  
  2250.         AnythingOutput = 0
  2251.         InitialLetter = 0
  2252.         OutTxt = ""
  2253.  
  2254.         FOR j = 1 TO 27
  2255.  
  2256.  
  2257.             BITSET = 0
  2258.  
  2259.             IF j < 27 THEN
  2260.                 BITSET = ProcDefType(j) = i
  2261.                 BITSET = BITSET AND QBBinDefType(j) <> i
  2262.             END IF
  2263.  
  2264.             IF BITSET AND InitialLetter = 0 THEN
  2265.  
  2266.                 InitialLetter = j + 64
  2267.  
  2268.             ELSEIF InitialLetter AND NOT BITSET THEN
  2269.  
  2270.                 IF AnythingOutput THEN OutTxt = OutTxt + ", "
  2271.  
  2272.                 OutTxt = OutTxt + CHR$(InitialLetter)
  2273.  
  2274.                 Range = j + 64 - InitialLetter
  2275.                 IF Range > 1 THEN OutTxt = OutTxt + "-" + CHR$(j + 63)
  2276.  
  2277.                 AnythingOutput = -1
  2278.                 InitialLetter = 0
  2279.             END IF
  2280.         NEXT j
  2281.  
  2282.         IF AnythingOutput THEN
  2283.             PUSH 0, MKI$(&H10D)
  2284.             PUSH QBBinTok.DEFTYPE, "DEF" + GetTypeAbbr(i) + " " + OutTxt
  2285.             FlushStack
  2286.         END IF
  2287.    
  2288.     NEXT i
  2289.  
  2290.     FOR i = 1 TO 26: QBBinDefType(i) = ProcDefType(i): NEXT i
  2291.  
  2292.  
  2293. FUNCTION ProcessToken
  2294.  
  2295.     ProcessToken = 0
  2296.     IF NOT ReadToken THEN EXIT FUNCTION
  2297.  
  2298.     IF PCODE = 8 THEN EXIT FUNCTION
  2299.  
  2300.     ProcessToken = -1
  2301.     DefaultParseRule
  2302.  
  2303.  
  2304. SUB PUSH (ID AS INTEGER, Txt AS STRING)
  2305.  
  2306.     IF SP = UBOUND(STACK) THEN EXIT SUB
  2307.  
  2308.     SP = SP + 1
  2309.     STACK(SP) = MKI$(ID) + Txt
  2310.  
  2311.  
  2312. SUB QBBinCloseFile
  2313.  
  2314.     CLOSE #QBBinFile
  2315.     QBBinFile = 0
  2316.     QBBinEOF = -1
  2317.  
  2318.  
  2319. DEFSNG A-Z
  2320. FUNCTION QBBinGetFileType
  2321.  
  2322.  
  2323. DEFINT A-Z
  2324. '
  2325. FUNCTION QBBinGetProcName$
  2326.  
  2327.  
  2328. SUB QBBinOpenFile (FileName AS STRING)
  2329.  
  2330.     QBBinFile = FREEFILE
  2331.     QBBinEOF = 0
  2332.  
  2333.     OPEN FileName FOR BINARY AS #QBBinFile
  2334.  
  2335.     GET #QBBinFile, , Magic%
  2336.     GET #QBBinFile, , Version%
  2337.  
  2338.     '----------------------------------------------------------------------------
  2339.     ' Only QB45 is currently supported
  2340.     '----------------------------------------------------------------------------
  2341.     IF (Magic% <> &HFC) OR (Version% <> 1) THEN
  2342.         RESET
  2343.         PRINT "ERROR: The file you provided does not have a valid QB45 header."
  2344.         SYSTEM 1
  2345.     END IF
  2346.  
  2347.     ' Don't delete this - alpha sorter needs it!
  2348.     x = GetTotalLines
  2349.  
  2350.     '----------------------------------------------------------------------------
  2351.     ' Read symbol table size and convert to long to lose sign bit
  2352.     '----------------------------------------------------------------------------
  2353.     GET #QBBinFile, 27, szSymTbl%
  2354.     szSymTbl& = szSymTbl% AND &HFFFF&
  2355.  
  2356.     '----------------------------------------------------------------------------
  2357.     ' Load symbol table to memory and return file number
  2358.     '----------------------------------------------------------------------------
  2359.     REDIM SYMTBL(1 TO (szSymTbl& + 1) \ 2) AS INTEGER
  2360.     ReadToArrayINT QBBinFile, SYMTBL(), szSymTbl&
  2361.  
  2362.     IF NOT LoadMainModule THEN EXIT SUB
  2363.  
  2364.     '----------------------------------------------------------------------------
  2365.     ' If main module is empty, look for non-empty procedure
  2366.     '----------------------------------------------------------------------------
  2367.     WHILE CODE(IP) = 8
  2368.         IF NOT LoadNextProcedure THEN EXIT SUB
  2369.     WEND
  2370.  
  2371.  
  2372. FUNCTION QBBinReadLine$ (Meta AS LONG)
  2373.  
  2374.  
  2375.     STATIC NewProc
  2376.  
  2377.     Meta = 0
  2378.  
  2379.     PostProcess
  2380.  
  2381.     IF QBBinLineReady THEN
  2382.         QBBinReadLine = QBBinProgramLine
  2383.         QBBinLineReady = 0
  2384.         QBBinProgramLine = ""
  2385.         EXIT FUNCTION
  2386.     END IF
  2387.  
  2388.     IF QBBinEOF THEN EXIT FUNCTION
  2389.  
  2390.     DO
  2391.         IF NoMoreTokens THEN
  2392.             QBBinCloseFile
  2393.             EXIT FUNCTION
  2394.         END IF
  2395.  
  2396.         IF NOT ReadToken THEN EXIT FUNCTION
  2397.         DefaultParseRule
  2398.  
  2399.         '------------------------------------------------------------------------
  2400.         ' Trap some special tokens
  2401.         '------------------------------------------------------------------------
  2402.         SELECT CASE PCODE
  2403.                                                                        
  2404.             '------------------------------------------------------------------------
  2405.             ' Token 0x008 appears at the end of the code (before the watch list)
  2406.             '------------------------------------------------------------------------
  2407.             CASE 8:
  2408.                 IF NOT LoadNextProcedure THEN
  2409.                     NoMoreTokens = -1
  2410.                 ELSE
  2411.                     PUSH 0, MKI$(&H10D) ' Force blank line before SUB/FUNCTION
  2412.                     ProcessProcDefType
  2413.                     NewProc = -1
  2414.  
  2415.            
  2416.                     'ProcessProcDefType
  2417.  
  2418.                 END IF
  2419.  
  2420.                 'END SELECT
  2421.  
  2422.                 'SELECT CASE StackPeek(0)
  2423.        
  2424.             CASE QBBinTok.SUBDEF: Meta = QBBinMeta.SUB
  2425.             CASE QBBinTok.FUNCDEF: Meta = QBBinMeta.FUNCTION
  2426.  
  2427.         END SELECT
  2428.  
  2429.         PostProcess
  2430.  
  2431.     LOOP WHILE NOT QBBinLineReady
  2432.  
  2433.     QBBinReadLine = QBBinProgramLine
  2434.     QBBinLineReady = 0
  2435.     QBBinProgramLine = ""
  2436.  
  2437.  
  2438. SUB QBBinSetOption (OptionName AS STRING, OptionValue AS INTEGER)
  2439.  
  2440. FUNCTION Quote$ (Txt AS STRING)
  2441.  
  2442.     Quote$ = CHR$(34) + Txt + CHR$(34)
  2443.  
  2444.  
  2445. FUNCTION ReadKey$
  2446.     DO: LOOP WHILE INKEY$ <> ""
  2447.     DO: Key$ = INKEY$: LOOP WHILE Key$ = ""
  2448.  
  2449.     ReadKey = UCASE$(Key$)
  2450.  
  2451.  
  2452. FUNCTION ReadParseRule (TokenID AS INTEGER, OpLen AS INTEGER, ParseRule AS STRING)
  2453.  
  2454.     '------------------------------------------------------------------------
  2455.     ' Ugh... static. I'm being lazy.
  2456.     '------------------------------------------------------------------------
  2457.     STATIC RuleItem AS STRING
  2458.     STATIC DefaultRuleID AS STRING
  2459.  
  2460.     '------------------------------------------------------------------------
  2461.     ' If RuleItem isn't empty, extract the next rule.
  2462.     '------------------------------------------------------------------------
  2463.     IF RuleItem <> "" THEN
  2464.         ParseRule = DelimitParseRule(RuleItem, DefaultRuleID)
  2465.         ReadParseRule = -1
  2466.         EXIT FUNCTION
  2467.     END IF
  2468.  
  2469.     ReadParseRule = 0
  2470.  
  2471.     READ RuleItem
  2472.  
  2473.     '------------------------------------------------------------------------
  2474.     ' Loop until we have something which isn't the .default directive
  2475.     '------------------------------------------------------------------------
  2476.     WHILE MID$(RuleItem, 1, 8) = ".default"
  2477.  
  2478.         DefaultRuleID = LTRIM$(RTRIM$(MID$(RuleItem, 9)))
  2479.         READ RuleItem
  2480.  
  2481.     WEND
  2482.  
  2483.     '------------------------------------------------------------------------
  2484.     ' The rule list is terminated by a period.
  2485.     '------------------------------------------------------------------------
  2486.     IF RuleItem = "." THEN
  2487.         RuleItem = ""
  2488.         DefaultRuleID = ""
  2489.         EXIT FUNCTION
  2490.     END IF
  2491.  
  2492.     '------------------------------------------------------------------------
  2493.     ' If RuleItem is a number, then assume it is the start of a new token.
  2494.     ' Otherwise, we assume it is an additional rule of the previous token.
  2495.     '------------------------------------------------------------------------
  2496.     IF (StringToINT(RuleItem, TokenID)) THEN
  2497.  
  2498.         READ RuleItem
  2499.  
  2500.         '--------------------------------------------------------------------
  2501.         ' If the token length is not omitted, then we need to read again
  2502.         ' to fetch the token parse rule. Also, an asterisk may be used to
  2503.         ' represent a variable length token, so we need to check for that.
  2504.         '--------------------------------------------------------------------
  2505.         IF StringToINT(RuleItem, OpLen) THEN
  2506.             READ RuleItem
  2507.  
  2508.         ELSEIF RuleItem$ = "*" THEN
  2509.             OpLen = -1
  2510.             READ RuleItem
  2511.  
  2512.         ELSE
  2513.             OpLen = 0
  2514.         END IF
  2515.  
  2516.     END IF
  2517.  
  2518.  
  2519.     '------------------------------------------------------------------------
  2520.     ' Extract rule and return
  2521.     '------------------------------------------------------------------------
  2522.     ParseRule = DelimitParseRule(RuleItem, DefaultRuleID)
  2523.     ReadParseRule = -1
  2524.  
  2525.  
  2526. SUB ReadToArrayINT (FileNumber AS INTEGER, Array() AS INTEGER, ByteCount AS LONG)
  2527.  
  2528.     CONST BlockReadSize = 1024 ' must be a multiple of 2
  2529.  
  2530.     IF BlockReadSize AND 1 THEN PRINT "BlockReadSize error.": SYSTEM 1 'ERROR 255
  2531.  
  2532.     DIM i AS LONG
  2533.     DIM BytesToRead AS LONG
  2534.  
  2535.     '----------------------------------------------------------------------------
  2536.     ' REDIM the array if necessary, but keep the lower bound in place
  2537.     '----------------------------------------------------------------------------
  2538.     IF (UBOUND(Array) - LBOUND(Array)) * 2 < ByteCount THEN
  2539.         REDIM Array(LBOUND(Array) TO LBOUND(Array) + (ByteCount + 1) \ 2) AS INTEGER
  2540.     END IF
  2541.  
  2542.     FOR i = 0 TO ByteCount - 1 STEP BlockReadSize
  2543.  
  2544.         BytesToRead = ByteCount - i
  2545.  
  2546.         IF BytesToRead > BlockReadSize THEN BytesToRead = BlockReadSize
  2547.  
  2548.         Buffer$ = STRING$(BytesToRead, 0)
  2549.         GET FileNumber, , Buffer$
  2550.  
  2551.         '------------------------------------------------------------------------
  2552.         ' Copy data from string to integer array (even number of bytes only)
  2553.         '------------------------------------------------------------------------
  2554.         FOR j = 1 TO BytesToRead - 1 STEP 2
  2555.             Index = LBOUND(Array) + i \ 2 + j \ 2
  2556.             Array(Index) = CVI(MID$(Buffer$, j, 2))
  2557.         NEXT j
  2558.  
  2559.         '------------------------------------------------------------------------
  2560.         ' The final block may have had an odd number of bytes
  2561.         '------------------------------------------------------------------------
  2562.         IF BytesToRead AND 1 THEN
  2563.             Index = LBOUND(Array) + i \ 2 + j \ 2
  2564.             Array(Index) = ASC(RIGHT$(Buffer$, 1))
  2565.         END IF
  2566.  
  2567.     NEXT i
  2568.  
  2569.  
  2570.  
  2571. '
  2572. ' Reads a token into the globals PCODE and HPARAM. IP is updated to point
  2573. ' To the next token, and DP points to the start of the token data.
  2574. '
  2575. FUNCTION ReadToken
  2576.  
  2577.     DIM TokLen AS LONG
  2578.  
  2579.     ReadToken = 0
  2580.  
  2581.     IF IP < LBOUND(CODE) OR IP > UBOUND(CODE) THEN EXIT FUNCTION
  2582.  
  2583.     '----------------------------------------------------------------------------
  2584.     ' Fetch basic token information
  2585.     '----------------------------------------------------------------------------
  2586.     TOKEN = MKI$(CODE(IP))
  2587.     PCODE = CODE(IP) AND &H3FF
  2588.     HPARAM = (CODE(IP) AND &HFC00&) \ 1024
  2589.     ReadToken = -1
  2590.  
  2591.  
  2592.     '----------------------------------------------------------------------------
  2593.     ' If the token is outside the known token range, we have a problem.
  2594.     '----------------------------------------------------------------------------
  2595.     IF PCODE < LBOUND(ParseRules) OR PCODE > UBOUND(ParseRules) THEN
  2596.         IP = IP + 1
  2597.         PRINT "Bad token found.": SYSTEM 1 'ERROR QBErrBadToken
  2598.         PCODE = 0: HPARAM = 0: TOKEN = MKI$(0)
  2599.         EXIT FUNCTION
  2600.     END IF
  2601.  
  2602.     '----------------------------------------------------------------------------
  2603.     ' If the token has no information in the parse rules, then we clearly don't
  2604.     ' understand what it does, so increment IP and return. We will try to
  2605.     ' soldier on and parse the rest of the file
  2606.     '----------------------------------------------------------------------------
  2607.     IF ParseRules(PCODE) = "" THEN
  2608.         AOutput "REM ®QB45BINÂŻ Unkown token - " + HEX$(PCODE)
  2609.         IP = IP + 1
  2610.         EXIT FUNCTION
  2611.     END IF
  2612.  
  2613.     '----------------------------------------------------------------------------
  2614.     ' Fetch the token data length from the parse rules to determine if the token
  2615.     ' is fixed or variable length
  2616.     '----------------------------------------------------------------------------
  2617.     IF PCODE >= LBOUND(ParseRules) AND PCODE <= UBOUND(ParseRules) THEN
  2618.         IF LEN(ParseRules(PCODE)) > 2 THEN
  2619.             TokLen = CVI(LEFT$(ParseRules(PCODE), 2)) AND &HFFFF&
  2620.         END IF
  2621.     END IF
  2622.  
  2623.     '----------------------------------------------------------------------------
  2624.     ' If the token is variable length it will be followed by the size word, so
  2625.     ' read it now.
  2626.     '----------------------------------------------------------------------------
  2627.     IF TokLen = &HFFFF& THEN
  2628.         IP = IP + 1
  2629.         TokLen = CODE(IP) AND &HFFFF&
  2630.     END IF
  2631.  
  2632.     '----------------------------------------------------------------------------
  2633.     ' Read the token data into the TOKEN string. Note that due to a bug in QB64,
  2634.     ' we can not use IP as the control variable.
  2635.     '----------------------------------------------------------------------------
  2636.     FOR DP = IP + 1 TO IP + (TokLen + 1) \ 2
  2637.         TOKEN = TOKEN + MKI$(CODE(DP))
  2638.     NEXT DP
  2639.     IP = DP
  2640.  
  2641.     TOKEN = LEFT$(TOKEN, TokLen + 2)
  2642.  
  2643.  
  2644. SUB RestoreParseRules
  2645.  
  2646.     '
  2647.     ' This is so I can change parse rules later if I add QB40 support.
  2648.     '
  2649.     RESTORE QB45TOKENS
  2650.  
  2651.  
  2652. SUB SanityCheck (DP AS INTEGER)
  2653.  
  2654.     DIM ThAddr AS LONG
  2655.  
  2656.     ThAddr = FetchINTASLONG(DP)
  2657.  
  2658.     IF ThAddr = &HFFFF& THEN EXIT SUB
  2659.  
  2660.     ThAddr = ThAddr \ 2 - 1
  2661.  
  2662.     IF ThAddr >= LBOUND(CODE) AND ThAddr <= UBOUND(CODE) - 1 THEN
  2663.  
  2664.         IF (CODE(LBOUND(CODE) + ThAddr) AND &H1FF) = PCODE THEN EXIT SUB
  2665.  
  2666.     END IF
  2667.  
  2668.     'ERROR QBBinErrInsane
  2669.  
  2670. FUNCTION SetHashedSymbol% (ParseRuleSymbol AS STRING, SymbolID AS INTEGER)
  2671.     DIM LookupSymbol AS STRING
  2672.  
  2673.     SymbolName$ = LTRIM$(RTRIM$(ParseRuleSymbol))
  2674.  
  2675.     '----------------------------------------------------------------------------
  2676.     ' Parse rule symbols my be literal integers
  2677.     '----------------------------------------------------------------------------
  2678.     IF StringToINT(SymbolName$, SymbolID%) THEN EXIT FUNCTION
  2679.  
  2680.     Hash = HashPJW(SymbolName$)
  2681.  
  2682.     LookupSymbol = "[" + SymbolName$ + "]"
  2683.  
  2684.     SymbolOffset = INSTR(SymbolHashTable(Hash), LookupSymbol)
  2685.  
  2686.     IF SymbolOffset = 0 THEN
  2687.  
  2688.         SymbolHashTable(Hash) = SymbolHashTable(Hash) + LookupSymbol + MKI$(SymbolID)
  2689.  
  2690.         SetHashedSymbol = SymbolID
  2691.  
  2692.     ELSE
  2693.  
  2694.         SymbolOffset = SymbolOffset + LEN(LookupSymbol)
  2695.  
  2696.         ID$ = MID$(SymbolHashTable(Hash), SymbolOffset, 2)
  2697.         SetHashedSymbol = CVI(ID$)
  2698.  
  2699.     END IF
  2700.  
  2701.     'GetHashedSymbol% = SymbolID% + UBOUND(ParseRules) + 1
  2702.  
  2703.  
  2704.  
  2705.  
  2706. '
  2707. ' Peeks at the ID of a stack item
  2708. '
  2709. FUNCTION StackPeek (OffsetSP)
  2710.  
  2711.     StackPeek = -1
  2712.  
  2713.     IF OffsetSP < 0 OR OffsetSP >= SP THEN EXIT FUNCTION
  2714.  
  2715.     StackPeek = CVI(LEFT$(STACK(SP - OffsetSP), 2))
  2716.  
  2717.  
  2718. '
  2719. ' STAG is a shortcut function for creating numeric stack tags dynamically
  2720. ' such as {1}.
  2721. '
  2722. FUNCTION STAG$ (n)
  2723.  
  2724.     STAG$ = "{" + LTRIM$(RTRIM$(STR$(n))) + "}"
  2725.  
  2726.  
  2727. '
  2728. ' Parses a STRING into an INTEGER, returning 0 if the string contained
  2729. ' any invalid characters (not including leading and trailing whitespace).
  2730. ' Only positive integers are recognised (no negative numbers!).
  2731. '
  2732. ' The actual numeric value is returned in OutVal
  2733. '
  2734. FUNCTION StringToINT (Txt AS STRING, OutVal AS INTEGER)
  2735.  
  2736.     x$ = UCASE$(LTRIM$(RTRIM$(Txt)))
  2737.  
  2738.     SignCharacter$ = LEFT$(x$, 1)
  2739.     SignMultiplier = 1
  2740.  
  2741.     IF (SignCharacter$ = "+" OR SignCharacter$ = "-") THEN
  2742.         SignMultiplier = 45 - ASC(SignCharacter$)
  2743.         x$ = MID$(x$, 2)
  2744.     END IF
  2745.  
  2746.     FoundBadDigit = LEN(x$) = 0
  2747.  
  2748.     SELECT CASE LEFT$(x$, 2)
  2749.         CASE "&H", "0X": nBase = 16: FirstDigitPos = 3
  2750.         CASE "&O": nBase = 8: FirstDigitPos = 3
  2751.         CASE ELSE: nBase = 10: FirstDigitPos = 1
  2752.     END SELECT
  2753.  
  2754.     IF nBase THEN
  2755.  
  2756.         FOR i = FirstDigitPos TO LEN(x$)
  2757.             Digit = ASC(MID$(x$, i, 1)) - 48
  2758.             IF Digit > 16 THEN Digit = Digit - 7
  2759.             IF Digit < 0 OR Digit >= nBase THEN FoundBadDigit = -1
  2760.  
  2761.             IF NOT FoundBadDigit THEN
  2762.                 Value = Value * nBase
  2763.                 Value = Value + Digit
  2764.             END IF
  2765.  
  2766.         NEXT i
  2767.     END IF
  2768.  
  2769.     StringToINT = NOT FoundBadDigit
  2770.     IF NOT FoundBadDigit THEN OutVal = Value * SignMultiplier
  2771.  
  2772.  
  2773. FUNCTION SubstTagCIRCLE$
  2774.  
  2775.     DIM ParseRule AS STRING
  2776.  
  2777.     ParseRule = "{?}, {?}, {?}, {?}, {?}, {?}"
  2778.  
  2779.     ArgC = 0
  2780.     ArgI = 0
  2781.  
  2782.     '
  2783.     ' The last 3 arguments are optional.
  2784.     '
  2785.     FOR i = 0 TO 2
  2786.  
  2787.         IF StackPeek(ArgC) = &H7E + i THEN
  2788.  
  2789.             IF ArgI = 0 THEN ArgI = 28 - i * 5
  2790.  
  2791.             MID$(ParseRule, 27 - i * 5, 1) = CHR$(ArgC + 48)
  2792.             ArgC = ArgC + 1
  2793.  
  2794.         END IF
  2795.  
  2796.     NEXT i
  2797.  
  2798.     ' PCODE 0x09f means no colour argument
  2799.     IF PCODE <> &H9F THEN
  2800.         IF ArgI = 0 THEN ArgI = 13
  2801.         MID$(ParseRule, 12, 1) = CHR$(ArgC + 48)
  2802.         ArgC = ArgC + 1
  2803.     END IF
  2804.  
  2805.     ' The last 3 arguments are required
  2806.     IF ArgI = 0 THEN ArgI = 8
  2807.     MID$(ParseRule, 7, 1) = CHR$(ArgC + 48): ArgC = ArgC + 1
  2808.     MID$(ParseRule, 2, 1) = CHR$(ArgC + 48): ArgC = ArgC + 1
  2809.  
  2810.     ' Remove unused arguments
  2811.  
  2812.     ParseRule = LEFT$(ParseRule, ArgI)
  2813.  
  2814.     DO
  2815.         ArgI = INSTR(ParseRule, "?")
  2816.         IF ArgI <= 1 THEN EXIT DO
  2817.         ParseRule = LEFT$(ParseRule, ArgI - 2) + MID$(ParseRule, ArgI + 2)
  2818.     LOOP
  2819.  
  2820.     SubstTagCIRCLE = ParseRule
  2821.  
  2822.  
  2823. '
  2824. ' 0x01B : DEF(INT|LNG|SNG|DBL|STR) letterrange
  2825. '
  2826. ' The DEFxxx token is followed by 6 bytes of data. The first two bytes give
  2827. ' the absolute offset in the p-code to the correspdoning bytes of the next
  2828. ' DEFxxx statement (!), or 0xFFFF if there are no more DEFxxx statements.
  2829. '
  2830. ' Naturally, we can ignore these two bytes.
  2831. '
  2832. ' The next 4 bytes form a 32-bit integer. The low 3 bits give the data-type
  2833. ' for the DEFxxx. The upper 26 bits represent each letter or the alphabet,
  2834. ' with A occupying the highest bit, and Z the lowest.
  2835. '
  2836. FUNCTION SubstTagDEFxxx$ (DefTypeArray() AS INTEGER)
  2837.  
  2838.     DIM AlphaMask AS LONG
  2839.     DIM OutTxt AS STRING
  2840.  
  2841.     AlphaMask = FetchLNG(2)
  2842.     DefType = LIMIT(AlphaMask AND 7, 0, 5)
  2843.     OutTxt = "DEF" + GetTypeAbbr(DefType) + " "
  2844.  
  2845.     ' Shift the mask right once to avoid overflow problems.
  2846.     AlphaMask = AlphaMask \ 2
  2847.     InitialLetter = 0
  2848.     AnythingOutput = 0
  2849.  
  2850.     ' We will loop one extra time to avoid code redendancy after the loop to
  2851.     ' clean up the Z. To ensure everything works out, we just need to make
  2852.     ' sure the bit after the Z is clear. We also need to clear the high 2 bits
  2853.     ' every time to avoid overflow ploblems.
  2854.  
  2855.     FOR i = 0 TO 26
  2856.  
  2857.         ' Get the next bit and shift the mask
  2858.         BITSET = (AlphaMask AND &H40000000) <> 0
  2859.         AlphaMask = AlphaMask AND &H3FFFFFE0
  2860.         AlphaMask = AlphaMask * 2
  2861.  
  2862.         '------------------------------------------------------------------------
  2863.         ' Update current DEFtype state
  2864.         '------------------------------------------------------------------------
  2865.         IF i < 26 AND BITSET THEN DefTypeArray(i + 1) = DefType
  2866.  
  2867.         IF BITSET AND InitialLetter = 0 THEN
  2868.  
  2869.             InitialLetter = i + 65
  2870.  
  2871.         ELSEIF InitialLetter AND NOT BITSET THEN
  2872.  
  2873.             IF AnythingOutput THEN OutTxt = OutTxt + ", "
  2874.  
  2875.             OutTxt = OutTxt + CHR$(InitialLetter)
  2876.  
  2877.             Range = i + 65 - InitialLetter
  2878.             IF Range > 1 THEN OutTxt = OutTxt + "-" + CHR$(i + 64)
  2879.  
  2880.             AnythingOutput = -1
  2881.             InitialLetter = 0
  2882.         END IF
  2883.  
  2884.     NEXT i
  2885.  
  2886.     SubstTagDEFxxx$ = OutTxt
  2887.  
  2888.  
  2889. FUNCTION SubstTagINPUT$
  2890.  
  2891.     CONST fPrompt = &H4
  2892.     CONST fSemiColon = &H2
  2893.     CONST fComma = &H1
  2894.  
  2895.     Flags = ASC(MID$(TOKEN, 3, 1))
  2896.  
  2897.     IF Flags AND fSemiColon THEN OutTxt$ = "; "
  2898.  
  2899.     IF Flags AND fPrompt THEN
  2900.         Tail = 59 - (((Flags AND fComma) = 1) AND 15)
  2901.         OutTxt$ = OutTxt$ + "{$+0}" + CHR$(Tail)
  2902.     END IF
  2903.  
  2904.     SubstTagINPUT = OutTxt$
  2905.  
  2906.  
  2907. FUNCTION SubstTagKEY$
  2908.  
  2909.     SELECT CASE CVI(MID$(TOKEN, 3, 2))
  2910.         CASE 1: SubstTagKEY$ = "ON"
  2911.         CASE 2: SubstTagKEY$ = "LIST"
  2912.         CASE ELSE: SubstTagKEY$ = "OFF"
  2913.     END SELECT
  2914.  
  2915.  
  2916. FUNCTION SubstTagLINE$
  2917.  
  2918.     LineForm = PCODE - &HBB
  2919.  
  2920.     SELECT CASE FetchINT(0) AND 3
  2921.  
  2922.         CASE 1: BF$ = "B"
  2923.         CASE 2: BF$ = "BF"
  2924.         CASE ELSE: BF$ = ""
  2925.  
  2926.     END SELECT
  2927.  
  2928.     ' 0x0bb : LINE x-x, ,[b[f]]
  2929.     ' 0x0bc : LINE x-x,n,[b[f]]
  2930.     ' 0x0bd : LINE x-x,n,[b[f]],n
  2931.     ' 0x0be : LINE x-x, ,[b[f]],n
  2932.  
  2933.  
  2934.     IF BF$ <> "" THEN
  2935.  
  2936.         SELECT CASE LineForm
  2937.  
  2938.             CASE 0: Rule$ = "{0}, , " + BF$
  2939.             CASE 1: Rule$ = "{1}, {0}, " + BF$
  2940.             CASE 2: Rule$ = "{2}, {1}, " + BF$ + ", {0}"
  2941.             CASE 3: Rule$ = "{1}, , " + BF$ + ", {0}"
  2942.  
  2943.         END SELECT
  2944.  
  2945.     ELSE
  2946.  
  2947.         SELECT CASE LineForm
  2948.  
  2949.             CASE 0: Rule$ = "{0}"
  2950.             CASE 1: Rule$ = "{1}, {0}"
  2951.             CASE 2: Rule$ = "{2}, {1}, , {0}"
  2952.             CASE 3: Rule$ = "{1}, , , {0}"
  2953.  
  2954.         END SELECT
  2955.  
  2956.     END IF
  2957.  
  2958.     SubstTagLINE = Rule$
  2959.  
  2960.  
  2961. FUNCTION SubstTagLOCK$
  2962.  
  2963.     DIM Flags AS LONG
  2964.  
  2965.     Flags = FetchINTASLONG(0) AND &HFFFF&
  2966.  
  2967.     IF (Flags AND 2) = 0 THEN
  2968.         SubstTagLOCK$ = "{0}"
  2969.     ELSE
  2970.    
  2971.         ' check high 2 bits
  2972.         SELECT CASE Flags \ &H4000
  2973.             CASE 0: SubstTagLOCK$ = "{2}, {1} TO {0}"
  2974.             CASE 1: SubstTagLOCK$ = "{2}, TO {0}"
  2975.             CASE 2: SubstTagLOCK$ = "{1}, {0}"
  2976.         END SELECT
  2977.  
  2978.     END IF
  2979.  
  2980.  
  2981. FUNCTION SubstTagOPEN$
  2982.  
  2983.     DIM ModeFlags AS LONG
  2984.     DIM ForMode AS STRING
  2985.     DIM AccessMode AS STRING
  2986.     DIM LockMode AS STRING
  2987.     DIM OutTxt AS STRING
  2988.  
  2989.     ModeFlags = FetchINT(0) AND &HFFFF&
  2990.  
  2991.     SELECT CASE ModeFlags AND &H3F
  2992.         CASE &H1: ForMode = "FOR INPUT"
  2993.         CASE &H2: ForMode = "FOR OUTPUT"
  2994.         CASE &H4: ForMode = "FOR RANDOM"
  2995.         CASE &H8: ForMode = "FOR APPEND"
  2996.         CASE &H20: ForMode = "FOR BINARY"
  2997.     END SELECT
  2998.  
  2999.     SELECT CASE ModeFlags \ 256 AND 3
  3000.         CASE 1: AccessMode = "ACCESS READ"
  3001.         CASE 2: AccessMode = "ACCESS WRITE"
  3002.         CASE 3: AccessMode = "ACCESS READ WRITE"
  3003.     END SELECT
  3004.  
  3005.     SELECT CASE ModeFlags \ &H1000 AND &H7
  3006.         CASE 1: LockMode = "LOCK READ WRITE"
  3007.         CASE 2: LockMode = "LOCK WRITE"
  3008.         CASE 3: LockMode = "LOCK READ"
  3009.         CASE 4: LockMode = "SHARED"
  3010.     END SELECT
  3011.  
  3012.     OutTxt = ForMode
  3013.     IF (OutTxt <> "" AND AccessMode <> "") THEN OutTxt = OutTxt + " "
  3014.     OutTxt = OutTxt + AccessMode
  3015.     IF (OutTxt <> "" AND LockMode <> "") THEN OutTxt = OutTxt + " "
  3016.     OutTxt = OutTxt + LockMode
  3017.  
  3018.     SubstTagOPEN = OutTxt
  3019.  
  3020.  
  3021. FUNCTION SubstTagVERB$
  3022.  
  3023.     Verbs$ = "0OR|1AND|2PRESET|3PSET|4XOR|"
  3024.  
  3025.     VerbBegin = INSTR(Verbs$, CHR$(48 + LIMIT(FetchINT(0), 0, 4))) + 1
  3026.     VerbEnd = INSTR(VerbBegin, Verbs$, "|")
  3027.  
  3028.     SubstTagVERB$ = MID$(Verbs$, VerbBegin, VerbEnd - VerbBegin)
  3029.  
  3030.  
  3031. '
  3032. ' Splits a {ruletag} into it's constituent components.
  3033. '
  3034. FUNCTION TokenizeTag (TagTxt AS STRING, TagParam AS INTEGER)
  3035.  
  3036.     DIM ParamTxt AS STRING
  3037.  
  3038.     Delimiter = INSTR(TagTxt, ":")
  3039.  
  3040.     ParamTxt = LTRIM$(MID$(TagTxt, Delimiter + 1))
  3041.  
  3042.     IF LEFT$(ParamTxt, 1) = "$" THEN
  3043.  
  3044.         TokenizeTag = TagType.StackREL
  3045.  
  3046.         IF NOT StringToINT(MID$(ParamTxt, 2), TagParam) THEN
  3047.             Delimiter = LEN(TagTxt) + 1
  3048.             TagParam = 0
  3049.         END IF
  3050.  
  3051.     ELSE
  3052.  
  3053.         TokenizeTag = TagType.StackABS
  3054.  
  3055.         IF NOT StringToINT(MID$(ParamTxt, 1), TagParam) THEN
  3056.             Delimiter = LEN(TagTxt) + 1
  3057.             TagParam = 0
  3058.         END IF
  3059.  
  3060.     END IF
  3061.  
  3062.     IF Delimiter THEN Delimiter = Delimiter - 1
  3063.  
  3064.     TagTxt = LTRIM$(RTRIM$(LEFT$(TagTxt, Delimiter)))
  3065.  
  3066.     IF LEFT$(TagTxt, 2) = "##" THEN
  3067.  
  3068.         TokenizeTag = TagType.Recursive
  3069.         TagTxt = MID$(TagTxt, 3)
  3070.  
  3071.     ELSEIF LEFT$(TagTxt, 1) = "#" THEN
  3072.  
  3073.         TokenizeTag = TagType.TokenData
  3074.         TagTxt = MID$(TagTxt, 2)
  3075.  
  3076.     END IF
  3077.  
  3078.  
  3079.  
  3080. FUNCTION ValidateStackTag (RuleID AS INTEGER, TagTxt AS STRING, OffsetSP AS INTEGER)
  3081.  
  3082.  
  3083.     DIM RuleSymbol AS STRING
  3084.  
  3085.     '------------------------------------------------------------------------
  3086.     ' If the specified stack offset is invalid, only the null tag will do.
  3087.     '------------------------------------------------------------------------
  3088.     IF (OffsetSP < 0 OR OffsetSP >= SP) THEN
  3089.         ValidateStackTag = (TagTxt = "")
  3090.         EXIT FUNCTION
  3091.     END IF
  3092.  
  3093.     TagLen = LEN(TagTxt)
  3094.     TagOffset = 1
  3095.  
  3096.     DO WHILE TagOffset <= TagLen
  3097.      
  3098.         Delimiter = INSTR(TagOffset, TagTxt, "|")
  3099.         IF Delimiter = 0 THEN Delimiter = TagLen + 1
  3100.  
  3101.         RuleSymbol = MID$(TagTxt, TagOffset, Delimiter - TagOffset)
  3102.         RuleSymbol = LTRIM$(RTRIM$(RuleSymbol))
  3103.  
  3104.         IF NOT StringToINT(RuleSymbol, RuleSymbolID) THEN
  3105.             RuleSymbolID = GetHashedSymbol(RuleSymbol)
  3106.         END IF
  3107.  
  3108.         IF RuleSymbol = "*" THEN EXIT DO
  3109.         IF RuleSymbol = "self" THEN RuleSymbolID = RuleID
  3110.  
  3111.         IF StackPeek(OffsetSP) = RuleSymbolID THEN EXIT DO
  3112.  
  3113.         TagOffset = Delimiter + 1
  3114.  
  3115.     LOOP
  3116.  
  3117.     ValidateStackTag = NOT (TagLen AND TagOffset > TagLen)
  3118.  
  3119.     IF TagLen AND TagOffset > TagLen THEN
  3120.         ValidateStackTag = 0
  3121.     ELSE
  3122.         ValidateStackTag = -1
  3123.     END IF
  3124.  
  3125.  
« Last Edit: January 31, 2020, 08:03:00 pm by odin »

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Extended syntax highlighting added
« Reply #1 on: April 27, 2019, 08:12:35 pm »
Was this a request? Anyway, I hope it didn't take too much time or effort. I figure if I'm interested in the code, I'm going to run it, not just look at it. So it either winds up in NotePad in black on white, or the IDE, which I don't color code, so white on blue. Hopefully you'll find other fans for this forum color code addition. Although I personally don't think it was needed, I love ambition. As long as it made whoever wrote it happy. That's a win, period; although it would be twice as cool if it had a code-qb64 code button next to the standard code button.

Pete 
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

FellippeHeitor

  • Guest
Re: Extended syntax highlighting added
« Reply #2 on: April 27, 2019, 08:15:24 pm »
It was a request by me :-)

Anyway, it was just a matter of linking to the proper syntax highlighting library (GeSHI in this case) and adding in the keywords. Took 20 minutes.

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Extended syntax highlighting added
« Reply #3 on: April 27, 2019, 08:36:58 pm »
Nice new feature, and i see there is an extra "colorful" code button for it in the editor page, but why are IF, THEN, ELSE, TO, FUNCTION and others in different color and not linked to its respective wiki pages?
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline odin

  • Administrator
  • Newbie
  • Posts: 92
  • I am.
    • View Profile
Re: Extended syntax highlighting added
« Reply #4 on: April 27, 2019, 08:43:27 pm »
it would be twice as cool if it had a code-qb64 code button next to the standard code button.

 [ You are not allowed to view this attachment ]  

Nice new feature, and i see there is an extra "colorful" code button for it in the editor page, but why are IF, THEN, ELSE, TO, FUNCTION and others in different color and not linked to its respective wiki pages?

Check again :-)
« Last Edit: April 27, 2019, 08:47:01 pm by odin »

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Extended syntax highlighting added
« Reply #5 on: April 27, 2019, 08:51:26 pm »
Grr, look at lines 1091 or 1127 of your sample code. Can wordwrap be deactivated? It will trash the code layout.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline Raven_Singularity

  • Forum Regular
  • Posts: 158
    • View Profile
Re: Extended syntax highlighting added
« Reply #6 on: April 27, 2019, 08:52:38 pm »
Thanks for your work.  Looks nice, I love syntax highlighting.  Saves a lot of brain power when skimming code!  Nice to see you've already fixed a minor implementation bug and implemented a new feature request.  :-P

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Extended syntax highlighting added
« Reply #7 on: April 27, 2019, 09:10:47 pm »
Code: QB64: [Select]
  1. REM OK, you added the button.
  2. ' Not bad for 20-minutes work, and whatever the button addition took.
  3. PRINT "Nice Job!"
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

FellippeHeitor

  • Guest
Re: Extended syntax highlighting added
« Reply #8 on: April 27, 2019, 09:39:58 pm »
Grr, look at lines 1091 or 1127 of your sample code. Can wordwrap be deactivated? It will trash the code layout.

Haven't found a way to disable line wrapping for now.

Edit: I think I just found the way. Please check.
« Last Edit: April 27, 2019, 09:48:36 pm by FellippeHeitor »

FellippeHeitor

  • Guest
Re: Extended syntax highlighting added
« Reply #9 on: April 27, 2019, 09:49:51 pm »
Line-wrapping seems to have been properly disabled:

Code: QB64: [Select]
  1. noGo$ = "@DO@LOOP@WHILE@WEND@THEN@ELSE@ELSEIF@IF@FOR@TO@NEXT@STEP@GOTO@GOSUB@CALL@CALLS@SUB@FUNCTION@RETURN@RESUME@SELECT@CASE@UNTIL@"
  2.  
  3. kw$ = "@?@$CHECKING@$CONSOLE@ONLY@$DYNAMIC@$ELSE@$ELSEIF@$END@$ENDIF@$EXEICON@$IF@$INCLUDE@$LET@$RESIZE@$SCREENHIDE@$SCREENSHOW@$STATIC@$VERSIONINFO@$VIRTUALKEYBOARD@ABS@ABSOLUTE@ACCESS@ALIAS@AND@APPEND@AS@ASC@ATN@BASE@BEEP@BINARY@BLOAD@BSAVE@BYVAL@CALL@CALLS@CASE@IS@CDBL@CDECL@CHAIN@CHDIR@CHR$@CINT@CIRCLE@CLEAR@CLNG@CLOSE@CLS@COLOR@COM@COMMAND$@COMMON@CONST@COS@CSNG@CSRLIN@CUSTOMTYPE@CVD@CVDMBF@CVI@CVL@CVS@CVSMBF@DATA@DATE$@DECLARE@DEF@DEFDBL@DEFINT@DEFLNG@DEFSNG@DEFSTR@DIM@DO@DOUBLE@DRAW@DYNAMIC@ELSE@ELSEIF@END@ENDIF@ENVIRON@ENVIRON$@EOF@EQV@ERASE@ERDEV@ERDEV$@ERL@ERR@ERROR@EVERYCASE@EXIT@EXP@FIELD@FILEATTR@FILES@FIX@FN@FOR@FRE@FREE@FREEFILE@FUNCTION@GET@GOSUB@GOTO@HEX$@IF@IMP@INKEY$@INP@INPUT@INPUT$@INSTR@INT@INTEGER@INTERRUPT@INTERRUPTX@IOCTL@IOCTL$@KEY@KILL@LBOUND@LCASE$@LEFT$@LEN@LET@LIBRARY@LINE@LIST@LOC@LOCATE@LOCK@LOF@LOG@LONG@LOOP@LPOS@LPRINT@LSET@LTRIM$@MID$@MKD$@MKDIR@MKDMBF$@MKI$@MKL$@MKS$@MKSMBF$@MOD@NAME@NEXT@NOT@OCT$@OFF@ON@OPEN@OPTION@OR@OUT@OUTPUT@PAINT@PALETTE@PCOPY@PEEK@PEN@PLAY@PMAP@POINT@POKE@POS@PRESET@PRINT@PSET@PUT@RANDOM@RANDOMIZE@READ@REDIM@REM@RESET@RESTORE@RESUME@RETURN@RIGHT$@RMDIR@RND@RSET@RTRIM$@RUN@SADD@SCREEN@SEEK@SEG@SELECT@SETMEM@SGN@SHARED@SHELL@SIGNAL@SIN@SINGLE@SLEEP@SOUND@SPACE$@SPC@SQR@STATIC@STEP@STICK@STOP@STR$@STRIG@STRING@STRING$@SUB@SWAP@SYSTEM@TAB@TAN@THEN@TIME$@TIMER@TO@TROFF@TRON@TYPE@UBOUND@UCASE$@UEVENT@UNLOCK@UNTIL@USING@VAL@VARPTR@VARPTR$@VARSEG@VIEW@WAIT@WEND@WHILE@WIDTH@WINDOW@WRITE@XOR@_ACOS@_ACOSH@_ALPHA@_ALPHA32@_ARCCOT@_ARCCSC@_ARCSEC@_ASIN@_ASINH@_ATAN2@_ATANH@_AUTODISPLAY@_AXIS@_BACKGROUNDCOLOR@_BIT@_BLEND@_BLINK@_BLUE@_BLUE32@_BUTTON@_BUTTONCHANGE@_BYTE@_CEIL@_CLEARCOLOR@_CLIP@_CLIPBOARD$@_CLIPBOARDIMAGE@_COMMANDCOUNT@_CONNECTED@_CONNECTIONADDRESS$@_CONNECTIONADDRESS@_CONSOLE@_CONSOLETITLE@_CONTINUE@_CONTROLCHR@_COPYIMAGE@_COPYPALETTE@_COSH@_COT@_COTH@_CSC@_CSCH@_CV@_CWD$@_D2G@_D2R@_DEFAULTCOLOR@_DEFINE@_DELAY@_DEPTHBUFFER@_DESKTOPHEIGHT@_DESKTOPWIDTH@_DEST@_DEVICE$@_DEVICEINPUT@_DEVICES@_DIR$@_DIREXISTS@_DISPLAY@_DISPLAYORDER@_DONTBLEND@_DONTWAIT@"
  4. kw$ = kw$ + "_ERRORLINE@_EXIT@_EXPLICIT@_FILEEXISTS@_FLOAT@_FONT@_FONTHEIGHT@_FONTWIDTH@_FREEFONT@_FREEIMAGE@_FREETIMER@_FULLSCREEN@_G2D@_G2R@_GLRENDER@_GREEN@_GREEN32@_HEIGHT@_HIDE@_HYPOT@_ICON@_INCLERRORFILE$@_INCLERRORLINE@_INTEGER64@_KEYCLEAR@_KEYDOWN@_KEYHIT@_LASTAXIS@_LASTBUTTON@_LASTWHEEL@_LIMIT@_LOADFONT@_LOADIMAGE@_MAPTRIANGLE@_MAPUNICODE@_MEM@_MEMCOPY@_MEMELEMENT@_MEMEXISTS@_MEMFILL@_MEMFREE@_MEMGET@_MEMIMAGE@_MEMNEW@_MEMPUT@_MIDDLE@_MK$@_MOUSEBUTTON@_MOUSEHIDE@_MOUSEINPUT@_MOUSEMOVE@_MOUSEMOVEMENTX@_MOUSEMOVEMENTY@_MOUSEPIPEOPEN@_MOUSESHOW@_MOUSEWHEEL@_MOUSEX@_MOUSEY@_NEWIMAGE@_OFFSET@_OPENCLIENT@_OPENCONNECTION@_OPENHOST@_OS$@_PALETTECOLOR@_PI@_PIXELSIZE@_PRESERVE@_PRINTIMAGE@_PRINTMODE@_PRINTSTRING@_PRINTWIDTH@_PUTIMAGE@_R2D@_R2G@_RED@_RED32@_RESIZE@_RESIZEHEIGHT@_RESIZEWIDTH@_RGB@_RGB32@_RGBA@_RGBA32@_ROUND@_SCREENCLICK@_SCREENEXISTS@_SCREENHIDE@_SCREENICON@_SCREENIMAGE@_SCREENMOVE@_SCREENPRINT@_SCREENSHOW@_SCREENX@_SCREENY@_SEC@_SECH@_SETALPHA@_SHELLHIDE@_SINH@_SNDBAL@_SNDCLOSE@_SNDCOPY@_SNDGETPOS@_SNDLEN@_SNDLIMIT@_SNDLOOP@_SNDOPEN@_SNDOPENRAW@_SNDPAUSE@_SNDPAUSED@_SNDPLAY@_SNDPLAYCOPY@_SNDPLAYFILE@_SNDPLAYING@_SNDRATE@_SNDRAW@_SNDRAWDONE@_SNDRAWLEN@_SNDSETPOS@_SNDSTOP@_SNDVOL@_SOURCE@_STARTDIR$@_STRCMP@_STRICMP@_TANH@_TITLE@_TITLE$@_UNSIGNED@_WHEEL@_WIDTH@_WINDOWHANDLE@_WINDOWHASFOCUS@_GLACCUM@_GLALPHAFUNC@_GLARETEXTURESRESIDENT@_GLARRAYELEMENT@_GLBEGIN@_GLBINDTEXTURE@_GLBITMAP@_GLBLENDFUNC@_GLCALLLIST@_GLCALLLISTS@_GLCLEAR@_GLCLEARACCUM@_GLCLEARCOLOR@_GLCLEARDEPTH@_GLCLEARINDEX@_GLCLEARSTENCIL@_GLCLIPPLANE@_GLCOLOR3B@_GLCOLOR3BV@_GLCOLOR3D@_GLCOLOR3DV@_GLCOLOR3F@_GLCOLOR3FV@_GLCOLOR3I@_GLCOLOR3IV@_GLCOLOR3S@_GLCOLOR3SV@_GLCOLOR3UB@_GLCOLOR3UBV@_GLCOLOR3UI@_GLCOLOR3UIV@_GLCOLOR3US@_GLCOLOR3USV@_GLCOLOR4B@_GLCOLOR4BV@_GLCOLOR4D@_GLCOLOR4DV@_GLCOLOR4F@_GLCOLOR4FV@_GLCOLOR4I@_GLCOLOR4IV@_GLCOLOR4S@_GLCOLOR4SV@_GLCOLOR4UB@_GLCOLOR4UBV@_GLCOLOR4UI@_GLCOLOR4UIV@_GLCOLOR4US@_GLCOLOR4USV@_GLCOLORMASK@_GLCOLORMATERIAL@_GLCOLORPOINTER@_GLCOPYPIXELS@_GLCOPYTEXIMAGE1D@_GLCOPYTEXIMAGE2D@_GLCOPYTEXSUBIMAGE1D@"
  5. kw$ = kw$ + "_GLCOPYTEXSUBIMAGE2D@_GLCULLFACE@_GLDELETELISTS@_GLDELETETEXTURES@_GLDEPTHFUNC@_GLDEPTHMASK@_GLDEPTHRANGE@_GLDISABLE@_GLDISABLECLIENTSTATE@_GLDRAWARRAYS@_GLDRAWBUFFER@_GLDRAWELEMENTS@_GLDRAWPIXELS@_GLEDGEFLAG@_GLEDGEFLAGPOINTER@_GLEDGEFLAGV@_GLENABLE@_GLENABLECLIENTSTATE@_GLEND@_GLENDLIST@_GLEVALCOORD1D@_GLEVALCOORD1DV@_GLEVALCOORD1F@_GLEVALCOORD1FV@_GLEVALCOORD2D@_GLEVALCOORD2DV@_GLEVALCOORD2F@_GLEVALCOORD2FV@_GLEVALMESH1@_GLEVALMESH2@_GLEVALPOINT1@_GLEVALPOINT2@_GLFEEDBACKBUFFER@_GLFINISH@_GLFLUSH@_GLFOGF@_GLFOGFV@_GLFOGI@_GLFOGIV@_GLFRONTFACE@_GLFRUSTUM@_GLGENLISTS@_GLGENTEXTURES@_GLGETBOOLEANV@_GLGETCLIPPLANE@_GLGETDOUBLEV@_GLGETERROR@_GLGETFLOATV@_GLGETINTEGERV@_GLGETLIGHTFV@_GLGETLIGHTIV@_GLGETMAPDV@_GLGETMAPFV@_GLGETMAPIV@_GLGETMATERIALFV@_GLGETMATERIALIV@_GLGETPIXELMAPFV@_GLGETPIXELMAPUIV@_GLGETPIXELMAPUSV@_GLGETPOINTERV@_GLGETPOLYGONSTIPPLE@_GLGETSTRING@_GLGETTEXENVFV@_GLGETTEXENVIV@_GLGETTEXGENDV@_GLGETTEXGENFV@_GLGETTEXGENIV@_GLGETTEXIMAGE@_GLGETTEXLEVELPARAMETERFV@_GLGETTEXLEVELPARAMETERIV@_GLGETTEXPARAMETERFV@_GLGETTEXPARAMETERIV@_GLHINT@_GLINDEXMASK@_GLINDEXPOINTER@_GLINDEXD@_GLINDEXDV@_GLINDEXF@_GLINDEXFV@_GLINDEXI@_GLINDEXIV@_GLINDEXS@_GLINDEXSV@_GLINDEXUB@_GLINDEXUBV@_GLINITNAMES@_GLINTERLEAVEDARRAYS@_GLISENABLED@_GLISLIST@_GLISTEXTURE@_GLLIGHTMODELF@_GLLIGHTMODELFV@_GLLIGHTMODELI@_GLLIGHTMODELIV@_GLLIGHTF@_GLLIGHTFV@_GLLIGHTI@_GLLIGHTIV@_GLLINESTIPPLE@_GLLINEWIDTH@_GLLISTBASE@_GLLOADIDENTITY@_GLLOADMATRIXD@_GLLOADMATRIXF@_GLLOADNAME@_GLLOGICOP@_GLMAP1D@_GLMAP1F@_GLMAP2D@_GLMAP2F@_GLMAPGRID1D@_GLMAPGRID1F@_GLMAPGRID2D@_GLMAPGRID2F@_GLMATERIALF@_GLMATERIALFV@_GLMATERIALI@_GLMATERIALIV@_GLMATRIXMODE@_GLMULTMATRIXD@_GLMULTMATRIXF@_GLNEWLIST@_GLNORMAL3B@_GLNORMAL3BV@_GLNORMAL3D@_GLNORMAL3DV@_GLNORMAL3F@_GLNORMAL3FV@_GLNORMAL3I@_GLNORMAL3IV@_GLNORMAL3S@_GLNORMAL3SV@_GLNORMALPOINTER@_GLORTHO@_GLPASSTHROUGH@_GLPIXELMAPFV@_GLPIXELMAPUIV@_GLPIXELMAPUSV@_GLPIXELSTOREF@_GLPIXELSTOREI@_GLPIXELTRANSFERF@_GLPIXELTRANSFERI@_GLPIXELZOOM@_GLPOINTSIZE@_GLPOLYGONMODE@_GLPOLYGONOFFSET@_GLPOLYGONSTIPPLE@"
  6. kw$ = kw$ + "_GLPOPATTRIB@_GLPOPCLIENTATTRIB@_GLPOPMATRIX@_GLPOPNAME@_GLPRIORITIZETEXTURES@_GLPUSHATTRIB@_GLPUSHCLIENTATTRIB@_GLPUSHMATRIX@_GLPUSHNAME@_GLRASTERPOS2D@_GLRASTERPOS2DV@_GLRASTERPOS2F@_GLRASTERPOS2FV@_GLRASTERPOS2I@_GLRASTERPOS2IV@_GLRASTERPOS2S@_GLRASTERPOS2SV@_GLRASTERPOS3D@_GLRASTERPOS3DV@_GLRASTERPOS3F@_GLRASTERPOS3FV@_GLRASTERPOS3I@_GLRASTERPOS3IV@_GLRASTERPOS3S@_GLRASTERPOS3SV@_GLRASTERPOS4D@_GLRASTERPOS4DV@_GLRASTERPOS4F@_GLRASTERPOS4FV@_GLRASTERPOS4I@_GLRASTERPOS4IV@_GLRASTERPOS4S@_GLRASTERPOS4SV@_GLREADBUFFER@_GLREADPIXELS@_GLRECTD@_GLRECTDV@_GLRECTF@_GLRECTFV@_GLRECTI@_GLRECTIV@_GLRECTS@_GLRECTSV@_GLRENDERMODE@_GLROTATED@_GLROTATEF@_GLSCALED@_GLSCALEF@_GLSCISSOR@_GLSELECTBUFFER@_GLSHADEMODEL@_GLSTENCILFUNC@_GLSTENCILMASK@_GLSTENCILOP@_GLTEXCOORD1D@_GLTEXCOORD1DV@_GLTEXCOORD1F@_GLTEXCOORD1FV@_GLTEXCOORD1I@_GLTEXCOORD1IV@_GLTEXCOORD1S@_GLTEXCOORD1SV@_GLTEXCOORD2D@_GLTEXCOORD2DV@_GLTEXCOORD2F@_GLTEXCOORD2FV@_GLTEXCOORD2I@_GLTEXCOORD2IV@_GLTEXCOORD2S@_GLTEXCOORD2SV@_GLTEXCOORD3D@_GLTEXCOORD3DV@_GLTEXCOORD3F@_GLTEXCOORD3FV@_GLTEXCOORD3I@_GLTEXCOORD3IV@_GLTEXCOORD3S@_GLTEXCOORD3SV@_GLTEXCOORD4D@_GLTEXCOORD4DV@_GLTEXCOORD4F@_GLTEXCOORD4FV@_GLTEXCOORD4I@_GLTEXCOORD4IV@_GLTEXCOORD4S@_GLTEXCOORD4SV@_GLTEXCOORDPOINTER@_GLTEXENVF@_GLTEXENVFV@_GLTEXENVI@_GLTEXENVIV@_GLTEXGEND@_GLTEXGENDV@_GLTEXGENF@_GLTEXGENFV@_GLTEXGENI@_GLTEXGENIV@_GLTEXIMAGE1D@_GLTEXIMAGE2D@_GLTEXPARAMETERF@_GLTEXPARAMETERFV@_GLTEXPARAMETERI@_GLTEXPARAMETERIV@_GLTEXSUBIMAGE1D@_GLTEXSUBIMAGE2D@_GLTRANSLATED@_GLTRANSLATEF@_GLVERTEX2D@_GLVERTEX2DV@_GLVERTEX2F@_GLVERTEX2FV@_GLVERTEX2I@_GLVERTEX2IV@_GLVERTEX2S@_GLVERTEX2SV@_GLVERTEX3D@_GLVERTEX3DV@_GLVERTEX3F@_GLVERTEX3FV@_GLVERTEX3I@_GLVERTEX3IV@_GLVERTEX3S@_GLVERTEX3SV@_GLVERTEX4D@_GLVERTEX4DV@_GLVERTEX4F@_GLVERTEX4FV@_GLVERTEX4I@_GLVERTEX4IV@_GLVERTEX4S@_GLVERTEX4SV@_GLVERTEXPOINTER@_GLVIEWPORT@SMOOTH@STRETCH@_ANTICLOCKWISE@_BEHIND@_CLEAR@_FILLBACKGROUND@_GLUPERSPECTIVE@_HARDWARE@_HARDWARE1@_KEEPBACKGROUND@_NONE@_OFF@_ONLY@_ONLYBACKGROUND@_ONTOP@_SEAMLESS@_SMOOTH@_SMOOTHSHRUNK@_SMOOTHSTRETCHED@"
  7. kw$ = kw$ + "_SOFTWARE@_SQUAREPIXELS@_STRETCH@_ALLOWFULLSCREEN@_ALL@_ECHO@_INSTRREV@_TRIM$@_ACCEPTFILEDROP@_FINISHDROP@_TOTALDROPPEDFILES@_DROPPEDFILE@_DROPPEDFILE$@_SHR@_SHL@"
  8.  
  9.     at1 = INSTR(kw$, "@")
  10.     at2 = INSTR(at1 + 1, kw$, "@")
  11.     k$ = MID$(kw$, at1 + 1, at2 - at1 - 1)
  12.  
  13.     IF INSTR(noGo$, "@" + k$ + "@") = 0 THEN
  14.         temp$ = temp$ + "'" + k$ + "', "
  15.         IF LEN(temp$) > 75 THEN
  16.             result$ = result$ + temp$ + CHR$(10)
  17.             temp$ = ""
  18.         END IF
  19.     END IF
  20.  
  21.     kw$ = MID$(kw$, at2)
  22. LOOP WHILE LEN(kw$) > 1
  23.  
  24. _CLIPBOARD$ = result$
  25. PRINT result$

Offline Raven_Singularity

  • Forum Regular
  • Posts: 158
    • View Profile
Re: Extended syntax highlighting added
« Reply #10 on: April 27, 2019, 10:00:13 pm »
For the issue of not being able to select the code, you could possibly have a plain text version of the code in a div tag that is hidden with CSS, and when the Javascript button is pushed, it copies that text to the clipboard.

That would be a "Copy" button, not a "Select" button.  Which would make more sense than Select for the other code block as well.

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Extended syntax highlighting added
« Reply #11 on: April 27, 2019, 10:08:05 pm »
Nothing more to say than in this song :) https://m.youtube.com/watch?v=CKZUEpdJhO8
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

FellippeHeitor

  • Guest
Re: Extended syntax highlighting added
« Reply #12 on: April 27, 2019, 10:12:28 pm »
For the issue of not being able to select the code, you could possibly have a plain text version of the code in a div tag that is hidden with CSS, and when the Javascript button is pushed, it copies that text to the clipboard.

That would be a "Copy" button, not a "Select" button.  Which would make more sense than Select for the other code block as well.

Only issue is that it's beyond my skill, maybe one day ;-)

Nothing more to say than in this song :) https://m.youtube.com/watch?v=CKZUEpdJhO8

:-)

FellippeHeitor

  • Guest
Re: Extended syntax highlighting added
« Reply #13 on: April 27, 2019, 11:14:43 pm »
Ok, I guess this is the final tweak for the night: You can now [Select] a code block even with the new formatting.

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(600, 600, 32)
  2. _DEST _NEWIMAGE(300, 300, 32)
  3. COLOR , 0
  4. difficulty! = 1.5
  5.     _PRINTSTRING (0, 0), "SCORE:" + STR$(score&), 0
  6.     letter$ = CHR$(INT(RND * 26) + 65)
  7.     x = RND * (_WIDTH - 30)
  8.     y = RND * (_HEIGHT - 30)
  9.     LINE (x, y)-STEP(30, 30), _RGB32(x MOD 200 + (100 * ABS((x MOD 200) < 50)), y MOD 150 + (100 * ABS((y MOD 150) < 50)), x MOD 255 + (100 * ABS((x MOD 150) < 50))), BF
  10.     _PRINTSTRING (x + 11, y + 7), letter$
  11.     start! = TIMER
  12.     DO
  13.         k$ = UCASE$(INKEY$)
  14.         DO WHILE TIMER - start! > difficulty!
  15.             GOTO checkKey
  16.         LOOP
  17.         LINE (0, _HEIGHT - 5)-(((_WIDTH + 5) * ((TIMER - start!) / difficulty!)), _HEIGHT), _RGB32(x MOD 200 + (100 * ABS((x MOD 200) < 50)), y MOD 150 + (100 * ABS((y MOD 150) < 50)), x MOD 255 + (100 * ABS((x MOD 150) < 50))), BF
  18.         _PUTIMAGE , _DEST, _DISPLAY
  19.         _LIMIT 30
  20.     LOOP WHILE k$ < "A" OR k$ > "Z"
  21.     checkKey:
  22.     LINE (x - (300 * (k$ = letter$)), y - (300 * (k$ = letter$)))-STEP(30, 30), _RGBA32(0, 0, 0, 200), BF
  23.     LINE (x - (300 * (k$ <> letter$)), y - (300 * (k$ <> letter$)))-STEP(30, 30), _RGB32(255, 255, 0), B
  24.     score& = score& + ABS(k$ = letter$) - (ABS(k$ <> letter$) * ABS(LEN(k$) > 0))
  25.     difficulty! = difficulty! - (ABS(k$ = letter$) / 100)
  26. LOOP UNTIL score& < 0 OR score& = 21
  27. _PRINTSTRING (_WIDTH / 2 - (LEN("You " + LEFT$("lose...", 7 * ABS(score& < 0)) + LEFT$("win!", 4 * ABS(score& > 0))) * 8) / 2, _HEIGHT / 2 - 8), "You " + LEFT$("lose...", 7 * ABS(score& < 0)) + LEFT$("win!", 4 * ABS(score& > 0)), 0

Note that even though the line numbers look selected, they won't be copied.

Offline Raven_Singularity

  • Forum Regular
  • Posts: 158
    • View Profile
Re: Extended syntax highlighting added
« Reply #14 on: April 27, 2019, 11:23:30 pm »
Ok, I guess this is the final tweak for the night: You can now [Select] a code block even with the new formatting.

Excellent!  I already noticed it was working when I copied the code I posted a few moments ago.  :-)


Note that even though the line numbers look selected, they won't be copied.

On Windows 10 / Firefox, the line numbers are not being highlighted.