Functions and Directives

Directive #command | #translate Foundation

Defines a user-defined command or translation directive.

Syntax
#command    <search rule> => <replacement rule>
#translate  <search rule> => <replacement rule>
Parameters
<search rule>
<search rule> is a character string that defines a rule describing the search pattern to use when the preprocessor scans source code.
<replacement rule>
<replacement rule> is a character string that defines a rule describing how to output the text matched using the <search rule>.
The symbol => is a component of the syntax for the preprocessor directives #command and #translate. This symbol is used to syntactically separate <search rule> and <replacement rule>, and must always be present as part of the directive.
Description

Before the Xbase++ compiler compiles a program, the source code is scanned by the preprocessor. The preprocessor creates an intermediary file from the source code, either copying the source code exactly as it is written or performing certain translations and outputting the results of those translations. The directives #command and #translateprovide a mechanism to define translation rules that the preprocessor should use when translating source code to this intermediary file. The combination of the preprocessor and these two directives results in a powerful tool with which the language of Xbase++ can be greatly enhanced, as well as user code significantly simplified. For example, Directives can be used to add new commands to the Xbase++ language, or to make complex user-defined function calls easier to read and maintain.

Each #command or #translate preprocessor directive consists of two parts: the search rule and the replacement rule. The search rule is located to the left of the => symbol, and the replacement rule to the right. The search rule indicates a pattern for which the preprocessor should scan in the program source code. The replacement rule determines how the preprocessor should translate the text it finds using the search rule.

The #command directive is used to define search rules that match only source code representing a complete statement. The #translatedirective is used to define search rules that can match source code that does not represent a complete statement, including pseudofunctions, expressions or individual parts of a command.

Search rules using #command and #translate use only the first four letters of the keywords to identify matching text, and match text regardless of case. #xcommand and #xtranslate are used to require all letters in the keywords be considered for a match.

A #command or #translate directive is recognized by the preprocessor from the point it is defined until the end of that program file. Directives can also be stored in Include files and are recognized by the preprocessor from the line in which they are referenced with the #include directive, until the end of the program file. Since STD.CH is the source of an implicit #includein every program file, any additions to this header file are automatically recognized in every program. In addition, the command line compiler switch /U allows the programmer to specify a replacement for STD.ch, so that any directives included in the replacement header file will also be recognized in any program they are used.

The directives #command, #translate, #xcommand and #xtranslate are more powerful than the similar directive #define. With #define, symbolic names are defined which can be the subject of strict substitution or used for conditional compilation. When using #define, case is significant (see #define). Unlike #define, #command and #translatedirectives are not affected by #undef, so that once defined in a source file are always recognized to the end of the file.

The order of precedence for the various directives is, from highest to lowest: #define, #translate, and #command.

For each file, each line of code is iteratively scanned and translated until no more matches can be found using the available search rules. The preprocessor examines each line of code for matches according to the precedence of the directives. When directives have equal precedence, the directive entered last is used first. This means, for example, that the #translate directives in an Include file are used from the end of the Include file to the beginning.

The order of directives within an Include file is especially important when commands are defined which require more than one translation rule. This is always the case when a command requires keywords that are mutually exclusive or when a command must be coded in a general form with various special variations. In these cases several directives must be written for the same command. The most important ground rule of writing directives like these is that the general form of the command should be placed toward the top of the Include file, and any special variations placed toward the end of the Include file. Examples of this can be found in the various #command directives in the Include file STD.CH, which is used to implement the entire Xbase++ command set.

Search rules

The first part of a translation rule is the search rule. A search rule is made up of characters representing a search pattern used by the preprocessor to match text in a source file. Characters in a search rule can be grouped into three components: keywords, literals and match markers. The following code example using a #translate directive demonstrates the difference between the three. It defines a pseudofunction MinMax(), which is translated from the preprocessor into an .AND. expression:

|-------------------- Keyword 
|    |--------------- Match marker 
|    |    |     | 
#translate  MinMax(<x>,<min>,<max>) => (<x> >= <min>.AND.<max> >= <x>) 
   |   |     |     | 
   |----------------- Literal 

Keywords are characters that are recognized based on the dBase convention for valid identifiers. That is, only the first four letters are significant (however note that the directives #xcommand and #xtranslate require all characters of a keyword before a match is made) and case is ignored.

Literals are characters which must appear in the source code exactly as they appear in the search rule before the preprocessor will recognize a match.

Match markers are symbolic names for matched source code which can then be used to output the matched source code using the replacement rule. They must appear in angled brackets and follow the same naming rules as variables. That is, they must begin with a letter or an underscore (_) and consist entirely of alphanumeric characters.

Using the sample #translate directive MinMax() is demonstrated in the following program:

// Code as it appears in the source (PRG) file 
   IF MinMax( 5, 1, 10 ) 

// How the compiler sees it after the preprocessor is finished 
   IF (5 >= 1.AND.10 >= 5) 

The search rule in a directive defines the pattern by which the preprocessor matches source code. Within the search rule, keywords and literals match text identified ahead of time by the rule. In the sample above, only when the preprocessor finds the characters identified ahead of time (the character string MinMax, the parentheses, and the commas) does it consider the keywords and literals matched. Match markers are used to match text that is unknown ahead of time. In the example above, the directive is written to match any values the programmer may write for the three arguments to MinMax(). Match markers can be thought of as preprocessor variables. When the keywords and literals are matched in a particular code sequence, the preprocessor determines whether the remaining text in that sequence matches the pattern associated with the match marker used in the search rule. If it does, the contents of that text is assigned to the match marker. The match marker can then be used in the replacement rule to appropriately output the contents of what was matched.

The flexibility inherent in search rules is due to the fact that there are several match marker types. Each type represents a different rule to use when matching text. The following table lists the possible match markers which can be used with a search rule:

Match marker for the preprocessor
Match marker Designation
<Symbol> Regular Match Marker
<Symbol,...> List Match Marker
<Symbol:Wordlist> Restricted Match Marker
<*Symbol*> Wild Match Marker
<#Symbol> Single Expression Match Marker
<(Symbol)> Extended Expression Match Marker

Regular match marker

This marker is identified by a simple symbol within angled brackets <>. It is the most general match marker and is thus used most often. If the keywords and literals have matched, the preprocessor will match any expression contained in the location specified by the match marker. The Regular match marker is generally used with the Regular result marker, the Blockify result marker and the Stringify result marker.

List match marker

<Symbol,...> causes the preprocessor to match a comma separated list of valid expressions. This becomes very helpful in defining pseudofunctions or commands that take a variable number of arguments. The list match marker is used, for example, with the command USE...INDEX, which opens a data file and one or more index files.

Restricted match marker

<Symbol:Wordlist> consists of a symbol followed by a comma separated list of keywords. If the preprocessor matches one of the keywords, the expression following the keyword is assigned to the match marker symbol. If none of the keywords is found, the match marker symbol is not assigned a value. The Restricted match marker is typically implemented as a single keyword that expects no argument, and paired with a Logify result marker. In this way, the Logify result marker will be .T. (true) if the user enters the keyword, otherwise it will be .F. (false). An example of this can be found in the command SET PRINTER TO. That command has an optional clause ADDITIVE, implemented as a Restricted match marker. If the ADDITIVE clause is present, a .T. (true) value is passed as one of the arguments to the function call written out by the replacement rule. Otherwise, the function call is passed a .F. (false) value for this argument.

Wild match marker

When the preprocessor encounters the match marker <*Symbol*>, it matches all the code in the source file from the actual position of the match marker to the end of the source code identified by the directive. The Wild match marker is almost always used together with the Stringify result marker. In this way, code sequences which have no valid expressions are translated to character strings. When the Wild match marker is used in a search rule, no other match marker can be used in the same search rule.

Single expression match marker

If it is necessary to have the effect of a Wild match marker but only for a single expression, use the <#Symbol> match marker. This match marker causes the preprocessor to match all text up to the next space. This provides the same functionality as the Wild match marker but also has the benefit of allowing additional match markers in the same directive.

Extended expression match marker

<(Symbol)> is always used when an argument of a command can be indicated as a literal or as a character expression in parentheses. It is generally used with the Smart Stringify result marker to ensure that literals in the translated code are quoted, but extended expressions are left alone.

Optional clauses within search rules

A search rule can contain optional clauses by enclosing them in [] brackets. This enables commands or pseudofunctions to be written that can be matched even if the optional clauses are not found. Optional clauses may be nested in the search rule, but cannot be nested in the replacement rule. The order of optional clauses within a search rule is not significant. However, avoid the temptation to use this feature such that it renders source code hard to follow.

Optional clauses in a search rule consist either of a keyword followed by a match marker (for example, the INDEX option in the USE command) or a keyword by itself (for example, the ADDITIVE option in the SET PRINTER command). When an optional clause uses only one keyword, the Restricted match marker must be used in the search rule. Optional clauses in the search rule generally contain a keyword and the Regular match marker, or a keyword and the List match marker.

Within a search rule, having two optional clauses consisting only of a match marker and not separated from each other by a keyword or literal is not allowed. In addition, all match markers used in a search rule must be unique.

Replacement rules

The second part of a translation rule is the replacement rule. A replacement rule is made up of characters representing a pattern used by the preprocessor to output text matched by the search rule to the intermediary file. In normal compilation, the intermediary file is deleted after the compiler is finished. However, the compiler switch /P prevents the intermediary file from being deleted. In this case, the intermediary file is named the same as the source file but with a PPO (preprocessed output file) extension.

Similar to a search rule, a replacement rule is made up of three components: keywords, literals and result markers. Keywords (commands and function names, for example) and literals (operators and constants, for example) are output to the intermediary file exactly as they appear in the replacement rule. Result markers (like match markers) consist of a symbol name, which must appear in angle brackets. Within a replacement rule, the only symbols that can be used for result markers are those used within the corresponding search rule as match markers. This is logical, as the purpose of the replacement rule is to use the matched text found and assigned to the match markers by the search rule.

The flexibility inherent in replacement rules is due to the fact that there are several result marker types. Each type represents a different rule to use when writing to the intermediary file. The following table lists the possible result markers which can be used with a replacement rule:

Result marker for the preprocessor
Result marker Designation
<Symbol> Regular Result Marker
#<Symbol> Dumb Stringify Result Marker
<"Symbol"> Normal Stringify Result Marker
<(Symbol)> Smart Stringify Result Marker
<{Symbol}> Blockify Result Marker
<.Symbol.> Logify Result Marker

Regular result marker

<Symbol> causes the matched source code to be written to the intermediary file exactly as it appears in the source file. If no value was matched in the search rule, nothing is written to the intermediary file. This result marker is the most general and is used the most often.

Dumb Stringify result marker

#<Symbol> causes the preprocessor to output the matched text enclosed in double quotes. If no match is found in the source file, the preprocessor nevertheless outputs a null string (""). If the text was matched using the List match marker, the entire list, not individual items in the list, is enclosed in double quotes. The Dumb Stringify result marker is used when command arguments are anticipated and the output expression requires a string for the argument, even if no value is given.

Normal Stringify result marker

<"Symbol"> also causes the preprocessor to output the matched text enclosed in double quotes. However unlike the Dumb Stringify result marker, if no match is made in the search rule for the match marker, no string is written to the intermediary file. If the text was matched using the List match marker, the individual items in the list are enclosed in double quotes. The Normal Stringify result marker is used when expressions or command arguments in the source code should be compiled as well as saved in a stringified form.

Smart Stringify result marker

<(Symbol)> also causes the preprocessor to output the matched text enclosed in double quotes. However, it only stringifies the text when the text is not enclosed in parentheses. This result marker is generally used when commands support literal values as well as extended expressions. For example, consider the USE command, which can be written in the form USE Address or USE (cFileName). In the first example, the Smart Stringify result marker would output the file name as "Address". In the second example, it would output the file name exactly as it appeared in the source file.

If no match is made in the search rule for the match marker, no string is written to the intermediary file. If the text was matched using the List match marker, the individual items in the list are Smart Stringified separately.

Blockify result marker

<{Symbol}> causes the preprocessor to output the matched text as a code block. The code block that is written does not contain any arguments, and follows the form: {|| <matched text here> }. If no match is made in the search rule for the match marker, no code block is written to the intermediary file. If the text was matched using the List match marker, the items in the list are individually Blockified.

The Blockify result marker is used, for example, with commands like SET FILTER or SET RELATION, where expressions are defined once and used later or in an iterative fashion. For example, the expression for a filter condition is executed not when SET FILTER is encountered, but when the record pointer is moved (typically with a GO TOP command sometime after the SET FILTER is issued). Since a code block is a piece of executable code that can be passed as data, it becomes the perfect vehicle for the expression required by SET FILTER. The Blockify result marker then becomes the method by which the expression indicated by the SET FILTER command is translated into the required code block.

Logify result marker

<.Symbol.> causes the preprocessor to output a logical value instead of the matched text. If no match is made in the search rule for the match marker, the preprocessor outputs the value .F. (false). If a match was made, the preprocessor outputs the value .T. (true). The Logify result marker is generally paired with the Restricted match marker to indicate whether a specific keyword was matched. If so, then a .T. is output, otherwise a .F. is output. Review the ADDITIVE clause in the SET PRINTER command for an example of this behavior.

Repeating replacement clauses

Some commands require clauses that may be repeated as many times as there are arguments that require it. An example of this is the command REPLACE, which can appear in the source text as REPLACE ..WITH.. , ..WITH.. , ..WITH.. , etc., for an unknown number of fields. In order to support this type of requirement, the replacement rule uses square brackets ( [] ) to identify that part of the rule that can repeat. If no match is made for repeating clauses, nothing is written to the intermediary file. If a repeating clause is matched, the matched text is output to the intermediary file as many times as it is repeated in the source file.

It should be noted that the [] brackets identify an optional clause within the search rule as well as with the result rule. The search rule can contain nested optional clauses. Since the brackets also identify a repeating mechanism in the replacement rule, nested brackets in the replacement rule are not allowed.

Line continuation

When directives are written that span several lines, a semicolon must appear at the end of each line to indicate line continuance.

Special characters

The characters [ and < have a different meaning for the compiler than for the preprocessor. The compiler interprets the symbol [ as part of the array element operator and the symbol < as the smaller than operator. When one or both of these symbols must be used in a preprocessor directive, a back slash (\) should precede them.

The following examples show directives with various combinations of match markers and result markers. Some of the examples are standard commands which are contained in the Include file STD.CH, and others are sample directives which can be very useful in daily programming work. Each example is arranged in three parts:

CH file (Directive or Include file)

PRG file (Original source code )

PPO file (Result code or preprocessed output file)

In some cases the PPO code output shown below has been altered so that the entire listing appears on the page, when normally it would output as one long line. In addition, all examples are code fragments only and are not intended to represent working code. Rather, they are listed to give the reader a feel for how the various directives work.

Examples
Regular match marker => Regular result marker

// These two markers are paired when the variable part of the 
// directive should be copied exactly as it is found in the source 
// to the intermediary file. This is most often the case with 
// pseudofunctions. 

** CH file 
#translate  MinMax( <x>, <min>, <max> )     ; 
        =>  (<x> >= <min> .AND. <max> >= <x>) 

** PRG file 
IF MinMax( 5, 1, 10 ) 

** PPO file 
IF (5 >= 1 .AND. 10 >= 5) 

List match marker => Regular result marker
** CH file 
#command  ?  [<list,...>]  =>  QOut( <list> ) 

** PRG file 
? "Xbase++", 1, Date(), .F. 

** PPO file   QOut("Xbase++", 1, Date(), .F.) 

List match marker => Smart stringify result marker
** CH file 
#command  COPY STRUCTURE TO <(dbFile)> [FIELDS <fields,...>] ; 
   =>  DbCopyStruct( <(dbFile)>, {<(fields)>} ) 

** PRG file 
COPY STRUCTURE TO Temp FIELDS Last_name, first_name 
COPY STRUCTURE TO (cFileName) FIELDS (cField1), (cField2) 

** PPO file 
DbCopyStruct( "Temp", { "Last_name ", "First_name " } ) 
DbCopyStruct( (cFileName), { (cField1), (cField2) } ) 

Restricted match marker => Logify result marker
** CH file 
#command  SET PRINTER TO <(file)> [<add: ADDITIVE>] ; 
      =>  Set( _SET_PRINTFILE, <(file)>, <.add.> ) 

** PRG file 
SET PRINTER TO Temp.txt 
SET PRINTER TO (cOutput) ADDITIVE 

** PPO file 
Set( 23, "Temp.txt", .F. ) 
Set( 23, (cOutput) , .T. ) 

Wild match marker =>
** CH file 
#command  ENDFOR <*what*>    =>  NEXT 

** PRG file 

FOR i:=1 TO 10 
  nSum += i 
ENDFOR Summation 

** PPO file 

FOR i:=1 TO 10 
  nSum += i 
NEXT 
Wild match marker => Stringify result marker
** CH file 
#command  SET PATH TO <*path*>      =>  Set( _SET_PATH, <(path)> ) 
#command  SET COLOR TO [<*spec*>]   =>  SetColor( #<spec> ) 

** PRG file 
SET PATH TO d:\xpp\samples\data\misc 
SET PATH TO (cPath) 

SET COLOR TO N/BG,W+/B 
SET COLOR TO 

** PPO file 
Set( 6, "d:\xpp\samples\data\misc" ) 
Set( 6, (cPath) ) 

SetColor( "N/BG,W+/B" ) 
SetColor( "" ) 
Regular match marker => Blockify result marker
** CH file 
#command  SET FILTER TO <exp> ; 
      =>  dbSetFilter(<{exp}>, <"exp">) 

** PRG file 
SET FILTER TO city=="Chicago" 

** PPO file 
dbSetFilter({||city=="Chicago"}, 'city=="Chicago"') 

Extended expression match marker => Smart stringify result
marker 

** CH file 
#command  RENAME <(oldFile)> TO <(newFile)> ; 
      =>  FRename( <(oldFile)>, <(newFile)> ) 

** PRG file 
RENAME Temp.dbf TO Address.dbf 
RENAME (cSourceFile) TO Address.dbf 
RENAME (cSourceFile) TO (cNewFile) 

** PPO file 
FRename( "Temp.dbf", "Address.dbf" ) 
FRename( (cSourceFile), "Address.dbf" ) 
FRename( (cSourceFile), (cNewFile) ) 

Optional match and result markers
** CH file 
#command  STORE <val> TO <var1> [, <varN> ] ; 
      =>  <var1> := [<varN> :=] <val> 

** PRG file 
STORE  0  TO nCount, nTotal, nAverage 

** PPO file 
nCount := nTotal := nAverage := 0 

#translate and repeating replacement clauses
// The example shows how a code can be translated in more than 
// one way by defining two versions of the same general #translate 

** CH file 
#translate  MATCH( <Var>, <Value> ) ; 
        =>  (<Var> == <Value>) 

#translate  MATCH( <Var>, <Value> , <List,...> ) ; 
        =>  (<Var> == <Value>) .OR. MATCH( <Var>, <List> ) 

** PRG file 
LOCAL nKey 
nKey := Inkey(0) 

DO CASE 
CASE MATCH (nKey, 1, 3 ) 
CASE MATCH (nKey, 2, 4, 8 ) 
ENDCASE 

** PPO file 
LOCAL nKey 
nKey := Inkey(0) 

DO CASE 
CASE (nKey == 1) .OR. (nKey == 3) 
CASE (nKey == 2) .OR. (nKey == 4) .OR. (nKey == 8) 
ENDCASE 

Control structure REPEAT...UNTIL
** CH file 
#command  REPEAT ; 
      =>  DO WHILE .T. 

#command  UNTIL <lExp> ; 
      =>  IF (<lExp>) ;; 
             EXIT ;; 
          ENDIF ;; 
          ENDDO 

** PRG file 

REPEAT 

   nKey := Inkey(0) 

UNTIL nKey == K_ESC 

** PPO file 
DO WHILE .T. 

   nKey := Inkey(0) 

IF (nKey == K_ESC) ; EXIT ; ENDIF ; ENDDO 
Feedback

If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.