home *** CD-ROM | disk | FTP | other *** search
- Introduction
-
- PRE/DB is a database language preprocessor. It processes your
- program source file and produces an output file acceptable to the
- compiler/interpreter being used. The source file may be
- embellished with preprocessor directives. The preprocessor
- processes these directives and then removes them from the file it
- produces.
-
- C programmers have long enjoyed the convenience of a
- preprocessor. That language's compiler automatically invokes its
- preprocessor as a first pass in the compilation process. PRE/DB,
- however, must be explicitly run on the source program - the
- database language processors now available do not automatically
- invoke it.
-
- By using a preprocessor, then, the programmer can use programming
- commands not supported by the compiler/interpreter being used.
- The preprocessor processes these commands and produces a
- translated output file acceptable to the language processor being
- used.
-
- PRE/DB is language independent - it does not care what language
- the program is written in. It simply processes the preprocessor
- directives and leaves the other statements untouched.
-
-
- Here are the directive PRE/DB recognizes
-
- A preprocessor directive consists of the character #. followed by
- a command. The preprocessor supports the same commands in the
- same manner as the C preprocessor. These commands are:
-
- 1) #define
- 2) #undef
- 3) #ifdef
- 4) #else
- 5) #endif
- 6) #ifndef
- 7) #include
-
- These commands will now be examined one at a time.
-
- #define
-
- The #define directive is used in one of two ways, either to
- symbolically define a constant, or to define a compiler macro.
- Let us look at the definition of a constant first.
-
- #define and Constants
-
- While the dBASE language allows a constant to be used, as in:
-
- IF key = 27
-
- there is no way to associate the constant 27 with a name. The
- constant could be defined as a memory variable with:
-
- ESC = 27
-
- IF key = ESC
-
- but it is no longer a constant. There is nothing to stop the
- variable being modified. Still, this method of using memory
- variables to hold constant data is popular because it makes
- programs more readable.
-
- A common practice is to group all such definitions in a common
- procedure which is called once by the program's startup code.
- These memory variables are defined as PUBLIC, which allows them
- to be seen outside of this routine. The following procedure is
- an example. It defines a number of key values and a few color
- definitions:
-
- PROCEDURE init_consts
-
- PUBLIC, ESC,UP_ARROW,PG_UP,DOWN_ARROW,PG_DOWN
-
- PUBLIC END_KEY, HOME, ENTER
-
- PUBLIC F1, F2, F3, F4, F5, F6, F7
-
- PUBLIC F8, F9, F10, LT_ARROW, RT_ARROW
-
- PUBLIC BRIGHT, NORM, ENH, HIDDEN
-
- ESC = 27
- UP_ARROW = 5
- PG_UP = 18
- DOWN_ARROW = 24
- PG_DOWN = 3
- END_KEY = 6
- HOME = 1
- ENTER = 13
- F1 = 28
- F2 = -1
- F3 = -2
- F4 = -3
- F5 = -4
- F6 = -5
- F7 = -6
- F8 = -7
- F9 = -8
- F10 = -9
- LT_ARROW = 19
- ARROW = 4
- BRIGHT = "W+/ "
- NORM = "W/ "
- ENH = " /W"
- HIDDEN = " / "
-
- RETURN
-
- Although use of this technique results in a better-structured,
- more readable program, it does come at some expense both in terms
- of program speed and size. dBASE imposes a limit on the number
- of memory variables that may be active at one time. Clipper has
- a 22 byte overhead for each memory variable used. Both products
- can execute a constant access such as:
-
- IF key = 27
-
- faster than they can process a memory variable such as:
-
- IF key = ESC
-
- These memory variables occupy valuable memory space whether they
- are used or not. Some applications you write will use some of
- the variables, others different ones. It is tempting to write
- "custom" versions of this "init_consts" routine to save space,
- but this probably produces more problems than it solves.
-
- Using the preprocessor's #define directive overcomes all of the
- problems outlined above. It allows a constant to be defined with
- a name, but without using a memory variable.
-
- It does this as follows. When the preprocessor comes across a
- #define directive such as
-
- #define ESC 27
-
- it "remembers" that the constant ESC has the value 27 and deletes
- this line from the output file it is producing. (If the line
- remained in the output file, the language processor would
- complain when it encountered that line, as it is an invalid
- statement.) Then, when the preprocessor encounters a line such
- as
-
- IF key = ESC
-
- it substitutes the value 27 for the ESC constant in the output
- file, producing the line
-
- IF key = 27
-
- Since the language processor only sees the output file, it
- successfully processes the line. It has no idea that the #define
- line or the ESC constant ever existed.
-
- Using constants in this manner has three advantages. First, it
- makes the program more readable. Second, it leads to a greatly
- reduced program size in compiled applications. Third, these
- constants cannot be changed, protecting the programmer from
- inadvertently changing one of the "pseudo" constants.
-
- The "init_consts" procedure, therefore, could be replaced with
- the following set of #define statements:
-
- #define ESC 27
- #define UP_ARROW 5
- #define PG_UP 18
- #define DOWN_ARROW 24
- #define PG_DOWN 3
- #define END_KEY 6
- #define HOME 1
- #define ENTER 13
- #define F1 28
- #define F2 -1
- #define F3 -2
- #define F4 -3
- #define F5 -4
- #define F6 -5
- #define F7 -6
- #define F8 -7
- #define F9 -8
- #define F10 -9
- #define LT_ARROW 19
- #define RT_ARROW 4
- #define BRIGHT "W+/ "
- #define NORM "W/ "
- #define ENH " /W"
- #define HIDDEN " / "
-
- Note that the "=" signs are not included in the #define
- statement. The preprocessor performs a strict textual
- replacement of the constant being defined with the defined value.
- If ESC had been defined as:
-
- #define ESC = 27
-
- when the preprocessor came upon the line:
-
- IF key = ESC
-
- it would substitute ESC with "ESC = 27," producing the line:
-
- IF key = = ESC
-
- A large number of #defines such as this would typically be
- contained within a separate file included with the preprocessor
- #include directive (see page 9).
-
- Be careful not to define words that the database dialect uses as
- keywords. The most common example of this is defining RETURN as
- 13 when using a dBASE-like language. This would be done with
-
- #define RETURN 13
-
- Now, whenever the preprocessor encounters the word RETURN, it
- replaces it with the value 13. It does this both in constructs
- such as:
-
- IF key = RETURN
-
- replacing it in the output file with:
-
- IF key = 13
-
- AND in such statements as:
-
- PROCEDURE xyz
- .
- .
- .
- RETURN
-
- replacing it with:
-
- PROCEDURE xyz
- .
- .
- .
- 13
-
- which is definitely not what is required. In this case, use the
- word ENTER instead.
-
- Compiler Macros
-
- The #define directive can be used with parameters to produce what
- is known as a compiler macro. This must be distinguished from
- the dBASE language runtime macro. The compiler macro is
- processed by the preprocessor, whereas the runtime macro is
- processed by the runtime system of the language processor.
-
- Compiler macros are basically text substitutions which can be
- parameterized. They are defined by enclosing a formal parameter
- list within parentheses following the name of the defined macro.
- Here is an example:
-
- #define inc(x) x = x + 1
-
- This defines a macro "inc" with a formal parameter "x." The
- macro is defined as adding one to the parameter. To use this
- macro, the macro name is called, followed by the actual parameter
- in parentheses. For example:
-
- inc(y)
-
- would be expanded to:
-
- y = y + 1
-
- in the output file. This method of substituting actual
- parameters with formal parameters is exactly what Clipper does
- when a user-defined function is defined and invoked, and what
- Clipper, dBASE, and other database language processors do when a
- PROCEDURE is defined and called.
-
- Macros can be defined with several parameters separated by
- commas. The following macro, for example, produces code that
- adds parameter 2 to parameter 1:
-
- #define addto(x, y) x = x + y
-
- A macro to increment a field in a database can be written as:
-
- #define replinc(x, y) REPLACE x WITH x + y
-
- Macros are mainly used to make programs more readable. They are
- especially useful as substitutes for simple Clipper user-defined
- functions. When used in this manner, they result in smaller,
- more efficient programs. Here are some examples:
-
- #define isescape inkey() = ESC
-
- #define center(x) SPACE(80 - (len(x) / 2)) + x
-
- These could be used in the following manner:
-
- USE sales
-
- * list entire database or until escape key is hit
- LIST name, address1, address2 ..WHILE !isescape
-
- @ 24, 0 SAY center("A centered message on line 24")
-
- The following macro would be used to assign a default value to a
- parameter if it was not defined:
-
- #define p_default(x,not_def) x =IIF(type([x]) = 'U',not_def,x)
-
- This would be used in the following manner:
-
- FUNCTION test
-
- PARAM p1, p2, p3
-
- p_default(p1, 1)
- p_default(p2, 2)
- p_default(p3, 3)
-
- .
- .
- .
-
- RETURN .T.
-
- When used with a language such as dBASE, which does not support
- user-defined functions, this sort of macro gives that language
- the "look and feel" of a language that does support them.
-
- Macros are also used to save typing. Assume that a dBASE-like
- program wishes to validate that the memory variable "state_name"
- contained either the value "Ca," "Az," "Nv," or "Wa." This would
- be written as
-
- IF state_name = 'Ca' .OR. state_name = 'Az' .OR.;
- state_name = 'Nv' .OR. state_name = 'Wa'
- .
- .
- ENDIF
-
- If this validation had to be performed at many places in the
- program, this code would either be written as a user-defined
- function in Clipper (incurring the overhead of a function call),
- or would require a typing marathon! A better method would to
- write the condition as a macro, as in
-
- #define good_state(s) s='Ca' .OR. s='Az' .OR. s='Nv';
- .OR. s='Wa'
-
- and then reference it as
-
- IF good_state(state_name)
-
-
- #undef
-
- The #undef directive is basically the inverse of the #define
- directive. It "undefines" the specified variable or macro. This
- is useful when you wish to restrict the scope of a define to a
- well-defined area of the program.
-
-
- #ifdef / #else / #endif / #ifndef
-
- These four directives are used together to provide conditional
- compilation/interpretation. A section of code is enclosed within
- an #ifdef/#endif pair, and if the name following the #ifdef
- directive is defined, the code within the block is passed through
- to the output file. If it is not defined, the code is not passed
- through.
-
- The #else directive can be used within the #ifdef/#endif pair to
- delimit another section of code. If the name following the
- #ifdef directive is declared, the code delimited by the
- #ifdef/#else code is passed through; if not, the code delimited
- by the #else/#endif code is declared. Here is an example:
-
- #define CLIPPER
-
- #ifdef CLIPPER
- DO edit_code
- #else
- edit
- #endif
-
- In this case, the code "DO edit_code" is passed through to the
- output file, since "Clipper" is defined. If it were not, the
- code "edit" would be passed through. Once again, the directives
- are stripped from the output file so the language processor does
- not know that a pre-pass even took place.
-
- Do not confuse the above method of selective processing with that
- currently used by Clipper programmers. They use a runtime IF
- statement on a PUBLIC variable CLIPPER which the Clipper runtime
- system has set to .T. This takes place at runtime, and any code
- contained in the ELSE part of that statement is still compiled
- (often producing annoying compilation errors for non-supported
- commands).
-
- The #ifndef directive is the inverse of the #ifdef directive. It
- means "allow the following code if the following symbol is NOT
- defined." The #else directive used with a #ifndef thus means
- "allow the following code if the symbol following the preceding
- #ifndef was defined."
-
- #include
-
- The #include directive causes the specified file to be read and
- replaced into the current file. The file would usually contain a
- number of #define statements. It may contain regular code, but
- this is not recommended, since it will destroy the integrity of
- line numbering (see Line Numbers.)
-
- The preceding example we gave of the series of #defines for key
- values, etc., would be contained in a file that is #included into
- the main file. It then appears to the preprocessor as if the
- statements had been included directly in the main program file.
-
- It is recommended that the include file be given a .H ("header")
- extension.
-
- The preprocessor looks for the include file in the current
- directory. If it doesn't find it there, it will search all
- directories specified by the PPINCLUDE environment variable. For
- example, let's say you have issued the DOS command
-
- SET PPINCLUDE=\clipper;\mc\include
-
- and in your preprocessor file you have
-
- #include "KEYS.H"
-
- The preprocessor first looks in the current directory for the
- file "KEYS.H". If it doesn't find it there, it searches first
- the \clipper directory and then the \mc\include directory before
- returning a "file not found" error.
-
-
- Syntax
-
- Here is the syntax of the directives described above.
-
- #include "pathspec" "pathspec" is a regular DOS file
- specification. If no extension is
- specified, .PRG is assumed.
-
- #define a b "a" is a name, containing any
- sequence of letters or digits. "b"
- is any text.
-
- #define m(args) m_txt Define macro "m" with formal
- arguments "args" as "m_txt."
-
- #undef a Remove latest definition of "a."
-
- #ifdef a Include following block up to next
- #else or #endif if "a" is defined.
-
- #ifndef a Include following block up to next
- #else or #endif if "a" is not
- defined.
-
- #else Include following block up to next
- #endif if preceding #ifx is false.
-
- #endif End of a conditional block.
-
-
- PRE/DB and the dBASE Language
-
- PRE/DB takes into account certain idiosyncrasies of the dBASE
- language. It will not perform the above substitutions inside a
- TEXT/ENDTEXT block, inside a comment (started with either && or
- *), inside a NOTE, or inside a character string delimited with
- either ' or " (single or double quotation mark). However, it
- will perform a substitution inside strings delimited with [].
- This is because, for speed, PRE/DB takes a simplistic approach to
- parsing, and distinguishing between the use of [] for strings and
- Clipper's use of [] for array subscripts would be time-consuming.
-
- When using Clipper, ensure that any files automatically compiled
- by Clipper as a result of a DO <proc_name> or SET PROCEDURE TO
- command have been processed with the preprocessor; PRE/DB does
- not automatically read and process any procedures referenced in
- this manner, as Clipper does. When using dBASE, be sure that the
- preprocessor has been run on files specified by a SET PROCEDURE
- TO command.
-
-
- Running PRE/DB
-
- PRE/DB is a filter. It takes one file as input and produces
- another as output. If no extension is specified, an extension of
- .PRE is assumed for the input file and an extension of .PRG for
- the output. Running PRE/DB and specifying only an input file
- causes an output file with the same root name and an extension of
- .PRG. The following are thus equivalent:
-
- pp test.pre test.prg
-
- pp test test
-
- pp test
-
- The third method is preferred.
-
- When using the preprocessor with Clipper, you may wish to modify
- Nantucket's CL.BAT file to
-
- pp %1
- clipper %1
- link %1,,,\clipper\clipper \clipper\extend
-
- This automatically invokes PRE/DB as the first stage of the
- compilation.
-
- Two command line options are supported: -i and -d. These must
- appear after the file name. These must appear after the file
- name. The -i turns off case sensitivity. By default, PRE/DB is
- case-sensitive. This means that symbols such as
-
- test
-
- and
-
- TEST
-
- are different. Using the -i option makes them equivalent.
-
- The -d option is used to define a symbol on the command line.
- For example,
-
- pp test -dRick
-
- would define the symbol "Rick." A subsequent test such as
-
- #ifdef Rick
-
- would then evaluate to true.
-
-
- Line Numbers
-
- PRE/DB maintains line numbers between the input and the output
- file. A given program statement in the source file resides at
- the same line number in the output file as it does in the input
- file. This is important because compile and runtime errors, and
- debugger line numbers, refer to line numbers in the output file,
- but the programmer is working with the input file.
-
- There is one exception to this rule. When a #include file
- contains program code, rather than preprocessor directives, the
- lines of code are include as-is in the output file. For this
- reason, use of program code in #include files is not recommended.
-