'=========================================================================== ' Subject: OPTIMIZED COLORED SOURCE LISTER Date: 09-29-99 (22:06) ' Author: Antoni Gual Code: QB, PDS ' Origin: agual@eic.ictnet.es Packet: MISC.ABC '=========================================================================== DEFINT A-Z DECLARE FUNCTION filesel$ (prompt$, new%) DECLARE SUB loadkwds () DECLARE FUNCTION parsebas2$ (line$) DECLARE SUB colprint2 (TEXT$) DECLARE FUNCTION BufIn$ (FileNum%, done%) '---------------------------------------------------------------------------- ' Optimized Colored Source Lister ' by Antoni Gual agual@eic.ictnet.es '---------------------------------------------------------------------------- ' The idea is Sami Kyostila's. Jonathan Leger 's optimization unfortu- ' nately hangs somewhere (1), so i decided to try myself. ' My contributions are speed, a file selector,a QB format detector ' and a duplicated entries in keyword table detector. ' CREDITS ' Sami, off course . ' The buffered file reader is Ethan Winer's. ' The file selector is a new version of one I sent months ago. I think ' it works fine now. ' The parsing thing and color printer are all mine. Quite fast! ' ' SPEED CONTEST: ' Using CONVERT.BAS, a 95 Kb ABC file ' In a P166 in W95's DOS ' ' runing IDE COMPILED '-------------------------------------- ' Sami's original: 125 sec 47,4 sec ' Jonathan's 29 sec 13,7 sec ' This version 12 sec 8,6 sec '-------------------------------------- ' To test program speed set PAGE connstant to FALSE. A timer will appear. ' NOTES: ' -The parse routine is built by trial and error as you can see. No theory! ' A serious programmer could improve it further. ' -To increase speed I implemented a binary search in the keyword table. Thus ' I had to sort the table and remove duplicates. The counter will give you ' 224 kwds in front of the original 268. ' -I changed the color prefix for an ESC, that's not found in normal texts. ' -Added metacommands recognition. ' -The file select routine uses interrupt calls so it can't be run in QBasic ' ' KNOWN BUGS: ' -If one of the components of a dotted variable name looks like a kwd ' the program considers it a kwd. ' -The minus sign is colored erroneously as a math sign when it precedes ' a negative number (should be considered part of the number). ' Please report any problem! '---------------------------------------------------------------------------- ' (1) Jonathan's version hangs when it finds a line with a single & ' This can be solved changing the color prefix to ESC. '---------------------------------------------------------------------------- CONST FALSE = 0: CONST true = NOT FALSE '---------------------------------------------------------------------------- 'Edit constants here at your convenience '---------------------------------------------------------------------------- 'Colors CONST col.comment = 8 'Comment CONST col.label = 14 'a label CONST col.meta = 11 'a metacommand CONST col.statement = 9 'a QBasic keyword CONST col.number = 13 'a Number CONST col.cstring = 12 'a String expression CONST col.math = 10 'a Mathematical operator CONST col.sign = 6 'a punctuation sign CONST col.Default = 7 'Default color CONST col.type = 2 'a var type extension 'if you want to test program speed, set page=false to avoid paging CONST PAGE = true '--------------------------------------------------------------------------- ' '--------------------------------------------------------------------------- CONST numlin = 25 '$INCLUDE: 'qb.bi' DIM SHARED words AS INTEGER 'num of kwds found in data DIM SHARED errata AS INTEGER 'variable that brings back error code 'to the originating routine DIM SHARED INREG AS RegTypeX, outreg AS RegTypeX qbsign$ = CHR$(&HFC) + CHR$(0) + CHR$(1) 'qbasic format's signature DIM SHARED esc$: esc$ = CHR$(27) 'we use escape to insert color codes DIM SHARED cr$: cr$ = CHR$(13) '---------------------------------------------------------------------------- ' Here We go! '---------------------------------------------------------------------------- REDIM SHARED qword$(0) loadkwds DO Filename$ = filesel$("File To View", 0) WIDTH , numlin CLS COLOR 15, 4 LOCATE 1, 1 PRINT STRING$(80, " ") LOCATE 1, 1 PRINT " þ Parsing file "; Filename$ VIEW PRINT 2 TO numlin COLOR 7, 0 LOCATE 25, 1 init! = TIMER OPEN Filename$ FOR BINARY AS #1 a$ = SPACE$(3) GET #1, , a$ IF a$ = qbsign$ THEN PRINT "The file " + Filename$ + " is in QB internal format an can't be viewed by this app." ELSE '---------------------------------------------------------------------------- ' Main Loop '---------------------------------------------------------------------------- DO colprint2 parsebas2(BufIn(1, done)) 'SNIPPET SCREEN PAGER IF PAGE THEN cnt = cnt + 1: IF cnt = numlin - 2 THEN a$ = INPUT$(1): cnt = 0 'END SNIPPET IF a$ = esc$ THEN EXIT DO LOOP UNTIL done END IF CLOSE IF NOT PAGE THEN PRINT "Parse time: "; TIMER - init! COLOR 15: INPUT ; "Another file [Y]/N"; a$ LOOP UNTIL UCASE$(a$) = "N" END errorhandler: errata = ERR: RESUME NEXT '---------------------------------------------------------------------------- ' QBasic keyword data '---------------------------------------------------------------------------- QData: DATA "ABS", "ABSOLUTE","APPEND","ACCESS","AS", "ALIAS","ASC", "ANY", "ATN" DATA "BASE","BLOAD","BSAVE", "BEEP", "BYVAL", "BINARY" DATA "CALL","CLS","CALLS","COLOR","CASE","COMMAND$","COMMON","COM","CDBL" DATA "CONST","CDECL","COS","CHAIN","CSNG","CHDIR","CSRLIN","CHR$","CVD" DATA "CINT","CVDMBF","CIRCLE","CVI","CLEAR","CVL", "CLNG","CVS","CLOSE","CVSMBF" DATA "DATA","DEF","DEFINT","DEFLNG","DATE$","DEFSNG", "DEFSTR", "DECLARE","DIM" DATA "DO","DOUBLE","DRAW", "DEFDBL","$DYNAMIC" DATA "ELSE", "ERDEV", "ELSEIF", "ERDEV$", "END", "ERROR", "ERL", "ERR", "ENVIRON" DATA "ENVIRON$","EXIT","EOF","EXP", "EQV", "ERASE" DATA "FIELD","FOR", "FILEATTR", "FRE", "FILES", "FREEFILE", "FIX", "FUNCTION" DATA "GOTO","GOSUB","GET" DATA "HEX$" DATA "IF","INTERRUPT","INPUT$","IMP","INSTR","$INCLUDE","INT","INKEY$","INTEGER" DATA "INP","IOCTL","IOCTL$", "IS", "INPUT" DATA "KILL","KEY" DATA "LBOUND","LOCATE","LCASE$","LOCK","LEFT$","LOF","LEN","LOG","LET","LONG" DATA "LINE" DATA "LOOP" DATA "LPOS" DATA "LIST" DATA "LPRINT" DATA "LOC" DATA "LSET" DATA "LOCAL" DATA "LTRIM$" DATA "MID$","MKL$","MKS$", "MKD$","MKSMBF$", "MKDIR", "MOD","MKDMBF$", "MKI$" DATA "NAME","NEXT","NOT" DATA "OCT$","OFF", "ON", "OPEN", "OPTION", "OR", "OUT", "OUTPUT" DATA "PLAY" DATA "PAINT" DATA "POINT" DATA "PALETTE" DATA "POKE" DATA "PCOPY" DATA "POS" DATA "PEEK" DATA "PRESET" DATA "PEN" DATA "PRINT" DATA "PSET" DATA "PMAP" DATA "PUT" DATA "RANDOM" DATA "RETURN" DATA "RANDOMIZE" DATA "RIGHT$" DATA "READ" DATA "RMDIR" DATA "REDIM" DATA "RND" DATA "REM" DATA "RSET" DATA "RESET" DATA "RTRIM$" DATA "RESTORE" DATA "RUN" DATA "RESUME" DATA "SADD" DATA "STRIG" DATA "SPC" DATA "SCREEN" DATA "SQR" DATA "STATIC" DATA "SEEK" DATA "$STATIC" DATA "STEP" DATA "SELECT" DATA "STICK" DATA "SETMEM" DATA "STOP" DATA "SEG" DATA "SGN" DATA "STR$" DATA "SHARED" DATA "SHELL" DATA "SIGNAL" DATA "STRING" DATA "SIN" DATA "STRING$" DATA "SINGLE" DATA "SUB" DATA "SLEEP" DATA "SWAP" DATA "SOUND" DATA "SYSTEM" DATA "SPACE$" DATA "TAB","TAN","TO", "THEN","TROFF", "TRON", "TIME$","TYPE","TIMER" DATA "UBOUND","UNLOCK","UCASE$","UNTIL", "USING", "UEVENT" DATA "VAL","VARSEG","VIEW","VARPTR", "VARPTR$" DATA "WAIT","WINDOW","WEND","WRITE","WHILE", "WIDTH" DATA "@@@" FUNCTION BufIn$ (FileNum, done) STATIC 'Buffered text input from file '--------------------------------------------- IF NOT Reading THEN Reading = -1: done = 0: cr = 0: Remaining& = LOF(FileNum): BufSize = 4096 Buffer$ = SPACE$(BufSize) END IF DO WHILE Remaining& IF cr = 0 THEN IF Remaining& < BufSize THEN BufSize = Remaining&: Buffer$ = SPACE$(BufSize) GET #FileNum, , Buffer$: BufPos = 1 END IF DO cr = INSTR(BufPos, Buffer$, cr$) IF cr THEN SaveCR = cr BufIn$ = MID$(Buffer$, BufPos, cr - BufPos) BufPos = cr + 2 EXIT FUNCTION ELSE IF SEEK(FileNum) >= LOF(FileNum) THEN Output$ = MID$(Buffer$, SaveCR + 2) BufIn$ = Output$ Remaining& = BufSize EXIT DO END IF Slop = BufSize - SaveCR - 1 Remaining& = Remaining& + Slop SEEK #FileNum, SEEK(FileNum) - Slop END IF LOOP WHILE cr Remaining& = Remaining& - BufSize LOOP Reading = 0: done = -1 END FUNCTION SUB colprint2 (TEXT$) STATIC 'prints text to screen expanding esc-char->colors '--------------------------------------------------------------------------- 'ESC @ ->color 0 (black) 'ESC O ->color 15 (white) '--------------------------------------------------------------------------- i = 1: J = 1 DO a = INSTR(J, TEXT$, esc$) IF a THEN J = a + 2: PRINT MID$(TEXT$, i, a - i); : COLOR ASC(MID$(TEXT$, a + 1, 1)) - 64: i = J LOOP WHILE a PRINT MID$(TEXT$, i) END SUB SUB comments 'to do ' falta distinguir guion de signo -. Signo - tiene espacio izq y cifra der 'ok! tratar distinto . de extension y . decimal. 'OK! NO ENCUENTRA 1ER KWD EN LINEA SANGRADA (RECONOCE TABS?) 'ok! NO RECONOCE &H0 'ok! NO RECONOCE // DE COMMON SHARED /BLOQUE/ ' si un componente de variable con punto es kwd, lo reconoce como tal END SUB FUNCTION filesel$ (prompt$, dummy%) 'Allows user to select a file '------------------------------------ 'dummy% parameter not used '------------------------------------ 'error messages CONST pak$ = " Press a key.." CONST pnf$ = "Path not Found." CONST ddne$ = "Drive does not Exist." CONST dnr$ = "Drive not ready. Retry/Abort" CONST fdne$ = "File not found." CONST pfe$ = "Path-file error." 'screen reset, start error handler VIEW PRINT: COLOR 7, 0: CLS ON ERROR GOTO errorhandler DO GOSUB VIEWFILES askdrive: GOSUB question: INPUT ; "Drive [Enter:Current]>", drive$ 'if no input, go ask path IF LEN(drive$) THEN '---CHDRIVE drive$ in PDS----------------------- R3: DRIV% = ASC(UCASE$(drive$)) - 65 INREG.AX = &HE00: INREG.DX = DRIV% CALL INTERRUPTX(&H21, INREG, INREG) 'probably the drive we asked for does'nt exist INREG.AX = &H1900 CALL INTERRUPTX(&H21, INREG, INREG) IF (INREG.AX AND &HFF) <> DRIV% THEN ERROR 68 IF errata = 68 THEN msgerr$ = ddne$ + pak$: GOSUB errmsg: GOTO askdrive IF errata = 71 THEN msgerr$ = dnr$: GOSUB errmsg: IF a$ = "R" THEN GOTO R3 ELSE GOTO askdrive GOSUB VIEWFILES END IF askpath: 'what is in? Here we trap the no disk error R4: IF errata = 71 THEN msgerr$ = dnr$: GOSUB errmsg: IF a$ = "R" THEN GOTO R4 ELSE GOTO askdrive GOSUB question: INPUT ; "Path [Enter:Current]>", path$ 'if no input go ask filename IF LEN(path$) THEN R1: CHDIR path$ IF errata = 53 OR errata = 76 THEN msgerr$ = pnf$ + pak$: GOSUB errmsg: GOTO askpath IF errata = 71 THEN msgerr$ = dnr$: GOSUB errmsg: IF a$ = "R" THEN GOTO R1 ELSE GOTO askdrive GOSUB VIEWFILES END IF askname: GOSUB question: INPUT ; "File [Enter:New path]>", name$ 'does it exist? R2: OPEN name$ FOR INPUT AS #254 SELECT CASE errata CASE 76: msgerr$ = pnf$ + pak$: GOSUB errmsg: GOTO askname CASE 53, 64: LOCATE 2, 1: msgerr$ = fdne + pak$: GOSUB errmsg: GOTO askname CASE 71: msgerr$ = dnr$: GOSUB errmsg: IF a$ = "R" THEN GOTO R2 ELSE GOTO askdrive CASE 75: msgerr$ = pfe$ + pak$: GOSUB errmsg: GOTO askname END SELECT r5: CLOSE #254 LOOP UNTIL LEN(name$) 'file exists: return msgerr$ = "File " + UCASE$(name$) + " found.." + pak$: GOSUB errmsg VIEW PRINT: COLOR 7, 0: CLS ON ERROR GOTO 0 filesel$ = name$ EXIT FUNCTION VIEWFILES: VIEW PRINT 3 TO 25: COLOR 7, 0: CLS : FILES: VIEW PRINT 1 TO 2: COLOR 15, 4: CLS : RETURN question: LOCATE 1, 1: PRINT SPACE$(80); : LOCATE 1, 1: PRINT prompt$ + "? -->Select "; RETURN errmsg: LOCATE 2, 1: PRINT msgerr$; : a$ = UCASE$(INPUT$(1)): LOCATE 2, 1: PRINT SPACE$(80) errata = 0 RETURN END FUNCTION SUB loadkwds 'load kwds from DATA, sorts, counts and search for duplicates 'LOAD KWD DATA 'SNIPPET READ STRINGS FROM DATA WITH SENTINEL RESTORE QData words = 0 DO WHILE a$ <> "@@@" words = words + 1: READ a$ LOOP words = words - 1 REDIM qword$(1 TO words) RESTORE QData FOR i = 1 TO words READ qword$(i) NEXT 'END SNIPPET 'SNIPPET BUBBLESORT FOR i = 2 TO words J = i DO WHILE qword$(J) < qword$(J - 1) SWAP qword$(J), qword$(J - 1) J = J - 1 IF J < 2 THEN EXIT DO LOOP NEXT 'END SNIPPET CLS : COLOR 15 'snippet find equal FOR i = 2 TO words IF qword$(i) = qword$(i - 1) THEN PRINT "ERROR line "; i; ": "; qword$(i); "= "; qword$(i - 1) NEXT PRINT i; " Keywords in list": PRINT 'end snippet FOR i = 1 TO words: PRINT qword$(i); " "; : NEXT a$ = INPUT$(1) END SUB FUNCTION parsebas2$ (line$) STATIC 'parses bas line coloring kwds, nrs, text '---------------------------------------------------------------------- ' '---------------------------------------------------------------------- PB$ = "": COLR = col.Default: t$ = RTRIM$(line$): endl = LEN(t$): ptr = 1 found = FALSE: ptr2 = -1 DO WHILE ptr <= endl a$ = MID$(t$, ptr, 1) SELECT CASE a$ CASE "'": SWAP w$, a$: GOTO remparse CASE " ": SWAP w$, a$: GOSUB add1 CASE CHR$(9): SWAP w$, a$: GOSUB add1 CASE ".", "(", ")", ";", ",", ":": COLR = col.sign: SWAP w$, a$: GOSUB add1 CASE "!", "#", "%", "&", "$": COLR = col.type: SWAP w$, a$: GOSUB add1 CASE "/", "-": COLR = col.math: SWAP w$, a$: GOSUB add1 CASE CHR$(34): COLR = col.cstring: ptr2 = INSTR(ptr + 1, t$, CHR$(34)) IF ptr2 THEN w$ = MID$(t$, ptr, ptr2 - ptr + 1) ELSE w$ = MID$(t$, ptr) GOSUB add1: GOTO nextchar END SELECT nextloop: hex = FALSE parseword: ptr2 = endl + 1 x = INSTR(ptr, t$, CHR$(9)): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, " "): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, "("): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, ")"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, ";"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, ","): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, "/"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, "."): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x1 = INSTR(ptr, t$, ":"): IF x1 THEN IF x1 < ptr2 THEN ptr2 = x1 x3 = INSTR(ptr, t$, "&"): IF x3 THEN IF x3 < ptr2 THEN ptr2 = x3 x = INSTR(ptr, t$, "!"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x2 = INSTR(ptr, t$, "$"): IF x2 THEN IF x2 < ptr2 THEN ptr2 = x2 x = INSTR(ptr, t$, "#"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, "%"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, "'"): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, CHR$(34)): IF x THEN IF x < ptr2 THEN SWAP ptr2, x x = INSTR(ptr, t$, "-"): IF x THEN IF ptr2 <> ptr THEN IF x < ptr2 THEN ptr2 = x IF hex = FALSE THEN IF ptr = x3 THEN ptr = ptr + 1: hex = true: GOTO parseword IF hex = true THEN ptr = ptr - 1: hex = FALSE w$ = MID$(t$, ptr, ptr2 - ptr) 'symbols SELECT CASE w$ CASE "" CASE "MOD", "XOR", "NOT", "AND", "EQV": COLR = col.math: : GOSUB add1: GOTO nextchar CASE "REM": GOTO remparse CASE "<>", ">=", "<=", "OR": COLR = col.math: GOSUB add1: GOTO nextchar CASE ">", "<", "+", "^", "*", "/", "\", "=": COLR = col.math: GOSUB add1: GOTO nextchar CASE ELSE: IF x2 THEN IF x2 = ptr2 THEN w$ = w$ + "$" 'SNIPPET keyword binary table search found = FALSE IF w$ <> UCASE$(w$) GOTO nokwd IF w$ < qword$(1) GOTO nokwd IF w$ > qword$(words) GOTO nokwd P1 = 1: P2 = words DO p3 = (P1 + P2) \ 2: a$ = qword$(p3) IF w$ = a$ THEN found = true: EXIT DO IF w$ < a$ THEN P2 = p3 - 1: ELSE P1 = p3 + 1 LOOP UNTIL P1 > P2 nokwd: 'END SNIPPET IF found THEN COLR = col.statement: GOSUB add1: GOTO nextchar IF x2 THEN IF RIGHT$(w$, 1) = "$" THEN w$ = LEFT$(w$, LEN(w$) - 1) 'LABEL IF ptr = 1 THEN IF x1 THEN IF x1 = ptr2 THEN w$ = w$ + ":": COLR = col.label: GOSUB add1: GOTO finlin 'NUMBER IF VAL(w$) THEN COLR = col.number: GOSUB add1: GOTO nextchar IF w$ = "0" THEN COLR = col.number: GOSUB add1: GOTO nextchar IF w$ = "&H0" THEN COLR = col.number: GOSUB add1: GOTO nextchar IF w$ = "&O0" THEN COLR = col.number: GOSUB add1: GOTO nextchar 'ANYTHING ELSE COLR = col.Default: GOSUB add1 END SELECT nextchar: LOOP finlin: parsebas2$ = PB$ EXIT FUNCTION add1: IF oldcolr = COLR OR w$ = " " THEN PB$ = PB$ + w$ ELSE PB$ = PB$ + esc$ + CHR$(COLR + 64) + w$ SWAP oldcolr, COLR END IF ptr = ptr + LEN(w$): COLR = col.Default RETURN remparse: l1 = 0 IF ptr = 1 THEN l1 = true COLR = col.statement: GOSUB add1: IF l1 THEN IF LEFT$(LTRIM$(MID$(t$, ptr)), 1) = "$" THEN l1 = 2 w$ = MID$(t$, ptr) IF l1 = 2 THEN COLR = col.meta ELSE COLR = col.comment GOSUB add1 GOTO finlin END FUNCTION