The Harbour implementation of codeblocks. Ryszard Glab 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)