Clipper Tutorial: a Guide to Open Source Clipper(s)

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Quite a lot of people were interested in the previous versions of this page, when it was the most visited page in my old website and so, in addition to my personal interest that keeps me delving into the subject, I wish to complete it. The interest in this topic is confirmed by the fact that another Wikibook was created on a very similar theme: Application_Development_with_Harbour, started on September 15, 2010 by the user Raumi75 - on his page he states laconically this intention: «I like the Harbour programming language and hope we can create the missing manual». Unfortunately, the project didn't take off.

This page disappeared when GeoCities was closed (in fact it still contains some broken links to GeoCities pages), and it has been reloaded thanks to some requests I got, for example on Facebook. There you can find the "Harbour MiniGUI" group and many others.

As a newbie to Wikibooks, I did not yet properly format the few sources on this page (Wikibooks has syntax highlighting for Clipper, but it is a bit buggy). If you check it, you will see misplaced links and so... this page definitely needs proofreading!

I'd like to see this tutorial grow! If someone from newsgroups like comp.lang.clipper or mailing lists like HarbourUsers (http://lists.harbour-project.org/mailman/listinfo/harbourusers) or Facebook groups like ‎Harbour Project would give me help or clues, or contribute, it would be great. To apologize for its incompleteness, I can only say that this page contains everything I know about Clipper and xBase programming (in a given moment... I always try to learn new things...). I let digressions sneak in here and there, and also my sense of humor. This book not written in the serious style of all the other Wikibooks I've seen.

I tried to adhere to the classical tutorials' bottom-up approach of showing all the basic functions via very simple examples. The plan to include some bigger examples, with the top-down approach (how do I deal with this problem?) is suggested, but not yet pursued...

At the moment this guide deals mostly with (x)Harbour under Windows, although I plan to describe other environments.

I have decided to name this tutorial "a Guide to Open Source Clipper(s)" and not "a Guide to Open Source xBase" because I like the name Clipper, and as you can see by watching at the open source compilers I discuss (Clip and (x)Harbour) the influence of the name Clipper is great: only the old X2c and now this new thing called X# don't recall the name Clipper. I now consider the name inadequate. Maybe something like Programming With Harbour and Other Free xBase Languages?

Modern xBase open source/free dialects/implementations are:

  1. Harbour (https://harbour.github.io/)
  2. xHarbour (http://www.xharbour.org/)
    which are the most active and mature projects. The second is a fork of the first.
  3. Clip, which apparently exists in two versions on SourceForge: https://sourceforge.net/projects/x-clip/ (v 1.2.1.6, Last Update: 2017-06-04) and https://sourceforge.net/projects/clip-itk/
  4. X#, which is an implementation for .NET (https://www.xsharp.info/, https://github.com/X-Sharp/XSharpPublic). It looks interesting but the runtime is missing. The documentation is at https://www.xsharp.info/help/index.html.
  5. DBFree (http://www.dbfree.org) is used to create web applications and is open source, even if it contains components, such the fundamental component - the xBase interpreter MaxScript (http://maxscript.org/, http://www.maxsis.it) is not: it's just freeware.
X2c (http://web.archive.org/web/20090416070816/http://x2c.dtop.com/, http://freshmeat.sourceforge.net/projects/x2c) is very old - what use can it have for a modern programmer if the download links don't work and the only freely available C compiler can be found at the website Embarcadero Antique Software, namely https://cc.embarcadero.com/item/25636? However, I like some of its examples and I'll mention them.

This Guide was born as a set of notes when I followed a small project (I was asked about the possibility of porting an old Summer 87 program to Windows - and I did it by simply recompiling the source code to check Harbour's compatibility with Clipper, and creating a small Windows program, which showed a simple splash screen and an interface where the menu entries pointed to stubs). The experience was encouraging, although the Windows version of that application was never actually realized. Its by-product, my notes, evolved. Their first objective was to redo the examples of a "PC GUIDE", the first of eight booklets of a self-instruction course in the Clipper language bought on an Italian newsstand in 1993.

I noticed also that there were no good tutorials and that the books about dBase/Clipper/xBase/Visual Objects and so on couldn't be found in any bookstore (and only with great difficulty in libraries!).

Some old versions of these notes can be found on GeoCities mirrors - (I'd like to thank W. Birula for letting me know, not to mention his suggestion of uploading my set of notes on Wikibook, and also for the flowchart he kindly provided). Thanks to bpd2000 for the interesting link he provided.

When (and if) finished, it will prove a complete guide to Open Source Clipper programming. However, this page is still very incomplete.

The current plan consists of two introductory chapters, then the first part of the tutorial will cover the basic of the language, up to the procedural programming facilities and the native database-DBF file support. Part 3 will explain OOP and other programming topics, and the last part will be about programming a user interface and web applications. This could even make up for a nice introduction to computer science!

Michele Povigna
  1. Part 1: Introduction
  2. Part 2: The Common Ground
  3. Part 3: Additions to the Language
    • Chapter 5: Object Oriented Programming
    • Chapter 6: Other Features
  4. Part 4: Real Applications

Getting Started[edit]

A simple graph showing the different ways one can use (x)Harbour, and how they correspond to BASIC interpreters and Java.

In practice, (x)Harbour permits a great versatility, as it (they) can be used in four different ways:

  1. by running hbrun or xbscript and executing instructions interactively (much like a BASIC direct mode or immediate mode, although it is more similar to the dBase dot prompt). The main limit of this approach is that it cannot run expressions longer than a single line (but it is possible to enter more instructions on a line separating them with semicolons). However, much of my tutorial is thought to be entered, tested and understood one line at a time.
  2. by calling hbrun or xbscript specifying a .prg file as a line argument to execute it (which is again like specifying a file to run when invoking a BASIC interpreter)
  3. by compiling the file to a bytecode using the /gh option of the Harbour compiler and then running the resulting .hrb bytecode file with hbrun (this is similar to Java's workflow when you call the compiler javac and then the Java interpreter on the bytecode file)
  4. by using the Harbour compiler, C compiler and linker to get an executable file (utilities are given to get all the steps done in a single command)
  5. by using the compiler through an IDE

There are also commercial RADs, like Xailer (https://www.xailer.com/) or xHarbour Builder (https://www.xharbour.com/).

Antonino Perricone wrote an extension for Visual Studio Code, which is well documented at https://github.com/APerricone/harbourCodeExtension/wiki, https://medium.com/harbour-magazine/visual-studio-code-for-harbour-e148f9c1861a, https://harbour.wiki/index.asp?page=PublicArticles&mode=show&id=190401174818&sig=6893630672. The main problem with the precompiled version of Visual Studio Code is that it's not under the MIT license.

https://github.com/Petewg/harbour-core/wiki/Make-tools

xMate IDE: https://github.com/Petewg/MgM/tree/master/comp/ide

Packages providing syntax highlighting for various editors are available: for Sublime Text (https://www.sublimetext.com/) is available at https://github.com/asistex/Sublime-Text-harbour-Package, for SynWrite (http://www.uvviewsoft.com/synwrite/) at https://github.com/rafathefull/synwrite, for Atom Editor (https://atom.io/) at https://github.com/AtomLinter/linter-harbour, UltraEdit at http://forums.ultraedit.com/syntax-highlighting-wordfile-for-harbour-fivewin-t17880.html. I myself like Scintilla (https://www.scintilla.org/), a lightweight open source editor that supports the xBase syntax highlighting under the name Flagship - which is an implementation different from the open source ones we are considering but that does not make much difference and it's anyway highly configurable.

Using Harbour From a Windows Command Prompt[edit]

Simply open a command prompt and move to the directory where you store your sources. Issue a PATH command pointing to the bin directory of your Harbour system (this is to avoid problems if you've got different compilers in your system). I also add the path of a text editor to use from the prompt, like this:

D:\harbourcode>PATH c:\hb32\bin;D:\wscite

Getting Started With hbIDE[edit]

https://hbide.vouch.info/

  1. To create a new project, select File > New > New Project Wizard (Prototype Only). We will name this project hbidetest, enter the path C:/hb32/projects/hbidetest.hbp and click Save and Close.
  2. Select File > Open Project in the menu bar.
  3. Right click the project hbidetest in the projects dock on the right and "Set as Current" project.
  4. Select File > New > Source and create a file hbidetest.prg with the following content:
     function MAIN
     * This is an example
     clear
     ?"Hello, the weather is fine today"
     ?"(this is a test of HbIDE)"
     wait && will show "Press any key to continue..." and prevent the console window to close immediately
     return
    
  5. Double click hbidetest in the projects dock so that the and click the Select Sources button near the Project Output text box, then select Save and close. It is a button not very well indicated - I think a button clearly stating "Add source file" would have been better. "Select Sources" Button Position in hbIDE
  6. Now after selecting Build > Build and Launch our program will show up.

Working with Databases[edit]

Let's return to the Wikipedia entry Database application.

Different kinds of database applications exist as well. If you did store your friend's phone numbers and addresses into a word processor, you would have what someone calls a Free-Form Database (however, a similar expression is an oxymoron in computer science) - myBase®, askSam®, Personal Knowbase®, MyInfo®, Info Select®, and GeneralKB® are a bunch of specialized free-form database application, which actually means PIM (Personal information manager). Now, a word processor lets us search the information, but other operations, such as sorting them in alphabetical or numerical order, cannot be done automatically by a word processor.

What about attempting to store it into a spreadsheet? We may use one column for the name, one for the surname, one for the telephone number, one for the city. This quick database, stored in a spreadsheet, may be searched and sorted: for example, we can sort it by city and person's name in alphabetical order. This is a flat database, http://www2.research.att.com/~gsf/man/man1/cql.html: a flat database is a sequence of newline terminated records of delimiter separated fields, and a spreadsheet shows its limits in data entry and reporting (if you did want to use the data in your table to print out addresses on envelopes a spreadsheet is not a good tool). An example is MyDatabase (http://www.pcmag.com/article2/0,2817,760833,00.asp).

Spreadsheets are much better to do accounting: how much harder a book-keeper's work would be if his data were stored in a wordprocessing program? The purpose here is to have our data structured in a certain way: all the costs in a place, all earnings in another.

Before 1970 complex databases were managed using Hierarchical Databases (very little information is needed about them - see for example http://www.extropia.com/tutorials/sql/hierarchical_databases.html and http://people.cs.pitt.edu/~chang/156/14hier.html). An example of a hierarchical database is IBM IMS (Information Management System, which was developed during the mid-1960s for applications in the aerospace industry). Their implementation is based on trees, a hierarchical data structure. Hierarchical Databases and Network Databases together form what today are referred to as Legacy Database Systems. Network databases were born as an extension to the programming language COBOL by the Conference on Data Systems Languages (CODASYL). The hierarchical data model is based on data structures called graphs.

Today's standard is the Relational Database (RDBMS), which is "a database with relationships between more than one table of records based on common fields". We will speak of them in some detail, but we will briefly mention the fourth approach: Object Oriented Databases. These databases store objects (in the same sense the word is used in the expression object-oriented programming). They're not much used, mostly because objects are more complex than the simple fields a relational database stores in its tables. More information on the topic at http://www.odbms.org/odmg-standard/.

The Wikipedia entry about DBase reads: «dBase is application development language and integrated navigational database management system which Ashton-Tate labeled as "relational" but it did not meet the criteria defined by Dr. Edgar F. Codd's relational model». Codd's criteria (the so-called 12 rules, which really are 13 because the rule numbered '0' actually exists) are so strict that in practice a true relational database system does not even exist, but the point is that dBase accessed databases in another way, so that it's considered a Navigational Database (which works in a way that simulates relational databases).

http://www.databasedev.co.uk/design_basics.html

DBF Files in Other Languages[edit]

Because of the great success of dBase and its, the DBF file format became an industry standard. Many other database programs have used them to store data, like Lotus Approach. We also have many little utilities to view and convert to other formats these files. Here is a bunch of URLs: https://dbfview.com/, http://www.alexnolan.net/software/dbf.htm, https://dbfviewer.com/en/, https://www.dbf2002.com/, http://www.whitetown.com/dbf2sql/ («DBF to SQL Converter allows you to convert your dbf files to SQL script. Personal license $29.95», but compare https://www.vlsoftware.net/exportizer/). And is so widely used that interfaces for working with it are available for various languages, for example:

Well, now we will see how to work with DBF files the way it was intended.

Making a first database and recording some data[edit]

A verbose way[edit]

 && it was done this way at the Dot Prompt
 && we can type this interactively in hbrun
 CREATE TMPNAMES
 USE TMPNAMES
 APPEND BLANK
 REPLACE FIELD_NAME WITH "NAME"
 REPLACE FIELD_TYPE WITH "C"
 REPLACE FIELD_LEN WITH 15
 APPEND BLANK
 REPLACE FIELD_NAME WITH "ADDRESS"
 REPLACE FIELD_TYPE WITH "C"
 REPLACE FIELD_LEN WITH 30
 CLOSE
 CREATE NAMES FROM TMPNAMES && https://www.itlnet.net/programming/program/Reference/c53g01c/ngc785e.html
 ERASE TMPNAMES.DBF && we get rid of the temporary file

The code above created a DBF file, names.dbf, to be used by the following code. It will add a record to the DBF file. It is equivalent to the "First Sample Program" of my old PC GUIDE, which missed a line that is necessary in modern xBase.

 CLEAR
 ? "First Sample Program"
 SELECT 1
 USE NAMES
 APPEND BLANK
 REPLACE NAME WITH "MIKE BROWN"
 REPLACE ADDRESS WITH "ROME STREET, 56"
 CLOSE && this line is missing in my PC GUIDE but is needed in a compiled Harbour program
 QUIT

The CLOSE command is equivalent to the dbCloseArea() function, which closes a work area: Pending updates are written, pending locks are released.

A more concise way[edit]

The short code below does the same work of the two pieces of code of the previous section (it only produces a different file name, namesdb.dbf instead of names.dbf).

 local aStruct := { { "NAME",    "C", 15, 0 }, ;
                    { "ADDRESS", "C", 30, 0 }}
 REQUEST DBFCDX
 dbCreate( "namesdb", aStruct, "DBFCDX", .t., "NAMESDB" )
 && http://www.fivetechsoft.com/harbour-docs/api.html
 USE NAMESDB
 NAMESDB->(DbAppend())
 NAMESDB->NAME := "MIKE BROWN"
 NAMESDB->ADDRESS := "ROME STREET, 56"

This example uses the alias operator, ->. http://www.ousob.com/ng/clguide/ngcf412.php

The alias->field_name notation is used to allow access to fields of databases that are loaded but not active. The alias can be specified with the work area number (e.g. 2->std_id), with the work area alias (e.g. B->std_id), or with the database name (e.g. STUDENTS->std_id).

The result of this code is a file named namesdb.dbf. Informations about how DBF files are can be find at DBF File structure, http://www.dbf2002.com/dbf-file-format.html, where we find this list of Field type:

  • C – Character
  • Y – Currency
  • N – Numeric
  • F – Float
  • D – Date
  • T – DateTime
  • B – Double
  • I – Integer
  • L – Logical
  • M – Memo
  • G – General
  • C – Character (binary)
  • M – Memo (binary)
  • P – Picture
  • + – Autoincrement (dBase Level 7)
  • O – Double (dBase Level 7)
  • @ – Timestamp (dBase Level 7)

My PC GUIDE showed how a .dbf file is made with the DataBase Utility DBU. Clones of this utility are FiveDBU (with source code) at https://code.google.com/archive/p/fivewin-contributions/downloads, DBF Viewer Plus at http://www.alexnolan.net/software/dbf.htm, CLUT at http://www.scovetta.com/archives/simtelnet/msdos/clipper. Harbour includes its own HbDBU (the source is in \hb32\addons\hbdbu) and a component IdeDBU of HbIDE (the other two components are IdeEDITOR and IdeREPORTS).

From https://code.google.com/archive/p/fivewin-contributions/downloads we can get fivedbu_20130930.zip (New FiveDBU version with enhancements on ADO fields editing). It supports ADO, 3 RDD (DBFNTX, CBFCDX and RDDADS) and 6 languages - select "Bases de datos -> Preferencias -> Lenguaje: Inglés" to have it in English.

Let us see what is in our little file so far.

 USE NAMES
 LIST DATE(), TIME(), NAME, ADDRESS

Database Design Issue: the First Normal Form (1NF)[edit]

The work done in the previous section was intended to exactly reproduce the database presented in my PC GUIDE. There are, however, drawbacks: having only one NAME field, this database cannot sort its data on the last name. Also, a careless user might insert the data of some people with the last name first, and some other data with the first name last. When designing a database precautions should be taken of these possibilities. The first normal form (http://www.1keydata.com/database-normalization/first-normal-form-1nf.php, http://www.sqa.org.uk/e-learning/SoftDevRDS02CD/page_14.htm) requires you to define fields whose information cannot be divided into smaller parts. So, instead of a NAME field, we should have a FIRST_NAME and LAST_NAME fields. Complying to the first normal form, our little database would be on the on the right track to being a normalized database.

Designing the database is an essential part of the work and it is not always obvious how it should be done. See https://www.ntu.edu.sg/home/ehchua/programming/sql/relational_database_design.html.

A graphical device used to help is database design are the Entity-Relationship Diagrams: https://www.lucidchart.com/pages/er-diagrams, https://www.guru99.com/er-diagram-tutorial-dbms.html.

Complicating our simple database[edit]

Harbour contains a file named test.dbf. Launch hbrun in its directory and type in

use test
browse()

At this point, we see that it is a 500 record table. Move around with the cursor keys and, when you're finished, punch the Esc key to quit this interactive table browser and editor. To get the record number of a person called Ted issue:

locate for first="Ted"
? recno()

Here is the testdbf.prg source from \hb30\tests. It should be discussed in detail. It is a GPL piece of code poorly commented.

 /*
  * $Id: testdbf.prg 1792 1999-11-10 10:17:19Z bcantero $
  */

 function main()

   local nI, aStruct := { { "CHARACTER", "C", 25, 0 }, ;
                          { "NUMERIC",   "N",  8, 0 }, ;
                          { "DOUBLE",    "N",  8, 2 }, ;
                          { "DATE",      "D",  8, 0 }, ;
                          { "LOGICAL",   "L",  1, 0 }, ;
                          { "MEMO1",     "M", 10, 0 }, ;
                          { "MEMO2",     "M", 10, 0 } }

   REQUEST DBFCDX

   dbCreate( "testdbf", aStruct, "DBFCDX", .t., "MYALIAS" )

   ? "[" + MYALIAS->MEMO1 + "]"
   ? "[" + MYALIAS->MEMO2 + "]"
   ? "-"
   MYALIAS->( dbAppend() )
   MYALIAS->MEMO1 := "Hello world!"
   MYALIAS->MEMO2 := "Harbour power"
   ? "[" + MYALIAS->MEMO1 + "]"
   ? "[" + MYALIAS->MEMO2 + "]"
   MYALIAS->( dbAppend() )
   MYALIAS->MEMO1 := "This is a test for field MEMO1."
   MYALIAS->MEMO2 := "This is a test for field MEMO2."
   ? "[" + MYALIAS->MEMO1 + "]"
   ? "[" + MYALIAS->MEMO2 + "]"
   MYALIAS->NUMERIC := 90
   MYALIAS->DOUBLE := 120.138
   ? "[" + Str( MYALIAS->DOUBLE ) + "]"
   ? "[" + Str( MYALIAS->NUMERIC ) + "]"

   ? ""
   ? "Press any key..."
   InKey( 0 )

   ? ""
   ? "Append 50 records with memos..."
   for nI := 1 to 50
      MYALIAS->( dbAppend() )
      MYALIAS->MEMO1 := "This is a very long string. " + ;
                        "This may seem silly however strings like this are still " + ;
                        "used. Not by good programmers though, but I've seen " + ;
                        "stuff like this used for Copyright messages and other " + ;
                        "long text. What is the point to all of this you'd say. " + ;
                        "Well I am coming to the point right now, the constant " + ;
                        "string is limited to 256 characters and this string is " + ;
                        "a lot bigger. Do you get my drift ? If there is somebody " + ;
                        "who has read this line upto the very end: Esto es un " + ;
                        "sombrero grande rid¡culo." + Chr( 13 ) + Chr( 10 ) + ;
                        "/" + Chr( 13 ) + Chr( 10 ) + "[;-)" + Chr( 13 ) + Chr( 10 )+ ;
                        "\"
   next
   MYALIAS->( dbCommit() )

   ? "Records before ZAP:", MYALIAS->( LastRec() )
   ? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
      Directory( "testdbf.fpt" )[1][2]
   MYALIAS->( __dbZap() )
   MYALIAS->( dbCommit() )
   ? "Records after ZAP:", MYALIAS->( LastRec() )
   ? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
      Directory( "testdbf.fpt" )[1][2]
   ? "Value of fields MEMO1, MEMO2, DOUBLE and NUMERIC:"
   ? "[" + MYALIAS->MEMO1 + "]"
   ? "[" + MYALIAS->MEMO2 + "]"
   ? "[" + Str( MYALIAS->DOUBLE ) + "]"
   ? "[" + Str( MYALIAS->NUMERIC ) + "]"
   ? "Press any key..."
   InKey( 0 )
   dbCloseAll()

   dbCreate( "testdbf", aStruct,, .t., "MYALIAS" )

   for nI := 1 to 10
      MYALIAS->( dbAppend() )
      MYALIAS->NUMERIC := nI
      ? "Adding a record", nI
      if nI == 3 .or. nI == 7
         MYALIAS->( dbDelete() )
         ? "Deleting record", nI
      endif
   next
   MYALIAS->( dbCommit() )

   ? ""
   ? "With SET DELETED OFF"
   ? "Press any key..."
   InKey( 0 )

   MYALIAS->( dbGoTop() )
   do while !MYALIAS->( Eof() )
      ? MYALIAS->NUMERIC
      MYALIAS->( dbSkip() )
   enddo

   SET DELETED ON
   ? ""
   ? "With SET DELETED ON"
   ? "Press any key..."
   InKey( 0 )

   MYALIAS->( dbGoTop() )
   do while !MYALIAS->( Eof() )
      ? MYALIAS->NUMERIC
      MYALIAS->( dbSkip() )
   enddo

   ? ""
   ? "With SET DELETED ON"
   ? "and  SET FILTER TO MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8"
   ? "Press any key..."
   InKey( 0 )

   MYALIAS->( dbSetFilter( { || MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8 }, ;
                           "MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8" ) )
   MYALIAS->( dbGoTop() )
   do while !MYALIAS->( Eof() )
      ? MYALIAS->NUMERIC
      MYALIAS->( dbSkip() )
   enddo

   SET DELETED OFF
   ? ""
   ? "With SET DELETED OFF"
   ? "and  SET FILTER TO MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8"
   ? "Press any key..."
   InKey( 0 )

   MYALIAS->( dbSetFilter( { || MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8 }, ;
                           "MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8" ) )
   MYALIAS->( dbGoTop() )
   do while !MYALIAS->( Eof() )
      ? MYALIAS->NUMERIC
      MYALIAS->( dbSkip() )
   enddo

   ? "dbFilter() => " + dbFilter()
   ? ""

   ? "Testing __dbPack()"
   ? "Records before PACK:", MYALIAS->( LastRec() )
   ? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
      Directory( "testdbf.dbt" )[1][2]
   SET FILTER TO
   MYALIAS->( __dbPack() )
   MYALIAS->( dbCommit() )
   ? "Records after PACK:", MYALIAS->( LastRec() )
   ? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
      Directory( "testdbf.dbt" )[1][2]
   ? "Press any key..."
   InKey( 0 )
   ? "Value of fields:"
   MYALIAS->( dbGoTop() )
   do while !MYALIAS->( Eof() )
      ? MYALIAS->NUMERIC
      MYALIAS->( dbSkip() )
   enddo
   ? ""

   ? "Open test.dbf and LOCATE FOR TESTDBF->SALARY > 145000"
   ? "Press any key..."
   InKey( 0 )
   dbUseArea( ,, "test", "TESTDBF" )
   locate for TESTDBF->SALARY > 145000
   do while TESTDBF->( Found() )
      ? TESTDBF->FIRST, TESTDBF->LAST, TESTDBF->SALARY
      continue
   enddo
   ? ""
   ? "LOCATE FOR TESTDBF->MARRIED .AND. TESTDBF->FIRST > 'S'"
   ? "Press any key..."
   InKey( 0 )
   dbUseArea( ,, "test", "TESTDBF" )
   locate for TESTDBF->MARRIED .AND. TESTDBF->FIRST > 'S'
   do while TESTDBF->( Found() )
      ? TESTDBF->FIRST, TESTDBF->LAST, TESTDBF->MARRIED
      continue
   enddo

 return nil

Input Mask[edit]

A simple data base input mask (from the Wikipedia Clipper entry):

USE Customer SHARED NEW
clear
@  1, 0 SAY "CustNum" GET Customer->CustNum PICT "999999" VALID Customer->CustNum > 0
@  3, 0 SAY "Contact" GET Customer->Contact VALID !empty(Customer->Contact)
@  4, 0 SAY "Address" GET Customer->Address
READ

RDDs: What Functions Are Available?[edit]

http://harbourlanguage.blogspot.de/2010/06/understanding-harbour-rdd.html

ADO RDD: Much Ado About Nothing[edit]

https://searchsqlserver.techtarget.com/definition/ActiveX-Data-Objects http://cch4clipper.blogspot.com/2009/10/using-adordd-with-harbourxharbour.html

Case Study: Checkbook Balancing[edit]

Deleting records[edit]

? LASTREC()
DELETE RECORD 4
PACK
? LASTREC()

In this piece of code, the command DELETE marks the fourth record for deletion. But the file is not altered, not even by a CLOSE command. The PACK command actually removes the records marked for deletion (and also makes some additional work). The RECALL command removes the deleted flags. The function DELETED() returns .T. if the current record is marked for deletion, .F. if not.

The PACK command, which does the actual deletion of data from the table, PACK requires that the current database be USEd EXCLUSIVEly. If this condition is not met when the PACK command is invoked, CA-Clipper generates a runtime error. Additional work that PACK does is to update indexes on the table it alters (if any).

The commands DELETE ALL and PACK are executed by a single command called ZAP.

     &&  This example demonstrates a typical ZAP operation in a network
     &&   environment:

        USE Sales EXCLUSIVE NEW
        IF !NETERR()
           SET INDEX TO Sales, Branch, Salesman
           ZAP
           CLOSE Sales
        ELSE
           ? "Zap operation failed"
           BREAK
        ENDIF

An Indexed Example[edit]

USE Clients NEW
INDEX ON Name TO Clients UNIQUE

Suppose a table containing these data:

FSTNAME	LSTNAME
John   	Doe
John   	Doe
John   	Doe
Jane   	Doe

We can create a little index file with this piece of code:

SELECT 1
USE ind
? FILE("ind.ntx")
INDEX ON FstName TO ind
? FILE("ind.ntx") // we verify that a NTX file has been created

Set Relation - Working with more than one table[edit]

Other programming topics[edit]

Hash Arrays (Associative Arrays, Also Known as "Hash Tables")[edit]

In an associative array the elements are accessed using a string and not a number as in the "normal" array.

There are online "Calorie checkers" (which are also a nice idea for a web application for the last part) and I think it will be fun to make us a very small one (BEWARE: the information obtained from this program should not replace a varied, balanced diet and a healthy lifestyle).

   hFood := { => }
   hFood["Cornflakes"] := "370"
   hFood["Spaghetti"] := "101"
   hFood["Beef"] := "280"
   hFood["Ham"] := "240"
   hFood["Broccoli"] := "32"
   hFood["Grapefruit"] := "32"
   hFood["Spinach"] := "8"
   hFood["Chocolate"] := "500"
   ACCEPT "Enter a food: " TO cFood
   IF hb_HHasKey( hFood, cFood )
      ? "&cFood is " + hFood[cFood] + " calories per 100 grams"
   ELSE
      ? "&cFood: information not available"
   ENDIF

Now we will redo the example of the array of months, showing an alternative syntax for loading data into an hash array:

LOCAL month
hMonths := { "January" => 31, "February" => 28, "March" => 30,  "April" => 30, "May" => 31, "June" => 30, "July" => 31, "August" => 31, "September" => 30, "October" => 31, "November" => 30, "December" => 31 }

FOR EACH month in hMonths
   ? month:__ENUMKEY()
   ?? month:__ENUMVALUE()
NEXT

hb_HEval() is the AEval() equivalent for hash arrays.

See https://vivaclipper.wordpress.com/2013/01/18/hash-vs-table/.

Morse Code[edit]

Morse code was invented around 1840 by Samuel Finley Breese Morse and Alfred Vail to transmit messages across wires using devices called "telegraphs" they invented as well, revolutionizing long-distance communication. Initially the signals sent from one telegraph operated an electromagnet on the receiver's telegraph embossing the message on a strip of paper, later the receiver could also produce a sound. The first message transmitted was a citation of the book of Numbers from the King James Version: What hath God wrought!

Let us try to convert a text to Morse code: to begin we load the chart of Morse code in an associative array and define a code block which uses the function TONE() to sound a speaker tone for a dot or a dash - employing the IF() function - which is essentially the same function we encounter in spreadsheets - to select the right duration tone.

Then we prompts the user for a message and if none is supplied it uses the pangram "The quick brown fox jumps over a lazy dog" (a pangram is a sentence that contains all of the letters of the alphabet).

We show what message we will translate and starts two a for each loop: the first will scan the message showing which letter it will translate, one at a line (and will also use TONE() with zero frequency and appropriate lengths to make the pauses between letters and words) and then a nested for each loop will show whether it is about to sound a dot or a dash, running the code block to have the right sound.

LOCAL letter, ditdah
hMorseCode := { => }

hMorseCode[ "a" ] := ".-"
hMorseCode[ "b" ] := "-..."
hMorseCode[ "c" ] := "-.-."
hMorseCode[ "d" ] := "-.."
hMorseCode[ "e" ] := "."
hMorseCode[ "f" ] := "..-."
hMorseCode[ "g" ] := "--."
hMorseCode[ "h" ] := "...."
hMorseCode[ "i" ] := ".."
hMorseCode[ "j" ] := ".---"
hMorseCode[ "k" ] := "-.-"
hMorseCode[ "l" ] := ".-.."
hMorseCode[ "m" ] := "--"
hMorseCode[ "n" ] := "-."
hMorseCode[ "o" ] := "---"
hMorseCode[ "p" ] := ".--."
hMorseCode[ "q" ] := "--.-"
hMorseCode[ "r" ] := ".-."
hMorseCode[ "s" ] := "..."
hMorseCode[ "t" ] := "-"
hMorseCode[ "u" ] := "..-"
hMorseCode[ "v" ] := "...-"
hMorseCode[ "w" ] := ".--"
hMorseCode[ "x" ] := "-..-"
hMorseCode[ "y" ] := "-.--"
hMorseCode[ "z" ] := "--.."
hMorseCode[ "0" ] := "-----"
hMorseCode[ "1" ] := ".----"
hMorseCode[ "2" ] := "..---"
hMorseCode[ "3" ] := "...--"
hMorseCode[ "4" ] := "....-"
hMorseCode[ "5" ] := "....."
hMorseCode[ "6" ] := "-...."
hMorseCode[ "7" ] := "--..."
hMorseCode[ "8" ] := "---.."
hMorseCode[ "9" ] := "----."

nDotDuration := 4 && The dot duration is the basic unit of time measurement in Morse code transmission and we set it to 4/18 seconds
bPlayMorse := {| cCurrent| if ( cCurrent = ".", Tone( 480, nDotDuration ), Tone( 480, nDotDuration * 3 ) ) }

ACCEPT "Enter message: " TO message
IF message == "" && if no message we use a default one
  message := "The quick brown fox jumps over a lazy dog" && an English-language pangram — a sentence that contains all of the letters of the alphabet
ENDIF

? "Converting '"+Upper( message )+"' in Morse..."

FOR EACH letter in Lower( message )
  ? Upper(letter)+" "
    IF letter==" " 
      Tone( 0, nDotDuration * 7 ) && a long pause between words...
    ELSE
      FOR EACH ditdah in hMorseCode[ letter ]
        ?? ditdah
        Eval( bPlayMorse, ditdah )
      NEXT
    ENDIF
  Tone( 0, nDotDuration * 3 ) && ... and a short pause between letters
NEXT

Regular Expressions[edit]

Regular expressions can be considered an extension of wildcards. In a DOS or Windows prompt, for example, we could list all dbf files in the current directory with the command

dir *.dbf

where the asterisk, as they say, matches one or more characters. The other wildcard, the question mark, will match any character, but exactly one: the commands

dir ?.dbf
dir ??.dbf

will show, respectively, every dbf file in the current directory whose name is exactly one or two characters long.

Regular expressions are much more flexible. They were invented by the mathematician Stephen Cole Kleene (the asterisk wildcard derives from an operator he defined and which is called Kleene star). Their flexibility allows us to write an expression that can match dbf filenames one or two characters long in a single expression which looks like this:

.{1,2}\.dbf

There are different types of regular expressions: basic, extended, PCRE (Perl-compatible regular expressions, http://www.pcre.org/), and many other different implementations.

As far as we are concerned we can limit ourselves to PCRE and refer to the documentation at these URLs: https://github.com/Petewg/harbour-core/wiki/Regular-Expressions and https://github.com/zgamero/sandbox/wiki/X_RegularExpressions, https://www.pcre.org/original/doc/html/pcrepattern.html, https://www.debuggex.com/cheatsheet/regex/pcre. There are large books entirely devoted about regular expressions, such as "Mastering Regular Expressions" by Jeffrey E.F. Friedl, just to say there's so much to explore about this topic.

Here https://www.rexegg.com/regex-cookbook.html is a cookbook to see examples; and here https://www.regexpal.com/ and here https://regexr.com/ there are interactive tools to try online (and interactively) expressions.

Regular expressions are not a panacea that solves all data validation problems: for example, it is not possible to use them to validate dates, as the number of days allowed for each month depends on the month (and in the case of February also on the year).

As a first example, we will redo the program that receives a letter from the keyboard and reports if it is a vowel or not.

LOCAL cRegEx := "[aeiou]"
WAIT "Key in a letter: " TO char
? char + " is" + iif( hb_regexLike( cRegEx, char, .F. ), "", " not" ) + " a vowel."

The synopsis of hb_RegExLike is this: hb_RegExLike(<hRegEx>|<cRegEx>, <cText> [, <lCaseSensitive>] [, <lMultiLine>]).

The next example is again the program to average of an array which grows until we insert numbers as input. This time we use a regular expression to validate the numbers we enter.

PROCEDURE MAIN

   LOCAL n, sum := 0, average    && let's initialize the variables
   LOCAL reNumber := "-?[0-9]+(\.?[0-9]+)?" && and a regular expression that matches numbers (optionally with a decimal point and a sign)
   aNumbers := {}                && and an empty array

   ? "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 hb_RegExLike ( reNumber, item ) && we replace IsDec() with hb_RegExLike()
         AAdd ( aNumbers, val(item) ) && if we did, then we add the number as a new element to the array
         sum := sum + val(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

Error Handling With “Try-Catch-Finally” Block[edit]

This construct is analogous to those found in Java, JavaScript and C# (and similar to the C++'s one). They are actually translated into the BEGIN SEQUENCE structure and therefore perhaps they are useful only for those who are accustomed to those languages.

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
      TRY
         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)...
      CATCH
         EXIT                    && ...if we did not, we quit this loop
      END
   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

Multithreading[edit]

http://harbourlanguage.blogspot.com/2010/04/harbour-multi-thread.html

https://github.com/Petewg/harbour-core/wiki/MultiThreading

see samples in \hb30\tests\mt

INET[edit]

https://github.com/Petewg/harbour-core/wiki/Harbour-INET-API

PDF[edit]

hbhpdf: Libharu http://libharu.org/ bindings. Documentation: https://github.com/libharu/libharu/wiki/API%3A-Document

http://www.pagescript32.com/

https://github.com/MRonaldo/MR-Tools/downloads

How to create a DLL[edit]

http://www.xharbour.com/xhdn/referenceguide/index.asp?page=article&article=create_dll

Debugging[edit]

http://www.kresin.ru/en/debugger.html

You may have heard or read the legend that the term bug appeared when an actual bug was found in the Harvard's Mark II computer. According to one of my books from the high school, it was a little butterfly found in the ENIAC!

Text books less prone to folklore call this a story without foundation, for example

https://www.computerworld.com/article/2515435/moth-in-the-machine--debugging-the-origins-of--bug-.html states that Thomas Edison used the word already in 1878. The same goes with the Wikipedia entry →Software bug which even includes a picture of the moth actually found in the Harvard Mark II and describes the whole history.

A page from the →Harvard Mark II electromechanical computer's log, featuring a dead moth that was removed from the device.

Object Oriented Programming[edit]

Clipper had very limited support for OO programming. His successor CA-Visual Objects was much more advanced in this respect. However Visual Objects never had a great success, and third-party producers provided OOP libraries for Clipper, among which the most famous were Class(y), TopClass, Fivewin and Clip4Win.

Looking at Object-Oriented Programming from a Safety Distance[edit]

Let us suppose we're dealing with the distance function given the Cartesian coordinates of the points (http://mathinsight.org/cartesian_coordinates). The formulas we'll apply are:

for (Euclidean) distance on a real line, Euclidean plane and Euclidean space respectively.

In procedural programming our functions would look like this:

? distance1d(4,-3)
? distance2d(2,-3,-1,-2)
? distance3d(1,1,1,4,4,4)

        FUNCTION distance1d( x1, x2 )
           RETURN sqrt((x2-x1)^2)

        FUNCTION distance2d( x1,y1,x2,y2 )
           RETURN sqrt((x2-x1)^2+(y2-y1)^2)

        FUNCTION distance3d( x1,y1,z1,x2,y2,z2 )
           RETURN sqrt((x1-x2)^2+(y1-y2)^2+(z1-z2)^2)

We defined three functions, with different names, which take as arguments the coordinates. But doing so we need to pass six arguments for the distance in a three-dimensional space.

If we're doing it with object oriented programming we may get something like this:

#include "hbclass.ch"

   CREATE CLASS Point1D

   VAR Abscissa    // the abscissa of our point

METHOD New( Abscissa )    // Constructor

METHOD Distance( Point )

   ENDCLASS

   CREATE CLASS Point2D INHERIT Point1D

   VAR Ordinate    // the ordinate of our point

METHOD New( Abscissa, Ordinate )    // Constructor

METHOD Distance( Point )

   ENDCLASS

   CREATE CLASS Point3D INHERIT Point2D

   VAR Zcoord    // the Z-coordinate of our point

METHOD New( Zcoord )    // Constructor

METHOD Distance( Point )

   ENDCLASS

&& Constructors Zone
METHOD New( Abscissa ) CLASS Point1D

   ::Abscissa := Abscissa

   RETURN Self

METHOD New( Abscissa, Ordinate ) CLASS Point2D

   ::Abscissa := Abscissa
   ::Ordinate := Ordinate

   RETURN Self

METHOD New( Abscissa, Ordinate, Zcoord ) CLASS Point3D

   ::Abscissa := Abscissa
   ::Ordinate := Ordinate
   ::Zcoord := Zcoord

   RETURN Self

&&Distances Methods

METHOD Distance( Point ) CLASS Point1D

   RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 )

METHOD Distance( Point ) CLASS Point2D

   RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 + ( Self:Ordinate - Point:Ordinate ) ^ 2 )

METHOD Distance( Point ) CLASS Point3D

   RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 + ( Self:Ordinate - Point:Ordinate ) ^ 2 + ( Self:Zcoord - Point:Zcoord ) ^ 2 )

PROCEDURE Main()

   FirstPoint := Point1D():New( 3 )
   SecondPoint := Point1D():New( - 3 )
   ? FirstPoint:Abscissa
   ? FirstPoint:Distance( SecondPoint )
   ThirdPoint := Point2D():New( 2, - 3 )
   FourthPoint := Point2D():New( - 1, - 2 )
   ? ThirdPoint:Distance( FourthPoint )
   FifthPoint := Point3D():New( 1, 1, 1 )
   SixthPoint := Point3D():New( 4, 4, 4 )
   ? FifthPoint:Distance( SixthPoint )

   RETURN

Here we've defined three classes, their constructors, and a distance method for each of them, and showed how to use them. It is also a simple example of how inheritance works. Other concepts are encapsulation (information hiding or data hiding), abstraction, polymorphism, overloading and overriding of methods. Inheritance plays a central role in a key concept: reuse. A class can be reused in a software project if it is exactly what was needed; or if it is not exactly what was needed it can be extended by defining a subclass. Much like the design of a database, the design of object oriented class is an art with its principles, see for example http://www.oodesign.com/, http://www.codeproject.com/articles/567768/object-oriented-design-principles and pages about UML like http://www.uml-diagrams.org/uml-object-oriented-concepts.html.

The first thing to note is that we start by including the clipper header file hbclass.ch, the header file for Class commands.

Access to variables and methods of an object is done via the colon operator. A prepended double colon refers to variables with a larger scope (such as those passed to a method).

In the code above we defined three classes, each one implementing a Point. Point2D for example was defined as a class extending Point1D, that is a generalization of the concept. A method Distance was given for each of the classes.

A line such as

  ? FifthPoint:Distance( SixthPoint )

contain the output command ?, the reference to an object (FifthPoint in this case), an invocation of the Distance method :Distance, to which another point was passed ( SixthPoint ).

It is also possible to write a Distance function which takes two arguments of a Point class, that may look like this:

FUNCTION Distance ( Point1, Point2 )
   RETURN Sqrt( ( Point1:Abscissa - Point2:Abscissa ) ^ 2 + ( Point1:Ordinate - Point2:Ordinate ) ^ 2 + ( Point1:Zcoord - Point2:Zcoord ) ^ 2 )

   ? Distance( FifthPoint, SixthPoint )

This is, however, not object-oriented programming, as we could have written the same function with a not object-oriented language such as Pascal or C, passing it two structs, or records as Pascal calls them, named Point1 and Point2.

It is important the fact that some data internal to the object (set by the real programmer of the thing) can't be changed by the object user. As a real life example, we can consider a car engine. The provider of the object set a number of cylinders, and we have not many chances of changing that: we've got to regard it as a constant. There is naturally a number of interesting formulas about engines that engineers use (some to be seen at http://www.thecartech.com/subjects/engine/engine_formulas.htm). For example, the one for computing the Engine Volumetric Efficiency given the volume of air taken into a cylinder and the cylinder swept volume. Here comes the importance of data hiding: nobody needs to know those informations to get his car going. Also, when someone designes an engine they probably don't expect the user to change the volumetric efficiency by operating on the engine. The same thing is obtained in object oriented programming using visibility modifiers, or access modifiers.

   [CREATE] CLASS <cClassName> [ FROM | INHERIT <cSuperClass1> [, ... ,<cSuperClassN>] ]
             [ MODULE FRIENDLY ] [ STATIC ] [ FUNCTION <cFuncName> ]

   [HIDDEN:]
      [ CLASSDATA | CLASSVAR  | CLASS VAR <DataName1>]
      [ DATA | VAR  <DataName1> [,<DataNameN>] [ AS <type> ] [ INIT <uValue> ]
             [[EXPORTED | VISIBLE] | [PROTECTED] | [HIDDEN]] [READONLY | RO] ]
      ...
      [ METHOD <MethodName>( [<params,...>] ) [CONSTRUCTOR] ]
      [ METHOD <MethodName>( [<params,...>] ) INLINE <Code,...> ]
      [ METHOD <MethodName>( [<params,...>] ) BLOCK  <CodeBlock> ]
      [ METHOD <MethodName>( [<params,...>] ) EXTERN <funcName>([<args,...>]) ]
      [ METHOD <MethodName>( [<params,...>] ) SETGET ]
      [ METHOD <MethodName>( [<params,...>] ) VIRTUAL ]
      [ METHOD <MethodName>( [<params,...>] ) OPERATOR <op> ]
      [ ERROR HANDLER <MethodName>( [<params,...>] ) ]
      [ ON ERROR <MethodName>( [<params,...>] ) ]
      ...
   [PROTECTED:]
      ...
   [VISIBLE:]
   [EXPORTED:]
      ...

   [FRIEND CLASS <ClassName,...>]
   [FRIEND FUNCTION <FuncName,...>]

   [SYNC METHOD <cSyncMethod>]

   ENDCLASS [ LOCK | LOCKED ]

Another example[edit]

Copied verbatim from w:Harbour (software)

 #include "hbclass.ch"

 PROCEDURE Main()

    LOCAL oPerson

    CLS

    oPerson := Person():New( "Dave" )

    oPerson:Eyes := "Invalid"

    oPerson:Eyes := "Blue"

    Alert( oPerson:Describe() )

    RETURN

 CREATE CLASS Person

    VAR Name INIT ""

    METHOD New( cName )
    METHOD Describe()

    ACCESS Eyes INLINE ::pvtEyes
    ASSIGN Eyes( x ) INLINE iif( HB_ISSTRING( x ) .AND. x $ "Blue,Brown,Green", ::pvtEyes := x, Alert( "Invalid value" ) )

    PROTECTED:

    VAR pvtEyes

 ENDCLASS

 // Sample of normal Method definition
 METHOD New( cName ) CLASS Person

    ::Name := cName

    RETURN Self

 METHOD Describe() CLASS Person

    LOCAL cDescription

    IF Empty( ::Name )
       cDescription := "I have no name yet."
    ELSE
       cDescription := "My name is: " + ::Name + ";"
    ENDIF

    IF ! Empty( ::Eyes )
       cDescription += "my eyes' color is: " + ::Eyes
    ENDIF

    RETURN cDescription