C5 Error Management

Clipper Error Management

You may encounter several categories of error messages when you develop applications in Clipper. Run-time errors occur when your application hits a snag, and Clipper gives you other messages when you compile and link the application.

You will discover that the greatest number of messages are returned to you by the Clipper Compiler. You don’t risk missing error messages by using a third-party linker.

Clipper’s Run-Time Errors

One product enhancement in the Summer ’87 release of Clipper is a total revamping of error handling. In the past, most error messages would prompt the operator to ‘Quit, Abort or Ignore’. The Summer ’87 release no longer routinely prompts the operator but simply

– present an error message, wich cities the
– procedure name,
– line number, and a simple explanation of the error;

– closes all files; and

– return to DOS.

There are two types of errors in Clipper applications;

– recoverable and

– non-recoverable.

‘Recoverable Errors’ are ones that you can trap and deal with in your applications. You may not always be able to fix the problem, but you can avoid aborting to DOS. This category represents the bulk of errors you will encounter.

‘Non-recoverable’ errors can be introduced by C or assembly routines, corrupted data files, hardware errors and so on. There is no way to handle nonrecoverable errors inside the application.

There are six basic error groups. Nearky all of them have to do with ‘source code errors’ and they occur because you may have tried to add a character string to a number, or you tried to print a variable that doesn’t exist, and so on. You will find 96 percent of potential errors that happen when operators type things that make the program “hiccup”. These errors are also programmer’s responsibility. In some way, the programmer didn’t validate the user input well enough to keep a request for nonexistent files from being processed, or the error occurs for some other similarly pedestrian reason.

Whenever you realize that some errant variable might cause a problem, you have half of the problem solved. Fully half of the battle is seeing what can go wrong – and why.

The other half of the battle is developing an error trap that will catch the bugs before they trigger a nonrecoverable error. What do you do when a user tries to duplicate account number ? What do you do when a record
locks fails ?

Trapping Errors with the
BEGIN SEQUENCE Command

Clipper gives you some interesting tools to trap error conditions before they can damage your application. For example, the Summer ’87 release include a new command construct, the BEGIN SEQUENCE command. BEGIN SEQUENCE has the following syntax:

BEGIN SEQUENCE
   program code ...
BREAK
   program code ...
END
   program code ... && Usually error recovery procedures.

The BEGIN SEQUENCE command  is similar to a DO WHILE loop in the contruction of its code. Unlike the DO WHILE loop, however, BEGIN SEQUENCE is meant to make an umbrella over a group of procedures. Any time the BREAK command is encountered, control of the program immediately passes to the first line after the END statement. Some Clipper programmers use BEGIN SEQUENCE as a sneaky way of emulating the unsupported RETURM TO MASTER command, but its actual intent is to enable you to program your own disaster recovery routines.

The BEGIN SEQUENCE command is most useful in keeping an error condition from exiting the program and displaying the DOS prompt. See the BEGIN SEQUENCE command listing in Part III for more information.

Recoverable Error Functions

The nonfatal or recoverable errors are divided into six basic groups. CLIPPER.LIB include six error-handling function – one for each of the six basic error groups. The error handling functions are written in Clipper code that you can alter to customize your application’s error handling. These are exracted for you on the Clipper disks in a file called ERRORSYS.PRG. The six group are:

Database errors
Expression errors
Miscellaneous errors
Open errors
Print errors
Undefined errors

Note: The file ALTERROR.PRG on your Clipper disks contains a set of error functions specifically designed for use with the Clipper debugger.

Producing Your Own Recovery Functions

You can modify the default error functions by replacing them with user-defined functions that have the same name. Group these replacement functions into a file and compile them. When it comes time to link your program together, add that .obj file to the FILES directive to the linker. Do not specify the error object file as the first name in the list. Always put your application’s main module first in the list. As long as your functions have the same name as the ones in CLIPPER.LIB, they will be linked into the program rather than the functions in the library.

Each of the error-handling functions that are supplied with the Clipper library share the same basic syntax:

XXX_ERROR(name, line, info [,model][,_1][,_2][,_3][,_4][,_5]

The XXX in the FUNCTION name is filled with the name of actual function itself.

The ‘name‘ parameter returns a character string equal to the name of the procedure or function that was active when the error was triggered.

The ‘line‘ argument refers to the program line number assigned by the Clipper compiler. If you compiled the application with the -l switch for no line number, this parameter will be zero.

The ‘info‘ (information) parameter contains a character string that describes the error-for example, DATA TYPE MISMATCH or DIVISION BY ZERO.

The ‘model‘ parameter is optional and is not supplied by some of functions When it is supplied, the model argument to the function is the portion of source code that triggered the error.

The _1 ,_2 ,_3 ,_4 ,_5 parameters – if they are supplied by the function – can be of any type; character, numeris, logical, or date. You can look at these as being the parameters that were passed to the command when the error was triggered, and they will contain values that were in effect at the time the error was triggered. The unusual names reflect a desire on the part of the Clipper development team not to conflict with any variable names to be reserved words. Also because Clipper is written in Microsoft C 5.0, you should aviod using using underscore (_) as the first character in a field, file or variable name. This could bugs that seem to make no sense by invoking internal Clipper functions.

By identifying and manupulating the errors and the information associated with an error, you can develop a strategy to recover from that error. To develop recovery procedure, look at each of the error functions individually. In general, the value that is returned by the error function determines what action the Clipper program takes.

The code included in the next six examples is reprinted by permission of Nantucket Corporation and is the actual code found in CLIPPER.LIB. It is also included on your factory disks in a file called ERROR.DOC.

Handling Database Errors

Database errors are classified as those errors that occur during the time
a file is open. This does not include the opening and closing operations.
REPLACE, DELETE, AVERAGE, and similar commands are the types of commands
that can trigger this error message.

The following syntax is for the error-hanfling function DB_ERROR():

***
*
* db_error( name,  line,  info)
*
FUNCTION db_error
   PARAM name, line, info
   SET DEVICE TO SCREEN
   @ 0,0
   @ 0,0 SAY 'Pro- c ' + M->name +;
             ' line ' + LTRIM(STR(M->line)) +;
             ', ' + M->info
   NOTE BREAK
   QUIT
RETURN .F.

After declaring the function name, ‘DB_ERROR()’ recognizes three parameters: ‘name’, ‘line’, and ‘info’.

The function then SETs the DEVICE to the SCREEN so that the error message is displayed even if the application is printing at the time.

The command NOTE BREAK suggests that you might prefer to issue a BREAK to your BEGIN SEQUENCE construct rather that the QUIT.

The ‘info‘ parameter may return one of these messages:

DATABASE REQUIRED

This message occurs when you attempt to access a data file in a work area where none is open.

LOCK REQUIRED

This error message is triggered when your application attempts to perform a command that requires file or record locking. When you have used a data file in a nonexclusive mode, you must continue to follow networking rules throughout the entire application.

EXCLUSIVE REQUIRED

This error message is given whenever your application execute a command that requires exclusive use of a file.

FIELD NUMERIC OVERFLOW

This message indicates an application attempted to write into a field a number that is larger than the length of the field.

INDEX FILE CORRUPTED

This message occurs when an application attempts to manipulate an index file that is damaged.

You can modify DB_ERROR() to perform actions that will try to rectify the situation. Use the value of the variable, ‘info’ to key a DO CASE structure to specify corrective action. For example:

IF info = 'INDEX FILE CORRUPTED'

you could have the function rebuild the index.

You have three choices of action when the DB_ERROR() function is triggered:

– You can perform actions to correct the situation and RETURN the value of the function as true (.T.) RETURNing a true (.T.) retries the command that triggered the error.

– You can return false (.F.) and abort the program to DOS.

– You can issue a BREAK statement as part of a BEGIN SEQUENCE contruct. The control of the program would revert to the next line following the END command. Your program then can back out of the situation as gracefully as possible, repaint the main menu, and start fresh.

Handling Expression Errors

Expression errors generally are associated with conditions such as division by zero or data type mismatches. A good example of this would be a command in your ptogram that tries to add a number to a character string. This causes a data type mismatch error to occur.

This error occurs whenever a Clipper application contains an invalid expression. For example, in VAL(‘AU-H20’) the value of function has no mathematical meaning.

The following syntax is for the error-handling function EXPR_ERROR();

***
*
* expr_error(name, line, info, model, _1, _2, _3 )
*
FUNCTION expr_error
   PARAM name, line, info, model, _1, _2, _3
   SET DEVICE TO SCREEN

   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' +;
             LTRIM(STR(M->line)) + ', ' + M->info
   QUIT
RETURN .F.

The ‘model‘ portion of the function argument contains the expression that triggered the error. In all cases, that expression is delivered as a character string. The operand parameters have values equal to the variables in effect when the error was triggered. In other words, the model is returned as a piece of code with the operands substitued as variable elements of the code. For example:

TONE( c#,277 )

would return the following values in EXPR_ERROR():

info = 'Expression Error'
model = 'TONE(_1,_2)'
_1 = 'c#'
_2 = 277

If you devise a lookup function so that your EXPR_ERROR function can “know” what the TONE() function should look like, it is fairly simple matter to compare data types of the operands to find out that _1 should be numeric. The function then could substitute a numeric value into _1 and RETURN &model. This would reevaluate the expression, and control would be passed to the next line in the program if the error condition was corrected.

The ‘information’ parameter of the EXPR_ERROR() function returns one of the following messages:

 TYPE MISMATCH

This error occurs when two different types of variable are in some way joined or operated upon jointly.

SUBSCRIPT RANGE

This error occurs if your Clipper application attempts to use an array element that does not exist.

ZERO DIVIDE

This error occurs whenever your Clipper application attempts to divide any number by 0, which is mathematically meaningless and therefore illegal.

Handling Miscellaneous Errors

Miscellaneous errors consist of lower-level errors that usually would not be considered fatal. This function also acts as a “dust bin,” handling those errors that the other error functions are not equipped to handle.

The following syntax is for the error-handling function MISC_ERROR():

***
*
* misc_error(name, line, info, model)
*
FUNCTION misc_error
   PARAM name, line, info, model
   SET DEVICE TO SCREEN
   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', ' + M->info
   NOTE BREAK
   QUIT
RETURN .F.

You cannot use the MISC_ERROR() function to return the model argument as executable code, as you can with the EXPR_ERROR() function.

To recover from a MISC_ERROR(), you can try retry the operation by returning true (.T.) after making some changes to variables. You can issue the BREAK command to jettison out of a BEGIN SEQUENCE construct, or you can abort to DOS.

The ‘info’ parameter may return one of the following messages:

TYPE MISMATCH

The TYPE MISMATCH error message occurs when a REPLACE command attempts to place the wrong data type into a field. For example, trying to place “123” into a numeric field would cause a type-mismatch, miscellaneous error.

Note: This is different from the TYPE MISMATCH in the expression error-handling function. The context of the error determines which function is called. If there is not a write to the file, the expression error will be triggered. If it involves writing to a file, the miscellaneous error will be called.

RUN ERROR

The RUN ERROR error message occurs when a Clipper application attempts to run another executable file by loading a new COMMAND.COM into memory. The underlying cause of this error can be determined by reading the value of DOSERROR() function. The ‘model’ parameter contains the portion of your Clipper code that triggered the error.

Handling Open Errors

Open errors occur when file-opening operations fail. Special consideration is given to opening data files. When the request to open a data file is not honored by DOS (usually by passing along a network operating system message), an evaluation is made. If NETERR() is true and the ‘model’ paramater is USE, the error function simply returns a value of false. In such a case, the application ignores the error.

This is why you have to structure the USE command the way you do in a Clipper application. If your application tries to open a data file and fails, the error is ignored, and the next portion of code is executed. If the next portion of code tries to open index files for a data file that could not be opened, a second, more serious, error occurs. The open error on the index files is not ignored, and the application aborts to DOS.

The following syntax is for the error-handling function OPEN_ERROR() :

 ***
 *
 * open_error(name, line, info, model, _1)
 *
 FUNCTION open_error
    PARAM name, line, info, model, _1
    IF NETERR() .AND. model == 'USE'
       RETURN .F.
    END
    SET DEVICE TO SCREEN
    @ 0,0
    @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', ' + M->info + ' ' +;
              M->_1 +' (' + LTRIM(STR(DOSERROR())) + ')'
              @ 0,65 SAY 'Retry ? (Y/N)'
    INKEY(0)
    DO WHILE .NOT. CHR(LASTKEY()) $ 'YyNn'
       INKEY(0)
    END
    IF .NOT. CHR( LASTKEY() ) $ 'Yy'
       QUIT
    END
    @ 0,0
RETURN .T.

OPEN_ERROR() is the most active of the six types of error-handling functions. First, it terminates itself in the case of USE errors where NETERR() is true. Thise helps you determine what you need to do about unavailable files. (Refer to Chapter 7, “Clipper and Networking” for more information.) Then, OPEN_ERROR() prompts you to retry the operation.

The ‘info’ parameter of the OPEN_ERROR() returns only the message OPEN ERROR. The ‘model’ argument to the function is the command that triggered the error. The operand parameter, _1, is the file name that triggered the error.

To recover from an OPEN_ERROR(), you can return true (.T.) and retry the operation. Returning false ignores the command that created the error. Further error messages are certain if the application tries to perform operations on a file that is not open. You could also issue a BREAK, which would provide you with your own error-recovery facility if you have a BEGIN SEQUENCE construct in your application.

Handling Print Errors

A print error occurs whenever your Clipper application is running DOS 3.1 or higher and is accessible a parallel port that is sensed to be not ready. Users running earlier versions of DOS get the straight DOS ‘Abort, Retry, Ignore?’ error message without being able to trap the error first. Usually, the operator has forgotten to place the printer on-line. This error occurs also when a network spooler is unable to continue receiving output or when you redirect @ … SAY commands to a file with the SET PRINTER TO command, and a disk error occurs.

If the printer has been redirected with MODE to a serial port, an OPEN ERROR error is triggered instead.

The following syntax is for the error-handling function PRINT_ERROR():

***
*
* print_error(name, line)
*
FUNCTION print_error
   PARAM name, line
   SET DEVICE TO SCREEN
   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', printer not ready'
   @ 0,65 SAY 'Retry ? (Y/N)'
   INKEY(0)
   DO WHILE .NOT. CHR( LASTKEY() ) $ 'YyNn'
      INKEY(0)
   END
   IF .NOT. CHR( LASTKEY() ) $ 'Yy'
      QUIT
   END
   @ 0,0
RETURN .T.

The ‘info’ parameter always returns the string PRINTER ERROR.

You can recover from the PRINT_ERROR function by prompting the operator to correct the situation, then returning true (.T.) which will retry the command.

BREAK can be used to terminate the error function and pass control to a custom error-handling portion of your Clipper application.

Returning false (.F.) continues the application without retrying the command that triggered the error message. This can lead to problems because the next command may also access the printer; your program’s operator will see an endless srtring of error messages.

Because the error message automatically SETs DEVICE TO SCREEN, a retry would cause any @ … SAY commands to be displayed on the screen, causing another error. If you are doing to develop your own PRINT_ERROR() function, use a semaphore variable that will store the condition of the SET DEVICE command. In that way, your error-recovery code will “know” if it must SET DEVICE TO PRINT in order to resume without further errors.

Handling Undefined Errors

The UNDEF_ERROR() function is triggered whenever you refer to a variable that has not been defined. You can consider this to be any situation in which the TYPE() function would return a value of ‘U’.

The following syntax is for the error-handling function UNDEF_ERROR():

***
*
* undef_error(name, line, info, model, _1)
*
FUNCTION undef_error
   PARAM name, line, info, model, _1
   SET DEVICE TO SCREEN
   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', ' + M->info + ' ' + M->_1
   QUIT
RETURN .T.

The ‘info’ parameter returns one of the following character strings:

UNDEFINED IDENTIFIER

This message is given whenever you attempt to access a memory variable name that does not exits.

NOT AN ARRAY

This error message indicates your Clipper application accessed an array element that does not exist, or the array itself does not exists.

MISSING INTERNAL

This error message occurs whenever your application refers to a user-defined function or procedure that has not been declared within the application itself. This would apply also to any function contained in the the Extend library that has not been referenced within the body of the application. This error message usually indicates that a function has been used in either a label or report form but has not been invoked in the application itself. Whenever you use a function in a report form that is not used is the application itself, use the EXTERNAL command so that the compiler includes the function in the .EXE file.

The ‘model’ parameter contains the portion of the source code that triggered the error. The _1 operand contains the name of the unidentified identifier.

In recovering from this error, returning true (.T.) retries the operation. The BREAK command could be used to exit out of a BEGIN SEQUENCE construct to a custom error-handling routine. Returning false (.F.) causes Clipper to close all files and return to DOS.
Nonrecoverable Errors

Yor Clipper application may trigger other errors that are not handled by the previously described error-handling functions. These errors include the following:

INTERNAL ERROR

An INTERNAL ERROR message generally is caused by a corrupted index file. Clipper presentsthe error message and pauses for a key to be pressed. Pressing any key causes Clipper to quit and return to DOS.

DISK FULL

This error occurs when the application is writing a database file back to disk, and the write attempts to go beyond the amount of available disk space. This error message displays a prompt, allowing the user to retry the operation, just in case the cause of the error is a floppy disk that is not ready. Answering ‘No’ to the prompt causes the application to quit and return you to DOS. In essence, there is little real choice here because the pause does not include the opportunity to delete any files.

MULTIPLE ERROR

This is triggered when the error that begins the process is so bad that it cause an error within one of the error functions. Clipper indicates this by posting this error message and prompting you to press any key. When you press any key, the application quits and return to DOS.

OUT OF MEMORY

This error displays when the upper limits of RAM have been reached. Clipper display the error message and pauses; pressing any key causes Clipper to abort and return you to the DOS prompt.

NOT ENOUGH MEMORY

This error happens whenever Clipper senses that the application does not have enough memory to perform an operation. Clipper displays the message and pauses. Pressing any key causes Clipper to quit, close all files, and return you to the DOS prompt.

Error Messages Returned at
Compile and Link Time

During the development cycle, you will get error messages telling you that there are errors in your program. Ninety percent of these errors come from the Clipper compiler. The other 10 percent are presented by the linker.

Compiler Error Messages

The compiler itself is your front-line debugging tools. Most syntactical bugs and errors in your program are caught by the compiler and noted for you.

You can redirect or echo the Clipper error messages to a printer or a file. To redirect Clipper error messages to the printer, simply enter your commands as you normally would, adding a standard argument for DOS output redirection. For example :

CLIPPER prognam > PRN

This sends any output from Clipper to the printer. There are a great number of errors that you might see as you use the compiler. Most are self-explanatory. They tell you the procedure name, the line number of the program, and show you the piece of code that is causing the problem. Clipper also points to problem with caret (^). Figure 8.1 shows a typical Clipper error message.

 -------------------------------------------------------------------------
 Fig. 8.1 Clipper error message.
 -------------------------------------------------------------------------
 C:\iii\clip\book>clipper dbedit
 The Clipper Compiler, Summer '87
 Copyright (c) Nantucket Corp 1985-1987, All Rights Reserved,
 Microsoft C Runtime Library Routines,
 Copyright (c) Microsoft Corp 1985-1987, All Rights Reserved,
 Compiling DBEDIT.PRG
 line 9: rest of line ignored
 CLEAR ALL the extra stuff off the screen
 1 error
 Code Pass 1
 Code Pass 2
 Code size 443, Symbols 160, Constants 181
 C:\iii\clip\book>
 -------------------------------------------------------------------------

Documenting all of the errors that the compiler can return to you would be nearly impossible. Almost any command with an incorrect syntax will produce the same type of error message that dBASE III Plus delivers in its interpretive mode except that Clipper does not ask if you want to cancel, suspend, or ignore. Other error messages, however are fairly common and require only small amounts of clarification. The following is an alphabetical list of errors returned by the compiler.

CASE WITHOUT DO CASE

This error message suggest that you have placed a CASE comparison in your program but forgot to begin with a DO CASE statement.

ELSE WITHOUT IF

You have probably placed an ELSE statement in the wrong place, The compiler does not find a matching IF for ELSE.

ENDCASE WITHOUT DO CASE

This error normally occurs when you remove a DO CASE statement from a program but forget to erase the ENDCASE at the bottom. Clipper is telling you that it has found an ENDCASE statement but no DO CASE statement to open the construct.

ENDDO WITHOUT WHILE

The compiler has found an ENDDO that is unbalanced. There is no corresponding DO WHILE to open the construct.

ENDIF WITHOUT IF

This error is most likely to occur when you are nesting IF … ELSE … ENDIF statements or have erased an IF construct but have forgotten to remove the ENDIF.

EXIT WITHOUT DO WHILE

Clipper has found an EXIT command, requesting Clipper to drop out of a DO WHILE loop, but Clipper does not find a DO WHILE construct.

 FATAL  AT <n> - INVALID PROCEDURE MODE

This error is triggered if two programs contain the same procedure name or if you have created two user-defined functions with the same name. This message is displayed only to the screen and is not affected by DOS redirection.

ILLEGAL DEVICE

You have a syntax error in a SET DEVICE TO command.

ILLEGAL VALUE

This is triggered whenever the left side of an expression using the equal sign (=) is an illegal expression, or the STORE command tries to perform an illegal variable initialization.

– for example, STORE 8 TO 5+5.

LOOP WITHOUT DO WHILE

Clipper has located a LOOP command that is not inside a DO WHILE construct.

MISSING SECOND QUOTE

This error is triggered by omitting the closing quotation marks on string expressions.

NEXT WITHOUT FOR

This error message means that a NEXT command has no balancing FOR command.

OTHERWISE WITHOUT DO CASE

Clipper has found an OTHERWISE command that is not inside a DO CASE construct.

REST OF LINE IGNORED

This error message is displayed usually when you have given an illegal argument or option to a command. The illegal portion is to the right of the command verb, so it is looked at as unnecessary baggage. Clipper simply ignores the existence of anything else on that line.

SYMBOL REDEFINITION ERROR

This error message is triggered usually when a procedure name has been duplicated in an application.

TOO MANY CONSTANTS

The compiler is limited to a 64K constant table to keep track of elements of the program while it is being compiled. If you get this message, break the program into two seperate object files. Include both object file names in the linking command.

TOO MANY SYMBOLS

The compiler is limited to 64K of memory for symbols table. If you receive this error message from Clipper, divide your program in half and compile it into two object files. Specify both object file names to the linker when you prepare the .EXE file.

UNBALANCED DO CASE

The compiler has found a DO CASE statement without an ENDCASE.

UNBALANCED DO WHILE

The compiler has found a DO WHILE that is not terminated by an ENDDO.

UNBALANCED FOR/NEXT

Clipper has found a FOR command without the NEXT statement that terminates it.

UNBALANCED IF/ELSE

Clipper has found an IF without a terminating ENDIF.

VERB NOT RECOGNIZED

Clipper does not recognize a command verb or keyword that has been included in your command. Usually, this error is caused by a spelling error.

PLink86 Plus Messages

PLink86 Plus, supplied with Clipper, is a product published by Phoenix Technologies, the same company that produces compatible ROMs for computer manufacturers. Most people simply refer to it as PLink or PLink86. PLink86 Plus is used widely by software delevopper.

PLink86 Plus is included with Clipper because of its capability to produce overlay files efficiently. The copy of PLink86 Plus included with your Clipper package has been modified by Nantucket to be Clipper-specific. Some of the error messages that PLink86 Plus is capable of returning actually should never be seen by a Clipper programmer unless he or she is plugging in assembly language routines and having problems. Each of these error messages is coded by number. If you experience problems with PLink86 Plus, refer to Nantucket technical support for help in resolving your problem. The following discussion centers on the messages most often occuring in the Clipper environment. For a complete listing of the PLink86 Plus error messages, see the Clipper manual.

The messages that are delivered by Plink86 Plus are broken into two categories: warnings and errors.

PLink86-Plus Warning Messages

Warning 3: This error message is triggered when a ‘group’, a set of memory segments that must be able to fit within a 64K frame in memory, exceeds the limit. This warning message happens usually when you are mixing C and assembly language routines within modules for the Clipper application. Assembly language class and segment names must match those used in the high-level language. If not, assembler segments may be assigned to areas of memory that are not within the 64K frame of the ‘group’. This error message may appear also if you name one of your procedures DATA or SYSTEM. These are reserved words; don’t use them.

Warning 4: A file has been specified in the MODULE command but does not appear in the directory.

Warning 7: Usually, this means you have included the .PRG extension with the file name when you access the CL.BAT batch file. PLink86-Plus is trying to link .PRG files instead of .OBJ files because the batch file specified .PRG as the file extensions.

Warning 8: This warning is usually the first indication of a corrupted object file.

Warning 9: This warning also is an early indication of a corrupted object file.

Warning 11: You have declared the same variable to be public more than once. The compiler ignores the second declaration of the public variable and continues to link the file.

Warning 12 or Warning 13: These warnings are triggered usually when an assembly language module you are linking as part of your program makes an errouneous memory assignment.

Warning 14: This message also deals with assembly language routines assigning memory for stack storage. Using PLink86 Plus and .LIB files from different Clipper versions generates this message also.

Warning 19: This usually indicates that you are nearing the limits of memory in linking your application. PLink86 Plus determines that there is not enough memory to store path names for a file that it cannot locate and will prompt you to enter the path name.

PLink86 Plus Error Messages

PLink86 Plus error messages are grouped into categories that have common roots.

Plink86 Plus File Error Messages

The first group deals wiht errors in the input to the Plink86 Plus command, usually dealing with macro-substituted file names.

Error 1 : This error message indicates that @ files are nested more than three deep or that the @ files contain a circular reference.

Error 2 : A DOS error occurred while trying to read the @ file.

Error 3 : The @ file specified cannot be found.

Error 5 : The Plink86-Plus input line exceeds the maximum lengh of 64 characters.

Error 10: This error message indicates an invalid file name. Plink86 Plus has been given a character string that is an illegal DOS file name.

Error 11: A keyword is missing from the .LNK file.

Error 14: This message indicates that PLink86 Plus is expecting an identifier. A section module segment or symbol name is missing from the .LNK file or was mistyped at interactive input time.

Error 15: An equal sign (=) is missing in the .LNK file.

Error 17: No files have been specified to be linked. Check the .LNK file to make sure the file command is present.

PLink86 Plus Work File Error Messages

Plink86 Plus requires at least 256K of memory to work properly. As PLink86 Plus runs out of memory, it opens a scratch file on disk to hold contents of the link temporarily. The following error messages indicate a problem with the work file.

Error 30: A work file cannot be created, probably because of a lack of disk space.

Error 31: A DOS I/O error happened while Plink86 Plus was writing the .EXE file.

Error 32: PLink86 Plus encountered a DOS I/O error as it was reading the work file.

Error 33: A DOS I/O error occured while Plink86 Plus was positioning the pointer within the file.

Error 34: This error message indicates that you have created an application that is too large for PLink86 Plus to handle. Please check the source code for possible inefficiencies. See the section outlining the SWITCH utility in Chapter 9, “Clipper Tools”.

PLink86 Plus Input Object File Error Messages

These errors involve the object files that have been presented to PLink86 Plus. They genereally indicate a corrupted object file that should be recompiled.

Error 41: Premature end of object file. PLink86 Plus is finding an object file that is a different size from that stated by the header at the beginning of the file. This error message can be triggered also by specifying the .PRG extension to the CL.BAT batch file. This error canbe the result also of high-order bits being turned on when you use WordStar in the document mode as an editor. While rare, this cause is possibility.

Error 42: Fatal read error in object file. This error message indicates that the object file is completely unusable.

Error 43: PLink86 Plus cannot find a specified object file. If PLink86 Plus was not invoked with a batch file, it will prompt you for a path name.

PLink86 Plus Output File Error Messages

This group of errors deals with problems in creating the .EXE file itself.

Error 45: A DOS error has occured as PLink86 Plus tried to creat the .EXE file. The disk is either full or possibly write-protected.

Error 46: Invalid output file type. You probably have included in your .LNK file an output command that specifies an invalid extension to the .EXE file.

Error 47: Fatal disk write error. PLink86 Plus was able to creat the file; but as it was writing the file, the disk drive probably was filled.

Error 48: Fatal read error in output file. PLink86 Plus is unable to read the file it has constructed. This is probably due to a hardware error.

Error 49: Cannot close output file. This error is probably caused by a hardware error or a write-protect error.

Error 50: Cannot construct memory map. The memory map cannot be constructed, probably because the disk is full.

PLink86 Plus Miscellaneous Error Messages

Error 51: Undefined symbol exist. This error occurs when you specify a procedure or function in your program, and the linker cannot find object code to construct it. Usually, you have forgotten to include the EXTEND.LIB in your PLink86 Plus command after including a function from EXTEND.LIB in your application.

Error 54: This error indicates there is not enough random access memory on the computer to run PLink86 Plus.

Error 57: This error indicates a problem with OVERLAY.LIB. Recopy the file from your factory disks.

Error 58: You have created a stack segment in an assembly language program that is too large.
PLink86 Plus Program Structure Error Messages

These error are triggered when there are problems within overlay for your application.

Error 80: Overlays are nested too deeply.

Error 81: There are more ENDAREA statements than BEGINAREA statements in your .LNK file.

Error 82: There are more BEGINAREA statements than ENDAREA statements in your .LNK file.

Error 83: Your application is too large, but it can be compiled if you break the application into a load module with overlays.

PLink86 Plus Diagnostic Error Messages

If you receive error messages greater than 200 from PLink86 Plus PLink86 Plus rest assured that your program could not have caused these error messages. Usually, these error messages indicate that the PLink86 Plus file itself has been corrupted. Recopy PLink86 Plus from the factory disk. If the problem repeats, record the error code and whatever information is necassary to produce repeatable results and contact Nantucket Technical Support.

Summary

In this chapter, you have seen the source code that produces Clipper’s run-time error messages. You should have some idea of how you might resolve them.

You should also be prepared to decipher error messages and warnings from both Clipper and PLink86 Plus. Don’t forget that most link errors occur simply because you have forgotten to declare a file to the linking process.

If you encounter a stubborn linkage problem with a third-party linker, come back to PLink86 Plus and decipher the error message it delivers. Correct the problem, and relink using the linker of your choice.

16:31:41 18/11/1991

The Art Of Simplicity

A discussion of how to create objects with Clipper using arrays, and ordinary Clipper syntax. Has several good examples.

An Introduction into Object Oriented Programming.

To me, the challenge of programming is in finding a simple clean way to implement a program. Making sure no matter how complex the specs are, the code itself stays small, strait forward, and easy to maintain.

To illustrate how to reduce the complexity of things, lets examine the box drawing routines. Normally to display a nondestructive box on the screen you write something like this:

 old_row:=row()
 old_col:=col()
 old_cursor:=setcursor()
 old_screen:=savescreen(10, 20, 16, 59)
 old_color:=setcolor("w+/n, w+/r")
 @ 10, 20, 16, 59 BOX 'ÚÄ¿³ÙÄÀ³ '

Then when you are done and wish to remove the box, you reverse the procedure:

 restscreen(10,20,16,59,old_screen)
 setcolor(old_color)
 setcursor(old_cursor)
 setpos(old_row,old_col)

This scheme results in using 9 lines of code, 5 memory variables, and requires that the programmer maintain the box coordinates in 3 different places. After going through this procedure a few times I started wondering if there was a better way of doing this.

When I tried to solve this problem, I had several false starts. I created a procedure to display the box that saved all the variables to statics, and the next time it was called it would restore the box. That didn’t work too well since I often wanted more than a single box on the screen.

Then I tried saving the memvars to an array that I used like a stack. But that didn’t work out too well either, since it required that all boxes be removed in the same order that they were created.

Then I decided that all the memvars being used to store the box information belonged in the calling routine, where they had been all along. Despite the fact that this seemed to bring me back to square one I continued with this train of thought.

If I stored all the memvars being used by the box routine in an array, then all the memvars could be stored in a single package, and passed to the calling routine without complications:

aBox := CreateBox(10, 20, 16, 59, "w+/n, w+/r")

And when I no longer needed the box and wished to restore the original screen:

DestroyBox( aBox )

Please compile DEMO1, to see the basic box functions.

CreateBox() and DestroyBox() are used to replace 10 lines of code, and the array aBox was used to replace 5 variables. Putting all the data into the array aBox and handling only the array, makes things much simpler.

Now that we have developed this technique, we could theoretically create a number functions that work together, like CreateBox() and DestroyBox(), and use the data contained in aBox. And in the file BOX.PRG, I have a group of sample functions that do just that:

 CreateBox()
 DestroyBox()
 BoldBox()
 MoveBox()

Another benefit of this technique, is that we can have multiple arrays that each correspond to separate boxes, and use them all at the same time.

For example, we could write a program with a couple of boxes:

 aBox1 := CreateBox( 05,  26,  20,  53,  "w+/n, w+/n" )
 aBox2 := CreateBox( 10,  20,  16,  59,  "w+/b, w+/b" )

To move the second box:

MoveBox( aBox2,  -08,  -18 )

Then we could give the second box a highlight:

BoldBox( aBox2, "w+/b, w+/b" )

Then to remove both boxes:

 DestroyBox(aBox2)
 DestroyBox(aBox1)

This example does some fairly complex things, and it does so, in only six lines of code. To run this example, compile the file DEMO2.PRG.

This programming technique has a name, it is called Object Oriented Programing (OOP).

According to OOP terminology the arrays aBox1 and aBox2 are objects, and the functions CreateBox, MoveBox, BoldBox, and DestroyBox are methods.

Objects are collections of related data, or in dBase terminology, arrays of related memvars. In our example, aBox1 and aBox2 are qualify as objects since they contain related data (the coordinates of the box, the original color, cursor position, cursor status, etc.).

In object oriented programming, several instances of an object can be created, and later destroyed when we are finished with them. In our example, aBox1 and aBox2, constitute two separate instances of box objects.

If you look at the example in DEMO3.PRG, you will see that the program creates an array of four box objects, and four separate instances of the box object are on the screen at once.

A methods is a special type of function. Methods are functions that are grouped together, and manipulate the same data objects. In the file BOX.PRG, you will see the code for four methods that use the box objects ( CreateBox, DestroyBox, BoldBox, MoveBox )

CreateBox is a special type of method called a constructor, because it creates a box object and initializes it.

DestroyBox is a special type of method called a destructor, because it destroys a box object and frees of the memory that the box object used.

Every time we call a method / function, we pass it the object we want the method to manipulate. In our example, we have two objects, aBox1 and aBox2. To move the first box, we called the method MoveBox() like this:

MoveBox( aBox1, 1, 1 )

And to bold the second box, we called the method BoldBox() like this:

BoldBox( aBox2, "w+/b,w+/n" )

The constructor CreateBox doesn’t need to be passed the object, because the constructor creates the object.

Now that you understand what an object is, you can create additional functions / methods that use the box object. And hopefully go on to create your own objects and methods.

Cynthia Allingham, 1991

/***
*
* BOX.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Displays exploding box on the screen
* Returns: Previous screen contents
*/
FUNC CreateBox (nTop, nLeft, nBottom, nRight, box_color)
local save_window:=savescreen(nTop, nLeft, nBottom, nRight)
local save_color:=setcolor(box_color)
local save_cursor:=setcursor()
local save_row:=row()
local save_column:=col()
@ nTop,nLeft,nBottom,nRight BOX 'ÚÄ¿³ÙÄÀ³ '
RETURN {nTop, nLeft, nBottom, nRight, save_window,;
 save_color, save_cursor, save_row, save_column}
/***
* Written By: Cynthia Allingham 11/01/91
* Purpose: destroys the box and restores old settings
*/
FUNC DestroyBox (aList)
restscreen(aList[1],aList[2],aList[3],aList[4],aList[5])
setcolor(aList[6])
setcursor(aList[7])
setpos(aList[8],aList[9])
aList:=nil
return nil
/***
* Written By: Cynthia Allingham 11/01/91
* Purpose: Changes the box border
*/
FUNC BoldBox (aList, cColor)
@ aList[1],aList[2],aList[3],aList[4];
 BOX 'ÛßÛÛÛÜÛÛ' color cColor
return nil
/***
* Written By: Cynthia Allingham 11/01/91
* Purpose: Redimensions the screen
*/
FUNC MoveBox (aList, nVert, nHorz)
local save_window:=savescreen(aList[1],aList[2],aList[3],aList[4])
dispbegin()
restscreen(aList[1],aList[2],aList[3],aList[4],aList[5])
aList[3] += nVert; aList[1]+=nVert
aList[4] += nHorz; aList[2]+=nHorz
aList[5]:=savescreen(aList[1],aList[2],aList[3],aList[4])
restscreen(aList[1],aList[2],aList[3],aList[4],save_window)
dispend()
return nil
* EOF BOX.PRG
/***
*
* DEMO1.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Simple program demonstrating the creation and
* destruction of a box object.
*/
local aBox
set procedure to box
@ 00,00,24,79 box 'ÚÄ¿³ÙÄÀ³°'
aBox:=CreateBox(10, 20, 16, 59, "w+/n, w+/r")
@22,19 say padc("Press any key to destroy the box",40)
inkey(10)
DestroyBox(aBox)
* EOF DEMO1.PRG
/***
*
* DEMO2.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Demonstates the use of two box objects
*/
local aBox1, aBox2
set procedure to box
@ 00,00,24,79 box 'ÚÄ¿³ÙÄÀ³°'
aBox1:=CreateBox(05, 26, 20, 53, "w+/n, w+/n")
message("Press any key to create a second box")
aBox2:=CreateBox(10, 20, 16, 59, "w+/b, w+/b")
message("Press any key to move the second box")
MoveBox(aBox2, -08, -18)
message ("Press any key to bold the second box")
BoldBox(aBox2, "w+/b, w+/b")
message("Press any key to destroy both boxes")
DestroyBox(aBox2)
DestroyBox(aBox1)
func message(ctext)
@22,19 say padc(ctext,40)
inkey(10)
* EOF DEMO2.PRG
/***
*
* DEMO3.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Demonstates the use of four box objects
*/
local aBox[4]
local cnt
set procedure to box
@ 00,00,24,79 box 'ÚÄ¿³ÙÄÀ³°'
aBox[1]:=CreateBox(05, 05, 09, 30, "w+/n, w+/n")
@ 06, 07 say "box #1"
aBox[2]:=CreateBox(18, 03, 22, 14, "w+/b, w+/b")
@ 20, 05 say "box #2"
aBox[3]:=CreateBox(20, 48, 22, 77, "w+/r, w+/r")
@ 21, 50 say "box #3"
aBox[4]:=CreateBox(02, 64, 12, 75, "w+/gr, w+/gr")
@ 03, 66 say "box #4"
for cnt:=1 to 12
 inkey(0.5)
 MoveBox(aBox[1], +1, 0)
 MoveBox(aBox[2], 0,+4)
 MoveBox(aBox[3], -1, 0)
 MoveBox(aBox[4], 0,-4)
next
inkey(10)
for cnt:=1 to 4
 DestroyBox(aBox[cnt])
next
* EOF DEMO3.PRG

RTE (Runtime error)

What is a RTE (Runtime error) ?

There are four ways that a Clipper program can terminate fatally. Each of these types of terminations represent different causes for the termination and need to be considered separately.

Runtime recoverable errors :

Runtime recoverable errors are expected to happen. These errors generally occur either because of mistakes in your code (e.g. type mismatch, divide by zero) or because of some condition of the environment (e.g. out of file handles, file sharing violations, memory low). These errors can be trapped in the error system and therefore do not necessarily terminate the application.

If the default error system is being used in the application then runtime errors are reported in the following format:

Error | Warning <subSystem>/<subCode> <message text>
              <filename> | <operation>

Overview on error recovery :

Error recovery failure

Clipper’s error system depends on communication taking place between the error handler and the subsystem that generates the error. The error handler communicates with the subsystem by returning a value indicating what the subsystem should attempt to do to recover from the error. The legal values that can be returned are determined by the values contained in the error object passed to the error handler for Error:canRetry, Error:canDefault, and Error:canSubstitute. If the error handler returns an invalid value to the subsystem (or returns to the subsystem at all when these values are all false), then an error recovery failure is reported and the application is terminated.

This exit condition always has the same format:

Error recovery failure, <operation> (<line number>)

User abort :

The user can abort your application by pressing Alt-C or Ctrl-Break at anytime during the execution of your application unless you have specifically disabled this feature. You can disable it with SETCANCEL (.F.) or SET(_SET_CANCEL, .F.).

This exit condition always has the same format:

Cancelled at: <operation> (<line number>)

Missing error handler :

If code is executed before any ERRORBLOCK() can be installed, an unrecoverable error will be generated that indicates that no error handler is present. This usually occurs if there is code in ErrorSys() before the ERRORBLOCK() function is called. All code should be moved after this line if possible.

No ERRORBLOCK() for error at: <operation>
    (<line number>)

Runtime recoverable error categories :

This section is a summary of runtime recoverable error messages that are possible when executing a Clipper application using the supplied subsystems. The messages are divided into categories according to subsystem. Each category is described below, followed by a listing of all messages in each category.

BASE Errors :

BASE error messages indicate errors generated by the Base system.

The general format of a BASE error message is as follows:

Error | Warning BASE/xxxx <message text> <filename> |
     <operation>
TERM Errors :

TERM error messages indicate errors generated by the Terminal subsystem. The general format of a TERM error message is as follows:

Error | Warning TERM/xxxx <message text> <filename> |
     <operation>
DBFNTX Errors :

DBFNTX error messages indicate that an error occurred during a database or index operation utilizing the DBFNTX database driver. The general format of a DBFNTX error message is as follows:

Error | Warning DBFNTX/xxxx <message text>
 <filename> | <operation>
DBFNDX Errors :

DBFNDX error messages indicate that an error occurred during a database or index operation utilizing the DBFNDX database driver. The general format of a DBFNDX error message is as follows:

Error | Warning DBFNDX/xxxx <message text>
      <filename> | <operation>
DBCMD Errors :

DBCMD error messages occur in the database command set and are unrelated to a particular driver. They occur as a result of command usage rather than from a failure of the driver itself.

Error | Warning DBCMD/xxxx <message text>
       <filename> | <operation>

Runtime unrecoverable errors :

Unrecoverable errors are runtime errors that for some reason cannot make use of the error system. Like runtime errors, it is normal for these errors to occur. This is usually because the system is unable to execute the error block. Almost all of these errors are therefore related to the environment (e.g. out of memory, errors reading code to execute from disk) and can be fixed by making a change to the environment.

Unrecoverable errors always have the same format:

<operation> (<line number>) Unrecoverable error xxxx:
     <message text>

DOS Error Messages

The following table provides a complete listing of DOS error numbers and their descriptions.

 Error
 Number Description
 ------ ------------------------------------------------
    1   Invalid function number
    2   File not found
    3   Path not found
    4   Too many open files (no handles left)
    5   Access denied
    6   Invalid handle
    7   Memory control blocks destroyed
    8   Insufficient memory
    9   Invalid memory block address
   10   Invalid environment
   11   Invalid format
   12   Invalid access code
   13   Invalid data
   14   Reserved
   15   Invalid drive was specified
   16   Attempt to remove the current directory
   17   Not same device
   18   No more files
   19   Attempt to write on write-protected diskette
   20   Unknown unit
   21   Drive not ready
   22   Unknown command
   23   Data error (CRC)
   24   Bad request structure length
   25   Seek error
   26   Unknown media type
   27   Sector not found
   28   Printer out of paper
   29   Write fault
   30   Read fault
   31   General failure
   32   Sharing violation
   33   Lock violation
   34   Invalid disk change
   35   FCB unavailable
   36   Sharing buffer overflow
  37-49 Reserved
   50   Network request not supported
   51   Remote computer not listening
   52   Duplicate name on network
   53   Network name not found
   54   Network busy
   55   Network device no longer exists
   56   Network BIOS command limit exceeded
   57   Network adapter hardware error
   58   Incorrect response from network
   59   Unexpected network error
   60   Incompatible remote adapter
   61   Print queue full
   62   Not enough space for print file
   63   Print file deleted (not enough space)
   64   Network name deleted
   65   Access denied
   66   Network device type incorrect
   67   Network name not found
   68   Network name limit exceeded
   69   Network BIOS session limit exceeded
   70   Temporarily paused
   71   Network request not accepted
   72   Print or disk redirection paused
  73-79 Reserved
   80   File already exists
   81   Reserved
   82   Cannot make directory entry
   83   Fail on INT 24H
   84   Too many redirections
   85   Duplicate redirection
   86   Invalid password
   87   Invalid parameter
   88   Network device fault

PlayDraw

Needless affairs !

Some 2 dimension drawing efforts on a totally 3 dimensioned world of today !

Experimentations on some HMG’s DRAW commands.

Everybody can play with any way like

Play and enjoy !

You can download source from here.

Order Management

What is Order Management ?

There are some primary elements in Order Management :

Order :

An Order is a set that has two elements in it :

– an Order Name, which is a logical name that can be referenced, and
– an Order Expression which supplies the view of the data.

The Order Name provides logical access to the expression and the Order Expression provides a way of viewing the underlying data source. Data ordering can also be modified to ascending or descending sequence.

Order Name :

An Order Name is a symbolic name, that you use to manipulate an Order, like a file’s alias. The difference between an Order Name and the Order Number with which you would normally access indexes (Orders), is that the Order Name is stored in the index file. It is available each time you run the program, and is maintained by the system.

The Order Number is generated each time the Order is added to an Order List and may change from one program execution to another. This makes Order Name the preferred means of referencing Orders.

Order Expression :

Is any valid Clipper expression. This is an index expression such as :

CUSTLIST->CUSTID

This expression produces the ordered view of the data. The values derived from this expression are sorted, and it is the relationship of these values to one another that provides the actual ordering.

Order Number :

An Order Number is provided by the Order List.

An Order Number is only valid as long as the work area to which it belongs is open.

– Order Numbers provide one of the services performed by Order Names, allowing you to access a specific Order. In general, you should avoid accessing Orders by number.

– The ORDNUMBER() function returns the ordinal position of the specified <orderName> within the specified <orderList>.

Order Bag :

Unsorted collection of Orders. Each Order contains two elements : Order Name and Order Expression. Each Order Bag may have zero to n Orders. The maximum is determined by the RDD driver being used.

Order Bags are similar to multiple-index files in that there’s no guarantee of any specific order within the container or Bag. Within an Order Bag you can access specific Orders by referencing a particular Order Name. Order Bags have persistence between activations of the program.

Order List :

An Order List orders the collection of Orders that are associated with and active in the current work area. It provides an access to the Orders active within a given work area. Each work area has an Order List, and there is only one Order List per work area.

An Order List is created when a new work area is opened, and exists only as long as that work area is active. Once you close a work area, the Order List ceases to exist.

When you SET INDEX TO, the contents of the Order Bag are emptied into the Order List. At this point, the Orders in the Order List are active in the work area, where they will be updated as the data associated with the work area is modified.

You may access an Order in the list by its Order Number or by its Order Name. You should access an Order by its name rather than a hard-coded ordinal position. You can make any Order in the Order List the controlling Order by giving it focus, as explained below.

Order List Focus :

Order List Focus is, essentially, a pointer to the Order that is used to change the view of the data. It is synonymous with controlling Order or controlling index, and defines the active index order.

The SET ORDER TO command does not modify the Order List in any way. It does not clear the active indexes. It only changes the Order List Focus (the controlling order in the Order List).

The following list contains specific information regarding Order Bag usage and limitations with DBFNDX and DBFNTX index files :

Single-Order Bags :

With DBFNDX and DBFNTX you can explicitly assign the Order Name within the Order creation syntax. You can then use the Order Name in any command or function that accepts an Order Name (Tag) as a parameter.

Single-Order Bag with INDEX ON : Single-Order Bags may retain the Order Name between activations. During creation, DBFNTX stores an optionally supplied Order Name in the file’s header for subsequent use. Therefore, the Order Name is not necessarily the same as that of the file. By contrast, DBFNDX cannot store an Order Name since this would prevent dBASE from accessing the file. By default DBFNDX Orders inherit the name of their index file.