forked from harbour/core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxhb-diff.txt
3538 lines (3153 loc) · 167 KB
/
xhb-diff.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
This text describes most important differences between Harbour and xHarbour
with some references to Clipper and other compatible compilers like Xbase++,
CLIP, FlagShip.
Many thanks to Pritpal and Viktor for updating this text.
I hope that it will be updated in the future also by xHarbour developers,
It describes status of both compiler at the end of October 2009:
Harbour 2.0.0 beta3 (revision 12788)
xHarbour 1.2.1 (revision 6629)
Przemek,
(Przemyslaw Czerpak, druzus /at/ priv.onet.pl)
### COMPILE TIME SUPPORT FOR MERGING MULTIPLE .PRG MODULES ###
====================================================================
Clipper allows to compile many .prg modules included by @<name>.clp
and/or SET PROCEDURE TO ... / DO ... [ WITH ... ] into single output
object. In such compilation it supports, separated for each .prg file,
file wide declarations when -n switch is used and allows to use more
then one static function with the same name if each of them is declared
in different .prg modules. This code illustrates such situation:
/***** t1. prg *****/
static s := "t01:s"
static s1 := "t01:s1"
proc main()
? "===="
? s, s1
p1();p2();p3()
? "===="
do t2
? "===="
return
proc p1 ; ? "t01:p1"
static proc p2 ; ? "t01:p2"
static proc p3 ; ? "t01:p3"
init proc pi ; ? "init t01:pi"
exit proc pe ; ? "exit t01:pe"
/***** t2. prg *****/
static s := "t02:s"
static s2 := "t02:s2"
proc t2()
? s, s2
p1();p2();p3()
return
static proc p1 ; ? "t02:p1"
proc p2 ; ? "t02:p2"
static proc p3 ; ? "t02:p3"
init proc pi ; ? "init t02:pi"
exit proc pe ; ? "exit t02:pe"
It needs -n switch for file wide declarations and uses static/init/exit
functions with the same names but declared in different modules.
It can be compiled and linked by Clipper and Harbour, i.e.:
cl t1.prg /n/w/es2
or:
hbmk2 t1.prg -n -w -es2
and then executed.
xHarbour does not have such functionality and above code has to be adopted
to work with this compiler. Additionally it does not work well with case
sensitive file systems what can be seen in above example where it converts
"t1" to "T1" and then tries to include "T1.prg".
For users which have old Clipper code written for DOS file systems with
mixed upper and lower letters in file names used directly or indirectly
by procedure name, Harbour provides compile time switches which enable
automatic file name conversions for all files opened by compiler:
-fn[:[l|u]|-] set file name casing (l=lower u=upper)
-fd[:[l|u]|-] set directory casing (l=lower u=upper)
-fp[:<char>] set path separator
-fs[-] turn file name space trimming on or off (default)
This functionality is also local to Harbour so cannot be used with xHarbour
as workaround for above problem though it should be easy to add it to this
compiler in the future.
Both compilers support runtime switches for file name conversions.
SET FILECASE LOWER | UPPER | MIXED
SET DIRCASE LOWER | UPPER | MIXED
SET DIRSEPARATOR <cDirSep>
Set( _SET_TRIMFILENAME, <lOnOff> )
which can be used in programs not intended to work with different
file system(s) and with different OS(s).
### NEW LANGUAGE STATEMENTS ###
=====================================
1. FOR EACH
Harbour supports all xHarbour functionality and it offers also additional
features which are not available in xHarbour.
a) it allows to iterate more then one variable
FOR EACH a, b, c IN aVal, cVal, hVal
? a, b, c
NEXT
b) it allows to set descending order by DESCEND flag, f.e.:
FOR EACH a, v IN aVal, cVal DESCEND
? a, b
NEXT
c) it has native support for hashes:
FOR EACH x IN { "ABC" => 123, "ASD" => 456, "ZXC" => 789 }
? x, "@", x:__enumKey()
NEXT
d) it allows to assign string items, f.e.:
s := "abcdefghijk"
FOR EACH c IN @s
IF c $ "aei"
c := Upper( c )
ENDIF
NEXT
? s // AbcdEfghIjk
e) it gives OOP interface to control enumerator variables what
is very important when more then one variable is iterated or
when FOR EACH is called recursively, f.e.:
hVal := { "ABC" => 123, "ASD" => 456, "ZXC" => 789 }
FOR EACH x IN hVal
? x:__enumIndex(), ":", x:__enumKey(), "=>", x:__enumValue(), ;
"=>", x:__enumBase()[ x:__enumKey() ]
NEXT
f) it gives very flexible OOP mechanism to overload FOR EACH behavior
for user defined classes adding to above enumerator methods also
__enumStart(), __enumStop(), __enumSkip() methods what allows to
implement many different enumeration algorithms depending on used
data
g) it does not have any hardcoded limitations for recursive calls
(it's limited only by available memory and HVM stack size), f.e.:
proc main()
p( 0 )
return
proc p( n )
local s := "a", x
? n
if n < 1000
for each x in s
p( n + 1 )
next
endif
return
In xHarbour there is function hb_enumIndex() which is supported by
Harbour in XHB library.
2. WITH OBJECT / END[WITH]
In Harbour it does not have any hardcoded limitations for recursive
calls (it's limited only by available memory and HVM stack size), f.e.:
proc main()
p( 0 )
return
proc p( n )
? n
if n < 1000
with object n
p( n + 1 )
end
endif
return
It also uses OOP interface just like FOR EACH, so it's possible to
use :__withObject() to access / assign current WITH OBJECT value.
In xHarbour there are functions hb_QWith(), hb_WithObjectCounter()
and hb_ResetWith() which are supported by Harbour in XHB library.
3. SWITCH / [ CASE / [EXIT] / ... ] OTHERWISE / END[SWITCH]
In Harbour it uses jump table with predefined values what gives
significant speed improvement in comparison to sequential PCODE
evaluation just like in DO CASE statements.
In xHarbour SWITCH does not use such jump table and generated
PCODE is similar to the one used for DO CASE or IF / ELSEIF
and only the main switch value calculation is optimized and
reused for all statements so speed improvement is relatively
small.
In xHarbour instead of OTHERWISE the DEFAULT clause is used.
As SWITCH values Harbour supports integer numbers and strings, f.e.:
switch x
case 1 ; [...]
case 10002 ; [...]
case "data" ; [...]
otherwise ; [...]
endswitch
xHarbour supports only integer numbers and one character length strings
like "A", "!", "x", " ", ...
4. BEGIN SEQUENCE [ WITH <errBlock> ]
[ RECOVER [ USING <oErr> ] ]
[ ALWAYS ]
END SEQUENCE
It's unique to Harbour. In xHarbour limited version of above statement
exists as:
TRY
[ CATCH [<oErr>] ]
[ FINALLY ]
END
TRY gives exactly the same functionality as:
BEGIN SEQUENCE WITH {| e | Break( e ) }
With the exception to SWITCH implementation, in all other statements
described above, xHarbour causes performance reduction in PCODE evaluation
even if user does not use them at all. In Harbour they are implemented in
different way which does not cause any overhead and slowness for other code.
### EXTENDED CODEBLOCKS ###
=================================
Both compilers support compile time extended codeblocks which allow
to use statements but with a little bit different syntax. Harbour uses
standard Clipper codeblock delimiters {}, f.e.:
? Eval( {| p1, p2, p3 |
? p1, p2, p3
return p1 + p2 + p3
}, 1, 2, 3 )
and xHarbour <>, f.e.:
? Eval( <| p1, p2, p3 |
? p1, p2, p3
return p1 + p2 + p3
>, 1, 2, 3 )
In Harbour extended codeblocks works like nested functions and supports
all function attributes, f.e. they can have own static variables or
other declarations which are local to extended codeblocks only and
do not effect upper function body.
In xHarbour the compiler was not fully updated for such functionality
and extended codeblocks were added to existing compiler structures what
causes that not all language constructs work in extended codeblocks
and creates a set of very serious compiler bugs, f.e., like in this code
with syntax errors but which is compiled by xHarbour without even single
warning giving unexpected results at runtime:
#ifndef __XHARBOUR__
#xtranslate \<|[<x,...>]| => {|<x>|
#xcommand > [<*x*>] => } <x>
#endif
proc main()
local cb, i
for i:=1 to 5
cb := <| p |
? p
exit
return p * 10
>
?? Eval( cb, i )
next
return
It's possible to create many other similar examples which are mostly
caused by missing in the compiler infrastructure for nested functions
support.
This can be fixed if someone invest some time to clean xHarbour compiler.
### HASH ARRAYS ###
=========================
Both compilers have support for hash arrays. They are similar to
normal arrays but also allow to use non integer values as indexes
like string, date, non integer number or pointer (in Harbour) items.
They can be created using => for list of keys and values enclosed
inside {}, f.e.:
hVal := { "ABC" => 123.45, ;
100.1 => Date(), ;
100.2 => 10, ;
100 => 5, ;
Date()-1 => .t. }
and then items can be accessed using [] operator, f.e.:
? hVal[ "ABC" ] // 123.45
? hVal[ 100 ] // 5
? hVal[ Date()-1 ] // .t.
? hVal[ 100.2 ] // 10
? hVal[ 100.1 ] // Date()
By default hash items in both compiler support automatic adding new elements
on assign operation. It can be disabled using one of hash item functions.
Harbour has additional extension which allows to enable auto-add with default
values also for access operation and reference operator. It also supports
well passing hash array items by reference and has some other minor
extensions.
xHarbour does not support auto-add on access or reference operations and
passing hash array items by reference does not work (see passing array and
hash item by reference).
Both compilers have set of functions to make different operations on hash
arrays which give similar functionality. In Harbour they use HB_H prefix
(f.e. hb_HScan()) in xHarbour H prefix (f.e. HScan())
xHarbour has additional functionality which can be enabled for each hash
array using HSetAACompatibility() function. It's an index where is stored
information about the order in which items were added to hash array and
set of functions with HAA prefix to operate on hash array items using this
index instead of real position in hash array, i.e. haAGetValueAt() which
works like HGetValueAt().
In Harbour such functionality also exists and is enabled by default in all
hash array but the internal implementation is completely different. Harbour
does not emulate associative arrays by special index which keeps assign order
but it uses real natural order in hash array. It means that associative array
indexes are equal to regular hash indexes so it does not need any translation
between them and Harbour users can use regular hash array functions for
associative array indexes. So separated functions for accessing by other
index are not necessary. The Harbour implementation is also more efficient
because it does not introduce linear index updating each time new key is
added to hash array.
Harbour emulates HAA*() xHarbour functions in XHB library but only for
compatibility with existing xHarbour code.
This functionality can be disabled for each hash array in Harbour by
hb_HKeepOrder() function which sorts key-value pairs in hash array and
removes the access index.
### REFERENCES TO VARIABLES STORED IN ARRAYS ###
======================================================
In xHarbour the behavior of references stored in array is reverted in
comparison to Clipper or Harbour.
In Clipper and Harbour VM executing code like:
aVal[ 1 ] := 100
clears unconditionally 1st item in aVal array and assigns to it value 100.
xHarbour checks if aVal[ 1 ] is an reference and in such case it resolves
the reference and then assign 100 to the destination item.
On access Clipper and Harbour VM executing code like:
x := aVal[ 1 ]
copy to x the exact value stored in aVal[ 1 ]. xHarbour checks is aVal[ 1 ]
is an reference and in such case it resolves the reference and then copy to x
the value of reference destination item.
It can be seen in code like:
proc main
local p1 := "A", p2 := "B", p3 := "C"
? p1, p2, p3
p( { @p1, p2, @p3 } )
? p1, p2, p3
proc p( aParams )
local x1, x2, x3
x1 := aParams[ 1 ]
x2 := aParams[ 2 ]
x3 := aParams[ 3 ]
x1 := Lower( x1 ) + "1"
x2 := Lower( x2 ) + "2"
x3 := Lower( x3 ) + "3"
Harbour and Clipper shows:
A B C
a1 B c3
but xHarbour:
A B C
A B C
It's not Clipper compatible so in some cases it may cause portability
problems. F.e. code like above was used in Clipper as workaround for
limited number of parameters (32 in Clipper). But it allows to directly
assign items of arrays returned by hb_AParams() and updating corresponding
variables passed by references (see functions with variable number of
parameters below).
Anyhow the fact that xHarbour does not have '...' operator which can
respect existing references in passed parameters and does not support
named parameters in functions with variable number of parameters causes
that reverted references introduce limitation, f.e. it's not possible
to make code like:
func f( ... )
local aParams := hb_AParams()
if Len( aParams ) == 1
return f1( aParams[ 1 ] )
elseif Len( aParams ) == 2
return f2( aParams[ 1 ], aParams[ 2 ] )
elseif Len( aParams ) >= 3
return f3( aParams[ 1 ], aParams[ 2 ], aParams[ 3 ] )
endif
return 0
which will respect references in parameters passed to f() function.
### PASSING ARRAY AND HASH ITEMS BY REFERENCE ###
=======================================================
Harbour supports passing array and hash items by reference, f.e.:
proc main()
local aVal := { "abc", "klm", "xyz" }, ;
hVal := { "qwe"=>"123", "asd"=>"456", "zxc"=>"789" }
? aVal[1], aVal[2], aVal[3], hVal["qwe"], hVal["asd"], hVal["zxc"]
p( @aVal[2], @hVal["asd"] )
? aVal[1], aVal[2], aVal[3], hVal["qwe"], hVal["asd"], hVal["zxc"]
proc p( p1, p2 )
p1 := '[1]'
p2 := '[2]'
Compiled by Harbour above code shows:
abc klm xyz 123 456 789
abc [1] xyz 123 [2] 789
In xHarbour only passing array items by reference works but does not work
passing hash items by reference though it does not generate either
compile time or run time errors so the above code can be compiled and
executed but it shows:
abc klm xyz 123 456 789
abc [1] xyz 123 456 789
### PASSING OBJECT VARIABLES BY REFERENCE ###
===================================================
Both compilers support passing object variables by reference though this
functionality in xHarbour is limited to pure instance or class variables
only and does not work for SETGET methods. In Harbour it works correctly.
This code illustrates the problem:
proc main()
local oBrw := TBrowseNew()
? oBrw:autoLite
oBrw:autoLite := !oBrw:autoLite
?? "=>", oBrw:autoLite
p( @oBrw:autoLite )
?? "=>", oBrw:autoLite
proc p( x )
x := !x
Harbour prints:
.T.=> .F.=> .T.
but xHarbour prints:
.T.=> .F.=> .F.
without generating any compile or run time errors.
### DETACHED LOCALS AND REFERENCES ###
============================================
When local variables are used in codeblocks then it's possible that
the codeblocks will exist after leaving the function when they were
created. It's potentially very serious problem which have to be resolved
to avoid internal VM structure corruption. In Clipper, Harbour and
xHarbour special mechanism is used in such situation. Local variables
are "detached" from VM stack so they are still accessible after leaving
the function, f.e.:
proc make_cb()
local n := 123
return {|| ++n }
We call such variables "detached locals".
Here there are two important differences between Clipper and [x]Harbour.
In Clipper variables are detached when function exits (returns) and it
does not know which variables were used but simply detach whole local
variable frame from VM stack. It's very important to know that because
it can be source of serious memory problems in OS like DOS.
This simple code illustrates it:
// link using RTLINK and run with //e:0 //swapk:0
// repeat test second time with additional non empty parameter <x>
#define N_LOOPS 15
#xcommand FREE MEMORY => ? 'free memory: ' + ;
AllTrim( Str( Memory( 104 ) ) )
proc main( x )
local n, a
a := Array( N_LOOPS )
FREE MEMORY
for n := 1 to N_LOOPS
a[n] := f( x )
FREE MEMORY
next
return
func f(x)
local cb, tmp, ref
tmp := Space( 60000 )
if Empty( x )
cb := {|| .t. }
else
cb := {|| ref }
endif
return cb
If you execute above program with non empty parameter then 'tmp' variable
is detached with codeblock which uses 'ref' variable and not released as
long as codeblock is still accessible. It means that in few iterations
all memory are allocated and program crashes. Clipper's programmers should
know that and be careful when use detached local and if necessary clear
explicitly other local variables before returning from the function by
setting NIL to them.
In Harbour and xHarbour only variables explicitly used in codeblocks
are detached and detaching is done when codeblock is created and original
local variables are replaced by references. It is possible because Harbour
and xHarbour support multilevel references chains so it works correctly
also for local parameters passed be reference from parent functions.
In Clipper only one level references are supported what creates second
important differences. When Clipper detaches frame with local parameters
then it has to unreference all existing references breaking them. This code
illustrates it:
proc main()
local cb, n := 100
mk_block( @cb, @n )
? "after detaching:"
? Eval( cb ), n
return
proc mk_block( cb, n )
n := 100
cb := {|| ++n }
? "before detaching:"
? Eval( cb ), n
return
Above code compiled by Clipper shows:
before detaching:
101 101
after detaching:
102 101
so after detaching the references to 'n' variable is broken and codeblocks
access his own copy of this variables.
In Harbour it works correctly so the results are correct and it shows:
before detaching:
101 101
after detaching:
102 102
In xHarbour ( for unknown to me reasons ) Clipper bug is explicitly emulated
though it was possible to fix it because xHarbour inherited from Harbour
the same early detaching mechanism with multilevel references so just like
in Clipper xHarbour programmers have to carefully watch for possibly broken
references by detached locals and add workarounds for it if necessary.
### DECLARATION AND INITIALIZATION OF VARIABLES ###
=========================================================
Clipper parses variable declaration in a little bit different way then
Harbour and xHarbour. It makes it in two passes. In first pass it collects
names and scope of all declared variables and then in second pass this
information is available during variable initialization. This can be
illustrated by the following example:
/*** tst.prg ***/
proc main()
local cb := {|| QOut( n + 5 ), QOut( f ) } // (*)
field f in table
local n := 10
Eval( cb )
return
In the line which initializes cb code (*) we are using local variable n
and field f. Both are declared below the line in which codeblock is
initialized anyhow Clipper does not recognize it as undeclared variables
and uses their later declarations. If you compile above code using Clipper
with -n -w -es2 switches, i.e.
cl tst -n -w -es2
then it's compiled without any compile time warnings or errors. Then
during execution it shows 15 for the first QOut() function call and
generates runtime error
Error BASE/1002 Alias does not exist: TABLE
for the second QOut() call. It means that it correctly recognized scope
of both variables and also bound alias TABLE with field F though it was
declared one line below codeblock initialization.
In fact Clipper probably does not make two passes but parsing declarations
which have to be at the beginning of function or module it stores names of
variables which should be initialized with the initialization expressions.
Then when all declarations are processed for each line with declared and
initialized variables it generates code which pushes on VM stack results
of initialization expressions and then code which pops them initializing
variables. As result in Clipper this code cannot work:
local x := 10, y := x + 2
because Clipper generate PCODE like:
push 10
push x
push 2
add
pop y
pop x
but this code:
local x := 10
local y := x + 2
works correctly because declarations were in separated lines and in such
case Clipper generates PCODE like:
push 10
pop x
push x
push 2
add
pop y
In Harbour and xHarbour all variables are declared in the moment when they
are processed. It means that during compilation of above example using
harbour tst -n -w -es2
both compilers generate compile time warnings:
tst.prg(2) Warning W0001 Ambiguous reference 'N'
tst.prg(2) Warning W0001 Ambiguous reference 'F'
but it also means that in Harbour and xHarbour it's possible to write code
like:
proc main()
local x := 10, y := x + 2
? x, y
return
and unlike Clipper both compilers generates correct PCODE which shows
10 12
Maybe in the future we add support for Clipper compatible local variable
initialization covered by -kc Harbour compiler switch.
Xbase++ uses mixed behavior. Just like Clipper it stores variables with
initialization expressions but then it generates slightly different code
initializing variables one by one without line groping like in Clipper.
Please also note that in Clipper PRIVATE and PUBLIC declarations are
executable statements so they are not used as declarations by
Clipper compiler even if -a compiler switch is used. So when we talk
about initialization then it means that we are talking about LOCAL
variables. STATIC variables are initialized in different way at
application startup so cannot use local variables as initializers though
due to bug in Clipper in some cases compiler can accept local variables
in such context and then it may cause VM crash or error at runtime,
i.e. this code:
proc main()
local n
static s := {|| n }
Eval( s )
return
is cleanly compiled by Clipper and Xbase++ but it causes RTE in
Clipper and FATAL ERROR LOG in Xbase++.
Harbour and xHarbour correctly report compile time error for it.
### FUNCTIONS WITH VARIABLE NUMBER OF PARAMETERS ###
==========================================================
Both compilers support them though xHarbour is limited to all parameters
and does not support unnamed parameters. In Harbour you can declare some
named parameters and then unnamed just like in many other languages, f.e.:
func f( p1, p2, p3, ... )
The unnamed parameters can be used in different statements passing them
by '...' operator, f.e. as array items:
proc main()
AEval( F( "1", "2", "A", "B", "C" ), {|x, i| QOut( i, x ) } )
func f( p1, p2, ... )
? "P1:", p1
? "P2:", p2
? "other parameters:", ...
return { "X", ... , "Y", ... "Z" }
or as array indexes:
proc main()
local a := { { 1, 2 }, { 3, 4 }, 5 }
? aget( a, 1, 2 ), aget( a, 2, 1 ), aget( a, 3 )
func aget( aVal, ... )
return aVal[ ... ]
or as function parameters:
proc main()
info( "test1" )
info( "test2", 10, Date(), .t. )
proc info( msg, ... )
QOut( "[" + msg +"]: ", ... )
The '...' operator saves references when push parameters and it can be
used also in codeblocks, f.e.:
bCode := {| a, b, c, ... | QOut( a, b, c ), QOut( "[", ..., "]" ) }
All parameters can be accessed also using hb_AParams() function but
in xHarbour it works correctly only for functions which does not use
any local parameters or declared with variable number of parameters
or when number of declared parameters is not smaller then number of
passed parameters. This code illustrates it:
proc main()
p1("A","B","C")
p2("A","B","C")
p3("A","B","C")
p4("A","B","C")
p5("A","B","C")
proc p1
? ProcName()+"(), parameters:", PCount()
AEval( hb_AParams(), {|x,i| QOut(i,"=>",x) } )
proc p2
local l
? ProcName()+"(), parameters:", PCount()
AEval( hb_AParams(), {|x,i| QOut(i,"=>",x) } )
proc p3(x)
? ProcName()+"(), parameters:", PCount()
AEval( hb_AParams(), {|x,i| QOut(i,"=>",x) } )
proc p4(...)
? ProcName()+"(), parameters:", PCount()
AEval( hb_AParams(), {|x,i| QOut(i,"=>",x) } )
proc p5(a,b,c,d,e)
? ProcName()+"(), parameters:", PCount()
AEval( hb_AParams(), {|x,i| QOut(i,"=>",x) } )
In xHarbour it's only possible to declare all parameters as unnamed, f.e.:
func f( ... )
and then access them using hb_AParams() or PValue() (in Harbour it's called
hb_PValue()) function. There is no support for named parameters and ...
operator.
In xHarbour due to reverted behavior of references stored in array items
assign operation to items in array returned by hb_AParams() changes
corresponding parameters passed by reference. It does not happen in
Harbour where item references stored in arrays work like in Clipper.
### hb_ArrayToParams() FUNCTION ###
=========================================
Harbour has special function which allows to convert array into
list of items which can be used as function parameters, array
values or array indexes in the same way as '...' operator:
hb_ArrayToParams( <aValue> ) -> <aValue>[ 1 ] [, <aValue>[ N ] ]
i.e.:
proc main( ... )
local aParams := hb_AParams(), n
/* remove parameters starting with "--" */
n := 1
while n < Len( aParams )
if Left( aParams[ n ], 2 ) == "--"
hb_ADel( aParams, n, .t. )
else
++n
endif
enddo
? "Public parameters:", hb_ArrayToParams( aParams )
return
Note for Clipper users: 'hb_ADel( aParams, n, .t. )' in above example
works like 'ADel( aParams, n ); ASize( aParams, Len( aParams ) - 1 )'.
This functionality is unique to Harbour and xHarbour does not support
hb_ArrayToParams() or similar function.
### MACROS WITH DECLARED SYMBOLS ###
==========================================
In Clipper all constant strings in PRG code with "&" character inside
are preprocessed at runtime by macro compiler. It checks if characters
after "&" are valid variable name (starts with "_" or letter and then
mix of "_", letters and digits until first different character) and if
yes and such private or public variable exists and it contains string
then the original string is modified and &<varname>[.] is substituted
by variable contents, i.e.
private var := "ABC"
? "[&var-rest][&var.rest]" // prints: [ABC-rest][ABCrest]
Please note that "." at the end of variable name has special meaning.
It can be used to mark end of macro variable name and is eaten during
substitution. This feature works only for memvars (private and public)
variables. If Clipper detects that variable declared as local, static
or field is used in such context then it generates compile time error,
i.e.:
local var := "text"
? "&var"
clipper tst.prg
[...]
Compiling T48.PRG
TST.PRG(2) Error C2081 Macro of declared symbol: '&OK'
By default Harbour is Cl*pper compatible and also generates such
compile time error (E0042).
Many people do not know about this functionality in Clipper and Harbour
and are quite often surprised when hearing about it.
Harbour offers two compiler switches to control this feature. It can be
completely disabled by -km compiler switch.
-km => turn off macro-text substitution
When this switch is used for constant strings with "&" character inside
faster code is generated which does not activate macro-compiler at runtime
so they are taken as is. This switch does not affect real macros.
It only changes the runtime behavior of strings constants with "&"
character inside (macro-text).
The second switch -kd extends above functionality and allows to use
macro-texts and macros with declared symbols:
-kd => accept macros with declared symbols
It means that also fields, local and static variables can be used in
macro-texts and macros.
The above example compiled with -kd switch does not generate compile
time error and final application shows "text" on the screen.
When -km switch is used then "&var" is shown by final application.
Harbour supports macro expansion for expressions with declared symbols
also for codeblocks when -kd is used. It allows to write code like:
cbVar := {|| &cLocal + cPrivate }
or:
cbVar := {|| &cLocalPref.func&cPriv1( cPriv2 ) }
or:
? &cLocalPref.func&cPriv1( cPriv2, &cStatic )
etc.
If it's possible then for macro-codeblocks Harbour compiler tries to
generate early eval code in which macros are expanded when codeblock
is created. Otherwise macros are expanded each time codeblock is
evaluated.
In xHarbour macro-text substitution for pure strings is always enabled
like in Clipper but it does not detect situation when fields, local
or static variables are used in macro-texts so it does not generate
compile time errors in such case.
xHarbour has similar to -kd extension always enabled but limited to
macro variables used inside code blocks and it works only if other
macros are not used in the same expression. When more complicated
examples are used xHarbour compiler generates broken code which
causes RTE or GPF during execution. It also does not support
codeblocks which contain mixed macros and refuses to compile such
code.
This example illustrates macros with declared symbols.
#ifndef __XHARBOUR__
#pragma -kd+
#endif
proc main()
memvar nPrivate
memvar cPriv1, cPriv2
local cbVar1, cbVar2
local cLocal := "10", cLocalPref := "hb_"
static cStatic := "'123xyz'"
private nPrivate := 10000
private cPriv1 := "test1", cPriv2 := "abc"
#ifndef __XHARBOUR__
cbVar1 := {|| &cLocal + nPrivate + Val( &cStatic ) }
cbVar2 := {|| &cLocalPref.func&cPriv1( cPriv2, &cStatic ) }
? Eval( cbVar1 )
? Eval( cbVar2 )
#endif
? &cLocal + nPrivate + Val( &cStatic )
cLocal := "upp"
? &cLocal.er( &cStatic )
? hb_funcTest1( cPriv2, &cStatic )
? &cLocalPref.func&cPriv1( cPriv2, &cStatic )
? &cLocalPref.func&cPriv1( cPriv2, &cStatic )
return
func hb_funcTest1( s1, s2 )
return s1 + ":" + s2
Above code can be compiled by xHarbour but it will not work exploiting
some problems in this compiler and generated code.
This feature can be useful also in porting some other xBase compatible
code to Harbour because some compilers just like xHarbour accepted
in some limited way officially unsupported syntax with macros using
declared symbols.
### MACRO MESSAGES ###
============================
Both compilers Harbour and xHarbour support macros as messages.
Clipper does not. This example shows such macro messages usage:
proc main()
memvar var
local o := ErrorNew(), msg := "cargo"
private var := "CAR"
o:&msg := "<cargo>"
o:&var.go += "<value1>"
o:&msg += "<value2>"
o:&( msg ) += "<value3>"
o:&( Upper( msg ) ) += "<value4>"
xHarbour does not support macro messages in assignment context.
Older xHarbour versions for macro messages with <op>= operators
or pre/post incrementation/decrementation GPF at runtime and
current ones compile above PRG code without any errors but
generate broken PCODE which may cause different side effects
(RTE, silent wrong processing or even memory corruption) - it
depends on the used context.
### MULTI-VALUE MACROS ###
================================
In the early Harbour days was added basic support for multi-value macros
which can be evaluated to list of values, f.e.:
? &("1,2,3")
should show:
1, 2, 3
The implementation of this extension was not accepted by many of Harbour
developers and it was one of the main reasons of the xHarbour fork.
In Harbour it was later fully removed and implemented from scratch using
different internal algorithms and structures. Now Harbour supports multi-value
macros in code like:
proc main()
local s1 := "'a', 'b', 'c'", s2 := "1,3", a
? &s1
a := { { "|", &s1, 'x', &s2, 'y' }, 'x', &s2 }
? "a[1] items:"
AEval( a[ 1 ], {| x, i | QOut( i, x ) } )
? "a["+s2+"] =>", a[ &s2 ]
return
xHarbour which inherited the original implementation after over 6 years
still cannot execute correctly above code.
### USING [] OPERATOR FOR STRING ITEMS ###
================================================
xHarbour supports using [] operator to access single characters in
string items. Harbour doesn't by default but it has strong enough
OOP API to allow adding such extension without touching core code
even by user at .prg level. It was implemented in Harbour in XHB.LIB.
This code can be compiled and executed by both compilers:
#ifndef __XHARBOUR__
#include "xhb.ch" // add xHarbour emulation to Harbour
#endif
proc main()
local s := "ABCDEFG"
? s, "=>", s[2], s[4], s[6]
s[2] := Lower( s[2] )
s[4] := Lower( s[4] )
s[6] := Lower( s[6] )
?? " =>", s
return
Warning!. There is one difference in above implementation introduced
intentionally to Harbour. xHarbour never generates errors for wrong
indexes in [] operator used for string items but simply returns "",
f.e. add to above code:
? ">" + s[0] + "<", ">" + s[1000] + "<"
If [] operator is used for other type of items RTE is generated.
Harbour will generate RTE in all cases. If someone needs strict XHB
compatibility here then he should adopt code overloading [] operator
for strings in XHB.LIB for his own preferences removing the RTE.
### NEGATIVE INDEXES IN [] OPERATOR USED TO ACCESS ITEMS FROM TAIL ###
============================================================================
xHarbour supports negative indexes in [] operator. They are used
to access items from tail, f.e. aVal[ -1 ] is the same as
aVal[ Len( aVal ) ] or ATail( aVal ).
By default Harbour core code does not give such functionality but
it has strong enough OOP API to allow adding such extension without
touching core code even by user at .prg level. It was implemented
in Harbour in XHB.LIB.
This code can be compiled and executed by both compilers:
#ifndef __XHARBOUR__
#include "xhb.ch" // add support for negative indexes in Harbour
#endif
proc main()
local s := "ABCDEF", a := {"1", "2", "3", "4", "5", "6"}
? s, "=>", s[1], s[2], s[3], s[4], s[5], s[6], "=>", ;
s[-1], s[-2], s[-3], s[-4], s[-5], s[-6]
? a[1], a[2], a[3], a[4], a[5], a[6], "=>", ;
a[-1], a[-2], a[-3], a[-4], a[-5], a[-6]
return
Warning! see above note about indexes out of bound used with [] operator
and string items.
### USING ONE CHARACTER LENGTH STRING AS NUMERIC VALUE ###
================================================================
xHarbour uses one byte strings as numeric values corresponding to ASCII
value of the byte in a string, f.e.:
? "A" * 10 // 650
By default Harbour core code does not give such functionality but
it has strong enough OOP API to allow adding such extension without
touching core code even by user at .prg level. It was implemented
in Harbour in XHB.LIB.
This code can be compiled and executed by both compilers:
#ifndef __XHARBOUR__
#include "xhb.ch"
#endif
proc main()
local c := "A"
? c * 10, c - 10, c + 10, c * " ", Chr( 2 ) ^ "!"
return
and gives the same results.
Anyhow the emulation is not full here. It works only for .prg code.
In xHarbour standard C API functions/macros were modified to use
one byte string items as numbers. It's potential source of very serious
problems, f.e. ordSetFocus("1") should chose index called "1" or maybe
index 49 or what should return AScan( {49,"1"}, "1" ) (1 or 2)? so
Harbour developers decided to not add anything like that to core code
so in Harbour functions written in C refuse to accept one byte string
as number and code like
? Str( "0" )
generates runtime error instead of printing ' 48'.
### OOP INTERFACE TO HASH ARRAYS ###
==========================================
xHarbour allows to access items in hash array using OOP interface.
hVal[ "ABC" ] := 100 can be alternatively written as hVal:ABC := 100.
Using OOP interface is slower then [] operator but it works for all
indexes which are valid upper case [x]Harbour identifiers.
By default Harbour core code does not give such functionality but
it has strong enough OOP API to allow adding such extension without
touching core code even by user at .prg level. It was implemented
in Harbour in XHB.LIB.
This code can be compiled and executed by both compilers:
#ifndef __XHARBOUR__
#include "xhb.ch"
#endif
proc main()
local hVal := {=>}
hVal["ABC"] := 100