Etap generowania - Rubinius
Author: Less Than Three. <3. http://less.thanthree.com
Description: Rubinius is an implementation of the Ruby programming language. The Rubinius bytecode virtual machine is written in C++. The bytecode compiler is written in pure Ruby. The vast majority of the core library is also written in Ruby, with some supporting primitives that interact with the VM directly.

1.2.3

## Etap generowania

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

See How-To - Write Documentation

Po utworzeniu Drzewa AST przez parser Melbourne jest ono przekazywane do generatora na wejście.

Etap generowania polega na utworzeniu instancji `Rubinius::Generator` i wywołaniu na głównym węźle drzewa AST metody, która wygeneruje bajtkod na obiekcie `Generator`.

Klasa `Generator` zapewnia mini język DSL (napisany w “czystym” Ruby) służący do generowania bajtkodu Rubiniusa. `Generator` udostępnia metody, które przekładają się bezpośrednio na instrukcje dla maszyny wirtualnej. Dla przykładu aby utworzyć instrukcję, która przekaże nil na stos możesz wywołać metodę `push_nil` na instancji `Generatora`.

Klasa `Generator` zapewnia również kilka pomocnych metod, które umożliwiają generowanie często występujących układów bajtkodu bez zagłębiania się w szczęgóły implementacji niektórych instrukcji maszyny wirtualnej.

Na przykład, aby wywołać metodę bezpośrednio przy użyciu bajtkodu Rubiniusa musiałbyś utworzyć literał reprezentujący nazwę metody i wywołać `send_stack` aby wywołać metodę. Dodatkowo jeśli chciałbyś wywołać metodę prywatną najpierw musiałbyś utworzyć bajtkod, który to umożliwi. Jeśli chciałbyś wywołać metodę `puts` bez argumentów, zezwalając na wywołanie prywatne musiałbyś wywołać:

    # tutaj, g jest instancją Generatora
    g.allow_private
    name = find_literal(:puts)
    g.send_stack name, 0

Wykorzystując metodę pomocniczą `send`, możesz to zrobić prościej:

    g.send :puts, 0, true

Generując bajtkod dla Drzewa AST Rubinius wywołuje metodę `bytecode` na każdym węźle drzewa, przekazując obecną instancję `Generatora` jako argument. Poniżej metoda `bytecode` dla węzła `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

Najpierw, metoda `bytecode` wywołuje metodę `pos`, która jest zdefiniowana w podstawowej klasie `Node`. `pos` wywołuje `g.set_line @line`. Jest to wykorzystywane przez maszynę wirtualną aby zapewnić informacje odnośnie działającego kodu służące do debuggowania.

Następnie, kod korzysta z metod pomocniczych `label` klasy `Generator. Podstawowy kod Rubiniusa nie ma żadnych struktur sterujących oprócz kilku instrukcji goto (czyste` goto`,` goto_if_true`oraz` goto_if_false`). Możesz używać formy uproszczonej` git`dla` goto_if_true`oraz` gif`dla` goto_if_false`. W tym przypadku tworzymy dwie etykiety (labels), jedną dla oznaczenia końca warunku` if`a drugą dla oznaczenia początku bloku` else`.

Po utworzeniu tych dwóch etykiet, węzeł `if` wywołuje metodę `bytecode` na swoim węźle warunku (@condition), przekazując aktualną instancję generatora jako argument. To wywołanie spowoduje utworzenie bajtkodu dla warunku.

Ten proces powinien umieścić wartość wykonania warunku na stosie, więc węzeł `if` tworzy instrukcję `goto_if_false`, która spowoduje przeskok do `else_label` jeśli warunek nie został spełniony. Następnie używamy podobnego wzorca co poprzednio - generator wywołuje metodę `bytecode` na węźle `@body`, co powoduje utworzenie bajtkodu dla @body. Po tym następuje umieszczenie bezwarunkowego `goto`, które przenosi wykonywanie programu na koniec bajtkodu całej instrukcji `if`.

Następnie, umieszczamy oznaczenie `else_label`. Dzięki rozdzieleniu tworzenia oznaczenia od jego użycia możemy przekazać to oznaczenie do `goto` zanim go wykorzystamy do oznaczenia lokalizacji. To bardzo ważna możliwość, która ma podstawowe znaczenie dla wielu struktur kontrolnych.

Następnie prosimy węzeł `@else` o jego bajtkod oraz oznaczamy lokalizację `done`.

Ten proces wykonuje się rekurencyjnie począwszy od głównego węzła drzewa, poprzez wszystkie węzły. Wynikiem tego jest utworzenie w obiekcie `Generator` bajtkodu reprezentującego całe drzewo.

Dobrym miejscem do sprawdzenia definicji wszystkich węzłow AST i ich bajtkodu jest katalog `lib/compiler/ast`. Znajdziesz tam również dobre, praktyczne przykłady wykorzystania API `Generatora`.

Po utworzeniu całego bajtkodu dla AST, `Generator` wywołuje następny etap czyli kodowanie.

## Pliki

- lib/compiler/generator_methods.rb: Generowany plik zawierający metody odnoszące się do instrukcji Rubiniusa
- lib/compiler/generator.rb: Definicja obiektu `Generator`. Klasa ta definiuje podstawowe metody generatora oraz metodą pomocnicze ułatwiające generowanie bajtkodu dla często występujących wzorców.
- lib/compiler/ast: Definicje wszystkich węzłów AST utworzone przez parser.

## Dostosowanie generowania

Naprostszym sposobem zmiany działania generatora jest utworzenie dodatkowych metod które będą korzystać z podstawowych metod dostarczanych przez klasę `Generator`.

Oprócz tego możesz zmienić klasę Generatora, która jest używana w procesie kompilacji. Aby dowiedzieć się więcej o dostosowywaniu poszczególnych etapów kompilacji do swoich potrzeb przeczytaj Modyfikacja procesu kompilacji.