/* * Las siguientes partes son derechos adquiridos de sus autores individuales. * www - http://www.harbour-project.org * * Copyright 2001 Ryszard Glab * Documentación en Inglés de codeblock.txt * * Copyright 2001 Alejandro de Gárate * Traducción al Español de codeblock.txt * * Vea doc/license.txt por los términos de la licencia. * */ Implementación de Harbour de codeblocks (bloques de código) =========================================================== Compilación de un codeblock Durante el tiempo de Compilación, el codeblock es almacenado en la siguiente forma: - la cabecera - la ristra de bytes de pcodes La cabecera almacena información acerca de variables locales referenciadas. +0: el byte pcode para _PUSHBLOCK +1: el número de bytes que definen a un codeblock +3: el número de parámetros codeblock (declarados entre || en el codeblock) +5: número de variables locales usadas declaradas en el procedimiento / función donde el codeblock es creado. +7: La lista de las posiciones de variables locales de los procedimientos / funciones, en la pila eval del procedimiento / función. Cada variable local usada en un codeblock usa 2 bytes en esta lista. Cuando son usados codeblocks anidados, entonces esta lista es creada solamente en el codeblock más externo. +x: La ristra de bytes pcode, siguiendo a la cabecera. +y: El byte pcode para _ENDBLOCK Creación de un codeblock ======================== Cuando el opcode HB_P_PUSHBLOCK es ejecutado entonces la estructura HB_ITEM es creada y puesta en la pila de evaluación. El tipo de item es IT_BLOCK. El valor de este item es un puntero a la estructura HB_CODEBLOCK. Adicionalmente este item almacena la base de las variables estáticas definidas para el procedimiento/función actual. - esto es usado durante la evaluación de un codeblock cuando la evaluación es llamada desde código desde otro módulo PRG. También el número de parámetros esperados es almacenado. La estructura HB_CODEBLOCK almacena un puntero a la ristra (stream) de pcodes que es ejecutada durante la evaluación de un codeblock. Este almacena también el puntero a la tabla con referencia a variables locales. Valores de todas las variables locales definidas en un procedimiento y usadas en un codeblock son reemplazadas con una referencia a un valor almacenado en un pool de variables de memoria global. Esto permite el correcto acceso a variables locales aisladas en un codeblock devuelto desde ésta función (sea directamente en una sentencia RETURN ó indirectamente por asignarlo éste a una variable estática ó MEMVAR). Este reemplazo automático e incondicional es requerido porque no hay un método seguro de encontrar si un codeblock será accedido desde fuera de una función dónde éste es creado. Cuando son usados codeblocks anidados, solamente el codeblock más externo crea la tabla - todo codeblock interno usa esta tabla. Esto permite compartir la tabla entre codeblocks anidados - , la tabla es borrada si no hay más referencias a ella. Esto es causado por el hecho que un codeblock interno puede ser creado durante la evaluación del codeblock exterior cuando las variables locales no existen como en este ejemplo: PROCEDURE MAIN() PRIVATE foo, bar Test() EVAL( foo ) EVAL( bar ) RETURN PROCEDURE Test() LOCAL a:='FOO', b:='BAR' foo ={ || a + ( bar:=EVAL( {|| b} ) ) } RETURN Evaluación de un codeblock ========================== Los parámetros pasados a un codeblock son puestos en la pila de evaluación antes de la evaluación del codeblock. Ellos son accedidos exactamente igual que cualquier parámetro de función. Cuando un parámetro de codeblock es referenciado, entonces su posición en la pila de evaluación es usada. Cuando una variable local de un procedimiento es referenciada entonces el índice dentro de la tabla de posiciones de variables locales (copiada de la cabecera) es usada. El valor negativo es usado como un índice para distinguirlo de la referencia a un parámetro del codeblock. Incompatibilidad con Clipper ============================ 1) Variables locales aisladas pasadas por referencia ------------------------------------------------- Hay una pequeña diferencia entre el manejo de las variables pasadas por referencia en un codeblock. El siguiente código lo explica (gracias a 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 ) En Clipper esto produce: Desde MakeBlock = NIL Desde Main = 42 En Harbour esta produce: (esta es la salida correcta, en mi opinión ) Desde MakeBlock = 42 Desde Main = 42 2) Alcance de variables sin declarar --------------------------------- Considere el siguiente código: PROCEDURE MAIN() LOCAL cb cb :=Detach() ? EVAL( cb, 10 ) RETURN FUNCTION Detach() LOCAL b:={|x| x+a} LOCAL a:=0 RETURN b En Clipper la variable 'a' en un codeblock tiene alcance *local* , sin embargo en Harbour la variable 'a' tiene un alcance *privado*. Como resultado de ello, en Clipper este código imprimirá 10 y en Harbour este producirá "error de argumento" en la operación '+'. Esto será cierto cuando la variable 'a' sea declarada como PRIVATE. PROCEDURE MAIN() LOCAL cb PRIVATE a cb := Detach() ? EVAL( cb, 10 ) RETURN El código de arriba también produce 10 en Clipper (aún si es compilado con el switch -a ó -v).