Author Topic: Microsoft RemLine - Line Number Removal Utility  (Read 154 times)

Offline George McGinn

  • Newbie
  • Posts: 11
Microsoft RemLine - Line Number Removal Utility
« on: July 07, 2020, 11:05:03 AM »

This program will go through any QB64 that has line numbers, remove the ones that do not have a reference to it.

According to the comments:
    REMLINE.BAS is a program to remove line numbers from Microsoft Basic Programs. It removes only those line numbers that       
    are not the object of one of the following statements: GOSUB, RETURN, GOTO, THEN, ELSE, RESUME, RESTORE, or RUN.

    When REMLINE is run, it will ask for the name of the file to be processed and the name of the file or device to receive the
    reformatted output. If no extension is given, .BAS is assumed (except for output devices). If filenames are not given,
    REMLINE prompts for file names. If both filenames are the same, REMLINE saves the original file with the extension .BAK.

This is a handy utility if you are like me and love structured code. I have already converted and tested it on 50 Vintage BASIC programs and they ran with no issues with bad/missing line numbers (I did have to change some of the statements as different dialects have different ways of doing things not compatible with QB64). I use this program a lot because I will be publishing a blog on Vintage BASIC programs, and show how to convert them to run on QB64, which will be in all my posts.

I hope you find this program useful.

BTW: This program does not display a finish message, so if you need one, you should be able to put it just infront of the END statement.


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

Offline Dav

  • Forum Regular
  • Posts: 241
Re: Microsoft RemLine - Line Number Removal Utility
« Reply #1 on: July 07, 2020, 02:07:16 PM »
Hey, George!  I had to do a double-take when I saw your name. There for a second I thought I was at another coding forum.  Good to see you again, and glad to have you here.  Welcome aboard.

I remember that remline tool. Can come in handy.  Thanks.

- Dav

Offline bplus

  • Forum Resident
  • Posts: 4589
  • B+ nots
Re: Microsoft RemLine - Line Number Removal Utility
« Reply #2 on: July 07, 2020, 03:19:05 PM »
You know Johnno was just telling me about George.

About the things that get done, by George!

This code would have helped me back in GW days transitioning to QB4.5