Clipper Tutorial: a Guide to Open Source Clipper(s)/Basic Language Tutorial
xBase/Clipper Tutorial
[edit | edit source]Please note: if anybody will contribute, the Clipper tutorial should contain only 'standard' code that would work on every Clipper compatible compiler. Compiler-specific code should be placed somewhere else, or clearly indicated in a box.
The code here should be of simple console-mode applications using only the simplest forms of input/output and focused only on algorithms, since the methods for creating and managing user interfaces will be described in Chapter 5, Making Up a User Interface.
The helloworld application line by line
[edit | edit source]Let's try to highlight the parts of helloworld:
function MAIN
* This is an example
clear
?"Hello, the weather is fine today"
return nil
(in GeoCities this and all other sources were highlighted by the on-line service CodeColorizer at http://www.chami.com/colorizer/, or, alternatively by the service at http://tohtml.com/Clipper/ - they tend to look a bit better than the Wikibook highlighting).
We will comment on each line of the helloworld program.
function MAIN
The first line defines a function named MAIN. Defining such a function is not compulsory with Harbour, as if it is missed errors would occur during compilation, but I will keep it to make sure the examples work with all compilers. A function cannot be defined when typing in commands at the dot prompt (hbrun/xbscript).
Every xBase language is case insensitive, which means that all the following lines are the same:
function MAIN FUNCTION main FuNcTiOn mAiN
Of course, this feature is beneficial only if you use it to improve the code readability.
We will learn later how to define and use functions and procedures.
* This is an example
The second line is a comment. Commenting your programs will help you when you are to modify them later. If they aren't commenting, modifying them will be a very hard task. It will be much more difficult if you are to modify programs written by others if they didn't comment them: figuring out what a program does using only its code is very difficult and even the cleanest programming language may prove to be write-only if not properly commented.
You can write comments in many different styles: using an asterisk (*), two sweeps (//), a double ampersand (&&) or a couple sweep-asterisk (/*) and asterisk-sweep (*/), as shown below:
* This is a comment... // ...and so is this... && ...and this. /* This is an example of the fourth commenting style, which may span over several lines.*/
The second and the fourth commenting styles are derived from the C programming language, the first and the third are peculiar of the Clipper/xBase standard. None of these comments are accepted by xbscript, but hbrun (Harbour) accepts the last three.
clear
No, the purpose of this command is not to make the source code clear (that would be too easy! It is up to us to write clear source code, and to do so we must comment it well), but instead to clean the screen. :-) You could also use clear screen or cls, although these two commands are not exactly the same of this clear (the difference will be clear later when we can GET the point - then, you could also appreciate the pun in this paragraph - but will more likely not).
What are commands?
Harbour commands are defined via the #command macro directive (http://harbour.edu.pl/clipper/en/ng122d3c.html). The first commands we encountered are defined in the file std.ch by these lines:
#command CLS => Scroll() ; SetPos(0,0) #command ? [<explist,...>] => QOut( <explist> )
? "Hello, the weather is fine today"
The ? is a command which means print. In the BASIC programming language, it is also an abbreviation for its print command (the syntax of xBase is quite similar to that of the BASIC programming language).
In this case ? print the string "Hello, the weather is fine today". Please note that a string can be enclosed in single quotes (or apostrophes), double quotes or square brackets: all of the following are the same:
? "Hello, the weather is fine today" ? 'Hello, the weather is fine today' ? [Hello, the weather is fine today]
The last line:
return nil
Return is used to mark the end of the function. We will explain later what the return exactly does.
Today Harbour needs not an explicitly defined main function/procedure. The following version of helloworld will compile and is exactly the same as the previous one:
&& hello world without commands and main
Scroll() ; SetPos(0,0)
QOut( "Hello world" )
The Harbour Interpreter and Virtual Machine
The program above, stored in a text file named hello.prg, can be run with the command
hbrun hello.prg
hbrun is an interpreter for Harbour. This means that if we run it, we get to a screen with a "Dot prompt" on its last line, where we can enter and run instructions. For example, entering
? "Hello world"
will get Hello world printed on the screen, but only after the interpreter hbrun itself is closed. To quit the interpreter and get the greeting printed type
QUIT
All in all, hbrun permits to feel how handling databases was done in the old dBase "Dot prompt" mode - that is, like this: http://manmrk.net/tutorials/database/dbase/IntrodBASEIIIPlus.htm
Running the compiler harbour (harbour.exe on Windows) on a printA.prg containing the line
? "A"
will output a printA.c file. The bottom of this file reads:
HB_FUNC( PRINTA )
{
static const HB_BYTE pcode[] =
{
36,1,0,176,1,0,106,2,65,0,20,1,7
};
hb_vmExecute( pcode, symbols );
}
that is, as an intermediate step the Harbour compiler compiles pcode, or bytecode for a virtual machine.
If the compilation is done with the /gh option
hbmk2 /gh printA.prg harbour /gh printA.prg
the result will be a file named printA.hrb, a "Harbour Portable Object" file which can be executed by
hbrun printA.hrb
In this way, hbmk2 and hbrun work in couple as a perfectly equivalent tools to the Java compiler javac and java interpreter java.
As a parenthesis, virtual machines are not a new concept. If we look into the retrocomputing field, we'll see that in the late 1970s and early 1980s the University of California San Diego has a portable operating system based on pcode, the UCSD P-System which was written in UCSD Pascal and could be run on 6502, 8080, Z-80, and on PDP-11 http://www.threedee.com/jcm/psystem/index.html (it actually was a competitor of MS-DOS, PC DOS and CP/M - http://www.digitalresearch.biz/CPM.HTM and http://www.seasip.info/Cpm/index.html).
Also, in 1979 Joel Berez and Marc Blank developed the Z-machine as a virtual machine to run Infocom's text adventure games, the name not hiding they were mainly thinking about Zork. The programming language for the Z-machine was called ZIL (Zork Implementation Language). A modern compiler for ZIL to Microsoft .NET is ZILF https://bitbucket.org/jmcgrew/zilf/wiki/Home and a Z-machine.NET is available https://zmachine.codeplex.com/. More information at http://inform-fiction.org/zmachine/index.html.
Apart from the JVM (Java Virtual Machine), the Microsoft .NET CLR (Common Language Runtime), is another modern virtual machine.
hbmk2 was created to support all shells, all compilers on all platforms, also to replace old 'bld.bat' solution, while staying compatible with existing hbmk script features and options.
Pieces of information on how to get an executable without using hbmk2 can be found at http://www.kresin.ru/en/hrbfaq.html.
Remember that the PATH Environment Variable (in Windows) must be updated to include the directory containing the compiler: use the command SET PATH=C:\hb32\bin;%PATH% locally in a Prompt Window or update it globally: "Start" → "Control Panel" → "System" → "Advanced system settings" → "Environment Variables..." → select "Path", then click "Edit..."
Data Types in General (and Their Operators)
[edit | edit source]The list of types supported by Clipper is as follows:
- A Array
- B (Code) Block
- C Character
- D Date
- L Logical
- M Memo
- N Numeric
- O Object
- U NIL
These letters can be used as a prefix to a variable name, to indicate "at first sight" what kind of item it is. This way oBox would be the name of an object, aNames an array, dBirthday a date and so on. An alternative for logical variables is to add an is prefix as in isOk or isExisting (somewhat more readable than lOk and lExisting). This naming convention is called the Hungarian notation and applies to functions as well, and to their synopsis: ACOPY( <aSource>, <aTarget>, [<nStart>], [<nCount>], [<nTargetPos>] ) is the function to copy elements from an array to another, which takes two arrays aSource and aTarget, and optionally three numeric parameters nStart, nCount, nTargetPos; CToD(cDate) takes a character argument and converts it to a date, DBCreate() is a function to create a database.
The type Memo can only be used in a database (it is a link to a subsidiary file to a DBF table, as described in the Wikipedia DBF entry).
This is the list of results returned by the function VALTYPE(). The function TYPE() returns also:
- U NIL, local, or static
- UE Error syntactical
- UI Error indeterminate
Harbour has a longer list (http://www.fivetechsoft.com/harbour-docs/harbour.html):
- HB_ISARRAY
- HB_ISBLOCK
- HB_ISCHAR
- HB_ISDATE
- HB_ISDATETIME
- HB_ISHASH
- HB_ISLOGICAL
- HB_ISMEMO
- HB_ISNIL
- HB_ISNULL
- HB_ISNUMERIC
- HB_ISOBJECT
- HB_ISPOINTER
- HB_ISPRINTER
- HB_ISREGEX
- HB_ISSTRING
- HB_ISSYMBOL
- HB_ISTIMESTAMP
ISPOINTER() (http://www.marinas-gui.org/projects/harbour_manual/ispointer.htm) for example is marked: Not available in CA-Cl*pper. Together with HB_IT_POINTER and HB_ISPOINTER it is used in Harbour's internals (the C-level API: http://www.fivetechsoft.com/harbour-docs/clevelapi.html#ispointer).
Let us see a little program that shows the use of the most common data types.
I wanted to show, in this example, a computation of π instead of that of √2, but xBase misses the ArcTan function. We may solve this problem by importing it from an external library, or by supplying it ourselves. (Both ways should be pursued in this tutorial).
The last two data types are a bit different from the preceding: "Memo" is not very useful when used outside of a database, and the arrays cannot be used in databases.
&& example of compilation command
&& A:\>c:\hb32\bin\hbmk2 -quiet -oc:\hbcode\test.exe test.prg -run
&& please note that this example has not a MAIN function
&& an example of string concatenation
? "Hello" + " " + "World!"
&& let us do now a few numerical computations, integer numbers are a good starting point
? 5+13
? 12*8
? 3/2
SET DECIMALS TO 15
sqrt2=sqrt(2) && computing the square root of 2...
? sqrt2 && ... and printing it
&& ? caporetto
&& if the line above was not commented, the compiler would issue the two following lines
&& Error BASE/1003 Variable does not exist: CAPORETTO
&& Called from TEST(8)
&& as xBase is not good at history, let us revise it: http://www.historyofwar.org/articles/battles_caporetto.html, http://www.firstworldwar.com/battles/caporetto.htm
&& we can then assign the correct date to this war
caporetto := ctod("10-24-1917")
a := date() && system date
? a + 1 && will print tomorrow's date
? a - caporetto && this will tell us how many days have passed since Caporetto's battle (difference between two dates)
SET DECIMALS TO 2
? (a - caporetto) / 365
?? " years have passed since Caporetto's battle"
? 1+1=3
&& it will print ".F.", that is, "FALSE" (of course...)
&& The following two instructions should be discussed to expand on the subject of operator precedence
? 1/2+2^3*sqrt(25)-3^2
? 1/2+2^3*(sqrt(25)-3^2)
The function SQR has been eliminated from Harbour and replaced by SQRT. Trying to use it will result in a linker error "undefined reference to 'HB_FUN_SQR'".
From the following example (which runs) we see that Harbour is a weakly typed programming language: a variable, such as a in our example can be a number, and then become a string of text.
a := 1+1
? a
a := "abc"
? a
There is a problem: if the variable type changed and the programmer did not get aware of it he may issue some instruction that will cause an error at run time. If we had another line of code
?a+1
in the program above, Harbour will attempt to add the number 1 to the string "abc", and the result will be the error
Error BASE/1081 Argument error: +.
Strings
[edit | edit source]Strings are essentially a list of characters. They are written between double quotes (eg. "Hello"). Note the difference:
123 => a number "123" => a string
Let's see how to use the function Stuff() to change a character within a string:
sTest:="fun"
?sTest, STUFF(sTest,2,1,"a")
&& => fun fan
Here is a list of the most commonly used functions for handling strings:
Lower() Convert uppercase characters to lowercase Upper() Converts a character expression to uppercase format Chr() Convert an ASCII code to a character value Asc() Convert a character to its ASCII value Len() Return the length of a character string or the number of elements in an array At() Return the position of a substring within a character string
Nevertheless in Clipper and Clipper Tools library can also be found very curious functions such as these:
AsciiSum() Finds the sum of the ASCII values of all the characters of a string Soundex() is a character function that indexes and searches for sound-alike or phonetic matches CHARONE() Search and remove repeating characters CHARONLY() Remove non-standard characters CHAROR() "OR" bytes in a string
Let's see what we can do with these functions.
We show the ASCII codes (http://www.asciitable.com/) corresponding to the keys pressed by the user, concluding the program when ESC is pressed.
#include "Inkey.ch"
DO WHILE .T.
WAIT "" TO cChar
?? " ", Asc( cChar )
IF ( LastKey() == K_ESC )
EXIT
ENDIF
ENDDO
inkey.ch is the eader file for Inkey() function, which serves to obtain the following key code from the keyboard buffer.
Other Considerations about the Classification of Data Types
[edit | edit source]A first classification of data types divides them into simple (or scalar) types and structured (or composite, or aggregate or compound) types.
In xBase the simple data types are numbers, logical values, strings and dates, that is, the types which can be field types in a database table. Memos are not, since they are a pointer to an external file.
Scalar data types can be composed to build up structured data types: arrays, associative arrays (hash tables), tables or databases (which are a "data type" which is stored on a disk). w:Primitive_data_type
Code blocks and objects are special data types.
Operators and Precedence
[edit | edit source]&& First we print the values of two expressions with the exponentiation operator
? 3**3**3 && is 19683
? 3**(3**3) && is 7625597484987
&& Then we let our compiler tell us if they are the same
? 3**3**3 = 3**(3**3) && result is .F.
&& We've a look at how our compiler deals with overflow and underflow...
? 9**9**9
&& ***********************
? 1/9**9**9
&& 0.00
? -(9**9**9)
&& ***********************
We see in this example that the exponentiation operator ** is left-associative (as opposed to mathematics where exponentiation is a right-associative operation).
The fact that 3**3**3 and 3**(3**3) give two different results means that parenthesis alter the operator precedence: whatever is enclosed in parenthesis is evaluated first.
Even when the operation is left-associative, the result of 9**9**9 is quite a "large" number (196627050475552913618075908526912116283103450944214766927315415537966391196809 to be precise, as you can easily check - maybe using some arbitrary precision calculator program, such as GNU bc, which is available on every Linux distribution and as a download for Windows at http://gnuwin32.sourceforge.net/packages/calc.htm - or by hand spending only some weekend time) which exceeds the resources Harbour allocates for a number (which is called overflow) and it, therefore, prints 23 times a * to indicate this error. It also gives zero (0.00) if we try to compute its reciprocal (which is called underflow). Overflow and underflow errors happen when the result of a computation is, respectively, too big or too small to be represented with the number of bits a compiler has allocated for the variable in which we're trying to store it in.
The same happens with dates. What it the result of
date()-1000000
(subtracting 1 million of days from the current date)? Let us see:
&& today my outputs are:
?date()
&& ==> 05/15/19
?date()-365000 && more or less 1000 years
&& ==> 01/13/20
set century on && we change an option...
?date()-365000 && ... and try ageing
&& ==> 01/13/1020
?date()-1000000 && more or less 2700 years (i.e. the results should be around 700 B.C.)
&& ==> 00/00/00
?ctod("31-12-9999")+1
&& ==> 00/00/00
Here, the last two outputs ("00/00/00") indicate invalid dates, just as the rows of 23 asterisks seen above indicate invalid numeric results. Acceptable dates are those that can be stored in a dBase table (ie a .dbf file) and in these files a date is a string of 8 characters in the "YYYYDDMM" format (4 characters for the year, 2 for the month and 2 for the day). Consequently, negative years (before Christ) or with more than 5 digits are not accepted.
Harbour displays the year with only two digits unless we change its behavior with a SET command and gives wrong results with these large number of years. The reason is simple: in a database application (inventory management, reservation systems) we won't have to deal with intervals of centuries and Harbour's routines for handling dates don't work correctly if we try to do that. In other fields, such as w:Archaeoastronomy this is the case and those who do deal with those problems (e.g. checking the precision of the alignment of the Winter solstice sunrise at Karnak in 2000 B.C. or the megalithic alignments) use their special routines for the computation of the dates.
There is another quandary when doing computation with computers. Look:
store 7 to n && an alternate way of an assignment (dates back to dBase II)
?sqrt(sqrt(n))^2^2=n
&& the result is .F.
set decimals to 15
?sqrt(sqrt(n))^2^2
&& 7.000000000000003
Now, if we extract the square root twice from a number and then square the result twice we should have the starting number. In fact, computers are not so precise and checking for the equality of two floating-point numbers can give unexpected results.
If you want to learn more you could have a look at What Every Computer Scientist Should Know About Floating-Point Arithmetic at https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html (which is part of a Sun Microsystems book entitled Numerical Computation Guide), and the Decimal Arithmetic FAQ at http://speleotrove.com/decimal/decifaq.html.
Fortunately, the precision of the variables used in xBase languages and the fact that in common business applications square roots, logarithms and other mathematical operations that cause loss of precision are rather rare, are circumstances that mitigate the problem.
SET OPTIONS
We've seen SET DECIMALS TO n and SET CENTURY ON/OFF commands.
But in fact there are many options which can be set.
The SETMODE(<nRows>, <nCols>) function is particularly useful when using hbrun or xbscript: its purpose is to determine the size of the window.
Boolean Algebra & Flow Control
[edit | edit source]We can print the result of a comparison, which is a Boolean value (represented as .T. or .F.), but they are useful with special words in order to branch or repeat the execution of one or more instructions.
We have a Boolean expression when its result is true (whose xBase symbol is .T.) or false (.F.). The easiest way to get a Boolean result is to use a comparison (relational) operator: =, ==, !=, <>, #, >, <, >=, and <=. Always remember the difference between the comparison operators = (equal), == (exactly equal) and the assignment operator :=.
Actually, the "real" (mathematical) definition of a Boolean expression is different from what is stated in the last paragraph. A "real" Boolean expression would contain only the two Boolean values .T. and .F. and the Boolean operators (.AND., .OR., .NOT.).
Boolean Algebra is applied to electronic digital circuits since the work of Claude Shannon in 1937. See http://www.allaboutcircuits.com/textbook/digital/chpt-7/introduction-boolean-algebra/ (and the successive pages of that online textbook) to have an idea of what Boolean Algebra means when it's applied to bits within a circuit (including a microprocessor). Here we're using it at a much higher level.
Consider Hamlet, Act 3, Scene 1: «[…]To be, or not to be? That is the question— Whether ’tis nobler in the mind to suffer The slings and arrows of outrageous fortune…». If Prince Hamlet asked Boole (but he could not, as it is set in the 14th or 15th century whereas Boole was born in 1815), his reply would have certainly baffled him. Why?
to_be=.T. && is it true?
?to_be .OR. .NOT. to_be
&& .T.
to_be=.T. && is it false?
?to_be .OR. .NOT. to_be
&& .T.
As Harbour told us, in Boolean algebra, the answer is "true". Have a look at this page: https://vicgrout.net/2014/07/18/to-be-or-not-to-be-a-logical-perspective/.
Conditional execution
[edit | edit source]A number of graphical ways have been developed for seeing how a program's flow control is transferred: these include flowcharts (https://www.lucidchart.com/pages/what-is-a-flowchart-tutorial), data flow diagrams (https://www.lucidchart.com/pages/data-flow-diagram), Nassi-Shneiderman diagrams and the JSP and JSD diagrams by Michael A. Jackson (which is almost certainly not the first Michael Jackson you'll think of).
The following example gets a number from the keyboard and prints it. If the number is 0 it comments that value (I presented this example as a typical nihilist programming). The flowchart shows why conditional execution is sometimes called bifurcation.
Function MAIN()
LOCAL number
INPUT "Key in a number: " TO number
IF number = 0
?? "Congratulations, you keyed the fabolous number "
ENDIF
? number
RETURN
This example prints two different comments, whether the condition of the remainder of the division of the number supplied by 2 is, or is not, 0.
Function MAIN()
LOCAL number
INPUT "Key in a number: " TO number
IF number % 2 = 0
? "You keyed in an even number"
ELSE
? "You keyed in an odd number"
ENDIF
RETURN
This example shows that more than a command can be executed.
Function MAIN()
LOCAL number
INPUT "Key in a number: " TO number
IF number % 2 = 0
? "You keyed in an even number"
? "I can prove it:"
? "the result of ",number," % 2 = 0 is ", number % 2 = 0
ELSE
? "You keyed in an odd number"
? "I can prove it:"
? "the result of ",number," % 2 = 0 is ", number % 2 = 0
ENDIF
RETURN
This example adds another keyword, ELSEIF, to show that the choices are not necessarily dichotomic. This is a chained conditional. Only one of the branches will be executed.
function MAIN
LOCAL nNumber := 0
//
INPUT "Key in a number: " TO nNumber
//
?? "Your number is "
IF nNumber < 50
? "less than 50"
ELSEIF nNumber = 50
? "equal to 50"
ELSE
? "greater than 50"
ENDIF
The following example checks whether the system time is earlier than 18 (6 p.m.) and prints a greeting appropriate to the time of day.
? Time()
// CToN source is numconv.prg, library is libct.
IF CToN( SubStr( Time(), 1, 2 ) ) < 18
? "Good day"
ELSE
? "Good evening"
ENDIF
There are three logical operators: .AND., .OR., and .NOT.
Here is an example with a rather long condition, coordinating more comparisons with .OR. operators. The example is terribly stupid, but it shows how a semicolon allows you to continue an instruction on the following line.
WAIT "Key in a letter: " TO char
IF char = "a" .OR. char = "A" .OR. char = "e" .OR. char = "E" .OR. ;
char = "i" .OR. char = "I" .OR. char = "o" .OR. char = "O" .OR. ;
char = "u" .OR. char = "U"
? char, " is a vowel"
ELSE
? char, " is not a vowel"
ENDIF
This same thing is however achieved in a more concise way using the substring comparison operator $ (or regular expressions, an advanced topic that we will see later).
WAIT "Key in a letter: " TO char
IF char $ [aeiouAEIOU]
? char, " is a vowel"
ELSE
? char, " is not a vowel"
ENDIF
Looping
[edit | edit source]DO WHILE, EXIT, LOOP, ENDDO are keywords used To repeatedly execute a series of statements (loop body) while a condition is true (i.e. its result is .T.).
Loops come in several flavours: the pre-tested loop, the post-tested loop, and the definite iteration, which is done via a count-controlled loop (usually called a for loop). In practice, we may have a middle-tested loop as well, but no specific syntax for it... we need to put an EXIT statement within an already existing loop body to get this one. The next program we will key in is of some interest for the mathematicians (Real Programmers aren't afraid of maths, do you know this famous adage?)
Here is it:
&& Function MAIN LOCAL number, sum, n
&& An anonymous contributor renamed the variable "num" into "number", increasing this short program readability, but the line above would give
&& Error E0030 Syntax error "syntax error at 'LOCAL'"
Function MAIN
LOCAL number, sum, n
CLS
? "Let's sum up the first n odd numbers."
INPUT "How many numbers shall I sum up? " TO n
sum=0
number=1
DO WHILE number <= 2*n
sum=sum+number
number=number+2
ENDDO
? "The sum requested is ", sum
As you can see, this looping statement is similar to the IF statement: both of them are ended by a END-corresponding statement, both of them contains a logical espression.
This looping statement will continue until its condition remains true (will evaluate to .T.).
The two instructions it repeats are sum=sum+num and num=num+2. The second is fundamental: if it wasn't there or was wrong (for example if you keyed in num=num/2), the condition would not evaluate to .F. and the program would not stop its execution (this is called infinite looping). When this happens to you, press the keys Ctrl and C at the same time. This should convince the computer to give his attention to you instead of running the loop.
The programs above is a nice example of how an extremely methodical calculator without any creativity would attack the problem of summing up the first n odd numbers. Notes about the creative approach to it can be found at this address: http://betterexplained.com/articles/techniques-for-adding-the-numbers-1-to-100/ (along with the usual anedocte about Gauss' show off about it in elementary school).
The WHILE looping statement is said to have the control expression in its head, opposed to the Pascal REPEAT-UNTIL looping statement (a post-test loop) which has the control of the condition in its tail (these are the Italian computer science slang for pre- and post- tested loops, aren't they funny?). How does the xBase/Clipper say REPEAT-UNTIL? It doesn't. Here is how to emulate it (copied & pasted from the Norton Guide):
LOCAL lMore := .T.
DO WHILE lMore
loopbody
lMore := condition
ENDDO
As you see, we first set a variable to be true, enter the loop, and specify the condition at the end of the loop, to make sure its body is executed at least once.
Here is an example, equivalent to the one at the pascal_repeat_until_loop page in tutorialspoint.com.
LOCAL a := 10
LOCAL lMore := .T.
DO WHILE lMore
? 'value of a: ', a
a := a + 1
lMore := ( a != 20 )
ENDDO
But the code above changed the condition. If we didn't want this to happen, we can simply negate the condition we'd use in the Pascal code:
becomes then:
LOCAL a := 10
LOCAL lMore := .T.
DO WHILE lMore
? 'value of a: ', a
a := a + 1
lMore := .NOT. ( a = 20 ) && or, in alternative, lMore := ! ( a = 20 )
ENDDO
Let us now see this kind of loop:
DO WHILE .T.
? "I won't stop."
ENDDO
This loop prints "I won't stop." as long as 'true' is... true. It is thus called an infinite loop because the ending condition will never be satisfied. It is the first example of control flaw studied in computer science classes. By definition, you incur in an infinite loop every time you specify a tautology as the condition to be checked. It is not always obvious to detect this and other control flaws and errors - it is indeed a process that involves the use of a specific piece of software called a debugger.
Counter-controlled Loops, and Nested Loops
[edit | edit source]In this section, we will see how to use FOR loops to print multiplication tables, how to nest loops, how to format output on the console screen and the importance of indenting code to improve readability.
The FOR...NEXT construct (counter-controlled loop) is useful when we know how many times we want the loop body be executed. We shall now use it to print out and revise multiplication tables.
CLEAR
FOR I := 1 TO 8
FOR J := 1 TO 8
?? I*J
NEXT
NEXT
(Please note in this example we have a loop inside the body of another loop: this is called a nested loop). Well, this works, but its output is not very nice... Moreover, it is customary to print the multiplication tables up to 10, since we're using a decimal numeration system... let us try it again!
CLEAR
FOR I := 1 TO 10
FOR J := 1 TO 10
@i,j*4 SAY I*J PICTURE "999"
NEXT
NEXT
this way we can print it much prettier... by the way, when nesting loops and branching statements, it becomes important to arrange the indentations in order to make the program easier to read... in The Oasis are available "source code beautifiers" such as dst314.zip. Harbour gives us the hbformat tool, https://vivaclipper.wordpress.com/tag/hbformat/.
PICTURE "999" is the instruction to specify we want the output format - as we specified three digits, the numbers in output will be printed as if they were three digits long, thus aligning them in our multiplication table.
As a second example of nested loops, the following is one of the most irritating programs I've ever written. It asks the user for an opinion and then always responds its contrary. It even quits when the user says he has more to tell him and keeps asking for input when the user says he has nothing else to add. Since it is particularly difficult for a program to answer the opposite of the user's assertions, this program simply prints the input string reversed, using the SubStr function (SubStr(<cString>, <nStart>, [<nCount>]) --> cSubstring - extract a substring from a character string cString, at index nStart with length nCount).
User input is obtained through the ACCEPT command (a COBOL reminiscence), and the control flow uses a DO WHILE loop (from FORTRAN) and a FOR ... TO ... STEP loop (from BASIC).
LOCAL cContinue := "N"
DO WHILE cContinue = "N"
ACCEPT "What's your opinion? " TO cOpinion
?""
?? "NO!! According to me, on the contrary: "
FOR i := Len( cOpinion ) TO 1 STEP -1
??SubStr( cOpinion, i, 1 )
NEXT
WAIT "Tell me, do you have any other opinions to discuss with me? (Y/N) " TO cContinue
cContinue := Upper( cContinue )
IF cContinue = "N"
? "Ok, then. "
ELSE
? "In this case let me go."
ENDIF
ENDDO
How do we deal with such an awkward customer? Just try typing "remmargorp revelc yrev a era uoy" and the solution will be clear. In Italy there is an expression to call a person who always contradicts his interlocutors: he is called a "bastian contrario". They do work like this program.
& - the Macro Evaluation Operator
[edit | edit source]Consider this short piece of code:
i = 2
myMacro = "i + 10"
i = &myMacro && will execute "i+10"
? i && will print "12"
The & is an operator that will evaluate (runtime compilation) an expressions and will permit text substitution within strings (see https://harbour.github.io/ng/c53g01c/ng110bc2.html). Let's make a simple calculator using it.
ACCEPT "Please enter the first number: " TO cNumber1
ACCEPT "Please enter the second number: " TO cNumber2
WAIT "Enter operation: " TO cOperation && we get a single character for the operation
? Number1+cOperation+Number2+"=" && we print the operation and an equal sign
? &(Number1+cOperation+Number2) && and the result on the following line, evaluating the string as it appeared on the line above
Here is an example of interaction with this program:
Please enter the first number: 12 Please enter the second number: 23.5 Enter operation: - 12-23.5= -11.5
Arrays
[edit | edit source]An array is an ordered grouping of data, whose elements are identified by a number called an index. In many programming languages the indices (or "indexes") start at 0, but in xBase they start from 1. Since in mathematics the indices are written next to the variable slightly below the line, sometimes they are called subscripts and, for this reason, arrays are sometimes called subscripted variables.
Consider this first example of arrays. We create two arrays, one with the names of the months of the year and one with their duration in days, and then we print them in an orderly manner. The same result can be obtained in a much more elegant way using associative arrays.
&& C:\hb32\bin\hbmk2 -quiet -oc:\hbcode\months.exe months.prg -run
DECLARE months [12]
? LEN (months) && the function LEN() shows how many items an array has
&& We shall now load some data into our arrays
PRIVATE month_names: = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
PRIVATE months: = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} && for simplicity we do not consider leap years
&& Let us make some output (using a FOR loop - see below)
FOR I: = 1 TO LEN (months)
? month_names [i], "is", months [i], "days long."
NEXT
In the next example we show that xBase has high-level support for dynamic arrays (implementing them in a low level language like C requires pointer arithmetic and memory management functions).
Note: This example will reappear many times in these notes. Often when we will introduce a new feature of the language we will write a new program which does the same thing of this program in a different way. It is one of the best pedagogical advice I can give you. Since I don't provide you with exercises, think of different ways of redoing some program using new language features, i.e. just to give you an idea: how difficult is it to redo the program that shows the multiplication tables on the screen using code blocks?
&& compile with ''hbmk2 averaging.prg -lhbmisc''
PROCEDURE MAIN
LOCAL n, sum := 0, average && let's initialize the variables
aNumbers := {} && and an array without elements
? "enter a list of integer numbers, and a non-number when you are finished"
WHILE .T. && we begin an endless loop
ACCEPT "next element: " TO item
IF IsDec( item ) && the function IsDec() from the libmisc library checks if we have entered a number
AAdd ( aNumbers, item ) && if we did, then we add the number as a new element to the array
sum := sum + &item && (and we update the sum of the elements in our array)...
ELSE
EXIT && ...if we did not, we quit this loop
ENDIF
ENDDO
average := sum / Len( aNumbers ) && we compute the average of the values in the array
?
? "to average the array you must correct each element by adding to it the corresponding value below"
?
FOR n := 1 to LEN ( aNumbers )
?? AVERAGE - aNumbers[n]
NEXT
RETURN
Here is the list of functions in hbmisc: https://harbour.github.io/doc/hbmisc.html.
The program above is not very satisfactory: due to the use of the IsDec() function it can treat only positive integer numbers. What if we'd like it to accept numbers like -8 and 2.78? Three solutions come to mind offhand: write ourselves function that checks if the string represents a number (an improved version of IsDec()), test input using regular expressions, using xBase functions for error handling. We will use this last technique in the next section of the tutorial.
FOR EACH n in aNumbers && FOR EACH is a more modern control flow statement Harbour syntax ?? AVERAGE - &n NEXT
In addition to a function to add elements to an array we have one to delete them:
aFruitSalad:={"peaches", "plums"}
AAdd ( aFruitSalad, "onions" ) && we add onions
ADel(aFruitSalad, 3) && on second thought, onions don't mix well: we remove them
local f; for each f in aFruitSalad; ?? f," " ; next && we print our ingredients
&& => peaches plums NIL
Harbour provides a function, hb_ADel, which accepts a third Boolean element and (if it's .T.) shrinks the array.
Multidimensional arrays are made by nesting arrays within other arrays.
Error Handling (the Clipper Way)
[edit | edit source]All of the programs showed up to this point will fail and show a runtime error if the user entered an unexpected input (in simple words, they crash). Programs called "robust" don't. Checking the conditions in which errors can occur and take countermeasures to prevent crashes is a job that was normally done with if-then-else.
BEGIN SEQUENCE ... END[SEQUENCE] is a control structure that executes some code and optionally some statements if the code produced an error. In our example if the conversion of the user input into a number generates an error, we will call EXIT to leave the while loop and continue our program.
PROCEDURE MAIN
LOCAL n, sum := 0, average && let's initialize the variables
local bError := errorblock ( { |oError| break ( oError ) } )
aNumbers := {} && and an array without elements
? "enter a list of integer numbers, and a non-number when you are finished"
WHILE .T. && we begin an endless loop
BEGIN SEQUENCE
ACCEPT "next element: " TO item
AAdd ( aNumbers, &item ) && if we did, then we add the number as a new element to the array
sum := sum + &item && (and we update the sum of the elements in our array)...
RECOVER
EXIT && ...if we did not, we quit this loop
END SEQUENCE
ENDDO
average := sum / Len( aNumbers ) && we compute the average of the values in the array
?
? "to average the array you must correct each element by adding to it the corresponding value below"
?
FOR n := 1 to Len ( aNumbers )
?? average - aNumbers[n]
NEXT
RETURN
Clipper 5 (C5) provided four classes:
Error Provides objects with information about runtime errors Get Provides objects for editing variables and database fields TBrowse Provides objects for browsing table-oriented data TBColumn Provides the column objects TBrowse objects
The Clipper 5.3 Norton Guide lists these classes:
Error class Provides objects with information about runtime errors CheckBox class Provides objects for creating checkboxes Get class Provides objects for editing variables and database fields ListBox class Provides objects for creating list boxes MenuItem class Provides objects for creating menu items PopUpMenu class Provides objects for creating pop-up menus PushButton class Provides objects for creating push buttons RadioButto class Provides objects for creating radio buttons RadioGroup class Provides objects for creating radio button groups Scrollbar class Provides objects for creating scroll bars TBColumn class Provides objects for browsing table-oriented data TBrowse class Provides the column objects TBrowse objects TopBarMenu class Provides objects for creating top menu bars
Functions and Procedures
[edit | edit source]A function is a piece of code that returns a result every time it is called. Let us consider a function to convert Celsius into Fahrenheit (the same function is provided by libct, but we redo it for educational purposes):
FUNCTION cels2f( tempCelsius )
tempFahrenheit := (tempCelsius*1.8 + 32)
RETURN tempFahrenheit
FUNCTION MAIN()
? cels2f(100)
? cels2f(0)
But have a look at the following:
/**
* by choosing better variable names this function becomes self-explanatory
*/
function convert_Celsius_to_Fahrenheit(Celsius_temperature)
return Celsius_temperature * 1.8 + 32
FUNCTION MAIN()
? convert_Celsius_to_Fahrenheit(100)
? convert_Celsius_to_Fahrenheit(0)
Consider this example of use for the function HB_Random():
PROCEDURE Main
// Random number between 0.01 and 0.99
? HB_Random()
// Random number between 0.01 and 9.99
? HB_Random(10)
// Random number between 8.01 and 9.99
? HB_Random(8,10)
// Random integer number between -100 and 100
? HB_RandomInt(-100,100)
RETURN
A good function would function as a "black box", that is you can use it without worrying about how it internally works. An example usually given at this point is that of driving a car: you can use it without worrying about how the engine works (do you know anybody which looked at the pistons of their new car before buying it?). Someone else designed the engine. But there may be problems: if we change our Celsius into Fahrenheit function as follows
FUNCTION cels2f( tempCelsius )
tempFahrenheit := (tempCelsius*1.8 + 32)
number := number + 1
RETURN tempFahrenheit
FUNCTION MAIN()
number := 0
? cels2f( 100 )
? cels2f( 0 )
? number
we'll get a side effect. This program output is
212.0 32.0 2
That is, our new function did not only return a value, it also increased by 1 the variable number.
The Clipper Norton Guides read, "A procedure in CA-Clipper is the same as a user-defined function, with the exception that it always returns NIL" (http://www.oohg.org/cl53/ng10a778.html), thus a procedure is really just a type of function that relies on these side effects to get something done.
The utility of functions is that they improve program readability, maintainability. Splitting a program into functions is a good design approach, which lets breaking a complex problem into smaller, simpler problems (top-down).
Harbour has four different kinds of subroutines/subprograms:
- the FUNCTION and
- the PROCEDURE (which is a FUNCTION with no return value),
- the method (which has no associated keyword, works like every object-oriented programming language) and
- the code block.
Why the Main Function?
[edit | edit source]The main function is the designated start of a program written in C or C++ (Java uses a static main method). Since a procedure is a function that returns NIL, we can have a procedure main instead of a function main in our program. With Harbour it is not necessary anymore.
Another characteristic of variables: scope identifiers
[edit | edit source]Variables have a name, a type, a scope and a lifetime (http://www.dbase.com/Knowledgebase/dbulletin/bu05vari.htm) which may be explicitly declared as (list below copied verbatim from w:Harbour (software)):
- LOCAL: Visible only within the routine which declared it. Value is lost upon exit of the routine.
- STATIC: Visible only within the routine which declared it. Value is preserved for subsequent invocations of the routine. If a STATIC variable is declared before any Procedure/Function/Method is defined, it has a MODULE scope, and is visible within any routine defined within that same source file, it will maintain its life for the duration of the application lifetime.
- PRIVATE: Visible within the routine which declared it, and all routines called by that routine.
- PUBLIC: Visible by all routines in the same application.
They do not make sense before we work with functions and procedures.
/* Work on the following.
* hbformat was run on this piece of code
* need to provide comments, nest more functions and procedures to help figuring what goes on with scope modifiers
*/
STATIC x := 9
? x
A()
? x
B()
? x
C()
? x
D()
? x
E()
? x
PROCEDURE A
LOCAL x := 10
? "x from A=", x
RETURN
PROCEDURE B
PRIVATE x := 5
? "x from B=", x
RETURN
PROCEDURE C
PUBLIC x := -1
? "x from C=", x
RETURN
PROCEDURE D
? "x from D before updating value=", x
x := 12
? "x from D=", x
RETURN
PROCEDURE E
? "x from E=", x
RETURN
On running this, we get a strange output:
9 x from A= 10 9 x from B= 5 9 x from C= -1 9 x from D before updating value= -1 x from D= 12 9 x from E= 12 9
This program sets a variable x and five different procedures, A, B, C, D, E. The first three procedures define a variable x within themselves, assign it a value and print it. The fourth function assigns a new value to some variable named x without declaring it. The fifth function shows the value of some x variable, which happens to be the value of the fourth x variable. The main fact to remember here is that two variables are not the same, even if hey have the same name, provided they have different scopes and this feature is called shadowing.
x := 9
? x
A()
? x
B()
? x
C()
? x
D()
? x
E()
? x
PROCEDURE A
LOCAL x := 10
? "x from A=", x
RETURN
PROCEDURE B
PRIVATE x := 5
? "x from B=", x
RETURN
PROCEDURE C
PUBLIC x := -1
? "x from C=", x
RETURN
PROCEDURE D
? "x from D before updating value=", x
x := 12
? "x from D=", x
RETURN
PROCEDURE E
? "x from E=", x
RETURN
9 x from A= 10 9 x from B= 5 9 x from C= -1 -1 x from D before updating value= -1 x from D= 12 12 x from E= 12 12
As static variables seem to require further informations (they work like C static variables), here is an example:
// adapted from "An example of static local variable in C" (from Wikipedia Static_variable)
FUNCTION func()
static x := 0
/* x is initialized only once, the first time func() is called */
x := x+1
? x
RETURN
FUNCTION main()
func() // calls func() a first time: it prints 1
func() // value 1 was preserved; this time it prints 2
func() // prints 3
func() // prints 4
func() // prints 5
See pascal_variable_scope in tutorialspoint.com and http://aelinik.free.fr/c/ch14.htm
Variable scope is a foretaste of object oriented programming, in the sense that it anticipates some encapsulation concepts.
The search for the Ludolphine
[edit | edit source]Part 1: Using the Clipper Tools (CT) Library
[edit | edit source]If you checked the list of functions you will have noticed that the xBases have no trigonometric functions. Not that in general they miss them: these languages are targeted database applications such as "accounting systems and airline reservations systems" (see w:Database application. ) in which trigonometric functions are not really important. But we now ask ourselves how to compute an approximation to the Ludolphine number, π (more information on this "search" at http://www.mathpages.com/home/kmath457.htm), We can do it using the CT library (see http://vouch.info/harbour/index.html?hbct.htm), or importing the C standard library function atan.
&& compile with ''hbmk2 computepi.prg -lhbct''
PROCEDURE MAIN
SET DECIMAL TO 14
? 4 * ATAN (1)
Part 2: Mixing C and Harbour Code
[edit | edit source]We get the idea from Harbour for beginners by Alexander Kresin (http://www.kresin.ru/en/hrbfaq_3.html).
#pragma BEGINDUMP
#include <extend.h>
#include <math.h>
HB_FUNC( ATAN )
{
double x = hb_parnd(1);
hb_retnd( atan( x ) );
};
#pragma ENDDUMP
SET DECIMAL TO 14
? 4 * ATAN (1)
It must be noted that the extend.h file (* Compatibility header file for CA-Cl*pper Extend System) contains this warning:
/* DON'T USE THIS FILE FOR NEW HARBOUR C CODE */ /* This file is provided to support some level of */ /* Harbour compatibility for old Clipper C extension code */
In fact this example is here only to consider how the open source cross-compilers (Harbour in this case) extend themselves with C library functions, which gives them a lot of flexibility. Not that computing π would be useful in itself for the average xBase programmer (by the way the CT library contains a function to directly compute π). We shall not concern ourselves with a method to do that, but move to something more conventional.
The FizzBuzz interview question: critical thinking and algorithms, with an introduction to table trace and the concept of flag
[edit | edit source]FizzBuzz is a little problem that is quite often asked in job interviews. A good discussion is found at the page https://micheleriva.medium.com/about-coding-the-fizzbuzz-interview-question-9bcd08d9dfe5. As always there are many ways of solving even a simple problem like this, that can be worded as follows,
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
The first thing one should do is to think as many ways as possible to solve the problem. Also, you may be asked to show that your solution works without having access to a computer: the way of doing so is also one of the best ways of reading a program written by someone else, and even of checking the program you yourself wrote. You take a piece of paper and a pencil and check how the program instructions change the variables and what input they produce by arranging the content of the variables in a table and behaving like the computer. I heard this being called called w:Trace table, tracing an algorithm, hand tracing, dry running. For the moment I point you to a tutorial on YouTube which should give you a taste of how the thing works: https://www.youtube.com/watch?v=UbANyxE7pGE (Trace tables tutorial GCSE Computer Science).
Take for example the following trivial solution of FizzBuzz:
FOR I = 1 TO 100
FLAG = 0
IF I % 5 = 0
FLAG = 1
?? "Fizz"
ENDIF
IF I % 3 = 0
FLAG = 1
?? "Buzz"
ENDIF
IF FLAG = 0
?? I
ENDIF
?? ", "
NEXT
This example introduces a flag variable, that is, a variable that is checked to know whether the program executed a particular instruction or set of instructions.
Sorting Algorithms
[edit | edit source]Sorting data is an important application in computer science. In fact, in the ancient times (which means in the 50s), an important component of a computer was a piece of hardware called a Card Sorter, which was used to sort punched cards, see one at http://www.columbia.edu/cu/computinghistory/sorter.html, but the Census Machine built by Hollerith in the 1880's already had its own sorter box, and the French word for computer, ordinateur, can even be literally translated to sorter.
Let's start with a piece of code
// we'll declare and load random numbers from 1 to 100 in an array
DECLARE arrayToSort [12]
FOR I = 1 TO 12
arrayToSort [I] = Int( HB_Random( 1,101 ) )
NEXT
// we review our array
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
// we'll do Exchange Sort as described in http://www.ee.ryerson.ca/~courses/coe428/sorting/exchangesort.html
// where there is also a nice animation showing how it works! :)
for i := 2 TO Len(arrayToSort)
for j := Len(arrayToSort) TO i step -1
IF arrayToSort [j - 1] > arrayToSort [j]
TEMP := arrayToSort [j - 1]
arrayToSort [j - 1] := arrayToSort [j]
arrayToSort [j] := TEMP
ENDIF
NEXT
NEXT
?
? "Sorted array:"
?
// we look at our sorted array
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
From the version above we can (and should) separate the sorting routine in a procedure, this way:
PROCEDURE ExchangeSort(array)
// we'll do Exchange Sort as described in http://www.ee.ryerson.ca/~courses/coe428/sorting/exchangesort.html
// where there is also a nice animation showing how it works! :)
for i := 2 TO Len(array)
for j := Len(array) TO i step -1
IF array [j - 1] > array [j]
TEMP := array [j - 1]
array [j - 1] := array [j]
array [j] := TEMP
ENDIF
NEXT
NEXT
RETURN
FUNCTION MAIN
// we'll declare and load random numbers from 1 to 100 in an array
DECLARE arrayToSort [12]
FOR I = 1 TO 12
arrayToSort [I] = Int( HB_Random( 1,101 ) )
NEXT
// we review our array
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
ExchangeSort(arrayToSort) // we call the procedure and pass to it our array called arrayToSort
?
? "Sorted array:"
?
// we show the array is sorted
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
Usually the first sorting algorithm introduced (even in my high school textbook) is Bubble Sort, which is a bad choice. Here we use Exchange Sort, which is equally bad, but at least we vary a little.
Grasping Recursion
[edit | edit source]We have recursion when a function calls itself. The definition is really this simple.
Put like that, it doesn't seem like a good idea, because it seems that a recursive function will trap the program in an infinite loop. In reality a recursive function does not just call itself, but includes a criterion to stop doing it and provide some actual result.
A first example is the factorial function, of which we take a definition from a dictionary (https://www.dictionary.com/browse/factorial): « Mathematics. the product of a given positive integer multiplied by all lesser positive integers: The quantity four factorial (4!) = 4 ⋅ 3 ⋅ 2 ⋅ 1 = 24. Symbol: n!, where n is the given integer».
FUNCTION main
? factorial(10)
RETURN 0
FUNCTION factorial(n)
IF n = 0
RETURN 1
ELSE
RETURN n * factorial(n - 1)
ENDIF
RETURN -1
Code blocks
[edit | edit source]https://github.com/vszakats/harbour-core/blob/master/doc/codebloc.txt
Sites around the Net say that Clipper 5 documentation called them unnamed assignable functions.
Let us get back at our Celsius to Fahrenheit temperature converting function:
FUNCTION cels2f( tempCelsius )
tempFahrenheit := (tempCelsius*1.8 + 32)
RETURN tempFahrenheit
and redo it as a codeblock:
Cels2F := { | celsius | Fahrenheit := (Celsius * 1.8 + 32) } // our first codeblock
? Eval(Cels2F,100)
? Eval(Cels2F,0)
In the first line we assigned a code block to a variable named Cels2F. The code block is comprised in braces. The first thing is a variable that is to be passed to the block, between vertical bars (|). More variables should be separated by commas. Then we can write the instructions (more than one should be separated by commas).
Later we use two times the Eval function giving it two arguments: the name we assigned to our codeblock and the parameter to pass it. In addition to Eval, other functions can evaluate code block: AEval() and dbEval(). Ascan() and Asort() can be given code blocks to change their behavior.
As code blocks can't contain commands, we can't put a ? command in them, but have to use the corresponding QOut() function.
Command Line Arguments
[edit | edit source]Suppose that we wish to write a clone of the Unix cat command, which means that we will have a program to which we'll pass a command line argument that is a file name and output the content of the file.
PROCEDURE MAIN(file)
? MEMOREAD(file)
RETURN
That's it! In fact, it is quite limited, and it is more likely a clone of the DOS type command (or even of the CP/M type command!). Procedure This file's procedure MAIN receives one argument, and gets it from the command line. Then passes it to the MEMOREAD function (description: Return the text file’s contents as a character string), and sends the result to the console output.
File Management
[edit | edit source]Accessing files can be done using low-level functions, which are: FOPEN(), FCREATE(), FWRITE(), FREAD(), FREASDSTR(), FERASE(), FERROR(), FSEEK(), FCLOSE() and FRENAME().
How should we code an example using these functions? Here is an idea:
- We create a new file is created using FCREATE() and text is written into it with FWRITE().
- The pointer is positioned at the end of the file using FSEEK().
- The contents of the file are read using FREAD().
- The file is closed with FCLOSE().
- The file is renamed using FRENAME().
- Finally, the renamed file using FERASE() is deleted.
Harbour, however, provides functions that permit manipulating text files in a similar way to DBF files (https://vivaclipper.wordpress.com/tag/memoread/).