The BPP Basic Pre-Processor

BPP is a pre-processor designed to be used on files containing BASIC language source code.

The source files read by BPP contain a mixture of BASIC language source code and of BPP-specific commands. The BPP commands allow for conditional processing of the source code, for definition of macros, and for other specific actions.

BPP reads the source file, processes it according to the conditional processing commands, performs macro expansions, and generates a processed output file. This output file will typically be compiled by a BASIC compiler.
  1. Command-line Syntax
  2. The BPP command-line syntax is the following:

    bpp [options] <input_file> <output_file>

    Where <input_file> is the name of the input file and <output_file> is the name of the output file.

    The following options are accepted by BPP:
    • -D<symbol>[=<value>]
    • This defines a symbol (macro) with name <symbol> and optionally assigns it the value <value>.  Multiple symbols can be defined on the command line by the use of multiple -D options.

    E.g. The following are all valid bpp command-lines:
    • bpp   source.in   source.out
    • bpp -DDEBUG=1 -DLANG=QB  test.src   test.bas
    • bpp -DDEBUG test.src test.bas
  3. BPP Commands
  4. BPP commands have a pound sign (#) as the first non-blank character of a line, and are immediately followed by a command keyword. Note that BPP commands can be indented within a line, but they MUST be the first non-blank text of the line.

    The following is a list of all the BPP commands.

    #INCLUDE Process specified file.
    #INCLUDESRCProcess specified file outputting its source code.
    #DEFINE Define a macro.
    #UNDEF Undefine a previously defined macro
    #IF Process next block if symbol is non-zero
    #IFDEF Process next block if symbol is defined
    #IFNDEF Process next block if symbol is not defined
    #ELIF Process next block if symbol is non-zero and previous blocks have not been processed.
    #ELIFDEF Process next block if symbol is defined and previous blocks have not been processed.
    #ELSE Process next block if previous blocks have not been processed.
    #ENDIF Terminate a conditional block
    #OUT Output text
    #ERROR Display error message and terminate
  5. Including Files
  6. #INCLUDE

    The #INCLUDE command opens and processes a file, then continues processing the current file. A file opened through the #INCLUDE command can contain itself other #INCLUDE commands. There is no limit to the number of files nested in this fashion.

    The syntax of the #INCLUDE command is:

    #INCLUDE "<file>"

    Where <file> is the name of the file to be processed. Note that the name of the file must be enclosed with double quotes.

    E.g.

    #INCLUDE "source2.bas"

    IMPORTANT!

    Note that BPP only outputs code from the top-level file it processes, i.e. the file specified in the BPP command line. All files included via the #INCLUDE command are only processed for BPP commands, any other code being ignored. If you want the code from the file being included to be written to the output file, then use the #INCLUDESRC command instead of #INCLUDE

    The purpose of the above behavior is to allow BPP to process BASIC include files without writing the output of those include files into the output of the source file that includes those files.

    This way macros related to a specific BASIC include file (say xxx.bi) can be processed when processing the BASIC source file (say xxx.bi) that includes it, without having the actual BASIC code in xxx.bi being written to the output file.

    Example:

    The file "source.bas" contains the following:

    #INCLUDE "source.bi"
    SUB FOO
    x = BAR
    END SUB


    And the file "source.bi" contains the following:

    DECLARE SUB FOO()
    #DEFINE BAR 35


    Then bpp source.bas source.out will generate the following file "source.out":
    SUB FOO
    x = 35
    END SUB


    Note that the text "DECLARE SUB FOO()" in file "source.bi" was not written to "source.out".

    How to handle BASIC commands

    Say that you have a BASIC source file "xxx.bas" that includes a file "xxx.bi". Your QBasic code looks something like this:

    ': 'xxx.bi'
    ... Rest of source code...


    But now you have defined macros in the file "xxx.bi", and you want any file which includes "xxx.bi" to be able to use those macros.

    You can do one of two things:

    1.- Substitute every line containing ': 'xxx.bi' for the following two lines:

    #INCLUDE "xxx.bi"
    ': 'xxx.bi'


    2.- Define a macro that will expand to the two lines you need:

    #DEFINE INCLUDEFILE(name)   #INCLUDE "name"_
    ': 'name'
    And now substitute every line containing ': 'xxx.bi' for the following line:

    INCLUDEFILE(xxx.bi)

    Which will expand to the following two lines:

    #INCLUDE "xxx.bi"
    : 'xxx.bi'


    The first one will instruct BPP to include xxx.bi, while the second one will be output to the output file.

    #INCLUDESRC

    The #INCLUDESRC command is similar to the #INCLUDE command, but while #INCLUDE only processes BPP commands while ignoring any other source code in the included file, the #INCLUDESRC command outputs the source code present in the included file, just as it does with the main file being processed by BPP.

    The syntax of the #INCLUDESRC command is:

    #INCLUDE "<file>"

    Where<file> is the name of the file to be processed. Note that the name of the file must be enclosed with double quotes.
  7. Defining Macros
  8. Macros can be defined in the bpp command line using the -D option, or in the source file using the #DEFINE command. The syntax of the #DEFINE command is:

    #DEFINE <name>[(Arguments)] [Value]

    Where <name> is the name of the macro being defined, and [Value] is the optional value that the macro will expand to. The macro can optionally contain parameters.

    E.g.

    #DEFINE FOO Defines FOO with no value
    #DEFINE PI 3.1416 Defines PI with value 3.1416
    #DEFINE SAY(x) PRINT xMacro with argument
    #DEFINE ASSERT(x) _
        IF (NOT (x)) THEN_
            CALL ProgramError( "__FILE__", __LINE__ )_  
        END IF
    Macro expands to multiple lines

    In the first example, FOO is defined but no value is assigned to it. If the source code contains FOO, it is substituted by nothing. FOO can be tested using #IFDEF, #IFNDEF or #ELIFDEF.

    In the second example, PI is assigned a value of "3.1416:". Any appearance of PI in the source code will be subsituted by 3.1415. For example, the following line:

    Area = PI * Diameter

    will generate the following output:

    Area = 3.1416 * Diameter

    In the third example, a macro SAY is defined which requires one argument. Text in the source such as:

    SAY("Hello world")

    will generate the following output:

    PRINT "Hello world"

    The fourth example shows a macro that expands to multiple lines of output. An underscore at the end of a line indicates that the macro value continues in the following line. So the following source text:

    ASSERT(y=2)

    will generate the following output:

    IF (NOT (y=2)) THEN
        CALL ProgramError( "source.bas", 132 )
    END IF


    Note that when expanding a macro, the expanded value is itself processed so one macro can use other macros. e.g.

    #DEFINE SAY(x) PRINT x
    #DEFINE GREETING(name) SAY("Hello "); : SAY("name")
    GREETING(Joe)


    The above will result in the following output:

    PRINT "Hello "; : PRINT "Joe"

    Undefining macros

    Macros can be undefined via the #UNDEF command:

    #UNDEF FOO

    Undefines the macro FOO. The macro must have been previously defined. Once undefined, FOO can be re-defined.
  9. Predefined Macros
  10. The following macros are predefined in BPP. Their names are reserved and they cannot be undefined.

    __FILE__Name of output file
    __LINE__Line number in the output file
    __DATE__Current date
    __TIME__Current time
    __VERSION__BPP version
  11. Conditional processing
  12. BPP supports conditional processing. The conditional compilation directives in BPP work like the similar directives on C or C++:

    #IF symbol-1
    <section-1>
    #ELIF symbol-2
    ...
    #ELIF symbol-n
    <section-n>
    #ELSE
    <section-n+1>
    #ENDIF


    #IF and #ELIF require a symbol that expands to a numeric expression. If the numeric expression is non-zero, the corresponding section is processed. All other sections are ignored. The section after #ELSE is processed if no other section within the current conditional block has been processed.

    #IFDEF symbol-1
    <section-1>
    #ELIFDEF symbol-2
    ...
    #ELIFDEF symbol-n
    <section-n>
    #ELSE
    <section-n+1>
    #ENDIF


    In #IFDEF and #ELIFDEF, If the specified symbol is defined, the corresponding section is processed. All other sections are ignored. The section after #ELSE is processed if no other section within the current conditional block has been processed. #IFNDEF is similar to #IFDEF, but the section is processed if the specified symbol is NOT defined.

    Note that only one #ELSE is allowed in a conditional block.

    Note that #ELIFDEF and #ELIF can coexist with a conditional block:

    #IFNDEF symbol-1
    <section-1>
    #ELIF symbol-2
    ...
    #ELSE
    <section-n>
    #ENDIF


    Conditional blocks can be nested:

    #IFNDEF symbol-1
    <section-1>
    #ELIF symbol-2
        #IF symbol-3
     <section-3>
     #ELSE
     <section-4>
     #ENDIF
    #ELSE
    <section-5>
    #ENDIF
  13. Other commands
  14. BPP supports the following commands:

    #OUT

    #OUT processes the rest of the line and outputs it to the output file. Note that this command is executed even in files included via the #INCLUDE command.

    E.g.

    #OUT ' The date today is __DATE__

    will output something like:

    ' The date today is 1/7/2002

    #ERROR

    #Error processes the rest of the line, outputs it to the standard error file (usually the screen), and stops execution of BPP.

    E.g.

    #ERROR Attention! An error was encountered.

    will terminate BPP after displaying the following message:

    Attention! An error was encountered.