How I have joined …

How I have joined HMG Family – A Story

It was in the year 1990, I had seen a computer, first time in my life.

I was a commerce student, studying in a 100+ years old school in my home town Sivakasi (famous for fireworks ), Tamilnadu, Southern part of India. I was studying +2 (12th and final year in school). Once, the exams were over, it was time to join a College. Not to waste the exam holidays, one of my friends had asked me to accompany him for a part time computer course in a near by Polytechnic College. I told him, “Ok”. We joined that course which was for six months, conducted on Saturdays and Sundays. I studied some basics about computers and languages like BASIC, Pascal, COBOL and an introduction to Lotus 1-2-3, Wordstar and dBase III Plus.

After that I had joined a college and my main subject was Commerce. After finishing my Under Graduation degree in the year 1993, I had joined Chartered Accountancy Course. And, once I had finished this course in the year 1996, I joined my brother to manage our family business.

Even though there was a computer in our office, I was not allowed to touch that.  The reason was, at that time, computers (AT 386) were costlier and one cannot take the risk of losing precious data and computers were operated only by computer professionals.

In the year 1997, I had purchased a computer on my own, and started implementing something which I had studied some seven years back.

I had studied under the DOS environment and I had got Windows ’95 in my new system as my operating environment. Even though it was easy to operate, I could not do any programming. I had so much of works before hand and I was involved in them for about 1 year. It was a Costing project in Excel with about 150 sheets, everything interlinked. It was a nice experience. I had done some macro programming in Excel for introducing thousand’s comma according to Indian tradition.

In the year 1998, the computer professional who had programmed for the accounting, invoice processing and payroll processing for our business firm had gone for a better job. We could not create new reports according to the requirements and we had to play only with the old options. Only at that time, I had realized about the importance of Database programming. Having the knowledge of DBase III+ programming, I had started to write small utilities to take self configured reports from the existing tables. I had the reference of the source codes for the existing software. It was done in Clipper Summer ’87 version.

There is a saying, ‘Necessity is the mother of invention’. Yes. Necessity had driven me to do more and more programming. Actually, I had started staring huge prg files with various do while .not. eof() loops and virtually indefinite nested if … endif conditions. Once understood, I had the confidence of creating bigger software too. So, in addition to the accounting software, I had created order processing and inventory maintenance and integrated the same with the existing project.

It was in the mid, 1998, I could get an internet connection with a dial up modem. In the mean time, I had a dream of using GUI in my programming. I had tried Visual Basic. I could not link with my existing dbf tables and dropped. In the beginning, I misunderstood about OOPS and GUI. Actually I had an allergy over this OOPS, I don’t know why even now.  So, I had abandoned my dream  of GUI because of OOPS.

In the year 1999, while searching the net, I had come across a site called Oasis, a site which had shared source code/libraries for Clipper and many utilities. From that site, I had got an excellent library called Super. It had some assembly language routines too to capture mouse gestures and enabled Clipper code to be ‘RAT’ified. Apart from this mouse functionality, the library had so many small utilities like sorting multiple arrays, finding out Day of the week like that. Actually I had realized about the advantages of Open Source on seeing that library source codes. I had studied in depth, and I had known about optimization of codes, effective memory management, different routes to a same destination in programming etc.

With the SuperLib, I had changed all my projects to be mouse enabled, there by satisfying half of my dreams. However, I could not stop dreaming about creating a full fledged Windows program eliminating the dark DOS command box. As new versions of Windows came in the market, I was afraid that, one day there won’t be this DOS Command Box and all my programs would not be useful at all.

I was, why was? even now am, very fond of Open Source. In my system I don’t use any copyright protected software except the Windows Operating System. I would list all the open source software I use in a separate thread for all my requirements.

I had found out Harbour from the Oasis site I had already mentioned. I had tested a lot but could not use it because of SuperLib, as it could not be linked with Harbour.

I was in a confused state. I liked Harbour a lot because of its open nature. I could not abandon SuperLib too, since all of my projects were linked with this library. So, I was desperately searching for a GUI library (at least mouse enabled for Harbour).

On one fine day in my life, my search was fruitful.

I could even remember the date. It was 4th of October, 2002, I had downloaded Harbour MiniGUI and could create a window with my limited xBase programming knowledge. Hurray! I could not describe my feelings in words!

Immediately on compiling my hello world prg, I had commented in the yahoo group from where I had downloaded the 0.40 version of the library with the following words,

Dear Roberto,

It is wonderful to use your Harbour Minigui Library, an open
source library for Harbour.

Thank you very much for your sincere efforts.

I would like to make a small suggestion regarding text boxes.

Shall you please make provision for right aligning the text boxes
for numeric fields?

Thank you once again.


From that day, Harbour MiniGUI page was literally my home page.  I read all the messages from various users of the library. It helped me a lot to understand programming, inside out of the library, even about calling C API.

I had happily started to convert all of my projects to HMG. As the product matured, all my projects were fully converted to HMG and I had seen my dream come true.

Most of my programs are used in-house. However, I had created software for many of my friends. I had earned some money from my programs too. As you know, my income is not based on programming, since basically I am a Chartered Accountant and managing business.

I wish to list some of my major HMG projects,

  1. PyroERP (an ERP software for manufacturing companies with accounting, inventory, order processing,payroll)
  2. PyroBase (a License maintenance software given to Explosives department, Government of India)
  3. FACE (Financial Accounting under Computer Environment)
  4. Interest(ing) Calculator (Calculate interesting part of interest for loans and deposits) – An open source product, hosted in
  5. FBT Reference (Fringe Benefit Tax referencer)
  6. Phataka (A Cultural Event maintenance software used by local Rotary Club)
  7. DBU
  8. GSM Calculator

There are many tiny projects too. I am not listing them to show my talents. It shows the simplicity of HMG and my craze on HMG.

It is my nature, to escape from any politics. I wish to be good for all. During July 2005, when Roberto had decided to move on MingW and introduced HMG, MiniGUI Extended product had also born. I didn’t tell a word about anything in the group. I believe in Karma and thought, “if this happens, this is also for the good”.

I had actively participated in the yahoo group up to April, 2006 and kept silence after that.

However, I used HMG in a full fledged manner and I liked MingW version as Roberto distributed them. This is mainly because of the single installation of the whole thing (ie., MiniGUI library + Harbour Compiler + C Compiler) and full utilization of Open Source Software.

All the software projects listed above where developed by me during the period from 2004 till now. I too had contributed some parts like drawing pie graph, alternative syntax (with the valuable guidance of Roberto) (now Roberto had revamped in a better way!), Grid2Print which Roberto has accepted kindly.

Can you believe that this forum had been created in just 2 days time? On 29th of July 2008, I had asked Roberto, breaking my long silence, by an email about the need for a forum exclusively for HMG. Roberto also liked and generously accepted to guide and participate in the forum. On that day itself, I registered this domain and installed PHPBB forum software and on 1st of August 2008, the forum was officially opened.

I am so HAPPY and PROUD to be part of the HMG family.

Thus, this story has a happy ending.

Sri Rathinagiri
Sivakasi, India


Courtesy of author, this article borrowed from here.



Execute one of several alternative blocks of statements


       IF <lCondition1>
       [ELSEIF <lCondition2>]


<lCondition> is a logical control expression. If it evaluates to true (.T.), all following statements are executed until an ELSEIF, ELSE, or ENDIF is encountered.

ELSEIF <lCondition> identifies statements to execute if the associated condition evaluates to true (.T.) and all preceding IF or ELSEIF conditions evaluate to false (.F.). Any number of ELSEIF statements can be specified within the same IF…ENDIF control structure.

ELSE identifies statements to execute if the IF and all preceding ELSEIF conditions evaluate to false (.F.).


The IF control structure works by branching execution to statements following the first true (.T.) evaluation of the IF or any ELSEIF condition. Execution then continues until the next ELSEIF, ELSE, or ENDIF is encountered whereupon execution branches to the first statement following the ENDIF.

If no condition evaluates to true (.T.), control passes to the first statement following the ELSE statement. If an ELSE statement is not specified, control branches to the first statement following the ENDIF statement.

IF…ENDIF structures may be nested within IF…ENDIF structures and other control structure commands. These structures, however, must be nested properly. The IF…ELSEIF…ENDIF form of the IF construct is identical to DO CASE…ENDCASE. There is no specific advantage of one syntax over the other.

The IF construct is also similar to the IF() function which can be used within expressions.


       .  This example evaluates a number of conditions using an
       IF...ELSEIF...ENDIF construct:
       LOCAL nNumber := 0
       IF nNumber < 50
          ? "Less than 50"
       ELSEIF nNumber = 50
          ? "Is equal to 50"
          ? "Greater than 50"





Execute a block of statements a specified number of times


       FOR <idCounter> := <nStart> TO <nEnd> [STEP <nIncrement>]


<idCounter> is the name of the loop control or counter variable. If the specified <idCounter> is not visible or does not exist, a private variable is created.

<nStart> is the initial value assigned to <idCounter>. If <nIncrement> is negative, <nStart> must be less than <nEnd>.

TO <nEnd> defines the final value of <idCounter>. If <nIncrement> is negative, <nStart> must be greater than <nEnd>; otherwise, <nStart> must be less than <nEnd>.

STEP <nIncrement> defines the amount <idCounter> is changed for each iteration of the loop. <nIncrement> can be either positive or negative. If the STEP clause is not specified, <idCounter> is incremented by one for each iteration of the loop.

EXIT unconditionally branches control from within a FOR…NEXT construct to the statement immediately following the nearest NEXT statement.

LOOP branches control to the most recently executed FOR or DO WHILE statement.


FOR…NEXT is a control structure that executes a block of statements a specified number of times. The control structure loops from the initial value of <idCounter> to the boundary specified by <nEnd>, moving through the range of values of the control variable for an increment specified by <nIncrement>. All expressions in the FOR statement are reevaluated for each iteration of the loop. The <nStart> and <nEnd> expressions, therefore, can be changed as the control structure operates.

A FOR loop operates until <idCounter> is greater than <nEnd> or an EXIT statement is encountered. Control then branches to the statement following the corresponding NEXT statement. If a LOOP statement is encountered, control branches back to the current FOR statement.

If <nIncrement> is a negative value, <idCounter> is decremented rather than incremented. The FOR loop, however, continues until <idCounter> is less than <nEnd>. This means that <nEnd> must be less than <nStart> when the FOR loop begins.

FOR loops are useful for traversing arrays where <idCounter> is used as the array subscript. See the example below.

FOR…NEXT constructs may be nested within any other control structures to any depth. The only requirement is that each control structure is properly nested.


       .  This example traverses an array in ascending order:
       nLenArray := LEN(aArray)
       FOR i := 1 TO nLenArray
       .  To traverse an array in descending order:
       nLenArray := LEN(aArray)
       FOR i := nLenArray TO 1 STEP -1



Array Basics

    An array is a distinct data type which may contains multiple data items under same name.  Data items stored in an array referred as an “element” and can be any data type. An individual element of array referenced by array name and position number of element as an integer in array, called “index” or “subscript”.

 Defining / Building:

    An array is a variable and like all variables has “scope”; arrays can be defined PRIVATE, PUBLIC and LOCAL as well as STATIC.

   Building an array is quite simple: for example to define an array named “aColors” with 5 elements we can use a statement like this:

LOCAL aColors[ 5 ]


LOCAL aColors := ARRAY( 5 )


LOCAL  aColors := {  , , , , }

Results of these three methods are exactly same; we can inspect easily:

? ValType( aColors )      // A

? LEN( aColors )          // 5
? HB_ValToExp( aColors )  // {NIL, NIL, NIL, NIL, NIL}

NIL is a special data type with meaning “not defined”.

We can define an array with initial value(s)

aColors := {  “green”, ”yellow” ,  “red”,  “black”, “white” }

or assign values after defined:

aColors[ 1 ] := ”green”
aColors[ 2 ] := ”yellow”
aColors[ 3 ] := ”red”
aColors[ 4 ] := ”black”
aColors[ 5 ] := ”white”
? HB_ValToExp( aColors ) // {"green", "yellow", "red", "black", "white"}


As seen in our first array statement

LOCAL aColors[ 5 ]

used a special sign square brackets as “Array element indicator” ( used also as string delimiter ).

As cited at the beginning, an individual element of array is referenced by array name and position number of element as an integer (enclosed by square brackets) in array, called “index” or “subscript”, and this notation called “subscripting”.

Note that subscribing begins with one.

To specify more than one subscript ( i.e. when using multi-dimensional arrays), you can either enclose each subscript in a separate set of square brackets, or separate the subscripts with commas and enclose the list in square brackets. For example, if aArray is a two dimensional array, the following statements both addresses the second column element of tenth row:

aArray[ 10 ][ 2]
aArray[ 10, 2]

Of course it’s illegal to address an element that is outside of the boundaries of the array (lesser than one or greater than array size / length; one is also illegal for an empty array). Attempting to do so will result in a runtime error.

When making reference to an array element using a subscript, you are actually applying the subscript operator ([]) to an array expression, not only an array identifier (array variable name). An array expression is any valid expression that evaluate to an array. This includes function calls, variable references, subscripting operations, or any other expression that evaluate to an array. For example, the following are all valid:

 { “a”, “b”, “c” }[ 2 ]

x[ 2 ]
ARRAY(3)[ 2 ]
&(<macro expression>)[ 2 ]
(<complex expression>)[ 2 ]

Syntax :

 <aArrayName> [ <nSubscript> ]

<nSubscript> is integer value and indicate sequence number of element into this array.

With this way we can also traverse an array:

FOR nColor := 1 TO LEN( aColors )
     ? aColors[ nColor ]
NEXT nColor

In fact, with the other FOR loop, traversing an array doesn’t require subscripting:

cColor := “” // FOR EACH loop require a predefined loop element

FOR EACH cColor IN aColors
  ? cColor
NEXT nColor

Elements of an array may be different data type; thus arrays called as “ragged” arrays in Clipper language.

aRagged := { 1, "One", DATE(),  .T. } 
FOR nIndex := 1 TO LEN( aRagged )
   x1Elem := aRagged[ nIndex  ]
   ? VALTYPE( x1Elem ), x1Elem
NEXT nIndex

A build-in array function AEVAL() can be use instead of a loop :

AEVAL( aRagged, { | x1 | QOUT(x1 ) } )


Adding one element to end of an array:

The architect of array is quite versatile. Array may change ( in size and element values ) dynamically at run time.

 AADD() function can be used for add a new element to the end of an array

AADD(<aTarget>, <expValue>) --> Value

<aTarget> is the array to which a new element is to be added.

<expValue> is the value assigned to the new element.

AADD() is an array function that increases the actual length of the target array by one.  The newly created array element is assigned the value specified by <expValue>.

AADD() is used to dynamically grow an array.  It is useful for building dynamic lists or queues.

For example an array may build empty and later add element(s) to it :

aColors := {}
AADD( aColors, ”green” )
AADD( aColors, ”yellow” )
AADD( aColors, ”red” )
AADD( aColors, ”black” )
AADD( aColors, ”white” )
? HB_ValToExp( aColors ) // {"green", "yellow", "red", "black", "white"}
Inserting one element to an array:

AINS() function can be use for insert a NIL element into an array

AINS (<aTarget>, <nPosition>) --> aTarget

<aTarget> is the array into which a new element will be inserted.

<nPosition> is the position at which the new element will be  inserted.

AINS() is an array function that inserts a new element into a specified array.  The newly inserted element is NIL data type until a new value is assigned to it.  After the insertion, the last element in the array is discarded, and all elements after the new element are shifted down one position.

For a lossless AINS() ( HL_AINS() ) look at attached .prg.

Deleting one element from an array:

ADEL(<aTarget>, <nPosition>) --> aTarget

ADEL() is an array function that deletes an element from an array.  The content of the specified array element is lost, and all elements from that position to the end of the array are shifted up one element.  The last element in the array becomes NIL. So, ADEL() function doesn’t change size of array.

For an another ADEL()  ( HL_ADEL() ) look at attached .prg.


 ASIZE() function can be use for grow or shrink, that is changing size of an array.

ASIZE( <aTarget>, <nLength>) --> aTarget

<aTarget> is the array to grow or shrink.

<nLength> is the new size of the array.


     ASIZE() is an array function that changes the actual length of the   <aTarget> array.  The array is shortened or lengthened to match the specified length.  If the array is shortened, elements at the end of the array are lost.  If the array is lengthened, new elements are added to the end of the array and assigned NIL.

     ASIZE() is similar to AADD() which adds a single new element to the end   of an array and optionally assigns a new value at the same time.  Note that ASIZE() is different from AINS() and ADEL(), which do not actually      change the array’s length.

Assigning a fixed value to all elements of an array:

Changing values of all element of an array can not be accomplish by a simple assign statement. For example:

aTest := ARRAY( 3 )
aTest := 1

change type of aTest from array to numeric with a value 1.

Instead, AFILL() function gives a short way to fill an array with a fixed value.

AFILL() :  Fill an array with a specified value

Syntax :

AFILL(<aTarget>, <expValue>,
    [<nStart>], [<nCount>]) --> aTarget
    <aTarget> is the array to be filled.

<expValue> is the value to be placed in each array element.  It can be an expression of any valid data type.

<nStart> is the position of the first element to be filled.  If this argument is omitted, the default value is one.

<nCount> is the number of elements to be filled starting with  element <nStart>.  If this argument is omitted, elements are filled from the starting element position to the end of the array.

Code evaluation on an array:


Execute a code block for each element in an array


AEVAL(<aArray>, <bBlock>,
    [<nStart>], [<nCount>]) --> aArray


<aArray> is the array to traverse.

<bBlock> is a code block to execute for each element encountered.

<nStart> is the starting element.  If not specified, the default is  element one.

<nCount> is the number of elements to process from <nStart>.  If not specified, the default is all elements to the end of the array.


     AEVAL() returns a reference to <aArray>.


AEVAL() is an array function that evaluates a code block once for each element of an array, passing the element value and the element index as  block parameters.  The return value of the block is ignored.  All      elements in <aArray> are processed unless either the <nStart> or the  <nCount> argument is specified.

AEVAL() makes no assumptions about the contents of the array elements it is passing to the block.  It is assumed that the supplied block knows  what type of data will be in each element.

AEVAL() is similar to DBEVAL() which applies a block to each record of a  database file.  Like DBEVAL(), AEVAL() can be used as a primitive for  the construction of iteration commands for both simple and complex array  structures.

Refer to the Code Blocks section in the “Basic Concepts” chapter of the   Programming and Utilities Guide for more information on the theory and syntax of code blocks.

Examples :

This example uses AEVAL() to display an array of file names  and file sizes returned from the DIRECTORY() function:

#include “”


LOCAL aFiles := DIRECTORY(“*.dbf”), nTotal := 0

AEVAL(aFiles, { | aDbfFile |;
     QOUT( PADR(aDbfFile[F_NAME], 10), aDbfFile[F_SIZE]),;
     nTotal += aDbfFile[F_SIZE])} )
? "Total Bytes:", nTotal

This example uses AEVAL() to build a list consisting of  selected items from a multi-dimensional array:

#include ""
LOCAL aFiles := DIRECTORY("*.dbf"), aNames := {}
AEVAL(aFiles, { | file | AADD(aNames, file[F_NAME]) } )

This example changes the contents of the array element depending on a condition.  Notice the use of the codeblock  parameters:


LOCAL aArray[6]
{|cValue,nIndex| IF( cValue == "old",;
aArray[nIndex] := "new",)})

Searching a value into an array :

ASCAN() : Scan an array for a value or until a block returns true (.T.)


ASCAN(<aTarget>, <expSearch>,
       [<nStart>], [<nCount>]) --> nStoppedAt


<aTarget> is the array to be scanned.

<expSearch> is either a simple value to scan for, or a code block.   If <expSearch> is a simple value it can be character, date, logical, or  numeric type.

<nStart> is the starting element of the scan.  If this argument is not specified, the default starting position is one.

<nCount> is the number of elements to scan from the starting  position.  If this argument is not specified, all elements from the  starting element to the end of the array are scanned.


ASCAN() returns a numeric value representing the array position of the last element scanned.  If <expSearch> is a simple value, ASCAN() returns the position of the first matching element, or zero if a match is not  found.  If <expSearch> is a code block, ASCAN() returns the position of the element where the block returned true (.T.).


ASCAN() is an array function that scans an array for a specified value and operates like SEEK when searching for a simple value.  The  <expSearch> value is compared to the target array element beginning with  the leftmost character in the target element and proceeding until there are no more characters left in <expSearch>.  If there is no match,  ASCAN() proceeds to the next element in the array.

Since ASCAN() uses the equal operator (=) for comparisons, it is  sensitive to the status of EXACT.  If EXACT is ON, the target array  element must be exactly equal to the result of <expSearch> to match.

If the <expSearch> argument is a code block, ASCAN() scans the <aTarget>   array executing the block for each element accessed.  As each element is encountered, ASCAN() passes the element’s value as an argument to the code block, and then performs an EVAL() on the block.  The scanning operation stops when the code block returns true (.T.), or ASCAN()  reaches the last element in the array.


This example demonstrates scanning a three-element array using simple values and a code block as search criteria.  The code block criteria show how to perform a case-insensitive search:

aArray := { "Tom", "Mary", "Sue" }
? ASCAN(aArray, "Mary")               // Result: 2
? ASCAN(aArray, "mary")               // Result: 0
? ASCAN(aArray, { |x| UPPER(x) == "MARY" }) // Result: 2

This example demonstrates scanning for multiple instances of a search argument after a match is found:

LOCAL aArray := { "Tom", "Mary", "Sue","Mary" },; 
      nStart := 1
// Get last array element position
nAtEnd := LEN(aArray)
DO WHILE (nPos := ASCAN(aArray, "Mary", nStart)) > 0
   ? nPos, aArray[nPos]
   // Get new starting position and test
   // boundary condition
   IF (nStart := ++nPos) > nAtEnd

This example scans a two-dimensional array using a code block.  Note that the parameter aVal in the code block is an array:

LOCAL aArr:={}
? ASCAN(aArr, {|aVal| aVal[2] == "four"})         // Returns 2

Sorting an array:

ASORT() :  Sort an array


ASORT(<aTarget>, [<nStart>],
[<nCount>], [<bOrder>]) --> aTarget


<aTarget> is the array to be sorted.

<nStart> is the first element of the sort.  If not specified, the default starting position is one.

<nCount> is the number of elements to be sorted.  If not specified, all elements in the array beginning with the starting element are sorted.

<bOrder> is an optional code block used to determine sorting order.  If not specified, the default order is ascending.


ASORT() returns a reference to the <aTarget> array.


 ASORT() is an array function that sorts all or part of an array  containing elements of a single data type.  Data types that can be  sorted include character, date, logical, and numeric.

If the <bOrder> argument is not specified, the default order is ascending.  Elements with low values are sorted toward the top of the  array (first element), while elements with high values are sorted toward the bottom of the array (last element).

If the <bOrder> block argument is specified, it is used to determine the sorting order.  Each time the block is evaluated; two elements from the target array are passed as block parameters.  The block must return true     (.T.) if the elements are in sorted order.  This facility can be used to create a descending or dictionary order sort.  See the examples below.

When sorted, character strings are ordered in ASCII sequence; logical values are sorted with false (.F.) as the low value; date values are sorted chronologically; and numeric values are sorted by magnitude.


ASORT() is only guaranteed to produce sorted output (as defined by the block), not to preserve any existing natural order in the process.

Because multidimensional arrays are implemented by nesting sub-arrays within other arrays, ASORT() will not directly sort  a multidimensional array.  To sort a nested array, you must supply a code block which properly handles the sub-arrays.


This example creates an array of five unsorted elements, sorts the array in ascending order, then sorts the array in descending  order using a code block:

aArray := { 3, 5, 1, 2, 4 }
// Result: { 1, 2, 3, 4, 5 }
ASORT(aArray,,, { |x, y| x > y })
// Result: { 5, 4, 3, 2, 1 }

This example sorts an array of character strings in ascending order, independent of case.  It does this by using a code block that  converts the elements to uppercase before they are compared:

aArray := { "Fred", Kate", "ALVIN", "friend" }
ASORT(aArray,,, { |x, y| UPPER(x) < UPPER(y) })

This example sorts a nested array using the second element of each sub-array:

aKids := { {"Mary", 14}, {"Joe", 23}, {"Art", 16} }
aSortKids := ASORT(aKids,,, { |x, y| x[2] < y[2] })


{ {“Mary”, 14}, {“Art”, 16}, {“Joe”, 23} }

Last element in an array:

ATAIL() : Return the highest numbered element of an array


      ATAIL(<aArray>) --> Element


<aArray> is the array.


ATAIL() returns either a value or a reference to an array or object.  The array is not changed.


ATAIL() is an array function that returns the highest numbered element  of an array.  It can be used in applications as shorthand for <aArray>[LEN(<aArray>)] when you need to obtain the last element of an      array.


The following example creates a literal array and returns that last element of the array:

     aArray := {"a", "b", "c", "d"}
     ? ATAIL(aArray)                     // Result: d

Getting directory info:

ADIR() is a array function to obtain directory information. But it’s  a compatibility function and therefore not recommended. It is superseded by the DIRECTORY() function which returns all file information in a multidimensional array.

A sample .prg : ArrayBasics.prg 



Using code blocks, again

Using code blocks again (.pdf)

Simple Clipper Extensions

Simple extensions

Exact comparisons,
Name precedence,
Alias functions,
Call by reference and value,
Other Clipper extensions

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

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:

   program code ...
   program code ...
   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
   @ 0,0
   @ 0,0 SAY 'Pro- c ' + M->name +;
             ' line ' + LTRIM(STR(M->line)) +;
             ', ' + M->info

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:


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


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.


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


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


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:


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

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

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:


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


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


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
   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', ' + M->info

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:


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.


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.
    @ 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)'
    IF .NOT. CHR( LASTKEY() ) $ 'Yy'
    @ 0,0

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
   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', printer not ready'
   @ 0,65 SAY 'Retry ? (Y/N)'
   IF .NOT. CHR( LASTKEY() ) $ 'Yy'
   @ 0,0

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
   @ 0,0
   @ 0,0 SAY 'Proc ' + M->name + ' line ' + ;
              LTRIM(STR(M->line)) + ', ' + M->info + ' ' + M->_1

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


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


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


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:


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.


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.


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.


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.


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

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.


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


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


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.


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


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.


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.


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.


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


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.


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


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


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


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


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.


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


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.


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.


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


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


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


Clipper has found an IF without a terminating ENDIF.


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.


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

C5 Flow Control Commands, Statements and Funtions

Commands :


Execute a C or Assembler procedure

CALL <idProcedure> [WITH <exp list>]


Terminate program processing


DO* :

Call a procedure

DO <idProcedure> [WITH <argument list>]


Terminate program processing



Execute a DOS command or program

RUN | !* <xcCommandLine>


Assign a procedure invocation to a key

SET KEY <nInkeyCode> TO [<idProcedure>]


Compile procedures/functions into the current .OBJ file

SET PROCEDURE TO [<idProgramFile>[.<ext>]]


Suspend program processing until a key is pressed

WAIT [<expPrompt>] [TO <idVar>]

Statements :


Declare a module identifier

ANNOUNCE <idModule>


Define a sequence of statements for a BREAK

    [BREAK [<exp>]]
    [RECOVER [USING <idVar>]]


Execute one of several alternative blocks of statements

   CASE <lCondition1>
   [CASE <lCondition2>]


Execute a loop while a condition is true (.T.)

[DO] WHILE <lCondition>


Declare an exit procedure

EXIT PROCEDURE <idProcedure>
    [FIELD <idField list> [IN <idAlias>]]
    [LOCAL <identifier> [[:= <initializer>]]]
    [MEMVAR <identifer list>]
    . <executable statements>


Declare a list of procedure or user-defined function names

EXTERNAL <idProcedure list>


Execute a block of statements a specified number of times

FOR <idCounter> := <nStart> TO <nEnd> [STEP <nIncrement>]


Declare a user-defined function name and formal parameters

[STATIC] FUNCTION <idFunction>[(<idParam list>)]
    [LOCAL <identifier> [[:= <initializer>], ... ]]
    [STATIC <identifier> [[:= <initializer>], ... ]]
    [FIELD <identifier list> [IN <idAlias>]]
    [MEMVAR <identifier list>]
    . <executable statements>
RETURN <exp>

IF :

Execute one of several alternative blocks of statements

IF <lCondition1>
[ELSEIF <lCondition2>]


Declare an initialization procedure

INIT PROCEDURE <idProcedure> [(<idParam list>)]
    [FIELD <idField list> [IN <idAlias>]]
    [LOCAL <identifier> [[:= <initializer>]]]
    [MEMVAR <identifer list>]
    . <executable statements>


Place a single-line comment in a program file

NOTE This is a comment line


Create private parameter variables

PARAMETERS <idPrivate list>


Declare a procedure name and formal parameters

[STATIC] PROCEDURE <idProcedure> [(<idParam list>)]
    [FIELD <idField list> [IN <idAlias>]
    [LOCAL <identifier> [[:= <initializer>], ... ]]
    [MEMVAR <identifier list>]
    [STATIC <identifier> [[:= <initializer>], ... ]]
    . <executable statements>


Declare a module request list

REQUEST <idModule list>


Terminate a procedure, user-defined function or program

RETURN [<exp>]

Functions :


Branch out of a BEGIN SEQUENCE…END construct

BREAK(<exp>) --> NIL

EVAL() :

Evaluate a code block

EVAL(<bBlock>, [<BlockArg list>]) --> LastBlockValue

IF() :

Return the result of an expression based on a condition

[I]IF(<lCondition>, <expTrue>, <expFalse>) --> Value


Determine the position of the last actual parameter passed

PCOUNT() --> nLastArgumentPos


Assign an action block to a key

SETKEY(<nInkeyCode>, [<bAction>]) --> bCurrentAction


Toggle Alt-C and Ctrl-Break as program termination keys

SETCANCEL([<lToggle>]) --> lCurrentSetting

WORD()* :

Convert CALL command numeric parameters from double to int

WORD(<nNumber>) --> NIL