-
Notifications
You must be signed in to change notification settings - Fork 95
/
codebloc.txt
154 lines (107 loc) · 4.64 KB
/
codebloc.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
The Harbour implementation of codeblocks.
Ryszard Glab <[email protected]>
Compilation of a codeblock.
During compile time the codeblock is stored in the following form:
- the header
- the stream of pcode bytes
The header stores information about referenced local variables.
+0: the pcode byte for _PUSHBLOCK
+1: the number of bytes that defines a codeblock
+3: number of codeblock parameters (declared between || in a codeblock)
+5: number of used local variables declared in procedure/function where
the codeblock is created
+7: the list of procedure/function local variables positions on the eval
stack of procedure/function. Every local variable used in a codeblock
occupies 2 bytes in this list. When nested codeblocks are used then this
list is created for the outermost codeblock only.
+x: The stream of pcode bytes follows the header.
+y: the pcode byte for _ENDBLOCK
Creation of a codeblock.
When HB_P_PUSHBLOCK opcode is executed then the HB_ITEM structure is created
and placed on the eval stack. The type of item is IT_BLOCK. The value of this
item is a pointer to HB_CODEBLOCK structure. Additionally this item stores the
base of static variables defined for the current function/procedure - this
is used during a codeblock evaluation when the evaluation is called from a code
from other PRG module. Also the number of expected parameters is stored.
The HB_CODEBLOCK structure stores a pointer to the pcodes stream that is
executed during a codeblock evaluation. It stores also the pointer to a table
with local variables references. Values of all local variables defined in a
procedure and used in a codeblock are replaced with a reference to a
value stored in a global memory variables pool. This allows the correct access
to detached local variables in a codeblock returned from this function (either
directly in RETURN statement or indirectly by assigning it to a static or
memvar variable. This automatic and unconditional replace is required because
there is no safe method to find if a codeblock will be accessed from an outside
of a function where it is created.
When nested codeblocks are used then only the outermost codeblock creates
the table - all inner codeblock uses this table. The first element of this
table contains a reference counter for this table. It allows to share the table
between nested codeblock - the table is deleted if there is no more references
to it. This is caused by the fact that a inner codeblock can be created during
evaluation of outer codeblock when local variables don't exist like in this
example:
PROCEDURE Main()
PRIVATE foo, bar
Test()
Eval( foo )
Eval( bar )
RETURN
PROCEDURE Test()
LOCAL a := "FOO", b := "BAR"
foo := {|| a + ( bar := Eval( {|| b } ) ) }
RETURN
Evaluation of a codeblock.
Parameters passed to a codeblock are placed on the eval stack before a
codeblock evaluation. They are accessed just like usual function
parameters. When a codeblock parameter is referenced then its position on the
eval stack is used. When a procedure local variable is referenced then the
index into the table of local variables positions (copied from the header) is
used. The negative value is used as an index to distinguish it from the
reference to a codeblock parameter.
Incompatibility with the Clipper.
1. Detached locals passed by reference
There is a little difference between the handling of variables passed by
the reference in a codeblock.
The following code explains it (thanks to David G. Holm)
PROCEDURE Main()
LOCAL nTest
LOCAL bBlock1 := MakeBlock()
LOCAL bBlock2 := {|| DoThing( @nTest ), QOut( "From Main: ", nTest ) }
Eval( bBlock1 )
Eval( bBlock2 )
RETURN
FUNCTION MakeBlock()
LOCAL nTest
RETURN {|| DoThing( @nTest ), QOut( "From MakeBlock: ", nTest ) }
PROCEDURE DoThing( n )
n := 42
RETURN
In Clipper it produces:
From MakeBlock: NIL
From Main: 42
In Harbour it produces (it is the correct output, IMHO)
From MakeBlock: 42
From Main: 42
2. Scope of undeclared variables
Consider the following code:
PROCEDURE Main()
LOCAL cb := Detach()
? Eval( cb, 10 )
RETURN
FUNCTION Detach()
LOCAL b := {| x | x + a }
LOCAL a := 0
RETURN b
In Clipper the 'a' variable in a codeblock has the *local* scope however in
Harbour the 'a' variable has the *private* scope. As a result, in Clipper
this code will print 10 and in Harbour it will raise 'argument error' in
'+' operation.
This will be true also when the 'a' variable will be declared as PRIVATE
PROCEDURE Main()
LOCAL cb
PRIVATE a
cb := Detach()
? Eval( cb, 10 )
RETURN
The above code also prints 10 in Clipper (even if compiled with -a or -v
switches)