108 lines
3.3 KiB
Plaintext
108 lines
3.3 KiB
Plaintext
The Harbour implementation of codeblocks.
|
|
Ryszard Glab <rglab@imid.med.pl>
|
|
|
|
|
|
The 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 the eval
|
|
stack of procedure/function. Every local variable used in a codeblock
|
|
occupies 2 bytes in this list.
|
|
+x: The stream of pcode bytes follows the header.
|
|
+y: the pcode byte for _ENDBLOCK
|
|
|
|
|
|
The evaluation of a codeblock.
|
|
Before a codeblock evaluation the virtual machine creates the eval stack
|
|
where all codeblock parameters are stored (just like 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. The table of local variables positions is created
|
|
during creation of a codeblock (in PushBlock() function).
|
|
During a codeblock creation, values of all local variables defined in a
|
|
procedure and accessed in a codeblock are replaced with a reference to
|
|
a value stored in a global memory variables pool. This allows to correct
|
|
access for 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.
|
|
|
|
|
|
Incompatbility 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)
|
|
|
|
Function Main()
|
|
Local nTest
|
|
Local bBlock1 := MakeBlock()
|
|
Local bBlock2 := {|| DoThing( @nTest ), qout("From Main: ", nTest ) }
|
|
|
|
eval( bBlock1 )
|
|
eval( bBlock2 )
|
|
|
|
Return( NIL )
|
|
|
|
Function MakeBlock()
|
|
Local nTest
|
|
Return( {|| DoThing( @nTest ), qout("From MakeBlock: ", nTest ) } )
|
|
|
|
Function DoThing( n )
|
|
|
|
n := 42
|
|
|
|
Return( NIL )
|
|
|
|
|
|
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
|
|
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)
|