| 1 | '\r |
| 2 | ' Microsoft RemLine - Line Number Removal Utility\r |
| 3 | ' Copyright (C) Microsoft Corporation 1985-1990\r |
| 4 | '\r |
| 5 | ' REMLINE.BAS is a program to remove line numbers from Microsoft Basic\r |
| 6 | ' Programs. It removes only those line numbers that are not the object\r |
| 7 | ' of one of the following statements: GOSUB, RETURN, GOTO, THEN, ELSE,\r |
| 8 | ' RESUME, RESTORE, or RUN.\r |
| 9 | '\r |
| 10 | ' When REMLINE is run, it will ask for the name of the file to be\r |
| 11 | ' processed and the name of the file or device to receive the\r |
| 12 | ' reformatted output. If no extension is given, .BAS is assumed (except\r |
| 13 | ' for output devices). If filenames are not given, REMLINE prompts for\r |
| 14 | ' file names. If both filenames are the same, REMLINE saves the original\r |
| 15 | ' file with the extension .BAK.\r |
| 16 | '\r |
| 17 | ' REMLINE makes several assumptions about the program:\r |
| 18 | '\r |
| 19 | ' 1. It must be correct syntactically, and must run in BASICA or\r |
| 20 | ' GW-BASIC interpreter.\r |
| 21 | ' 2. There is a 400 line limit. To process larger files, change\r |
| 22 | ' MaxLines constant.\r |
| 23 | ' 3. The first number encountered on a line is considered a line\r |
| 24 | ' number; thus some continuation lines (in a compiler-specific\r |
| 25 | ' construction) may not be handled correctly.\r |
| 26 | ' 4. REMLINE can handle simple statements that test the ERL function\r |
| 27 | ' using relational operators such as =, <, and >. For example,\r |
| 28 | ' the following statement is handled correctly:\r |
| 29 | '\r |
| 30 | ' IF ERL = 100 THEN END\r |
| 31 | '\r |
| 32 | ' Line 100 is not removed from the source code. However, more\r |
| 33 | ' complex expressions that contain the +, -, AND, OR, XOR, EQV,\r |
| 34 | ' MOD, or IMP operators may not be handled correctly. For example,\r |
| 35 | ' in the following statement REMLINE does not recognize line 105\r |
| 36 | ' as a referenced line number and removes it from the source code:\r |
| 37 | '\r |
| 38 | ' IF ERL + 5 = 105 THEN END\r |
| 39 | '\r |
| 40 | ' If you do not like the way REMLINE formats its output, you can modify\r |
| 41 | ' the output lines in SUB GenOutFile. An example is shown in comments.\r |
| 42 | DEFINT A-Z\r |
| 43 | \r |
| 44 | ' Function and Subprocedure declarations\r |
| 45 | DECLARE FUNCTION GetToken$ (Search$, Delim$)\r |
| 46 | DECLARE FUNCTION StrSpn% (InString$, Separator$)\r |
| 47 | DECLARE FUNCTION StrBrk% (InString$, Separator$)\r |
| 48 | DECLARE FUNCTION IsDigit% (Char$)\r |
| 49 | DECLARE SUB GetFileNames ()\r |
| 50 | DECLARE SUB BuildTable ()\r |
| 51 | DECLARE SUB GenOutFile ()\r |
| 52 | DECLARE SUB InitKeyTable ()\r |
| 53 | \r |
| 54 | ' Global and constant data\r |
| 55 | CONST TRUE = -1\r |
| 56 | CONST false = 0\r |
| 57 | CONST MaxLines = 400\r |
| 58 | \r |
| 59 | DIM SHARED LineTable!(MaxLines)\r |
| 60 | DIM SHARED LineCount\r |
| 61 | DIM SHARED Seps$, InputFile$, OutputFile$, TmpFile$\r |
| 62 | \r |
| 63 | ' Keyword search data\r |
| 64 | CONST KeyWordCount = 9\r |
| 65 | DIM SHARED KeyWordTable$(KeyWordCount)\r |
| 66 | \r |
| 67 | KeyData:\r |
| 68 | DATA THEN, ELSE, GOSUB, GOTO, RESUME, RETURN, RESTORE, RUN, ERL, ""\r |
| 69 | \r |
| 70 | ' Start of module-level program code\r |
| 71 | Seps$ = " ,:=<>()" + CHR$(9)\r |
| 72 | InitKeyTable\r |
| 73 | GetFileNames\r |
| 74 | ON ERROR GOTO FileErr1\r |
| 75 | OPEN InputFile$ FOR INPUT AS 1\r |
| 76 | ON ERROR GOTO 0\r |
| 77 | COLOR 7: PRINT "Working"; : COLOR 23: PRINT " . . .": COLOR 7: PRINT\r |
| 78 | BuildTable\r |
| 79 | CLOSE #1\r |
| 80 | OPEN InputFile$ FOR INPUT AS 1\r |
| 81 | ON ERROR GOTO FileErr2\r |
| 82 | OPEN OutputFile$ FOR OUTPUT AS 2\r |
| 83 | ON ERROR GOTO 0\r |
| 84 | GenOutFile\r |
| 85 | CLOSE #1, #2\r |
| 86 | IF OutputFile$ <> "CON" THEN CLS\r |
| 87 | \r |
| 88 | END\r |
| 89 | \r |
| 90 | FileErr1:\r |
| 91 | CLS\r |
| 92 | PRINT " Invalid file name": PRINT\r |
| 93 | INPUT " New input file name (ENTER to terminate): ", InputFile$\r |
| 94 | IF InputFile$ = "" THEN END\r |
| 95 | FileErr2:\r |
| 96 | INPUT " Output file name (ENTER to print to screen) :", OutputFile$\r |
| 97 | PRINT\r |
| 98 | IF (OutputFile$ = "") THEN OutputFile$ = "CON"\r |
| 99 | IF TmpFile$ = "" THEN\r |
| 100 | RESUME\r |
| 101 | ELSE\r |
| 102 | TmpFile$ = ""\r |
| 103 | RESUME NEXT\r |
| 104 | END IF\r |
| 105 | \r |
| 106 | '\r |
| 107 | ' BuildTable:\r |
| 108 | ' Examines the entire text file looking for line numbers that are\r |
| 109 | ' the object of GOTO, GOSUB, etc. As each is found, it is entered\r |
| 110 | ' into a table of line numbers. The table is used during a second\r |
| 111 | ' pass (see GenOutFile), when all line numbers not in the list\r |
| 112 | ' are removed.\r |
| 113 | ' Input:\r |
| 114 | ' Uses globals KeyWordTable$, KeyWordCount, and Seps$\r |
| 115 | ' Output:\r |
| 116 | ' Modifies LineTable! and LineCount\r |
| 117 | '\r |
| 118 | SUB BuildTable STATIC\r |
| 119 | \r |
| 120 | DO WHILE NOT EOF(1)\r |
| 121 | ' Get line and first token\r |
| 122 | LINE INPUT #1, InLin$\r |
| 123 | Token$ = GetToken$(InLin$, Seps$)\r |
| 124 | DO WHILE (Token$ <> "")\r |
| 125 | FOR KeyIndex = 1 TO KeyWordCount\r |
| 126 | ' See if token is keyword\r |
| 127 | IF (KeyWordTable$(KeyIndex) = UCASE$(Token$)) THEN\r |
| 128 | ' Get possible line number after keyword\r |
| 129 | Token$ = GetToken$("", Seps$)\r |
| 130 | ' Check each token to see if it is a line number\r |
| 131 | ' (the LOOP is necessary for the multiple numbers\r |
| 132 | ' of ON GOSUB or ON GOTO). A non-numeric token will\r |
| 133 | ' terminate search.\r |
| 134 | DO WHILE (IsDigit(LEFT$(Token$, 1)))\r |
| 135 | LineCount = LineCount + 1\r |
| 136 | LineTable!(LineCount) = VAL(Token$)\r |
| 137 | Token$ = GetToken$("", Seps$)\r |
| 138 | IF Token$ <> "" THEN KeyIndex = 0\r |
| 139 | LOOP\r |
| 140 | END IF\r |
| 141 | NEXT KeyIndex\r |
| 142 | ' Get next token\r |
| 143 | Token$ = GetToken$("", Seps$)\r |
| 144 | LOOP\r |
| 145 | LOOP\r |
| 146 | \r |
| 147 | END SUB\r |
| 148 | \r |
| 149 | '\r |
| 150 | ' GenOutFile:\r |
| 151 | ' Generates an output file with unreferenced line numbers removed.\r |
| 152 | ' Input:\r |
| 153 | ' Uses globals LineTable!, LineCount, and Seps$\r |
| 154 | ' Output:\r |
| 155 | ' Processed file\r |
| 156 | '\r |
| 157 | SUB GenOutFile STATIC\r |
| 158 | \r |
| 159 | ' Speed up by eliminating comma and colon (can't separate first token)\r |
| 160 | Sep$ = " " + CHR$(9)\r |
| 161 | DO WHILE NOT EOF(1)\r |
| 162 | LINE INPUT #1, InLin$\r |
| 163 | IF (InLin$ <> "") THEN\r |
| 164 | ' Get first token and process if it is a line number\r |
| 165 | Token$ = GetToken$(InLin$, Sep$)\r |
| 166 | IF IsDigit(LEFT$(Token$, 1)) THEN\r |
| 167 | LineNumber! = VAL(Token$)\r |
| 168 | FoundNumber = false\r |
| 169 | ' See if line number is in table of referenced line numbers\r |
| 170 | FOR index = 1 TO LineCount\r |
| 171 | IF (LineNumber! = LineTable!(index)) THEN\r |
| 172 | FoundNumber = TRUE\r |
| 173 | END IF\r |
| 174 | NEXT index\r |
| 175 | ' Modify line strings\r |
| 176 | IF (NOT FoundNumber) THEN\r |
| 177 | Token$ = SPACE$(LEN(Token$))\r |
| 178 | MID$(InLin$, StrSpn(InLin$, Sep$), LEN(Token$)) = Token$\r |
| 179 | END IF\r |
| 180 | \r |
| 181 | ' You can replace the previous lines with your own\r |
| 182 | ' code to reformat output. For example, try these lines:\r |
| 183 | \r |
| 184 | 'TmpPos1 = StrSpn(InLin$, Sep$) + LEN(Token$)\r |
| 185 | 'TmpPos2 = TmpPos1 + StrSpn(MID$(InLin$, TmpPos1), Sep$)\r |
| 186 | '\r |
| 187 | 'IF FoundNumber THEN\r |
| 188 | ' InLin$ = LEFT$(InLin$, TmpPos1 - 1) + CHR$(9) + MID$(InLin$, TmpPos2)\r |
| 189 | 'ELSE\r |
| 190 | ' InLin$ = CHR$(9) + MID$(InLin$, TmpPos2)\r |
| 191 | 'END IF\r |
| 192 | \r |
| 193 | END IF\r |
| 194 | END IF\r |
| 195 | ' Print line to file or console (PRINT is faster than console device)\r |
| 196 | IF OutputFile$ = "CON" THEN\r |
| 197 | PRINT InLin$\r |
| 198 | ELSE\r |
| 199 | PRINT #2, InLin$\r |
| 200 | END IF\r |
| 201 | LOOP\r |
| 202 | \r |
| 203 | END SUB\r |
| 204 | \r |
| 205 | '\r |
| 206 | ' GetFileNames:\r |
| 207 | ' Gets a file name by prompting the user.\r |
| 208 | ' Input:\r |
| 209 | ' User input\r |
| 210 | ' Output:\r |
| 211 | ' Defines InputFiles$ and OutputFiles$\r |
| 212 | '\r |
| 213 | SUB GetFileNames STATIC\r |
| 214 | \r |
| 215 | CLS\r |
| 216 | PRINT " Microsoft RemLine: Line Number Removal Utility"\r |
| 217 | PRINT " (.BAS assumed if no extension given)"\r |
| 218 | PRINT\r |
| 219 | INPUT " Input file name (ENTER to terminate): ", InputFile$\r |
| 220 | IF InputFile$ = "" THEN END\r |
| 221 | INPUT " Output file name (ENTER to print to screen): ", OutputFile$\r |
| 222 | PRINT\r |
| 223 | IF (OutputFile$ = "") THEN OutputFile$ = "CON"\r |
| 224 | \r |
| 225 | IF INSTR(InputFile$, ".") = 0 THEN\r |
| 226 | InputFile$ = InputFile$ + ".BAS"\r |
| 227 | END IF\r |
| 228 | \r |
| 229 | IF INSTR(OutputFile$, ".") = 0 THEN\r |
| 230 | SELECT CASE OutputFile$\r |
| 231 | CASE "CON", "SCRN", "PRN", "COM1", "COM2", "LPT1", "LPT2", "LPT3"\r |
| 232 | EXIT SUB\r |
| 233 | CASE ELSE\r |
| 234 | OutputFile$ = OutputFile$ + ".BAS"\r |
| 235 | END SELECT\r |
| 236 | END IF\r |
| 237 | \r |
| 238 | DO WHILE InputFile$ = OutputFile$\r |
| 239 | TmpFile$ = LEFT$(InputFile$, INSTR(InputFile$, ".")) + "BAK"\r |
| 240 | ON ERROR GOTO FileErr1\r |
| 241 | NAME InputFile$ AS TmpFile$\r |
| 242 | ON ERROR GOTO 0\r |
| 243 | IF TmpFile$ <> "" THEN InputFile$ = TmpFile$\r |
| 244 | LOOP\r |
| 245 | \r |
| 246 | END SUB\r |
| 247 | \r |
| 248 | '\r |
| 249 | ' GetToken$:\r |
| 250 | ' Extracts tokens from a string. A token is a word that is surrounded\r |
| 251 | ' by separators, such as spaces or commas. Tokens are extracted and\r |
| 252 | ' analyzed when parsing sentences or commands. To use the GetToken$\r |
| 253 | ' function, pass the string to be parsed on the first call, then pass\r |
| 254 | ' a null string on subsequent calls until the function returns a null\r |
| 255 | ' to indicate that the entire string has been parsed.\r |
| 256 | ' Input:\r |
| 257 | ' Search$ = string to search\r |
| 258 | ' Delim$ = String of separators\r |
| 259 | ' Output:\r |
| 260 | ' GetToken$ = next token\r |
| 261 | '\r |
| 262 | FUNCTION GetToken$ (Search$, Delim$) STATIC\r |
| 263 | \r |
| 264 | ' Note that SaveStr$ and BegPos must be static from call to call\r |
| 265 | ' (other variables are only static for efficiency).\r |
| 266 | ' If first call, make a copy of the string\r |
| 267 | IF (Search$ <> "") THEN\r |
| 268 | BegPos = 1\r |
| 269 | SaveStr$ = Search$\r |
| 270 | END IF\r |
| 271 | \r |
| 272 | ' Find the start of the next token\r |
| 273 | NewPos = StrSpn(MID$(SaveStr$, BegPos, LEN(SaveStr$)), Delim$)\r |
| 274 | IF NewPos THEN\r |
| 275 | ' Set position to start of token\r |
| 276 | BegPos = NewPos + BegPos - 1\r |
| 277 | ELSE\r |
| 278 | ' If no new token, quit and return null\r |
| 279 | GetToken$ = ""\r |
| 280 | EXIT FUNCTION\r |
| 281 | END IF\r |
| 282 | \r |
| 283 | ' Find end of token\r |
| 284 | NewPos = StrBrk(MID$(SaveStr$, BegPos, LEN(SaveStr$)), Delim$)\r |
| 285 | IF NewPos THEN\r |
| 286 | ' Set position to end of token\r |
| 287 | NewPos = BegPos + NewPos - 1\r |
| 288 | ELSE\r |
| 289 | ' If no end of token, return set to end a value\r |
| 290 | NewPos = LEN(SaveStr$) + 1\r |
| 291 | END IF\r |
| 292 | ' Cut token out of search string\r |
| 293 | GetToken$ = MID$(SaveStr$, BegPos, NewPos - BegPos)\r |
| 294 | ' Set new starting position\r |
| 295 | BegPos = NewPos\r |
| 296 | \r |
| 297 | END FUNCTION\r |
| 298 | \r |
| 299 | '\r |
| 300 | ' InitKeyTable:\r |
| 301 | ' Initializes a keyword table. Keywords must be recognized so that\r |
| 302 | ' line numbers can be distinguished from numeric constants.\r |
| 303 | ' Input:\r |
| 304 | ' Uses KeyData\r |
| 305 | ' Output:\r |
| 306 | ' Modifies global array KeyWordTable$\r |
| 307 | '\r |
| 308 | SUB InitKeyTable STATIC\r |
| 309 | \r |
| 310 | RESTORE KeyData\r |
| 311 | FOR Count = 1 TO KeyWordCount\r |
| 312 | READ KeyWord$\r |
| 313 | KeyWordTable$(Count) = KeyWord$\r |
| 314 | NEXT\r |
| 315 | \r |
| 316 | END SUB\r |
| 317 | \r |
| 318 | '\r |
| 319 | ' IsDigit:\r |
| 320 | ' Returns true if character passed is a decimal digit. Since any\r |
| 321 | ' Basic token starting with a digit is a number, the function only\r |
| 322 | ' needs to check the first digit. Doesn't check for negative numbers,\r |
| 323 | ' but that's not needed here.\r |
| 324 | ' Input:\r |
| 325 | ' Char$ - initial character of string to check\r |
| 326 | ' Output:\r |
| 327 | ' IsDigit - true if within 0 - 9\r |
| 328 | '\r |
| 329 | FUNCTION IsDigit (Char$) STATIC\r |
| 330 | \r |
| 331 | IF (Char$ = "") THEN\r |
| 332 | IsDigit = false\r |
| 333 | ELSE\r |
| 334 | CharAsc = ASC(Char$)\r |
| 335 | IsDigit = (CharAsc >= ASC("0")) AND (CharAsc <= ASC("9"))\r |
| 336 | END IF\r |
| 337 | \r |
| 338 | END FUNCTION\r |
| 339 | \r |
| 340 | '\r |
| 341 | ' StrBrk:\r |
| 342 | ' Searches InString$ to find the first character from among those in\r |
| 343 | ' Separator$. Returns the index of that character. This function can\r |
| 344 | ' be used to find the end of a token.\r |
| 345 | ' Input:\r |
| 346 | ' InString$ = string to search\r |
| 347 | ' Separator$ = characters to search for\r |
| 348 | ' Output:\r |
| 349 | ' StrBrk = index to first match in InString$ or 0 if none match\r |
| 350 | '\r |
| 351 | FUNCTION StrBrk (InString$, Separator$) STATIC\r |
| 352 | \r |
| 353 | Ln = LEN(InString$)\r |
| 354 | BegPos = 1\r |
| 355 | ' Look for end of token (first character that is a delimiter).\r |
| 356 | DO WHILE INSTR(Separator$, MID$(InString$, BegPos, 1)) = 0\r |
| 357 | IF BegPos > Ln THEN\r |
| 358 | StrBrk = 0\r |
| 359 | EXIT FUNCTION\r |
| 360 | ELSE\r |
| 361 | BegPos = BegPos + 1\r |
| 362 | END IF\r |
| 363 | LOOP\r |
| 364 | StrBrk = BegPos\r |
| 365 | \r |
| 366 | END FUNCTION\r |
| 367 | \r |
| 368 | '\r |
| 369 | ' StrSpn:\r |
| 370 | ' Searches InString$ to find the first character that is not one of\r |
| 371 | ' those in Separator$. Returns the index of that character. This\r |
| 372 | ' function can be used to find the start of a token.\r |
| 373 | ' Input:\r |
| 374 | ' InString$ = string to search\r |
| 375 | ' Separator$ = characters to search for\r |
| 376 | ' Output:\r |
| 377 | ' StrSpn = index to first nonmatch in InString$ or 0 if all match\r |
| 378 | '\r |
| 379 | FUNCTION StrSpn% (InString$, Separator$) STATIC\r |
| 380 | \r |
| 381 | Ln = LEN(InString$)\r |
| 382 | BegPos = 1\r |
| 383 | ' Look for start of a token (character that isn't a delimiter).\r |
| 384 | DO WHILE INSTR(Separator$, MID$(InString$, BegPos, 1))\r |
| 385 | IF BegPos > Ln THEN\r |
| 386 | StrSpn = 0\r |
| 387 | EXIT FUNCTION\r |
| 388 | ELSE\r |
| 389 | BegPos = BegPos + 1\r |
| 390 | END IF\r |
| 391 | LOOP\r |
| 392 | StrSpn = BegPos\r |
| 393 | \r |
| 394 | END FUNCTION\r |
| 395 | \r |