Table of Contents

MMBasic Source Code Formatter

This module is part of the original MMBasic library. It is reproduced here with kind permission of Hugh Buckle and Geoff Graham. Be aware it may reference functionality which has changed or is deprecated in the latest versions of MMBasic.

MMBasic Source Formatter ver 2.3 20/4/2013

Format.bas is a program to indent lines of MMBasic source code in order to highlight the program structure.

Four methods to invoke:

1. Implied Run Command

MMBasic ver 4.3A allows you to invoke a program from the prompt with the program name followed by a parameter list. The parameter list is passed to the program in the Read-Only Variable MM.CMDLINE$.

At the MMBasic prompt type:

FORMAT InFileName[.bas] OutFileName[.bas] [Indent] [/P]

Where:

InFileName  is the program source to be formatted.
OutFileName is the result of formatting.
	    OutFileName must not be the same as InfileName.
Indent      is the optional number of characters to indent at each level (default 2)
/P          is an optional switch to list the output, pausing each screenful
If an extension is not provided on the file names, Format inserts .bas.

If you prefer, you can separate parms with either a blank or a comma so this is also valid:

FORMAT InFileName[.bas],OutFileName[.bas],[Indent],[/P]

2. Chaining from another program

If you Chain to Format, you can either

  1. create a file FORMAT.DAT containing the parameters (see method 3 below) or
  2. let Format prompt for all four parameters as if you had just used method 4 below or
  3. you can pass the 4 parameters to it in a variable CMDParm$.

To use the latter, transfer control to Format using:

CMDParm$ = "InFileName[.bas] OutFileName[.bas] [Indent] [/p]"
CHAIN "FORMAT.BAS"

Format uses about 55kB of memory, so if your Chaining program uses large arrays, you may need to ERASE them before chaining to Format.

3. Config file FORMAT.DAT

If Format doesn't find the parameter list in MM.CMDLINE$ or CMDParm$, then it looks for a file FORMAT.DAT. This file contains a single line in the same format as the parameter list for the Implied RUN Command. i.e.

InFileName[.bas] OutFileName[.bas] [Indent] [/P] or
InFileName[.bas],OutFileName[.bas],[Indent],[/P]

4. "Normal" invocation

You can invoke Format by typing RUN “FORMAT[.BAS]” at the command prompt and Format will prompt for the four parameters.

FORMAT's standard set of rules:

  1. Lines starting with a Label are aligned to the left margin

as are SUB, END SUB, FUNCTION and END FUNCTION.

  1. The first non-label line is indented to the first level.
  1. Lines following a single line IF, DO and FOR are not further indented.
  1. Multiline DO and FOR have their respective LOOP and NEXT aligned.

Intervening lines are indented one level.

  1. The NEXT that closes multiple levels of FOR is aligned under the first FOR.
  1. Multiline IF statements have their ELSE, ELSEIF and ENDIF aligned

under the relevant IF. Intervening statements are indented one level.

  1. Nesting increases the indent one level.
  1. For numbered programs, the line number is left aligned and the rest of the source

uses the length of the longest number plus one as the left margin.

  1. Other than setting the indent, the source line is not altered.
  1. No attempt is made to check or report program errors.

Implementaion Details

Initialisation gets input and output file names, indent and pause. The input file is checked to make sure it exists and if the output exists, the user is asked to confirm that it be used. The program doesn't allow the output to overwrite the input in case of a power or equipment failure.

The program first runs through the input file looking for line numbers, storing the length of the longest line number. If found, the line numbers are aligned at the left margin and the margin for the rest of the code set at the longest line number plus one space..

Each line is read in turn and, working on a copy of the line, the indent of the current and next line is found. The original line is then stripped of any leading spaces, indented with an appropriate number of blanks and written out. If the Pause switch has been set, the program copies the formatted source to the screen, pausing at each screenful for the user to press a key.

Each line is examined and processed in turn, writing it out when completed.

The first text encountered on the line is tested to see if it is, in order of precedence:

- A comment or end-of-line. - SUB, END SUB, FUNCTION, END FUNCTION - DO - LOOP - FOR - NEXT - IF… THEN - ELSEIF - ELSE - Label

Appropriate indenting action is taken if one of these commands is found at the start of a line. The line is then searched for colons and indenting adjusted if another of these commands is found.

Lines starting with SUB, END SUB, FUNCTION, END FUNCTION or a label are aligned to the left margin.

Lines starting with DO, FOR and IF signal that the following line should be indented one level.

Lines starting with LOOP, NEXT, ELSEIF and ELSE are set to one less level of indent than the previous line. If the NEXT is followed by multiple loop variables, then an indent is removed for each variable.

If a LOOP or NEXT appears on the same line as it's DO and FOR, then the level change for the next line is cancelled.

If an IF… THEN is followed by a statement (and optional ELSE), then it is treated as a single-line-if and the level change for the next line cancelled.

Tables

Two tables, populated by DATA statements, are used to help parse a line:

- One contains reserved words which can be followed immediately by a colon. e.g. PRINT: This is used to prevent these words from being incorrectly identified as a label.

- The other table contains a list of operators which is used to differenitate between a label and any other statement. Contiguous text containing an operator cannot be a label. e.g. A=B: is not a label.

Change Log

20/4/2013 V2.3 - When a file has line numbers followed by one or more spaces and a colon, the colon is moved adjacent to the line number. This mod was triggerd by MMPREY.BAS in MMLib which exhibits this condition. - If a line starts with a colon (after the optional line number and its optional colon) then it is removed. - e.g. “1234 : Statement” is changed to

     "1234: Statement". 
     "1234:  :Statement"   is changed to 
     "1234: Statement".

“ :Statement :Statement” is changed to

     "   Statement :Statement"

- Fixed a problem when nested FOR/NEXT loops appear on a single line and one of the NEXT statements terminates more than one level.

Hugh Buckle

FORMAT.BAS:

  ' MMBasic Program Source Formatter v2.3
  ' Hugh Buckle  March 2013 - updated April 2013
  
  '**********************************************************
  ' You can run this version from the prompt using the Implied RUN Command
  '
  '   FORMAT InFileName[.BAS] OutFileName[.BAS] [Indent] [/p]
  '
  ' You can use either a comma or space between parameters
  '
  '   Where: OutFileName must not be the same as InFileName
  '          Indent will default to 2 characters.
  '          /p lists the result to the screen - press a key to continue.
  '   You will be prompted if:
  '          The input file doesn't exist
  '          The output file exists or is the same as the input file
  '          Indent is outside the range 1 to 6.
  '
  ' You can also run the program in the normal way using the RUN command
  ' in which case you will be prompted for file names, indent and pause.
  
  ' FORMAT's standard set of rules:
  ' - Lines starting with a Label are aligned to the left margin
  '   as are SUB, END SUB, FUNCTION and END FUNCTION.
  ' - The first non-label line is indented to the first level.
  ' - Lines following a single line IF, DO and FOR are not further indented.
  ' - Multiline DO and FOR have their respective LOOP and NEXT aligned.
  '   Intervening lines are indented one level.
  ' - The NEXT that closes multiple levels of FOR is aligned
  '   under the first FOR.
  ' - Multiline IF statements have their ELSE, ELSEIF and ENDIF aligned
  '   under the relevant IF. Intervening statements are indented one level.
  ' - Nesting increases the indent one level.
  ' - For numbered programs, the left margin is the longest number plus one.
  ' - Other than setting the indent, the source line is not altered. However,
  '   if the line starts with a colon (after the line number and its optional
  '   colon), then it is removed.
  ' - No attempt is made to check the program for errors.
  '**********************************************************
  
  true=1
  false=0
  Restore 'JD Addition
  
  CMDParmFile$ = "Format.dat"  'File of command line parms in format
  'InFileName[.bas] OutFileName[.bas] [Indent] [/P]
  
  ' Data contains operators, used to differentiate
  ' between a label and any other statement.
  Data "=","+","-","*","/","<",">","\","^"
  Operators=9
  Dim Operator$(Operators)
  For I=1 To Operators
    Read Operator$(i)
  Next
  
  ' Data contains MMBasic commands which could be interpreted as labels
  Data "PRINT","RETURN","CLS","RESTORE","CLEAR","EXIT","FILES","MEMORY","NEW"
  Data "KEYDOWN","TROFF","TRON","DO","LOOP","NEXT"
  Commands=15
  Dim Command$(Commands)
  For i=1 To Commands
    Read Command$(i)
  Next
  
Mainline:
  '**********************************************************
  ' Initialisation gets input and output file names, indent and pause.
  ' Each line is read in turn and, working on a copy of the line,
  ' the indent of the current and next line is found. The original line
  ' is indented with an appropriate number of blanks and written out.
  ' If the first line contains a line number then the program skips through
  ' to the last line to find the maximum sized line number. Lines are then
  ' processed as before, adding a padded line number and indent to the code.
  '**********************************************************
  
  initialise(Ifile$, Ofile$, Indent)
  Print
  Print "Formatting ";ifile$;" and writing to ";Ofile$
  Print String$(78,"_")
  CheckForLineNumbers
  
  CLI = Indent                      'Current line indent
  NLI = Indent                      'Next line indent
  
  Do While Not Eof(#1)
    CLI = NLI
    Line Input #1, iline$              ' Get next line
    oline$ = StripBlanks$(iline$)      ' Strip off leading blanks
    If LineNumbers Then ExtractLineNo  ' Remove the line number
    cline$ = oline$                    ' Make working copy
    AdjustIndent                       ' Find current & next line indents
    oline$ = Space$(CLI) + Oline$      ' Indent current line
    If LineNumbers Then Oline$ = LineNo$ + Oline$
    Print #2, oline$                   ' Write to output file
    If PauseList$="Y" Then
      Print oline$                     ' Print a copy to screen
      wait                             ' Pause at a screenful
    Else
      Print ".";
    EndIf
  Loop
  Close #1
  Close #2
  Print
  Print String$(78,"_")
  Print "Formatting finished. Please test the output file."
  If MasterFile$ <> "" Then 'JD Addition
    UpTo = UpTo + 1
    Chain MasterFile$
  EndIf
  End 'Mainline
  
Function StripBlanks$(A$)
  '**********************************************************
  ' Strips blanks, tabs and colons from the beginning of the line.
  '**********************************************************
  Local i
  i=1
  Do While Mid$(A$,i,1)=" " Or Mid$(A$,i,1)=Chr$(9) Or Mid$(A$,i,1)=":"
    i=i+1
  Loop
  StripBlanks$ = Mid$(A$,i)  ' Copies from i to end of a$ to the function
End Function 'StripBlanks
  
Function Variable(a$,s)
  '**********************************************************
  ' Checks the character after a command name to see if it's a variable.
  ' s points to the character after the command name
  '**********************************************************
  Variable=True
  b$=Mid$(a$,s,1)
  If Len(a$)<s Or b$=" " Or b$=":" Then Variable=False
End Function 'Variable
  
Sub GetNextNonBlankChar(a$,p,b$)
  '**********************************************************
  ' Returns b$ either empty indicating end of line or
  ' containing the next non-blank character.
  ' A comment (single quote or REM) also indicates end of line.
  ' Any text inside double quotes is skipped.
  ' On entry, p points to the next char to inspect in a$.
  ' On exit, P points to the next character after the one in b$.
  '**********************************************************
  Do
    If p>=Len(a$) Or Mid$(a$,p,1)="'" Or UCase$(Mid$(a$,p,3))="REM" Then
      b$=""
    Else
      b$=Mid$(a$,p,1)
      If b$=Chr$(34) Then
        Do                         ' skip over quoted text
          p=p+1
        Loop Until Mid$(a$,p,1)=Chr$(34) Or p = Len(a$)
      EndIf
      p=p+1
    EndIf
  Loop Until b$="" Or b$<>" "
End Sub 'GetNextNonBlankChar
  
Sub ExtractLineNo
  '**********************************************************
  ' Saves the line number if present and pads it to
  ' the length of the longest one. If some of the code uses line
  ' numbers and some not, then those without line numbers are
  ' indented as if they had a line number.
  ' If the line number is followed by one or more spaces and
  ' a colon, the spaces between line number and colon are removed.
  '**********************************************************
  Local j
  If Left$(Oline$,1)>="0" And Left$(Oline$,1)<="9" Then
    LineNo$=Left$(Oline$,Instr(1,Oline$," "))
    j=Len(LineNo$)
    GetNextNonBlankChar(Oline$,j,b$)
    If b$=":" Then
      LineNo$=Left$(LineNo$,Len(LineNo$)-1)+": "
      Oline$=Mid$(Oline$,j)
    EndIf
    
    If Len(LineNo$) < LineNoLen Then ' pad to longest line no
      LineNo$ = LineNo$ + Space$(LineNoLen-Len(LineNo$))
    EndIf
    Oline$=Right$(Oline$,Len(Oline$)-Instr(1,Oline$," "))
    Oline$=StripBlanks$(Oline$)
  Else
    LineNo$=Space$(LineNoLen)
  EndIf
End Sub 'ExtractLineNo
  
Sub AdjustIndent
  '**********************************************************
  ' For each command that affects the indent,
  ' adjust both current (CLI) and next line indent (NLI).
  ' Repeat until comment or end-of-line reached to handle multi-statement lines.
  '**********************************************************
  EOL=False
  ForNest=False         'Nest level of FOR/NEXT
  DoFound=False
  Do
    Found=False
    Test_LineEnd(Found,EOL)
    If Not Found Then Test_Sub_Fn(Found)
    If Not Found Then Test_Do(Found)
    If Not Found Then Test_Loop(Found)
    If Not Found Then Test_For(Found)
    If Not Found Then Test_Next(Found)
    If Not Found Then Test_If_Then(Found,MultiLine)
    If Not Found Then Test_ElseIf(Found,MultiLine)
    If Not Found Then Test_Else(Found,MultiLine)
    If Not Found Then Test_EndIf(Found)
    If Not Found Then Test_for_label(Found)
    ' Look for other statements in the line
    If Not EOL Then
      FlushToColon
      Cline$=Stripblanks$(Cline$)
      If Cline$="" Then EOL=True
    EndIf
  Loop Until EOL
End Sub 'AdjustIndent
  
Sub Test_LineEnd(Found,EOL)
  '**********************************************************
  ' Test if finished processing this line
  '**********************************************************
  If Len(CLine$)<=1 Or Left$(Cline$,1)="'" Or Left$(UCase$(Cline$),4)="REM " Then
    Found=True
    EOL=True
  EndIf
End Sub 'Test_LineEnd
  
Sub FlushToColon
  '**********************************************************
  ' Find the next statement on a multi-statement line
  '**********************************************************
  Local i
  i=1
  Do
    GetNextNonBlankChar(Cline$,i,a$)
  Loop Until a$ = ":" Or a$ = ""
  If a$="" Then
    Cline$=""      ' reached EOL
  Else
    Cline$=Right$(Cline$,Len(Cline$)-i+1)
  EndIf
End Sub 'FlushToColon
  
Sub Test_Sub_Fn(Found)
  '**********************************************************
  ' SUB & FUNCTION start & end stmts are aligned to the left margin
  '**********************************************************
  If UCase$(Left$(Cline$,4))="SUB " Then
    Found = True
  ElseIf UCase$(Left$(Cline$,7))="END SUB" Then
    Found = True
  ElseIf UCase$(Left$(Cline$,9))="FUNCTION " Then
    Found = True
  ElseIf UCase$(Left$(Cline$,12))="END FUNCTION" Then
    Found = True
  EndIf
  If Found Then
    NLI = CLI       'save current indent
    CLI = 0         'Move this line to the left margin
  EndIf
End Sub 'Test_Sub_Fn
  
Sub Test_Do(Found)
  '**********************************************************
  'Print the DO at the current indent but indent the next line further
  '**********************************************************
  If UCase$(Left$(Cline$,2))="DO" And Not Variable(Cline$,3) Then
    NLI = NLI + Indent
    DoFound=True
    Found = True
  EndIf
End Sub 'Test_Do
  
Sub Test_Loop(Found)
  '**********************************************************
  'If LOOP, print this and the next line one less indent
  '**********************************************************
  If UCase$(Left$(Cline$,4))="LOOP" And Not Variable(Cline$,5) Then
    If DoFound Then
      NLI=CLI
    Else
      CLI = CLI - Indent   'remove one level of indent
      NLI = CLI            'make the next line indent the same
    EndIf
    Found = True
  EndIf
End Sub 'Test_Loop
  
Sub Test_For(Found)
  '**********************************************************
  'Print the FOR at the current indent but indent the next line one indent
  '**********************************************************
  If UCase$(Left$(Cline$,3))="FOR" And Not Variable(Cline$,4) Then
    NLI = NLI + Indent
    ForNest=ForNest+1
    Found = True
  EndIf
End Sub 'Test_For
  
Sub Test_Next(Found)
  '**********************************************************
  'If NEXT, print this and the next line one less indent
  '**********************************************************
  If UCase$(Left$(Cline$,4))="NEXT" And Not Variable(Cline$,5) Then
    If ForNest Then
      NLI = CLI
      ForNest=ForNest-1
    Else
      CLI = CLI - Indent   'remove one level of indent
      NLI = CLI            'make the next line indent the same
    EndIf
    Ptr = 5
    ' Look for a comma which closes multiple For levels
    Do
      GetNextNonBlankChar(Cline$,Ptr,a$)
      If a$ = "," Then
        If ForNest Then
          ForNest=ForNest-1
        Else
          CLI = CLI - Indent  'remove another level of indent
          NLI = CLI           'make the next line indent the same
        EndIf
      EndIf
    Loop Until a$ = "" Or a$ = ":" ' Both end the NEXT statement
    Found=True
  EndIf
End Sub 'Test_Next
  
Sub Test_If_Then(Found,ML)
  '**********************************************************
  'Print the IF/ELSE at the current indent but indent the next line further
  '**********************************************************
  If UCase$(Left$(Cline$,3))="IF " Then
    ' test for one-line IF/THEN.
    ' "Then" will be followed by a non-blank character
    Ptr = Instr(4,UCase$(Cline$)," THEN")
    Ptr=Ptr+6
    GetNextNonBlankChar(Cline$, Ptr, b$)
    ' if statement continues on next line, indent the next line
    If b$="" Or b$=" " Then
      NLI = CLI + Indent
      ML=1              'Indicate a multi-line IF
    EndIf
    Found=True
  EndIf
End Sub 'Test_If_Then
  
Sub Test_ElseIf(Found,ML)
  '**********************************************************
  ' If on entry, MultiLn=1, the previous THEN/ELSE was a multiline statement
  '**********************************************************
  If UCase$(Left$(Cline$,6))="ELSEIF" And Not Variable(Cline$,7) Then
    If ML = 1 Then CLI = CLI - Indent
    ML=0
    ' test for one-line ELSEIF/THEN.
    ' The ELSE will be followed by a non-blank character
    Ptr = Instr(4,UCase$(Cline$)," THEN")
    Ptr=Ptr+6
    GetNextNonBlankChar(Cline$, Ptr, b$)
    If b$="" Or b$<>" " Then
      NLI = CLI + Indent
      ML=1
    EndIf
    Found=True
  EndIf
End Sub 'Test_ElseIf
  
Sub Test_Else(Found,ML)
  '**********************************************************
  ' If on entry, MultiLn=1, the previous THEN/ELSE was a multiline statement
  '**********************************************************
  If UCase$(Left$(Cline$,4))="ELSE" And Not Variable(Cline$,5) Then
    If ML = 1 Then CLI = CLI - Indent
    ML=0
    ' test for one-line ELSE/THEN.
    ' The ELSE will be followed by a non-blank character
    Ptr = Instr(4,UCase$(Cline$)," THEN")
    Ptr=Ptr+6
    GetNextNonBlankChar(Cline$, Ptr, b$)
    If b$="" Or b$<>" " Then
      NLI = CLI + Indent
      ML=1
    EndIf
    Found=True
  EndIf
End Sub 'Test_Else
  
Sub Test_EndIf(Found)
  '**********************************************************
  'If ENDIF, print this and the next line one less indent
  '**********************************************************
  If UCase$(Left$(Cline$,5))="ENDIF" And Not Variable(Cline$,6) Then
    CLI = CLI - Indent   'remove one level of indent
    NLI = CLI            'make the next line indent the same
    Found=True
  EndIf
End Sub 'Test_EndIf
  
Sub Test_for_Label(Found)
  '**********************************************************
  ' A line starting with a label is aligned to the left margin
  ' A label is terminated with a colon and musn't
  ' - have an imbedded blank or operator.
  ' - be an MMBasic command terminated with a colon.
  '**********************************************************
  Found=False
  p=Instr(1,Cline$,":")                       ' Look for first colon
  If p<>0 Then
    Found=True       'Provided there is no imbedded blank or operator
    For i=1 To p-1
      a$=Mid$(Cline$,i,1)
      If a$=" " Or IsOperator(a$) Then
        Found=False
        Exit For
      EndIf
    Next
  EndIf
  ' Check that it is not a one word MMBasic command
  If found And p<>0 Then
    For i=1 To Commands
      If UCase$(Left$(Cline$,p-1))=Command$(i) Then
        Found=False
        Exit For
      EndIf
    Next
  EndIf
  If Found=True Then
    CLI=0
  EndIf
End Sub 'Test_for_Label
  
Function IsOperator(a$)
  '**********************************************************
  ' Tests a$ to see if it is an operator
  '**********************************************************
  Local i
  IsOperator=False
  For i=1 To Operators
    If a$=Operator$(i) Then
      IsOperator=True
      Exit For
    EndIf
  Next
End Function 'IsOperator
  
Sub Initialise(Ifile$,Ofile$,Indent)
  '**********************************************************
  ' Input and Output file names, indent and pause switch can come from
  ' 4 sources (in order of precedence:
  '  - MM.CMDLine$, the implied RUN command
  '  - CMDParm$, a variable passed by a program that chains to Format.
  '  - FORMAT.DAT file in the same format as MM.CMDline$
  '  - User prompts
  ' If file names are missing from the first 2, the user is prompted to
  ' enter them. If indent and pause switch are omitted, defaults are used.
  '**********************************************************
  a$=MM.CmdLine$
  If a$<>"" Then
    Print "Getting parms from MM.CMDLine$."
    GetCMDLine(a$)
  ElseIf CMDParm$<>"" Then
    ' CMDParm$ is intended to be passed by a Chaining program
    Print "Getting parms from CMDParm$."
    GetCMDLine(CMDParm$)
  Else
    a$=GetParmsFromFile$(CMDParmFile$)
    If MM.Errno=0 Then Print "Getting parms from file ";CMDParmFile$
    If a$<>"" Then GetCMDLine(a$)
  EndIf
  Do
    If Ifile$="" Then
      Input "Give me the Input  filename (.BAS assumed) - 'Exit' to exit: ", Ifile$
    EndIf
    If LCase$(Ifile$)="exit" Then End
    CheckInputFileName(Ifile$)
  Loop Until Ifile$<>""
  Do
    If Ofile$="" Then
      Input "Give me the Output filename (.BAS assumed) - 'Exit' to exit: ", Ofile$$
    EndIf
    If LCase$(Ofile$)="exit" Then End
    CheckOutputFilename(Ofile$, Ifile$)
  Loop Until Ofile$<>""
  If Indent = 0 Then
    GetIndent(Indent)
    If Indent=0 Then End
  EndIf
  If PauseList$="" Then GetPause(PauseList$)
End Sub 'Initialise
  
Function GetParmsFromFile$(DATFile$)
  '**********************************************************
  'Gets the parameters from a file provided by Renumber
  '**********************************************************
  Local a$
  Option error continue
  Open DATFile$ For input As #3
  If MM.Errno = 0 Then
    Line Input #3,a$
    GetParmsFromFile$=a$
    Close #3
  EndIf
  Option error abort
End Function 'GetParmsFromFile
  
Sub GetCMDLine(a$)
  '**********************************************************
  ' MMBasic 4.3A onward which supports implied Run command
  ' User can start the program with FORMAT infilename outfilename [Indent] [/p]
  ' /p lists the output a screenful at a time. Press any key to continue.
  ' On exit Parm$(1) = infilename, Parm$(2) = outfilename, Parm3 = Indent 0r 0
  '**********************************************************
  Local i
  If a$<>"" Then
    ParmNo=5                 ' Examine up to 5 parms
    ' Set the delimiter between parms
    If Instr(1,a$,",") Then ParmDelimiter$="," Else ParmDelimiter$=" "
    Dim Parm$(ParmNo)
    ParseParm(a$,ParmNo,ParmDelimiter$)
    Ifile$=Parm$(1)
    Ofile$=Parm$(2)
    Indent=2
    PauseList$="N"
    For i=3 To ParmNo
      If Val(Parm$(i)) > 0 Then Indent=Val(Parm$(3))
      If UCase$(Parm$(i))="/P" Then PauseList$="Y"
    Next
  EndIf
  Erase Parm$               'Not needed any more - save the space
End Sub 'GetCMDLine
  
Sub ParseParm(p$,PNo,Delim$)
  '**********************************************************
  ' Parse the parameters on the Implied Run Command line,
  ' from CMDparm$ or the DAT file.
  '**********************************************************
  Local i,Strt,Ptr
  Strt=1
  For i=1 To PNo
    Ptr=Instr(Strt,p$,Delim$)
    If Ptr=0 Then Ptr=Len(p$)+1
    Parm$(i)=Mid$(p$,Strt,Ptr-Strt)
    If Ptr>=Len(p$) Then Exit For
    Strt=Ptr
    FindNextParmDelimiter(p$,Strt,Delim$)
  Next
  If UCase$(Parm$(3))="/P" Then   ' user opted to omit Indent so
    Parm$(4)=Parm$(3)           ' shift pause list parm to it's proper place
    Parm$(3)=""                ' and clear the Indent parm
  EndIf
End Sub 'ParseParm
  
Sub FindNextParmDelimiter(a$,p,Delim$)
  '**********************************************************
  ' Fine the start of the next parameter
  '**********************************************************
  Do
    p=p+1
  Loop Until Mid$(a$,p,1)<>Delim$ Or p=Len(a$)
End Sub 'FindNextParmDelimiter
  
Sub CheckInputFilename(i$)
  '**********************************************************
  ' Adds .BAS if an extension is not provided and checks that is accessible
  '**********************************************************
  If Instr(1,i$,".")=0 Then i$=i$+".bas"
  Option error continue
  Open i$ For INPUT As #1
  If MM.Errno <> 0 Then
    Print i$ " doesn't exist."
    Print
    i$=""
  EndIf
  Option error abort
End Sub 'CheckInputFilename
  
Sub CheckOutputFileName(o$,i$)
  '**********************************************************
  ' Adds .BAS if an extension is not provided and checks that it does't alread exit.
  ' If it exists, asks user if ok to overwrite.
  '**********************************************************
  If Instr(1, o$, ".") = 0 Then o$ = o$ + ".bas"
  If LCase$(o$)=LCase$(i$) Then
    Print "You cannot write to the input file - give me another."
    Print
    o$=""
  Else
    ' If old file exists, ask if it should be replaced. If not, get another filename
    Option error continue
    Open o$ For input As #2
    If MM.Errno = 0 Then
      Print "OK to overwrite "+o$+" Y/N";:Input ""; Reply$
      If LCase$(Left$(Reply$,1)) = "y" Then
        Close #2
        Open o$ For output As #2
      Else
        Close #2
        Print
        o$=""
      EndIf
    Else
      Open o$ For output As #2
    EndIf
    Option error abort
  EndIf
End Sub 'CheckOutputFileName
  
Sub GetIndent(i)
  '**********************************************************
  ' Asks user for number of spaces to use for each level of indent
  '**********************************************************
  Do
    Input "What indent should I use? (Range 1-6, 0 to exit): ", i
    If i<0 Or i>6 Then
      Print "Indent needs to be in the range 1 to 6, or type 0 to exit"
      Print
    EndIf
  Loop Until i>=0 And i<=6
End Sub 'GetIndent
  
Sub GetPause(a$)
  '**********************************************************
  ' Asks user if screen listing should be paused at end of each screenful.
  '**********************************************************
  Do
    Input "Pause the listing at the end of each screenful (y/n)?", a$
    a$ = UCase$(a$)
    If a$ <> "Y" And a$ <> "N" Then
      Print "Please answer Y or N."
      Print
    EndIf
  Loop Until a$ = "Y" Or a$ = "N"
End Sub 'GetPause
  
Sub PrintInstructions
  '**********************************************************
  Print @(MM.HRes/2-12*10,1) "Program Source Formatter v2.0"
  Print @(MM.HRes/2-12*10,12) "============================="
  Print "This program reads a basic program file and creates a new one following"
  Print "these indent rules:"
  Print " - Lines starting with a Label are aligned to the left margin"
  Print "   as are SUB, END SUB, FUNCTION and END FUNCTION."
  Print " - The first non-label line is indented to the first level."
  Print " - Lines following a single line IF, DO and FOR are not further indented."
  Print " - Multiline DO and FOR have their respective LOOP and NEXT aligned."
  Print "   Intervening lines are indented one level."
  Print " - Multiline IF statements have their ELSE, ELSEIF and ENDIF aligned"
  Print "   under the relevant IF. Intervening statements are indented one level."
  Print " - Nesting increases the indent one level."
  Print " - For numbered programs, the left margin is the longest number plus one."
  Print " - Other than setting the indent, the source line is not altered."
  Print " - No attempt is made to check the program for errors."
  Print
  Print "From MMBasic V4.3a you can invoke FORMAT from the Basic prompt using"
  Print "the implied RUN command. The syntax is:"
  Print
  Print "   FORMAT InFileName[.BAS] OutFileName[.BAS] [Indent] [/p]"
  Print
  Print "Where:   InFileName must not be the same as OutFileName"
  Print "         Indent defaults to 2 and"
  Print "         switch /p pauses the listing at a screenful."
  Print
End Sub 'PrintInstructions
  
Sub CheckForLineNumbers
  '**********************************************************
  ' Check for line numbers and find the longest number
  ' Length of longest number + 1 will be used as the left margin
  '**********************************************************
  Local i
  LineNumbers=False
  Do
    Line Input #1, Iline$
    ILine$=StripBlanks$(ILine$)  'Remove any leading blanks
    If Left$(ILine$,1)>="0" And Left$(ILine$,1)<="9" Then
      LineNumbers=True
      i=Instr(1,ILine$," ")
      If i>LineNoLen Then LineNoLen=i
    EndIf
  Loop Until Eof(#1)
  Close #1
  Open Ifile$ For Input As #1
End Sub 'CheckForLineNumbers
  
Sub wait
  '**********************************************************
  ' Causes the program to pause at the end of each screenful.
  ' Any keystroke causes the program to continue.
  '**********************************************************
  w=w+1
  If w>30 Then
    w=0
    Do
      k$=Inkey$
    Loop Until k$<>""
  EndIf
End Sub 'Wait