Anzeige
Anzeige
UX & Design
Artikel merken

Entwickeln domänenspezifischer Sprachen mit Ruby: Auf dem Weg zur nächsten Programmiersprache

Der folgende Artikel soll einen Einblick in die Entwicklung von domänenspezifischen Sprachen mit Ruby geben und dabei die wichtigsten Mittel des Sprachdesigns erläutern. Anhand eines Beispiels wird gezeigt, wie die notwendige Grammatik, der Lexer und der Parser definiert werden.

7 Min. Lesezeit
Anzeige
Anzeige

Im Laufe der Jahre hat die Popularität von Ruby stetig zugenommen, die Anzahl verfügbarer Anwendungen steigt mit jedem Tag. Doch hat dies nicht nur Vorteile für den Entwickler – so muss er immer komplexere Programmierschnittstellen (API) verstehen und beherrschen, um ein gewünschtes Problem lösen zu können. Trotz der Flexibilität von Ruby, die hoch geschätzt wird, gibt es immer wieder Sachverhalte, die man anders ausdrücken möchte beziehungsweise der eben noch geschriebene Programmcode fühlt sich einfach nicht gut an. In diesem Fall hat man wahrscheinlich Code geschrieben, der einen bestimmten Zustand mit einer bestimmten Semantik beschreibt und versucht, ein bestimmtes Problem innerhalb dieses eingeschränkten Kreises – der Domäne – zu lösen. Im nun folgenden Refactoring-Schritt würde man die API solange anpassen, bis diese sich gut in das Programmiermodell einbettet. Mit der Optimierung des geschriebenen Codes wurde schon der erste Schritt zu einer domänenspezifischen Sprache (DSL) getan, auch wenn man es auf den ersten Blick nicht glauben mag.

Anzeige
Anzeige

Doch bevor nun näher auf die eigentliche Sprache eingegangen wird, soll zuerst kurz definiert werden, was eine domänenspezifische Sprache überhaupt ist: Sie beschreibt ein Programmiermodell, das auf eine bestimmte Situation beschränkt ist und Verallgemeinerungen bewusst außen vor lässt, um sich voll und ganz dem eigentlichen Problem zu widmen. Dabei wird bei der DSL zwischen zwei verschiedenen Arten – interne und externe DSL – unterschieden. Die interne DSL probiert, den gewünschten Sachverhalt ganz mit den Mitteln der Wirtssprache auszudrücken. Ein Beispiel für ein solches Paradigma ist das bekannte Framework ActiveRecord aus Ruby on Rails. Hier werden durch geschickten Umgang mit den sprachlichen Mitteln von Ruby die Modelle mit deklarativen Mitteln definiert, die im Prinzip nicht zu der typischen imperativen Syntax passen. Eine externe DSL erkennt man daran, dass diese von einem Parser analysiert und dann von einem Interpreter verarbeitet wird – ein typisches Beispiel ist ein Bash-Skript unter Linux/Unix. Die Bash ist auf eine ganz bestimmte Domäne eingeschränkt, die möglichen Operationen der Sprache sind speziell auf den Umgang mit Dateien und Verzeichnissen optimiert.

Was ist nun der Unterschied zwischen externen domänenspezifischen Sprachen und vollwertigen Programmiersprachen (GPL)? Er besteht darin, dass eine GPL mit dem Ziel entworfen wurde, möglichst alle Probleme in den unterschiedlichsten Domänen zu lösen, während der Einsatzbereich von einer externen DSL auf eine Domäne beschränkt ist.

Anzeige
Anzeige

Grammatik, wie in der Schule?

Das wichtigste Werkzeug beim kreativen Prozess der Sprachentwicklung ist die Grammatik der jeweiligen Sprache. Grammatiken bilden dabei wie in der natürlichen Sprache den Regelsatz, nach dem Aussagen in der Sprache allgemein gültig formuliert werden.

Anzeige
Anzeige

In der Linguistik formulieren Grammatiken Ersetzungsregeln, die bestimmen, wie man von einem Ausgangspunkt zu einem Endpunkt kommt. Für den Sprachdesigner ist dabei das wichtigste Ziel, eine deterministische und finite Grammatik zu definieren.

Die Analyse der Eingabedaten beginnt mit der Zerlegung in die Grundbegriffe der Sprache, die Token, durch den Lexer. Übertragen auf die deutsche Sprache wäre dies die Zerlegung der ursprünglichen Zeichenkette (Beispiel: „Ich helfe dir.“) in die drei Grundelemente der Sprache, Subjekt, Prädikat und Objekt. Im nächsten Schritt werden mittels vordefinierter Regeln ( HS -> S P O ) die einzelnen Token (S, P, O) durch den Parser in ein Symbol (HS) ersetzt. Dies wird so lange fortgeführt, bis alle Token und Symbole durch Terminalsymbole – Symbole, für die keine weiteren Ersetzungsregeln definiert sind – ersetzt wurden.

Anzeige
Anzeige

Ein Rechenbeispiel

Das folgende Beispiel zeigt, trotz seines geringen Umfangs, die Möglichkeiten einer externen Sprache auf und stellt außerdem dar, wie einfach es ist, eine solche Sprache in Ruby mit Hilfe des Dhaka-Projekts [1] zu implementieren. Dhaka ist ein in Ruby geschriebener Parsergenerator zur einfachen Implementierung einer externen DSL. Dabei greift der Generator dem Entwickler hilfreich unter die Arme und bietet dennoch die von Ruby gewöhnte Flexibilität. Zielsetzung ist es, eine DSL zu erstellen, die einen in Präfixnotation eingegebenen String in das für Ruby verständliche Infixformat umwandelt, dieses evaluiert und das Ergebnis der Operation zurückgibt.

Es soll die Eingabe „(+ 2 1)“ analysiert und das Ergebnis 3 ausgegeben werden. Außerdem soll zusätzlich Klammerung erlaubt sein, sodass folgender Ausdruck „(+ (- 10 2) 1)“ den Wert 9 zur Folge hat. Wie oben schon erwähnt, wird im ersten Schritt ein Lexer benötigt, der die Eingabemenge in für den Parser verständliche Stückchen zerlegt. Benötigt wird eine Erkennung für die Klammern, die Operatoren und natürlich für die Zahlen. In der Dhaka-Syntax formuliert wird dies zu dem Code in Listing 1.

Lexer
class SimpleLexer < Dhaka::LexerSpecification
	ops = ["+", "-"]; keys = ["(", ")"]
	keys.each { |op|   for_symbol(op) {  create_token(op) } }
	ops.each { |op|  for_symbol(op) {  create_token("OPERATOR")  }}
	for_pattern('\d+') {  create_token('n') }
	for_pattern('\s+') {  }
end

Listing 1

In Zeilen 3 bis 9 werden für die Operatoren (‚+‘,‘-‚) und die Schlüsselwörter (‚(‚, ‚)‘) eigene Token angelegt. Reguläre Ausdrücke definieren dabei, was ein Token ist, bevor dieses angelegt wird. Whitespaces wie Zeilenumbrüche und Leerzeichen werden ignoriert. Damit ist der Lexer spezifiziert, welcher die Eingaben für den Parser vorbereitet.

Anzeige
Anzeige
BNF-Schreibweise der Grammatik
_Start_ : Expr
Expr :  (   OPERATOR   Expr   Expr ) | n

Listing 2

Nun muss die Grammatik definiert werden. Sie muss Ausdrücke erkennen, also zuordnen, dass eine Folge von Klammer, Operator, Zahl, Zahl und Klammer einen Ausdruck bildet. Zusätzlich dazu ist noch das Erkennen von Schachtelung zu ermöglichen. In der BNF-Schreibweise (eine spezielle Art, Ableitungsregeln zu schreiben) in Listing 2 kann man gut erkennen, dass die Ersetzungsregel rekursiv und so lange angewendet wird, bis ein Ausdruck nur noch ein Zahlsymbol ist. Für das Zahlsymbol sind keine Ersetzungsregeln definiert, es ist damit ein Terminalsymbol. Die Klammern in der BNF-Schreibweise definieren keine Präferenzregeln, eine Klammer stellt hier ein spezielles Token dar. Ähnlich einfach wie die Definition des Lexers ist die Definition des Parsers und seiner Grammatik.

Grammatik in Ruby-Code
class SimpleGrammar < Dhaka::Grammar  
	for_symbol(Dhaka::START_SYMBOL_NAME) {   start %w|  Expr | }
	for_symbol("Expr") do
			operation   %w| ( OPERATOR Expr Expr ) |
		integer     %w| n |
	end
end

Listing 3

Nachdem Lexer und Parser erstellt wurden, soll mit dem nächsten Schritt die Entwicklung der eigenen DSL vorangetrieben werden. Um das Ergebnis der Eingabe berechnen zu können, muss das Resultat der Analyse, der Parsebaum, ausgewertet werden. Die Dhaka-Evaluatorklasse wird nun zur Hilfe genommen, um den Parsebaum zu durchlaufen. Der Entwickler hat hierbei die Möglichkeit, an bestimmten Stellen Hooks in den Baum zu integrieren und so eigenen Code ausführen zu lassen.

Evaluator
class SimpleEval < Dhaka::Evaluator
	self.grammar = SimpleGrammar
	define_evaluation_rules do
		for_start { eval(evaluate(child_nodes[0])) }
		for_operation {  evaluate(child_nodes[2]) +   child_nodes[1].token.value + 
			evaluate(child_nodes[3]) }
		for_integer { child_nodes[0].token.value.to_s }
end

Listing 4

Listing 4 lässt gut erkennen, wie dies funktioniert. Zunächst wird im Evaluator mittels einer Klassenvariable definiert, dass die zuvor erstellte Grammatik als Grundlage für die Auswertung verwendet werden soll. Im Anschluss daran werden die notwendigen Hooks installiert, wobei es nur zwei Dinge zu beachten gibt: Hooks können nicht für die eigentliche Regel, sondern nur für Produktionen definiert werden und für jedes Terminalsymbol muss immer ein Hook definiert werden. Damit die Mathematik wieder stimmt, können nun Lexer und Parser zusammen mit dem gerade entstandenen Evaluator aufgerufen werden, sodass „(+ (- 10 2) 1)“ den Wert 9 ergibt.

Anzeige
Anzeige
Ausführen des Beispiels
lexer = Dhaka::Lexer.new(SimpleLexer)
parser = Dhaka::Parser.new(SimpleGrammar)
puts SimpleEval.new.evaluate( parser.parse(lexer.lex("(+ (- 10 2) 1)")) )

Listing 5

Ist das schon alles?

Natürlich ist das hier gezeigte Beispiel recht einfach und übersichtlich, doch zeigt es, wie schnell man eine eigene Programmiersprache entwickeln kann. Die Möglichkeiten, die eine externe DSL dem Entwickler bietet, sind nahezu unbegrenzt. Man sollte sich jedoch nicht verleiten lassen, für jeden Spezialfall eine DSL zu schreiben, denn wenn zusätzlich zum eigentlichen Applikationscode noch ein weiteres Projekt gewartet werden muss, kann dies die Kosten für die Softwareentwicklung steigern. Auf der anderen Seite kann eine DSL die nötige Flexibilität bieten, um schneller zum Ziel zu kommen beziehungsweise spezielle Anwendungsgebiete überhaupt abdecken zu können.

Als Ausblick zur Verwendung einer externen Sprache sei dabei das RMQL-Projekt genannt. RMQL steht für Ruby on Rails Model Query Language und erlaubt dem Benutzer, über eine Anfragesprache direkt auf ein Rails-Modell zuzugreifen. Die Sprache ist dabei an XQuery angelehnt und ermöglicht dem Benutzer, mit der Anfrage gleichzeitig das Ergebnisdokument zu definieren. Das RMQL-Projekt ist mit Hilfe des Dhaka-Frameworks implementiert und als Open Source verfügbar [2]. RMQL zeigt neben der eigentlichen Sprachimplementierung auch, wie bestimmte Konzepte aus dem Bereich der Virtuellen Maschinen wie Stack Frames und überprüfte Methodenaufrufe realisiert werden können.

Wer sich weitergehend mit dem Thema domänenspezifischer Sprachen beschäftigen will, dem sei das Buch „Antl3 – The ultimative Reference“ [3] empfohlen, das alle notwendigen Konzepte noch einmal tiefergehend erläutert.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige