A different way to program using Clipper 5.2 without commands, that is, without the file STD.CH.
This material appeared originally at http://www.geocities.com/SiliconValley/7737/clipper52clean.html, in 1996. Now it is incorporated inside the Italian document ``Appunti di informatica libera'', and might be reached at the URI <http://a2.swlibero.org/clean_the_clipper_5_2.html>.
Questo materiale è apparso in origine, nel 1996, presso http://www.geocities.com/SiliconValley/7737/clipper52clean.html. Adesso viene incorporato nel documento «Appunti di informatica libera» e può essere raggiunto attraverso l'URI <http://a2.swlibero.org/clean_the_clipper_5_2.html>. L'intento dell'autore è solo quello di conservare un vecchio lavoro che potrebbe essere ancora utile, nonostante si tratti di considerazioni su un compilatore proprietario, ormai obsoleto.
Clipper 5.2, (1) as the xBase tradition imposes, is not an ordered, clear, simple programming language. The question is: which is the right way to write a Clipper program? If the intention is not to make a xBase program, but a Clipper program, maybe it can be decided that it is better to use Clipper without commands.
476.1
Step 1: try to compile with the /P parameter
Supposing to compile the file TEST.PRG this way:
C:\>CLIPPER TEST.PRG /P[Enter]
It generates a preprocessed output file (test.PPO), that is a source file without comments, where commands are translated into real Clipper instructions. That is, all the #COMMAND substitution are executed and the translation is sent to the .PPO file. It may be difficult to read this file the first time.
476.2
Step 2: understand well the use of code blocks
The code block is a small piece of executable program code that can be stored inside a variable, or can be used as a literal constant. The good of it, is that pieces of code may be sent to functions.
A code block is something like a little user defined function where only a sequence of expressions (functions and/or assignments) may appear: no loops, no conditional structures.
A code block may receive arguments and return a value after execution, just like a function. The syntax is the following, where curly brackets are part of the code block:
{ | [argument_list] | exp_list }
That is: the argument_list is optional; the exp_list may contain one or more expressions separated with a comma.
For example, calling the following code block will give the string ``hello world'' as result.
{ || "hello world" }
The following code block requires a numeric argument and returns the number passed as argument incremented:
{ | n | n+1 }
The following code block requires two numeric arguments and returns the sum of the two square radix:
But code blocks may contain more expressions and the result of the execution of the code block is the result of the last expression. The following code block executes in sequence some functions and gives ``hello world'' as a result.
{ | a, b | functionOne(a), functionTwo(b), "hello world" }
To start the execution of a code block a function is used: EVAL(). For example, a code block is assigned to a variable and then executed.
B := { || "hello world" }
EVAL( B ) == "hello world"
Clipper 5.2 do not permit to create objects, but it gives some good objects to use: GET and TBROWSE. Before starting to clean programming from commands, it is necessary to understand how to use well the Clipper objects.
476.3.1
Classes and methods
A class defines the structure of a ``black box'', that is a data container; a method is an action to make on a piece of data contained inside the black box. There is no way to reach the data contained inside the black box without a method.
The black box can be called object.
The methods may be seen as they where special functions which interact with a specific piece of data contained inside the object.
476.3.2
Class definition
Supposing that Clipper permits to define classes (unluckily only predefined classes can be used), the hypothetical syntax could be:
CLASS ClassName[FROM ParentClass]
VAR Var1[,Var2[,...VarN]]
METHOD {method_definition_1} [, ...{method_definition_n} ]
ENDCLASS
This way, the class defines a group of variables and a group of method to use with these variables.
476.3.3
Object creation
The presence of classes permits to create objects: the black boxes.
Variable_name := ClassName
This way, a variable contains (is) an object. Please note that inside Clipper, an object may be generated also from a function, that is, a function can return an object. This way the example can be:
Variable_name := classfunction( ... )
The next problem is to handle this object.
476.3.4
Instantiating an object
As already stated before, methods are used to handle data contained inside an object. This is said to be instantiating an object. Clipper permits also to handle directly (apparently without methods) some variables contained inside objects. These are called ``Exported Instance Variables''. So, an object can be instantiated this way:
object:exported_instance_variable := new_value
object:method()
An exported instance variable may be read and/or modified depending on the allowed access to it; a method, inside Clipper, is something like a function with or without parameters (if parameters are present, these are usually used to modify data inside the object), that normally returns a value.
476.3.5
The ``send'' symbol
To instantiate an object or simply to access an exported instance variable, the ``send'' symbol (colon) is used.
476.3.6
More about objects
If this is not enough to understand objects inside Clipper, the following document should be read:
Peter M. Freese, o:Clip - An Object Oriented Extension to Clipper 5.01 1991, CyberSoft
A get object is created containing all the necessary information for editing the variable Var at the screen position nTop, nLeft. After that, this get object is added to a get objects array (usually called GetList). The get objects array will contain all the get objects used during a READ.
So, what happens when a READ command is encountered. The get objects array (GetList) is read and the editing of all get objects is executed. After that, the get objects array is cleared.
This method hides what Clipper really makes. The suggestion here is to create a GET() function that will substitute the @...GET command and to use the READMODAL() function to read the get objects array. Here is an example of it:
function GET( aoGet, nRow, nCol, bVar, cGetPicture,
cColorString, bPreValid, bPostValid )
// declare a local get object
local oGet
// create the get object using the function GETENV()
oGet := GETENV( nRow, nCol, bVar, NIL, cGetPicture, cGetColor )
// send to the get object the pre-validation condition code block (WHEN)
oGet:preBlock := bPreValid
// send to the get object the post-validation condition code block (VALID)
oGet:postBlock := bPostValid
// display the get on the screen using the display() method
oGet:display()
// add the get object to the get objects array
AADD( aoGet, oGet )
return NIL
aoGet is the get objects array (so here is explicitly passed). This get objects array is modified (grown) and there is no need to return it as inside Clipper, arrays are always passed by reference to functions.
nRow and nCol are the screen coordinates where the get field should appear at, as it works with the @...GET command.
bVar is a special code block that permits the editing of a variable. If the variable Var is to be edited, the code block is:
{ |x| iif( pcount() > 0, Var := x, Var }
cGetPicture is the picture to use: same as the @...GET command.
cColorString is the color string to use: same as the @...GET command.
bPreValid is a code block containing the condition that must be valid before the cursor can reach this get field. It is equivalent to the WHEN condition used with the @...GET command, but it must be converted into a code block. For example, if the condition is A > B, the code block is {|| A > B}
bPostValid is a code block containing the condition that must be valid before the cursor can leave this get field. It is equivalent to the VALID condition used with the @...GET command, but it must be converted into a code block. For example, if the condition is A > B, the code block is {|| A > B}
If there is a get function like the above one, screen I/O may be performed like the following example:
function do_some_editing()
// define a variable to use as a get objects array
// and initialise it to the empty array
local aoGet := {}
...
...
// add a new get object to the get objects array
get(;
aoGet,;
10, 10,;
{ |x| iif( pcount() > 0, myVariable := x, myVariable },;
"@s30@",;
"gb+/b, n/w, n, n, w/n",;
{ || .T. },;
{ || .T. };
)
...
// read the get objects array
readmodal( aoGet )
// clear the get objects array
aoGet := {}
...
return ...
If the function GET() is not liked, the above I/O may be done as it follows:
function do_some_editing()
// define a variable to use as a get object
local aoGet
// define a variable to use as a get objects array
// and initialise it to the empty array
local aoGet := {}
...
...
// add a new get object to the get objects array
oGet :=;
GETENV(;
10, 10,;
{ |x| iif( pcount() > 0, myVariable := x, myVariable },;
NIL,;
"@s30@",;
"gb+/b, n/w, n, n, w/n",;
)
AADD( aoGet, oGet )
...
// read the get objects array
readmodal( aoGet )
// clear the get objects array
aoGet := {}
...
return ...
476.5
Step 5: trying to stop using commands
To stop using commands, it is important to understand how commands are or may be translated into functions. Sometimes Clipper uses some undocumented functions: these are functions that start with a underline.
This is the command substitution made automatically, but it shouldn't be used to make clean programs. The step 4 (476.1) suggests to create a get function.
476.5.4
@...SAY
@ nTop, nLeft SAY exp[COLOR cColorString]
devpos(nTop, nLeft)
devout(exp[, cColorString])
@ nTop, nLeft SAY exp PICTURE cSayPicture[COLOR cColorString]
devpos(nTop, nLeft)
devoutpic(exp, cSayPicture, [cColorString])
476.5.5
@...TO
@ nTop, nLeft TO nBottom, nRight DOUBLE [COLOR cColorString]
Most of the SET... commands are translated into the SET() function that distinguishes different modes depending on a number. As this number is difficult to handle during programming (essentially because it is difficult to remember the meaning of it), Clipper offers the SET.CH include file that helps with manifest constants.
Now that no command is used, the standard include file STD.CH is no more necessary. Clipper uses STD.CH automatically, unless specified differently. Just compile this way:
C:>CLIPPER TEST.PRG /U[Enter]
476.7
Step 7: take control over all include files
Clipper comes with so many include files (*.CH). To avoid confusion, a single STANDARD.CH file containing all what is needed for the application may be prepared. At least, it is necessary the following.