Praktikum II zur Vorlesung Informatik III Domain Specific Language Prof. Dr. Nikolaus Wulff Zeitraum: 8. – 15. November 2011 Vorbesprechung: Donnerstag 27. Oktober 2011 Der Integrator aus Praktikum I ist lediglich in der Lage fest im Programm übersetzte Funktionen zu verarbeiten. Nun gilt es, die Funktionen in einer Textdatei oder als Zeichenkette frei zu spezifizieren und anschließend mit Hilfe eines Parsers in einen Abstrakten Syntax Baum (AST) zu überführen, der dann numerisch effizient ausgewertet, graphisch gezeichnet und differenziert oder integriert werden kann. Dieses Vorgehen ermöglicht es Funktionen frei zu erfinden und zu definieren, ohne das Programm neu übersetzen zu müssen. Hierzu wird eine eigene Domain Specific Language (DSL) für den Parser entwickelt. 1 Domain Specific Language Im Listing 1 finden Sie ein einfaches Beispiel einer DSL, die einen modifizierten Sinus darstellt und geeignet in einen AST überführt werden muss. 1 2 3 4 5 6 a = 0.5; b = 2; pi = 3.14; y(x) = 2∗sin(pi∗x) + b; x = 0.5; a∗y(x); // // // // // // specify variable a specify variable b specify variable pi declare function y specify variable x evaluate a∗y(x) at x=0.5 Listing 1: Beispiel eines DSL Scripts. Die DSL soll die wesentlichen mathematischen Operationen {+,-,*,/} mit den zugehörigen Klammerregeln, sowie ein Konzept zur Definition von Konstanten, Variablen und frei definierbare Funktionen zur Verfügung stellen. Am einfachsten wird solch eine DSL mit Hilfe der Erweiterten Backus-NaurForm (EBNF) in Form von Produktionsregeln spezifiziert1 . Einen ersten Entwurf einer solchen DSL des Mathematik Parsers ist in Abbildung (1) 1 Hierbei baut eine Regel auf die andere auf, ähnlich wie bei der DTD Beschreibung von XML Dokumenten aus Informatik II. 1 letter digit sign := (’a’..’z’ | ’A’..’Z’) := (’0’..’9’); := ’+’ | ’-’; integer float number := (sign)? (digit)+ := integer ’.’ (digit)* := integer | float; assign atom term expr := := := := variable ’=’ number; number | variable; atom (’*’|’/’) atom; term (’+’|’-’) term; Abbildung 1: Erster unvollständiger EBNF Entwurf für die DSL. zu sehen. Ähnlich zur DTD sind die Multiplizitäten 0:1 als ()?, 1:n als ()+ und 0:n als ()* annotiert. Wie in der Informatik üblich wird eine Alternative durch die oder-Operation | kenntlich gemacht. Obige DSL ist noch nicht vollständig ausspezifiziert, zeigt aber bereits, wie sich die fundamentalen Operationen der DSL definieren lassen. Im Sourcelisting 2 sehen Sie einen einfachen Test, der mit einer Zeichenkette2 als Skript, zunächst die Variable x = −1.5 definiert und den Ausdruck 3.75 + x berechnet. 1 #define EPS 1.E−10 2 3 4 5 6 7 8 9 void testScripting () { AST ast; double y; char script [] = ”x = −1.5; ” \ ”3.75 + x;”; ast = parseScript(script ) ; preCondition(ast != NULL, ”TEST FAILED: no AST from parser”); 10 y = eval(ast) ; printf (”eval : %f\n”,y); if (fabs(2.25 − y)<EPS) { printf (”TEST OK\n”); } else { printf (”TEST FAILED!\n”); } 11 12 13 14 15 16 17 18 } Listing 2: DSL Scripts Testlauf. 2 Diese kann natürlich auch aus einer Textdatei eingelesen werden, aber so ist der Test in sich geschlossen. 2 Das Ergebnis der Parser Analyse und die Auswertung des AST wird dann anschließend auf numerische Korrektheit überprüft. Ein erfolgreicher Testlauf produziert z. B. die nachfolgende Ausgabe. parsing line >x=-1.5< setVariable[x]=-1.5 parsing line >3.75+x< eval: 2.250000 TEST OK Aufgabe Erstellen Sie eine DSL zum Berechnen von mathematischen Formeln. Entwickeln Sie eine geeignete Implementierung eines DSL Parsers zur Auswertung der Formeln passend zur von Ihnen definierten DSL. Alle DSL Elemente, wie Variable, Term, etc., werden als Spezialisierungen des generischen Knoten Typen AST definiert. Die Headerdatei 3 zeigt einen Ausschnitt der entsprechenden Definitionen. Gut zu erkennen ist die in der Vorlesung besprochene kanonische Form des abstrakten C Datentypen (ADT). Erweiterungen des AST müssen strukturkonform zu diesem ADT sein. Hier können entsprechende Makros hilfreich sein. Der Parser analysiert das Skript und intitialisiert und verkettet die entsprechenden AST Knoten. Tip Fangen Sie zunächst an einfache Zuweisungen, wie z.B. x=3, und dann einfache Anweisungen, wie z.B. 2 + 5 oder x + 5, zu implementieren bevor Sie an den komplizierteren Klammerungen und Formeln verzweifeln :-). Bauen Sie erst nach dem erfolgreichen Test der einfacheren Syntax das System weiter aus. Es macht Sinn automatisierte Testtreiber a la Listing 2 zu verwenden. Für diese Aufgabe ist u.A. Arbeitsteilung und gute Vorbereitung gefragt, so dass unterschiedliche Module und Quelltexte – Parser, AST, Test, etc –, parallel von verschiedenen Entwicklern im Team entwickelt werden können. 3 1 2 #ifndef AST H #define AST H 3 4 typedef char ∗String; 5 6 7 8 typedef enum { ADD=’+’, SUB=’−’, MUL=’∗’, DIV=’/’, ASSIGN=’=’, DEFINE=’:’, CONSTANT=’C’, VARIABLE=’V’, LBRACK=’(’, RBRACK=’)’} Type; 9 10 11 typedef struct node instance struct ∗AST; typedef struct node class struct ∗ASTClass; 12 13 14 15 16 struct node class struct { double (∗value)(AST node); String (∗toString)(AST node); }; /∗ common methods for AST nodes ∗/ 17 18 19 20 21 typedef struct node instance struct { /∗ common attributs of AST nodes ∗/ ASTClass class; Type type; }; 22 23 24 #define eval(node) ((node)−>class−>value(node)) #define asString(node) ((node)−>class−>toString(node)) 25 26 27 /∗∗ parse the given script and return the AST. AST parseScript(const String script); ∗/ 28 29 30 31 32 /∗∗ various constructor methods, for different AST node types. ∗/ AST createConstant(const double val); AST createOperator(const AST left, const Type op, const AST right); AST createVariable(const String name); 33 34 35 #endif /∗ AST H is defined ∗/ Listing 3: Mögliche Definition des generischen AST. 4