QVINTVS · SCRIBET

Einführung in mruby, Teil 1: Einfach evaluieren

Ich habe heute angefangen, mich mal näher mit mruby zu beschäftigen. Nach einigen Stunden Lese- und Experimentierarbeit (sowie einem Bugreport) muss ich sagen: Das ist richtig cool. Und damit nicht nur ich das finde, habe ich mir vorgenommen, eine kleine Blogreihe zu mruby zu starten, deren erster Teil dieser Beitrag sein soll.

Erst einmal vorweg: Ich bin noch komplett neu in der Programmierung mit mruby. Ich habe bisher zwar schon einige C-Extensions sowohl für den kanonischen MRI als auch für Rubinius geschrieben, hatte mich bis dato aber nur vom Hörensagen mit mruby beschäftigt. Daher zunächst einmal ein kurzer Abriss zu mruby selbst.

Was ist mruby?

mruby ist zunächst einmal eine (weitere) Implementation der Programmiersprache Ruby. Im Gegensatz zu den bisher existierenden Implementierungen legt sie jedoch den Fokus auf Einbettbarkeit in andere Programme, d.h. mruby ist vor allem dafür gedacht, etwa aus Spielen heraus als Skriptelement eingesetzt werden zu können. Bisher war dieses Vorhaben nur mit großem Aufwand realisierbar — der MRI lässt sich nur unheimlich schwer in ein anderes Programm einbetten. Nur soviel: Es geht, aber der Aufwand steht in keinem vertretbaren Maß zum herausziehbaren Nutzen. Rubinius habe ich in dieser Hinsicht nicht untersucht, doch glaube ich nicht, dass dies viel einfacher ausfiele.—

Zurück zu mruby. Wir haben es also mit einem Interpreter für Ruby-Quellcode zu tun, allerdings nicht für beliebigen Ruby-Code. Das m in mruby steht für „minimal“, man kann also nicht davon ausgehen, dass man sein nächstes Spiel nun gleich mit einer eingebetteten Rails-Applikation verteilen kann. Stattdessen konzentriert man sich bei mruby auf Einfachheit, Einbettbarkeit und Modularität. Tatsächlich arbeitet man vor allem nach Rubys ISO-Standard, der für Interessierte für die Kleinigkeit von 238 Schweizer Franken als PDF oder Printvariante erhältlich ist. Für diejenigen, die wie ich die Preise der ISO für überzogen halten, sei gesagt: Grundsätzlich funktioniert alles so, wie man es vom MRI kennt. Trotzdem sollte man bei unerwartetem Verhalten erst einmal nachschauen, ob es nicht ein Spezifikum von mruby ist. Darüber hinaus beschäftigt sich mruby wirklich nur mit dem Ruby-Kern, d.h. die stdlib ist erstmal nicht dabei.

Wer aufmerksam mitgelesen hat und schon einmal mit eingebetteten Sprachen zu tun hatte, dem wird auffallen, dass Ruby hier in Konkurrenz zu einer anderen Sprache tritt: Lua. Es wäre falsch zu sagen, dass man sich mit anderen Zielen beschäftigt, aber grundsätzlich will mruby gerade bei komplexeren Projekten, die auf der Skriptseite Objektorientierung verwenden wollen, die bevorzugte Wahl werden. Wer schon einmal versucht hat, in Lua ohne Zuhilfenahme von diversen externen Projekten ein objektorientiertes System auf die Beine zu stellen, der weiß, was ich meine — Lua ist wie JavaScript eine prototypenbasierte Sprache, und eigentlich arbeitet man nur mit Tabellen. Über und über Tabellen, was zu Lernzwecken sogar sehr interessant sein kann, aber IMHO gerade bei komplexeren Projekten doch zu kurz fällt. (Kleiner Hinweis am Rande: ich habe für PegasusAlpha einen Wrapper für C++-Objekte in Lua 5.2 geschrieben; vor allem deshalb, weil ich keine Lib finden konnte, die Lua 5.2 unterstützt). Ich denke daher, dass es im Feld der eingebetteten Sprachen Platz für beide geben wird, auch, weil Lua für Nichtprogrammierer leichter verständlich ist. Auf der anderen Seite hat man mit mruby die Möglichkeit, sehr leicht ein voll funktionales und ausgereiftes Objektsystem in Kombination mit einer (für Programmierer) sehr angenehm zu lesenden Syntax einzubetten. Es bleibt daher abzuwarten, wie genau sich die Auswirkungen zeigen werden.

Der erste Kontakt

Noch kurz zur Information: mruby ist nicht stabil, sondern wird sehr aktiv entwickelt. Wer es jetzt schon einbetten möchte, wird feststellen, dass dies zwar möglich ist, aber a) die Dokumentation doch noch sehr zu wünschen übrig lässt und b) die Gefahr rascher API-Brüche besteht. Ein überaus interessantes Thema ist es aber dennoch :-).

Zunächst einmal gilt es, den Quellcode von mruby auszuchecken. mruby selbst wird als statische Library gebaut, ist also dafür gedacht, in etwaigen Projekten einfach mit ausgeliefert zu werden, daher würde es sich anbieten, bei der Verwendung von Git als Versionskontrollsystem einfach ein Submodul zu verwenden. Wir begnügen uns hier mit einem einfachen, nicht versionierten Beispiel.

% mkdir mruby-project
% cd mruby-project
% git clone git://github.com/mruby/mruby.git

Aus Gründen der Einfachheit unterlassen wir hier die weitere Konfiguration des Builds und begnügen uns mit den Standardeinstellungen (evtl. folgt dazu einmal ein weiterer Blogpost). Es geht also weiter mit der Kompilierung:

% cd mruby
% rake

Was, rake? Ja, rake:-). mruby verwendet Rake als Buildsystem, und zwar genau das Rake, an das Sie gerade gedacht haben. Oder, um es kurz zu sagen: Zur Kompilierung von mruby benötigen Sie eine Ruby-1.9- oder 2.0-Installation (1.8 geht vermutlich auch, habe ich aber nicht getestet).

Das Ergebnis der Kompilation befindet sich in build/host und schaut etwa so aus:

% tree build
build
└── host
    ├── bin
    │   ├── mirb
    │   ├── mrbc
    │   └── mruby
    ├── lib
    │   ├── libmruby.a
    │   ├── libmruby.flags.mak
    │   └── libmruby_core.a
  ...

Es werden noch diverse weitere Verzeichnisse angelegt, die für uns aber nicht weiter interessant sind. Die Programme in bin/ erlauben es Ihnen, mruby einfach mal schnell „on-the-fly“ auszuprobieren; ihr Quellcode dient gleichzeitig als Beispiel dafür, wie man mruby einbetten könnte. Versuchen Sie etwa einmal die mruby-IRB:

% build/host/bin/mirb
mirb - Embeddable Interactive Ruby Shell

This is a very early version, please test and report errors.
Thanks :)

> [1, 2, 3].map{|i| i ** 2}
 => [1, 4, 9]
> exit

Das Programm bettet einen mruby-Interpreter ein und nutzt ihn dann zum Ausführen der Eingabe. Ich werde darauf nicht weiter eingehen und überlasse es dem Leser, sich mit dem Quellcode von mirb auseinanderzusetzen.

mruby einbetten

mruby ist wie schon angesprochen zur Einbettung gedacht, nicht zur direkten Ausführung als Programm. Daher wollen wir uns nun genau das vornehmen: Ein kleines C-Programm schreiben, das einfach nur einen fixen String Ruby-Code evaluiert.

Dazu legen wir zunächst einmal ein neues Verzeichnis für unseren Projektcode an.

% cd ..
% mkdir foo
% cd foo
% cp ../mruby/build/host/lib/*.a .
% touch foo.c

Der Einfachheit halber kopieren wir die fertig kompilierten mruby-Libraries einfach schon mal in unser Projektverzeichnis. Für ein echtes Projekt sollte man natürlich mruby als Unterprojekt einbinden und im Buildsystem separat kompilieren lassen.

Nun zur Essenz des ganzen. Der Inhalt der Datei foo.c:

#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>

int main(int argc, char* argv[])
{
  /* Speicher für unsere Instanz allokieren */
  mrb_state* p_state = NULL;

  /* Den Interpreter initialisieren */
  p_state = mrb_open();

  /* Etwas Ruby-Code ausführen */
  mrb_load_string(p_state, "puts 'Hello from mruby!'");

  /* Den Ruby-Interpreter schließen */
  mrb_close(p_state);

  /* Ende */
  return 0;
}

Mehr ist es wirklich nicht. mrb_state() erzeugt eine neue Interpreter-Instanz, mrb_load_string() entspricht #eval in Ruby und führt einen String aus und mrb_close() deallokiert alle verbliebenen Ruby-Objekte und räumt den Interpreter weg. Die Datei wie folgt kompilieren:

% gcc -Wall -I../mruby/include foo.c libmruby.a libmruby_core.a -lm -o foo

Unbedingt darauf achten, die Mathematik-Bibliothek via -lm zu verlinken, ansonsten beschwert sich der GCC über eine Menge nicht definierter Funktionen.

Und der Test:

% ./foo
Hello from mruby!

Ein kleiner Hinweis noch für Leute, die schon einmal den MRI eingebettet haben (oder es zumindest versucht haben): Ja, es ist tatsächlich möglich, mehrere mruby-Instanzen zu öffnen. Einfach mehrmals mrb_open() bemühen und den Rückgabewert in verschiedenen Variablen speichern. mruby verlangt im Gegensatz zum MRI nicht, die einzige laufende Instanz im Prozess zu sein. Dies führt zu einigen API-Unterschieden im Vergleich zum MRI, worauf ich in einem späteren Blogpost eingehen werde.

Abschluss

So, und das war es auch schon wieder. Im nächsten Posting geht es dann darum, wie man eigentlich C-Structs in der Ruby-Umgebung zur Verfügung stellt. Bis dahin wünsche ich vergnügliche Stunden mit mruby! :-)

Valete.