HbMk2 : Harbour Maker

Harbour Make (hbmk2) 3.2.0dev (Rev. 18805)
Copyright (c) 1999-2013, Viktor Szakáts
http://harbour-project.org/

Syntax:

hbmk2 [options] [<script[s]>] <src[s][.prg|.c|.obj|.o|.rc|.res|.def|.po|.pot|.hbl|@.clp|.d|.ch]>

Options:
-o<outname> output file name
-l<libname> link with <libname> library. <libname> should be without
path, extension and ‘lib’ prefix (unless part of
libname). Do not add core Harbour libraries, they are
automatically added as needed.
-L<libpath> additional path to search for libraries
-i<p>|-incpath=<p> additional path to search for headers
-static|-shared link with static/shared libs
-mt|-st link with multi/single-thread Harbour VM
-gt<name> link with GT<name> GT driver, can be repeated to link
with more GTs. First one will be the default at runtime
-inc[-] enable/disable incremental build mode (default: disabled)
-hbexe create executable (default)
-hblib create static library
-hbdyn create dynamic library (without linked Harbour VM)
-hbdynvm create dynamic library
-hbimplib create import library

-gui|-std create GUI/console executable
-main=<mainfunc> override the name of starting function/procedure
-request=<func> force function/procedure to be linked
-fullstatic link with all static libs
-pic[-] create position independent object code (always enabled
in -hbdyn/-hbdynvm modes)
-[full|fix]shared create shared Harbour binaries without/with absolute dir
reference to Harbour library (default: ‘fullshared’ when
Harbour is installed on system location, ‘fixshared’
otherwise) (fix/full option in *nix only)
-nulrdd[-] link with nulrdd
-debug[-] add/exclude C compiler debug info. For Harbour level
debug, use Harbour option -b as usual
-optim[-] toggle C compiler optimizations (default: on)
-cpp[-] force C/C++ mode
-cpp=<value> select C++ mode. Allowed values are: def, yes, no
-map[-] create (or not) a map file
-implib[-] create (or not) an import library (in -hbdyn/-hbexe
mode). The name will have a postfix added.
-implib=<output> create import library (in -hbdyn/-hbexe mode) name to
<output> (default: same as output)
-ln=<link> create symbolic link pointing to <output> (<link> is
considered relative to <output>)
-strip[-] strip (no strip) binaries
-trace[-] show commands executed
-beep[-] enable (or disable) single beep on successful exit,
double beep on failure
-ignore[-] ignore errors when running compiler tools (default: off)
-hbcppmm[-] override standard C++ memory management functions with
Harbour ones
-winuni[-] select between UNICODE (WIDE) and ANSI compilation modes
(default: ANSI) (Windows only. For WinCE it is always set
to UNICODE)
-nohblib[-] do not use static core Harbour libraries when linking
-nodefgt[-] do not link default GTs (effective in -static mode)
-nolibgrouping[-] disable library grouping on gcc based compilers
-nomiscsyslib[-] do not add extra list of system libraries to default
library list
-traceonly show commands to be executed, but do not execute them
-warn=<lev> set C compiler warning level
<lev> can be: max, yes, low, no, def (default: yes)
-compr=<lev> compress executable/dynamic lib (needs UPX)
<lev> can be: yes, no, min, max
-run[-] run/do not run output executable
-vcshead=<file> generate .ch header file with local repository
information. SVN, CVS, Git, Mercurial, Bazaar, Fossil and
Monotone are currently supported. Generated header will
define macro _HBMK_VCS_TYPE_ with the name of detected
VCS and _HBMK_VCS_ID_ with the unique ID of local
repository
-tshead=<file> generate .ch header file with timestamp information.
Generated header will define macros _HBMK_BUILD_DATE_,
_HBMK_BUILD_TIME_, _HBMK_BUILD_TIMESTAMP_ with the
date/time of build
-icon=<file> set <file> as application icon. <file> should be a
supported format on the target platform
-manifest=<file> embed manifest <file> in executable/dynamic lib (Windows
only)
-sign=<key> sign executable with <key> (Windows and Darwin only)
-signpw=<pw> use <pw> as password when signing executable (Windows and
Darwin only)
-instfile=<g:file> add <file> in to the list of files to be copied to path
specified by -instpath option. <g> is an optional copy
group (case sensitive), it must be at least two
characters long. In case you don’t specify <file>, the
list of files in that group will be emptied.
-instpath=<g:path> copy target to <path>. if <path> is a directory, it
should end with path separatorm, in this case files
specified by -instfile option will also be copied. can be
specified multiple times. <g> is an optional copy group,
it must be at least two characters long. Build target
will be automatically copied to default (empty) copy
group.
-instforce[-] copy target to install path even if it is up to date
-depimplib[-] enable (or disable) import library generation for import
library sources specified in -depimplibs= options
(default: yes)
-stop[=<text>] stop without doing anything and display <text> if
specified
-echo=<text> echo text on screen
-pause force waiting for a key on exit in case of failure (with
alternate GTs only)
-info turn on informational messages
-quiet[-] suppress all screen messages

-bldf[-] inherit all/no (default) flags from Harbour build
-bldf=[p][c][l] inherit .prg/.c/linker flags (or none) from Harbour build
-F<framework> link with <framework> framework (Darwin only)
-cflag=<f> pass single flag to C compiler
-resflag=<f> pass single flag to resource compiler (Windows only)
-ldflag=<f> pass single flag to linker (executable)
-aflag=<f> pass single flag to linker (static library)
-dflag=<f> pass single flag to linker (dynamic library)
-iflag=<f> pass single flag to import library creation command
-prgflag=<f> pass single flag to Harbour compiler
-runflag=<f> pass single flag to output executable when -run option is
used
-cflag+=<f> pass single flag to C compiler overriding C compiler
flags added by hbmk2 itself. Use with caution.
-3rd=<f> options/flags reserved for 3rd party tools, always
ignored by hbmk2 itself
-env:<e>[<o>[<v>]] alter local environment. <e> is the name of the
environment variable to alter. <o> can be ‘=’ to
set/override, ‘-‘ to delete, ‘+’ to append to the end of
existing value, ‘#’ to insert to the beginning of
existing value. <v> is the value to set/append/insert.
-jobs=<n> start n compilation threads (multiprocess platforms only)
-head=<m> control source header parsing (in incremental build mode)
<m> can be: native (uses compiler to extract
dependencies), full (default, uses simple text parser on
the whole file), dep, off
-rebuild rebuild (in incremental build mode)
-rebuildall rebuild with sub-projects (in incremental build mode)
-clean clean (in incremental build mode)
-workdir=<dir> working directory
(default: .hbmk/plat/comp in incremental mode, OS temp
directory otherwise)

-hbl[=<output>] output .hbl filename. %{hb_lng} macro is accepted in
filename
-lng=<languages> list of languages to be replaced in %{hb_lng} macros in
.pot/.po filenames and output .hbl/.po filenames. Comma
separared list:
-lng=en,hu-HU,de
-po=<output> create/update .po file from source. Merge it with
previous .po file of the same name
-minipo[-] do (not) add Harbour version number and source file
reference to .po (default: add them)
-rebuildpo recreate .po file, thus removing all obsolete entries in
it

-hbx=[<.ch>] Create Harbour header (in .hbx format) with all external
symbols. Empty parameter will disable it.
-autohbc=<.ch:.hbc> <.ch> is a header file name. <.hbc> is a .hbc filename to
be automatically included in case the header is found in
any of the compiled sources. (EXPERIMENTAL)

-deppkgname=<d:n> <d> is the name of the dependency. <n> name of the
package depedency. Can be specified multiple times.
-depkeyhead=<d:h> <d> is the name of the dependency. <h> is the key header
(.h) of the package dependency. Multiple alternative
headers can be specified.
-depoptional=<d:f> <d> is the name of the dependency. <f> can be ‘yes’ or
‘no’, specifies whether the dependency is optional.
Default: no
-depcontrol=<d:v> <d> is the name of the dependency. <v> is a value that
controls how detection is done. Accepted values: no, yes,
force, nolocal, local. Default: content of envvar
HBMK_WITH_<d>
-depincroot=<d:r> <d> is the name of the dependency. Set <r> as root
directory for paths specified in -depincpath options.
-depincpath=<d:i> <d> is the name of the dependency. Add <i> to the header
detection path list.
-depincpathlocal= <d> is the name of the dependency. Add <i> to the header
<d:i> detection path list, where <i> is pointing to a directory
local to the project and containing an embedded (or
locally hosted) dependency.
-depimplibs=<d:dll> <d> is the name of the dependency. Add <dll> to the
import library source list.
-depimplibd=<d:lib> <d> is the name of the dependency. Set generated import
library name to <lib>

-plugin=<filename> add plugin. <filename> can be: .hb, .prg, .hrb
-pi=<filename> pass input file to plugins
-pflag=<f> pass single flag to plugins

Options below are available on command line only:

-target=<script> specify a new build target. <script> can be .prg (or no
extension) or .hbp file. Note that .hbp files are
automatically considered as separate targets.

-hbrun run target
-hbraw stop after running Harbour compiler
-hbcmp|-clipper stop after creating the object files
create link/copy hbmk2 to hbcmp/clipper for the same
effect
-hbcc accept raw C flags
create link/copy hbmk2 to hbcc for the same effect
-hblnk accept raw linker flags
-autohbm[-] enable (or disable) processing of hbmk.hbm in current
directory (default: yes)
-hb10 enable Harbour 1.0.x compatibility mode
-hb20 enable Harbour 2.0.x compatibility mode
-xhb enable xhb mode
-hbc enable pure C mode
-rtlink
-blinker
-exospace emulate Clipper compatible linker behavior
create link/copy hbmk2 to rtlink/blinker/exospace for the
same effect

-hbreg[=global] register Harbour Script (.hb) with hbmk2 (Windows only)
-hbunreg[=global] unregister Harbour Script (.hb) from hbmk2 (Windows only)

-hbmake=<file> convert hbmake project <file> to .hbp file
-xbp=<file> convert .xbp (xbuild) project <file> to .hbp file
-xhp=<file> convert .xhp (xMate) project <file> to .hbp file

–hbdirbin output Harbour binary directory
–hbdirdyn output Harbour dynamic library directory
–hbdirlib output Harbour static library directory
–hbdirinc output Harbour header directory
–hbinfo[=nested] output Harbour build information. Output is in JSON
format. The included paths always contain forward
slashes. Each JSON block is followed by an 0x0A byte.

-plat=<platform> force/select target platform (default: autodetection)
-comp=<compiler> force/select C compiler (default: autodetection)
Special value:
– bld: use original build settings (default on *nix)
-cpu=<cpu> select target CPU. (EXPERIMENTAL)
-build=<name> use a specific build name
-lang=<lang> override default language. Similar to HB_LANG envvar.
-width=<n> set output width to <n> characters (0=unlimited).
-shl show sub-project level in output lines
–version display version header only

Notes:
– <script> can be:
<@script> or <script.hbm>: command line options in file
<script.hbp>: command line options in file, it also marks a new target if
specified on the command line
<script.hbc>: package configuration file
– Multiple -l, -L, -i and <script> parameters are accepted.
– Regular Harbour compiler options are also accepted.
(see them with -harbourhelp option)
– hbmk.hbc option file in hbmk2 directory is always processed if it exists.
On *nix platforms ~/.harbour, /etc/harbour, <base>/etc/harbour, <base>/etc
are checked (in that order) before the hbmk2 directory.
– hbmk.hbm make script in current directory is always processed if it exists.
– .hbc options (they should come in separate lines): libs=[<libname[s]>],
hbcs=[<.hbc file[s]>], gt=[gtname], syslibs=[<libname[s]>],
frameworks=[<framework[s]>], prgflags=[Harbour flags], cflags=[C compiler
flags], resflags=[resource compiler flags], ldflags=[linker flags],
pflags=[flags for plugins], libpaths=[paths], sources=

,
headers=[Harbour header files], psources=

,
incpaths=[paths], requests=[func], instfiles=[files], instpaths=[paths],
autohbcs=[<.ch>:<.hbc>], plugins=[plugins],
gui|mt|pic|shared|nulrdd|nodefgt|debug|opt|map|strip|hbcppmm|winuni|implib|
run|inc=[yes|no], cpp=[yes|no|def], warn=[max|yes|low|no|def],
compr=[yes|no|min|max], head=[off|full|native|dep], skip=<reason>,
stop=<reason>, echo=<text>
Lines starting with ‘#’ char are ignored
– Platform filters are accepted in each .hbc line and with several options.
Filter format: {[!][<plat>|<comp>|<cpu>|<keyword>]}. Filters can be
combined using ‘&’, ‘|’ operators and grouped by parentheses. Ex.: {win},
{gcc}, {linux|darwin}, {win&!pocc}, {(win|linux)&!watcom}, {unix&mt&gui},
-cflag={win}-DMYDEF, -stop{dos}, -stop{!allwin},
{allwin|allmsvc|allgcc|allmingw|allicc|allbcc|allpocc|unix},
{x86|x86_64|ia64|arm|mips|sh},
{debug|nodebug|gui|std|mt|st|shared|static|winuni|winansi|xhb}
– Certain .hbc lines (libs=, hbcs=, prgflags=, cflags=, ldflags=, libpaths=,
instfiles=, instpaths=, echo=) and corresponding command line parameters
will accept macros: ${hb_root}, ${hb_dir}, ${hb_dirname}, ${hb_name},
${hb_self}, ${hb_curdir}, ${hb_tempdir}, ${hb_targetname},
${hb_targettype}, ${hb_plat}, ${hb_comp}, ${hb_comp_ver}, ${hb_build},
${hb_cpu}, ${hb_work}, ${hb_workdynsub}, ${hb_dynprefix}, ${hb_dynsuffix},
${hb_dynext}, ${hb_ver}, ${hb_verstr}, ${hb_major}, ${hb_minor},
${hb_release}, ${hb_status}, ${hb_revision}, ${hb_host_plat},
${hb_host_plat_unix}, ${hb_bin}, ${hb_lib}, ${hb_lib3rd}, ${hb_dyn},
${hb_inc}, ${hb_first}, ${hb_outputdir}, ${hb_outputname}, ${hb_level},
${<envvar>}. libpaths= also accepts %{hb_name} which translates to the name
of the .hbc file under search.
– Options accepting macros also support command substitution. Enclose command
inside “, and, if the command contains space, also enclose in double
quotes. F.e. “-cflag=`wx-config –cflags`”, or
ldflags={unix&gcc}”`wx-config –libs`”.
– Libraries and object files built with/for CA-Cl*pper won’t work with any
supported platform/compiler.
– Defaults and feature support may vary by platform/compiler.
– Options can also be specified in environment variable HBMK_OPTIONS
– .hb or .hrb file passed as first parameter will be run as Harbour script.
Note, for Harbour scripts, the codepage is set to UTF-8 by default.
– . (dot) passed as first parameter will enter the interactive Harbour shell.

Supported <comp> values for each supported <plat> value:
– linux : gcc, clang, icc, watcom, sunpro, open64
– darwin : gcc, clang, icc
– win : mingw, msvc, clang, bcc, bcc64, watcom, icc, pocc, xcc,
mingw64, msvc64, msvcia64, iccia64, pocc64
– wce : mingwarm, mingw, msvcarm, poccarm
– os2 : gcc, gccomf, watcom
– dos : djgpp, watcom
– bsd : gcc, clang, pcc
– hpux : gcc
– beos : gcc
– qnx : gcc
– android : gcc, gccarm
– vxworks : gcc, diab
– symbian : gcc
– cygwin : gcc
– minix : gcc, clang, ack
– aix : gcc
– sunos : gcc, sunpro

For further info :

 

 

 

Harbour Utilities / Tools

Harbour :

Harbour.exe : Harbour compiler

HbFormat : Formats the source code according to certain criteria

HbIDE  : Visual Development environment for the Harbour and other xBase dialects

Hbi18n : A tool that makes finding applications for other languages

HbMk2    : Ultimate project maker for Harbour applications

HbNetIO  : Harbour NETIO Server

HbPP   : Harbour preprocessor

HbRun  : Console interpreter and .prg/.hrb runner for the Harbour Language

HbTest   : Harbour Regression Test Suite

3rth Party:

rcc.exe  : Qt resource compiler

uic.exe  : Qt User Interface Compiler

upx.exe  : Ultimate Packer for eXecutables

windres  : Resource compiler

Enter  windres3011 –help for options.

Parsing Text – Tokens

/*
From Harbour changelog (at 2007-04-04 10:35 UTC+0200 By Przemyslaw Czerpak )
Added set of functions to manipulate string tokens:
HB_TOKENCOUNT( <cString>, [ <cDelim> ], [ <lSkipStrings> ],
 [ <lDoubleQuoteOnly> ] ) -> <nTokens>

 HB_TOKENGET( <cString>, <nToken>, [ <cDelim> ], [ <lSkipStrings> ],
 [ <lDoubleQuoteOnly> ] ) -> <cToken>

 HB_TOKENPTR( <cString>, @<nSkip>, [ <cDelim> ], [ <lSkipStrings> ],
 [ <lDoubleQuoteOnly> ] ) -> <cToken>

 HB_ATOKENS( <cString>, [ <cDelim> ], [ <lSkipStrings> ],
 [ <lDoubleQuoteOnly> ] ) -> <aTokens>

 All these functions use the same method of tokenization. They can
 accept as delimiters string longer then one character. By default
 they are using " " as delimiter. " " delimiter has special mening

 Unlike other delimiters repeted ' ' characters does not create empty
 tokens, f.e.: 

 HB_ATOKENS( " 1 2 3 " ) returns array:
 { "1", "2", "3" }

 Any other delimiters are restrictly counted, f.e.:

 HB_ATOKENS( ",,1,,2,") returns array:
 { "", "", "1", "", "2", "" }
And a strong suggession made at 2009-12-09 21:25 UTC+0100 ( By Przemyslaw Czerpak )
I strongly suggest to use hb_aTokens() and hb_token*() functions.
 They have more options and for really large data many times
 (even hundreds times) faster.

*/
#define CRLF HB_OsNewLine()
PROCEDURE Main()
LOCAL cTextFName := "Shakespeare.txt",;
 c1Line 

 SET COLO TO "W/B"
 SetMode( 40, 120 )

 CLS

 HB_MEMOWRIT( cTextFName,;
 "When in eternal lines to time thou grow'st," + CRLF + ;
 "So long as men can breathe, or eyes can see," + CRLF + ;
 "So long lives this, and this gives life to thee." )

 aLines := HB_ATOKENS( MEMOREAD( cTextFName ), CRLF )

 ?
 ? "Text file line by line :"
 ?
 AEVAL( aLines, { | c1Line | QOUT( c1Line ) } )
 ?
 WAIT "Press a key for parsing as words"
 CLS
 ?
 ? "Text file word by word :"
 ?
 FOR EACH c1Line IN aLines
 a1Line := HB_ATOKENS( c1Line ) 
 AEVAL( a1Line, { | c1Word | QOUT( c1Word ) } )
 NEXT 
 ?
 WAIT "Press a key for parsing directly as words"
 CLS
 ?
 ? "Text file directly word by word :"
 ?
 aWords := HB_ATOKENS( MEMOREAD( cTextFName ) )
 AEVAL( aWords, { | c1Word | QOUT( c1Word ) } ) 

 ?
 @ MAXROW(), 0
 WAIT "EOF TP_Token.prg" 

RETURN // TP_Token.Main()
 TP_Token

Parsing Text – FParse()

/*
FParse()
Parses a delimited text file and loads it into an array.
Syntax :
FParse( <cFileName>, <cDelimiter> ) --> aTextArray
Arguments :
<cFileName> : This is a character string holding the name of the text file to load 
 into an array. It must include path and file extension. 
 If the path is omitted from <cFileName>, 
 the file is searched in the current directory. 

 <cDelimiter> : This is a single character used to parse a single line of text. 
 It defaults to the comma.
Return :
The function returns a two dimensional array, or an empty array when the file 
cannot be opened. 

Description :

 Function FParse() reads a delimited text file and parses each line 
 of the file at <cDelimiter>. The result of line parsing is stored in an array.
This array, again, is collected in the returned array, 
 making it a two dimensional array
FParse() is mainly designed to read the comma-separated values (or CSV) file format, 
 were fields are separated with commas and records with new-line character(s). 

Library is : xHb 

*/
#define CRLF HB_OsNewLine()
PROCEDURE Main()
LOCAL cTextFName := "Shakespeare.txt",;
      a1Line 

 SET COLO TO "W/B"
 SetMode( 40, 120 )

 CLS

 HB_MEMOWRIT( cTextFName,;
              "When in eternal lines to time thou grow'st," + CRLF + ;
              "So long as men can breathe, or eyes can see," + CRLF + ;
              "So long lives this, and this gives life to thee." )

 aLines := FParse( cTextFName, " " )

 ?
 ? "Text file word by word :"
 ?
 FOR EACH a1Line IN aLines
    AEVAL( a1Line, { | c1Word | QOUT( c1Word ) } )
 NEXT 
 ?
 @ MAXROW(), 0
 WAIT "EOF TP_FParse.prg" 

RETURN // TP_FParse.Main()

TP_FParse

Clipper and Networking

Clipper and Networking

HMG-IDE as a DB Manager

HMG-IDE as a Database Manager

 

Beside other capabilities, HMG-IDE is a clever and practical database manager. You can easily build, open, browse and modify structure of tables (.dbf files).

Building a new table :

1° : Select “New Table” in Project menu :

NewTable

2°  “Table Name” input box will be open; enter a name for new table and press “Ok”:

 NewTableName

3° “Table Modify” page will be open :

TableModify

Define here structure of your new table.

After pressing “Save” button, you will see name of new table in “Tables” tab of project browser:

TablesTab

4° You can use the table by double-click its name in the “Tables” tab of project browser; “Table Edit” page will be open:

 TableEdit

 5° As seen, you can browse or modify structure of the table by pressing appropriate button. Modify structure page is same as the above 3.th step. Browse page is like this :

TableBrowse

Open an existing table :

If your table is in same folder of project ( .hbp) file IDE will open it automatically.  For open tables from other places use “Import File” option of project menu :

ImportFile

In this case, HMG-IDE will copy this table to your project folder.

Nested Hashes

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
/*
 Since a <Value> of a hash's pair may be in any scalar or complex type,
 a hash may be nested by assigning an another hash to a hash <Value>. 

*/
PROCEDURE Main()
   SET COLO TO "W/B"
   SetMode( 50, 120 )

   CLS

   hSouth := { 'Argentina' => "Buenos Aires",;
               'Brasil'    => "Brasilia",;
               'Chile'     => "Santiago" }

   hNorth:= { 'USA'    => "Washington DC",;
              'Canada' => "Ottawa",; 
              'Mexico' => "Mexico City" } 

   * a hash contains two hashes :

   hAmerica := { "America" => { "North" => hNorth,;
                                "South" => hSouth } } 

   * Standart array indexing syntax :

   ? hAmerica[ "America", "North", "USA" ] // Washington DC

   * Alternate syntax to indexing :

   ? hAmerica[ "America"][ "South" ][ "Chile" ] // Santiago

   ?
   @ MAXROW(), 0
   WAIT "EOF HashNest.prg"

RETURN // HashNest.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

Hash vs Table

Consider a table for customers records with two character fields : Customer ID and customer name:

Cust_ID Cust_Name
CC001 Pierce Firth
CC002 Stellan Taylor
CC003 Chris Cherry
CC004 Amanda Baranski

 It’s known all possible and necessary operations on a table: APPEND, DELETE, SEEK and so on; by the way, for SEEK we need an index file also.

Listing this table is quite simple:

USE CUSTOMER
WHILE .NOT. EOF()
   ? CUST_ID, CUST_NAME
   DBSKIP()
ENDDO

 If our table is sufficiently small, we can find a customer record without index and SEEK :

LOCATE FOR CUST_ID = “CC003”
? CUST_ID, CUST_NAME

If we want all our data will stand in memory and we could manage it more simple and quick way, we would use an array ( with some considerations about size of table; if it is too big, this method will be problematic ) :

aCustomer := {}    // Declare / define an empty array
USE CUSTOMER
WHILE .NOT. EOF()
   AADD(aCustomer, { CUST_ID, CUST_NAME } )
   DBSKIP()
ENDDO
Traversing this array is quite simple :

FOR nRecord := 1 TO LEN( aCustomer )

    ? aCustomer[ nRecord, 1 ], aCustomer[ nRecord, 2 ]
NEXT
or :

a1Record := {}

FOR EACH a1Record IN aCustomer
   ? a1Record[ 1 ], a1Record[ 2 ]
NEXT

And locating a specific record too:

nRecord := ASCAN( aCustomer, { | a1Record | a1Record[ 1 ] == “CC003” } )

? aCustomer[ nRecord, 1 ], aCustomer[ nRecord, 2 ]

A lot of array functions are ready to use for maintain this array : ADEL(), AADD(), AINS() etc …

Now, let’s see how we could use a hash for achieve this job :

hCustomer := { => } // Declare / define an empty hash

USE CUSTOMER
WHILE .NOT. EOF()
   hCustomer[ CUST_ID ] := CUST_NAME
   DBSKIP()
ENDDO
Let’s traversing :

h1Record := NIL

FOR EACH h1Record IN hCustomer
   ? h1Record: __ENUMKEY(),h1Record:__ENUMVALUE()
NEXT

Now, we have a bit complicate our job; a few field addition to the table :

No: Field Name Type Width  Dec Decription

1

 CUST_ID

C

 5

0

Id ( Code )

2

 CUST_NAME

C

10

0

Name

3

 CUST_SNAM

C

10

0

Surname

4

 CUST_FDAT

D

 8

0

First date

5

 CUST_ACTV

L

 1

0

Is active ?

6

 CUST_BLNCE

N

11

2

Balance

 While <key> part of an element of a hash may be C / D / N / L type; <xValue> part of hash too may be ANY type of data, exactly same as arrays.

So, we can make fields values other than first ( ID) elements of an array:

hCustomer := { => } // Declare / define an empty hash
USE CUSTOMER
WHILE .NOT. EOF()
   a1Data:= { CUST_NAME, CUST_SNAM, CUST_FDAT, CUST_ACTV, CUST_BLNCE }
   hCustomer[ CUST_ID ] := a1Data
   DBSKIP()
ENDDO
Let’s traversing :

h1Record := NIL

FOR EACH h1Record IN hCustomer
   a1Key  := h1Record:__ENUMKEY()
   a1Data := h1Record:__ENUMVALUE()
   ? a1Key
   AEVAL( a1Data, { | x1 | QQOUT( x1 ) } )
NEXT
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._
/*
Hash vs Tables
 
*/
#define NTrim( n ) LTRIM( STR( n ) )
#define cLMarj SPACE( 3 )
PROCEDURE Main()

  SET DATE GERM
  SET CENT ON
  SET COLO TO "W/B"
  SetMode( 40, 120 )
 
  CLS
 
  hCustomers := { => } // Declare / define an empty PRIVATE hash
 
  IF MakUseTable() 
 
     Table2Hash()
 
     * Here the hash hCustomers may be altered in any way
 
     ZAP
 
     Hash2Table()
 
  ELSE
      ? "Couldn't make / USE table"
  ENDIF
 
  ?
  @ MAXROW(), 0
  WAIT "EOF HashVsTable.prg"
 
RETURN // HashVsTable.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
PROCEDURE Table2Hash()
   hCustomers := { => } 
   WHILE .NOT. EOF()
     hCustomers[ CUST_ID ] := CUST_SNAM
     DBSKIP()
   ENDDO
 
   ListHash( hCustomers, "A hash transferred from a table (single value)" )
 
   hCustomers := { => } // Declare / define an empty hash
   DBGOTOP()
   WHILE .NOT. EOF()
      hCustomers[ CUST_ID ] := { CUST_NAME, CUST_SNAM, CUST_FDAT, CUST_ACTV, CUST_BLNCE }
      DBSKIP()
   ENDDO
 
   ListHash( hCustomers, "A hash transferred from a table (multiple values)" )
 
RETURN // Table2Hash()

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

PROCEDURE Hash2Table()
   LOCAL h1Record,;
         c1Key,;
         a1Record,;
         n1Field
 
   FOR EACH h1Record IN hCustomers
      c1Key := h1Record:__ENUMKEY()
      a1Record := h1Record:__ENUMVALUE()
      DBAPPEND()
      FIELDPUT( 1, c1Key )
      AEVAL( a1Record, { | x1, n1 | FIELDPUT( n1 + 1 , x1 ) } )
   NEXT h1Record
   DBGOTOP()
 
   ?
   ? "Data trasferred from hash to table :"
   ?
   WHILE ! EOF()
      ? STR( RECN(), 5), ''
      FOR n1Field := 1 TO FCOUNT()
         ?? FIELDGET( n1Field ), ''
      NEXT n1Field
      DBSKIP()
   ENDDO 
 
RETURN // Hash2Table()

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

PROCEDURE ListHash( hHash, cComment )
 
  LOCAL x1Pair
 
  cComment := IF( HB_ISNIL( cComment ), '', cComment )
 
  ? 
  ? cComment // , "-- Type :", VALTYPE( hHash ), "size:", LEN( hHash )
  ?
  IF HB_ISHASH( hHash ) 
     FOR EACH x1Pair IN hHash
        nIndex := x1Pair:__ENUMINDEX()
        x1Key := x1Pair:__ENUMKEY()
        x1Value := x1Pair:__ENUMVALUE()
        ? cLMarj, NTrim( nIndex ) 
*       ?? '', VALTYPE( x1Pair )
        ?? '', x1Key, "=>"
*       ?? '', VALTYPE( x1Key ) 
*       ?? VALTYPE( x1Value ) 
        IF HB_ISARRAY( x1Value ) 
           AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
        ELSE 
           ?? '', x1Value
        ENDIF 
     NEXT
  ELSE
    ? "Data type error; Expected hash, came", VALTYPE( hHash ) 
  ENDIF HB_ISHASH( hHash )
RETURN // ListHash()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

FUNCTION MakUseTable() // Make / USE table
 
 LOCAL cTablName := "CUSTOMER.DBF"
 LOCAL lRetval, aStru, aData, a1Record 
 
 IF FILE( cTablName ) 
    USE (cTablName)
 ELSE
    aStru := { { "CUST_ID", "C", 5, 0 },;
               { "CUST_NAME", "C", 10, 0 },;
               { "CUST_SNAM", "C", 10, 0 },;
               { "CUST_FDAT", "D", 8, 0 },;
               { "CUST_ACTV", "L", 1, 0 },;
               { "CUST_BLNCE", "N", 11, 2 } }
    * 
    * 5-th parameter of DBCREATE() is alias - 
    * if not given then WA is open without alias 
    *                              ^^^^^^^^^^^^^ 
    DBCREATE( cTablName, aStru, , .F., "CUSTOMER" ) 
 
    aData := { { "CC001", "Pierce", "Firth", 0d20120131, .T., 150.00 },; 
               { "CC002", "Stellan", "Taylor", 0d20050505, .T., 0.15 },;
               { "CC003", "Chris", "Cherry", 0d19950302, .F., 0 },;
               { "CC004", "Amanda", "Baranski", 0d20011112, .T., 12345.00 } }
 
    FOR EACH a1Record IN aData
        CUSTOMER->(DBAPPEND())
        AEVAL( a1Record, { | x1, nI1 | FIELDPUT( nI1, X1 ) } )
    NEXT a1Record 
    DBGOTOP()
 
 ENDIF 
 
 lRetval := ( ALIAS() == "CUSTOMER" )
 
RETURN lRetval // MakUseTable()

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._
 
HashVsTable

Hash Details – 2


( GET / SET Functions )

HB_HGET() : Returns a hash value

Syntax :   HB_HGET( <hsTable>, <Key> ) -> <Value>

HB_HPOS() : Locates the index of a key within a hash table

Syntax :      HB_HPOS( <hsTable>, <Key> ) -> nPosition

HB_HVALUEAT() : Gets/sets a hash value at a given position

Syntax :      HB_HVALUEAT( <hsTable>, <nPosition>, [<NewValue>] ) -> <Value>

HB_HVALUES() : Returns an array of the values of a hash table

Syntax :      HB_HVALUES( <hsTable> ) -> <aValues>

HB_HKEYAT() : Gets a hash table key at a given position

Syntax :      HB_HKEYAT( <hsTable>, <nPosition> ) -> <Key>

HB_HKEYS() : Returns an array of the keys of a hash table

Syntax :      HB_HKEYS( <hsTable> ) -> <aKeys>

HB_HPAIRAT() : Returns a two-dimensional array of a hash table entry key/value pair

Syntax :      HB_HPAIRAT( <hsTable>, <nPosition> ) -> <aKeyValue>

HB_HSCAN() : Scans a hash table for a value

Syntax :  HB_HSCAN( <hsTable>, <Value>, [<nStart>], [<nCount>, [<lExact>] ) -> nPosition

HB_HSET() : Sets a hash value

Syntax :      HB_HSET( <hsTable>, <Key>, <Value> ) -> <hsTable>

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

/*

Hash Details - 2

 ( GET / SET Funtions )

*/
#define xTrim( x ) IF( HB_ISNUMERIC( x ), LTRIM( STR( x ) ), ALLTRIM( x ) )
PROCEDURE Main()
   SetMode( 60, 120 )

   SET COLO TO "W/B"

   cLMarj := SPACE( 3 )

   CLS

   hFruits := { "apple" => "green",;
                "chery" => "pink",; 
                "apricot" => "yellow",;
                "orange" => "orange" }

   ListHash( hFruits, "Fruits" )

   * HB_HGET( <hsTable>, <Key> ) -> <Value>

   ? "Value found by literal of 'apple' : ",; 
      hFruits[ "apple" ] // green
   ? "Value found by function of 'apple' : ",; 
      HB_HGET( hFruits, "apple" ) // green
   ?

   * HB_HSET( <hsTable>, <Key>, <Value> ) -> <hsTable>
   hFruits[ "apple" ] := "red"
   ? "New (assigned literally) Value of 'apple' : ",; 
     hFruits[ "apple" ] // red

   HB_HSET( hFruits, "apple", "yellow" )
   ? "Newer (assigned by function) Value of 'apple' : ",; 
     hFruits[ "apple" ] // yellow
   ? 

   * HB_HPOS( <hsTable>, <Key> ) -> nPosition
   ? "Position of 'apple' : ", ;
     xTrim( HB_HPOS( hFruits, "apple" ) ) // 1
   * HB_HVALUEAT( <hsTable>, <nPosition>, [<NewValue>] ) -> <Value>
   ? "Position of 'melon' : ", ;
      xTrim( HB_HPOS( hFruits, "melon" ) ) // 0
   nPos := HB_HPOS( hFruits, "apricot" )
   ? "Position of 'apricot' : ",; 
     xTrim( HB_HPOS( hFruits, "apricot" ) ) // 4
   ?
   ? "Value of ", xTrim( nPos ) + ".th position :", ;
   HB_HVALUEAT( hFruits, nPos ) // yellow
   ?
   HB_HVALUEAT( hFruits, nPos, "fushia" ) 

   ? "Changed value in this position :",; 
   HB_HVALUEAT( hFruits, nPos ) // fushia
   ? 

   * HB_HKEYAT( <hsTable>, <nPosition> ) -> <Key>
   ? "Key in this position :" ,; 
     HB_HKEYAT( hFruits, nPos ) // apricot
   ?

   * HB_HKEYS( <hsTable> ) -> <aKeys>

   aKeys := HB_HKEYS( hFruits ) 

   ? "All keys in 'hFruits' : "
   AEVAL( aKeys, { | cKey | QQOUT( cKey, '' ) } ) 
                    // apple chery orange apricot
   ?

   * HB_HVALUES( <hsTable> ) -> <aValues>
   aValues := HB_HVALUES( hFruits ) 

   ? "All values in 'hFruits' : "
   AEVAL( aValues, { | cValue | QQOUT( cValue, '' ) } ) 
                     // green pink orange yellow
   ?

   * HB_HPAIRAT( <hsTable>, <nPosition> ) -> <aKeyValue>

   ? "All pairs in 'hFruits' : "

   Note : Compare this loop with below ListHash()

   FOR nPos := 1 TO LEN( hFruits )
      aKey_Value := HB_HPAIRAT( hFruits, nPos ) 
      ? xTrim( nPos ), aKey_Value[ 1 ], "=>", aKey_Value[ 2 ]
   NEXT
   ?

   * HB_HSCAN( <hsTable>, <Value>, [<nStart>], [<nCount>, [<lExact>] ) 
   *                                                     -> nPosition
   ? "Position found by scanning value 'pink' : ",;
   xTrim( HB_HSCAN( hFruits, "pink" ) ) // 2
   ?
   @ MAXROW(), 0
   WAIT "EOF HashDetails-2.prg"

RETURN // HashDetails-2.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
PROCEDURE ListHash( hHash, cComment )

 LOCAL x1Pair

   cComment := IF( HB_ISNIL( cComment ), '', cComment )

   ? cComment, '' 
   * ?? "-- Type :", VALTYPE( hHash ),'' 
   * ?? "size:", NTrim ( LEN( hHash ) ) 
   ?
   FOR EACH x1Pair IN hHash
      nIndex := x1Pair:__ENUMINDEX()
      x1Key := x1Pair:__ENUMKEY()
      x1Value := x1Pair:__ENUMVALUE()
      ? cLMarj, xTrim( nIndex ) 
      * ?? '', VALTYPE( x1Pair )
      ?? '', xTrim( x1Key ), "=>"
      * ?? '', VALTYPE( x1Key ) 
      * ?? VALTYPE( x1Value ) 
      IF HB_ISARRAY( x1Value ) 
        AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
      ELSE 
        ?? '', xTrim( x1Value )
      ENDIF 
   NEXT

   ? REPL( "~", 32 ) 

RETURN // ListHash()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
hashdets-2

Hash Details – 1


Some details of hash manipulations:

HB_HCOPY() function copies a hash to another.

Syntax :

   HB_HCOPY( <hsDestination>, <hsSource>, [<nStart>], [<nCount>] ) -> 
             <hsDestination>

 As noticed in syntax, copy operation may be limited by  [<nStart>], [<nCount>]  arguments.

 But a hash may NOT built by “COPY” method; because  <hsDestination> argument  isn’t optional.

   hFruits  := { "fruits" => { "apple", "cherry", "apricot" } }
   hFruits2 := HB_HCOPY( hFruits )   // Argument error !

Though it’s possible in two steps:

   hFruits2 := HB_HASH()   // Built first an empty hash
   hFruits2 := HB_HCOPY(hFruits2, hFruits ) // copy second onto first

As a result, for hash copy process two hashes should be exist:

   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } }
   hFruits := HB_HCOPY( hFruits, hDays )

or 

   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } }
   hTarget := HB_HASH()
   hTarget := HB_HCOPY( hTarget, hFruits )
   hTarget := HB_HCOPY( hTarget, hDays )

HB_HMERGE() function merge two hashes.

Syntax:

   HB_HMERGE( <hsDestination>, <hsSource>, <bBlock>|<nPosition> ) -> 
              <hsDestination>
   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } }
   hMerged := HB_HMERGE( hFruits, hDays )

hFruits :

    1 days => sunday monday

    2 fruits => apple cherry apricot

hTarget :

    1 days => sunday monday

    2 fruits => apple cherry apricot

AADD( hFruits[ "fruits" ], "melon" )

hFruits and hTarget :

  1 days => sunday monday

  2 fruits => apple cherry apricot melon

Result of above tests :

HB_HCOPY() and HB_HMERGE() doesn’t “physically” copy / merge hashes data; instead, copy / merge only by reference(s).

HB_HCLONE() function :  Cloning (exact copy of) hashes.

Syntax:

   HB_HCLONE( <hsTable> ) -> <hsDestination>
   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hClone := HB_HCLONE( hFruits )

hClone : fruits => apple cherry apricot

   AADD( hFruits[ "fruits" ], "melon" )  // Source changed

    hClone : fruits => apple cherry apricot

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._./*

Hash Details - 1

 Copy, Merge & Clone Hashes. 

*/
#define NTrim( x ) IF( HB_ISNUMERIC( x ), LTRIM( STR( x ) ), x )
PROCEDURE Main()
  SET COLO TO "W/B"

  cLMarj := SPACE( 3 )

  CLS
  ? "Copy one hash to another : HB_HCOPY() function : " 
  ? "Syntax :",;
  "HB_HCOPY( <hsDestination>, <hsSource>, [<nStart>]," 
  ? " [<nCount>] ) -> <hsDestination>"
  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hDays := { "days" => { "sunday", "monday" } } 

  * hFruits2 := HB_HCOPY( hFruits ) // Argument error !
  hFruits := HB_HCOPY( hFruits, hDays )
  ListHash( hFruits, "Copied-1 (Fruits)" )

  hDays[ "days" ] := "friday"
  ListHash( hFruits, "copied or referenced ?" )
  hTarget := HB_HASH()
  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hDays := { "days" => { "sunday", "monday" } } 

  hTarget := HB_HCOPY( hTarget, hFruits )
  hTarget := HB_HCOPY( hTarget, hDays )

  ListHash( hTarget, "Copied-2 ( Target )" )

  AADD( hFruits[ "fruits" ], "melon" )
  ListHash( hTarget, "copied or referenced ?" )

  ? "Merge two hashes : HB_HMERGE() function : " 
  ? "Syntax :",;
  "HB_HMERGE( <hsDestination>, <hsSource>, <bBlock>|"
  ? " <nPosition> ) -> <hsDestination>"

  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hDays := { "days" => { "sunday", "monday" } } 

  hMerged := HB_HMERGE( hFruits, hDays )

  ListHash( hFruits, "Merged (hFruits)" )
  ListHash( hMerged, "Merged (hTarget)" )
  AADD( hFruits[ "fruits" ], "melon" )
  ListHash( hFruits, "Merged or referenced ? ( hFruits) " )
  ListHash( hMerged, "Merged or referenced ? ( hMerged) " )

  * 
  * Result of above tests :
  * 
  * HB_HCOPY() and HB_HMERGE() doesn't "physically" copy / merge hashes data;
  * 
  * instead copy / merge only by reference(s).
  *
  ? "Cloning (exact copy of) hashes : HB_HCLONE() function : " 
  ? "Syntax :",;
  "HB_HCLONE( <hsTable> ) -> <hsDestination>"

  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hClone := HB_HCLONE( hFruits )
  ListHash( hClone, "Cloned" )
  AADD( hFruits[ "fruits" ], "melon" )
  ListHash( hClone, "Source changed" )

  ?
  @ MAXROW(), 0
  WAIT "EOF HashDetails-1.prg"

RETURN // HashDetails-1.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

PROCEDURE ListHash( hHash, cComment )

  LOCAL x1Pair := NIL

  cComment := IF( HB_ISNIL( cComment ), '', cComment )

  ? cComment, '' 
  * ?? "-- Type :", VALTYPE( hHash ),'' 
  * ?? "size:", NTrim ( LEN( hHash ) ) 
  ?
  FOR EACH x1Pair IN hHash
     nIndex := x1Pair:__ENUMINDEX()
     x1Key := x1Pair:__ENUMKEY()
     x1Value := x1Pair:__ENUMVALUE()
     ? cLMarj, NTrim( nIndex ) 
     * ?? '', VALTYPE( x1Pair )
     ?? '', NTrim( x1Key ), "=>"
     * ?? '', VALTYPE( x1Key ) 
     * ?? VALTYPE( x1Value ) 
     IF HB_ISARRAY( x1Value ) 
        AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
     ELSE 
        ?? '', NTrim( x1Value )
     ENDIF 
  NEXT

  ? REPL( "~", 32 ) 

RETURN // ListHash()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

HashDets1