Anterior: AST Subir: Tabla de Contenido Siguiente: Etapa de Codificación

Etapa de Generación

This topic has missing or partial documentation. Please help us improve it.

See How-To - Write Documentation

Una vez que Melbourne ha creado un AST, invoca la etapa de generación, con el AST como entrada.

La etapa de generación crea una nueva instancia de Rubinius::Generator y le comunica a la raíz del AST que genere su bytecode en el objeto Generator.

Un Generator dispone de un DSL escrito en Ruby para generar bytecode de Rubinius. En su interior, el generador expone métodos que son un reflejo directo de las Instrucciones de Rubinius. Por ejemplo, para crear una instrucción que añada un nil a la pila, se podría llamar el método push_nil en una instancia de Generator.

La clase Generator expone también ciertos métodos convenientes que permiten generar patrones comunes de bytecode sin tener que preocuparse por detalles a demasiado bajo nivel de algunas áreas del conjunto de instrucciones de Rubinius.

Por ejemplo, para llamar a un método directamente directamente utilizando bytecode de Rubinius, se debe crear primero un literal representando el nombre del método, y entonces llamar send_stack para llamar al método. Además, si se quiere llamar a un método privado, habría primero que crear un bytecode que permite específicamente la invocación de métodos privados. Si se quisiera invocar el método puts sin argumentos, permitiendo la invocación de métodos privados, habría que hacer lo siguiente:

# aquí, g es una instancia de Generator
g.allow_private
name = find_literal(:puts)
g.send_stack name, 0

Utilizar el método auxiliar send simplifica bastante el código anterior:

g.send :puts, 0, true

Cuando se genera bytecode para un AST, Rubinius invoca el método bytecode en cada nodo del AST con un argumento: la instancia actual de Generator. He aquí la implementación del método bytecode para el nodo if:

def bytecode(g)
  pos(g)

  done = g.new_label
  else_label = g.new_label

  @condition.bytecode(g)
  g.gif else_label

  @body.bytecode(g)
  g.goto done

  else_label.set!
  @else.bytecode(g)

  done.set!
end

En primer lugar, el método llama al método pos, un método en la superclase Node que llama a su vez g.set_line @line. Esto sirve a la máquina virtual a la hora de proveer información de debugging sobre el código que ejecutamos.

A continuación, el código utiliza los métodos auxiliares para tratar con etiquetas. Las instrucciones de Rubinius no tienen ningúna estructura de control de flujo excepto por algunas instrucciones "goto" (goto, goto_if_true y goto_if_false). Se puede usar la forma corta git para goto_if_true y gif para goto_if_false. En este caso creamos dos etiquetas nuevas: una para el final de la condición del if y otra que marca el comienzo del bloque else.

Después de crear las dos etiquetas, el nodo if invoca el método bytecode en su nodo hijo @condition, pasándole el actual objeto Generator. Esto emitirá el bytecode para la condición del if en el flujo de instrucciones actual.

Ese proceso debería dejar en la pila el valor que haya resultado de evaluar la condición, de modo que si el nodo if emite una instrucción goto_if_false, saltará inmediatamente a la etiqueta else_label. Entonces usa el mismo patrón que hemos visto anteriormente para pedir al @body que emita su bytecode en el flujo de instrucciones actual, y entonces emite un goto incondicional hasta el final de todo el if (su último end).

A continuación necesitamos marcar la posición del else_label. Desacoplando la creación de la etiqueta respecto a su uso, podemos pasársela a la instrucción goto antes de marcar dónde está, requerimiento crucial para ciertas estructuras de control.

Pedimos entonces al nodo @else que emita su bytecode y marcamos la posición de la etiqueta done.

Este proceso ocurre recursivamente desde el nodo raíz a través de todo el AST, lo cual popula el objeto Generator con una representación en bytecode del AST original empezando desde su raíz.

Probablemente es útil echarle un vistazo a las clases dentro del directorio lib/compiler/ast, donde están definidos todos los nodos AST y sus respectivos métodos bytecode. Es una buena forma también de ver ejemplos prácticos de uso de la API del Generator.

Una vez el Generator ha conseguido la representación en bytecode del AST, invoca la siguiente etapa del compilador: la etapa de Codificación.

Ficheros mencionados

Personalización

La forma más sencilla de personalizar la etapa de Generación del proceso de compilación es creando métodos de alto nivel complementando los comunes ya provistos por la implementación por defecto del Generator.

También se puede personalizar la clase del Generador que queremos utilizar. Para aprender más sobre cómo personalizar determinadas etapas del compilador, léase Personalizando las etapas.

Anterior: AST Subir: Tabla de Contenido Siguiente: Etapa de Codificación

Tweet at @rubinius on Twitter or email community@rubini.us. Please report Rubinius issues to our issue tracker.