von Martin Grund, 02.12.2007

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

Aus dem
t3n Magazin Nr. 10

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.

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.

Seite:  1 2 3 4 5

Empfohlene Artikel