Institut für Informatik Softwaretechnik und Programmiersprachen Universitätsstr. 1 D-40225 Düsseldorf Ein Parser für CSPM in Java Robin Bially Bachelorarbeit Beginn der Arbeit: Abgabe der Arbeit: Gutachter: 17. Mai 2016 17. August 2016 Prof. Dr. Michael Leuschel Prof. Dr. Jörg Rothe Erklärung Hiermit versichere ich, dass ich diese Bachelorarbeit selbständig verfasst habe. Ich habe dazu keine anderen als die angegebenen Quellen und Hilfsmittel verwendet. Düsseldorf, den 16. August 2016 _____________________________________ Robin Bially Zusammenfassung Diese Arbeit dokumentiert die Entwicklung des neuen CSPM-Parsers cspmj in Java, der für das Validierungstool ProB des Lehrstuhls für Softwaretechnik und Programmiersprachen entwickelt wurde. Als Leitfaden diente dabei die CSPM-Dokumentation von FDR3.4. Die im Rahmen dieses Projekts erreichten Ziele sind: Implementierung eines neuen CSPM-Parsers, der den zurzeit in ProB integrierten Parser cspmf ersetzen kann Unterstützung neuer CSPM-Sprach-Features Verbesserungen gegenüber den Parsing- und Übersetzungs-Zeiten von cspmf Förderung der Weiterentwicklungsbereitschaft durch Nutzung einer gegenüber Haskell bekannteren Programmiersprache Plattformunabhängige Unterstützung durch die Java Virtual Machine Syntaktische LTL/CTL-Formel-Überprüfung in der Parsing-Phase Mehr Flexibilität durch die Möglichkeit, eingebaute Funktionen und Konstanten zu überschreiben Vereinheitlichung des Parser-Toolsets von ProB, das hauptsächlich in Java geschrieben wurde Eine Herausforderung war die Darstellung aller wesentlichen Konstrukte der Prozessalgebra CSPM in einer kompakten Grammatik für SableCC. Der Entwicklungsaufwand ist aufgrund der Besonderheiten der Sprache als besonders hoch einzuschätzen. Die Software-Entwicklung von cspmj wurde nach dem Prinzip der kontinuierlichen Integration durch den Einsatz von Gradle und jUnit unterstützt. Das Build-Management-Tool Gradle kam beim Kompilieren von Java-Dateien, dem Herunterladen sämtlicher Tools und der automatisierten Projekterzeugung zum Einsatz. Mithilfe von jUnit wurde der Ausgabequellcode von cspmj gegenüber cspmf verglichen, um das Verhalten beider Parser anzugleichen. Ebenfalls erleichterte jUnit die Validierung bereits bestehender Funktionen durch ihre permanente Überwachung im laufenden Entwicklungsprozess. Performance-Tests haben gezeigt, dass die Übersetzungszeit von praxisnahen Beispieldateien gegenüber cspmf oft wesentlich geringer ist. Dazu wurden 133 CSPM-Dateien übersetzt und die Zeiten beider Parser miteinander verglichen. Ein zentrales Ergebnis der Arbeit ist dennoch, dass der Parsergenerator SableCC aufgrund seiner eingeschränkten Funktionalität für die Erzeugung von Parsern komplexer Sprachen wie CSPM ungeeignet ist. Aufgrund der speicherineffizienten Java-Implementierung von SableCC ist das Parsen von großen Dateien nicht möglich. Eine kürzere Schreibweise für das Setzen der Operatorrangfolge und die Erzeugung eines abstrakten Syntaxbaums hätten die Entwicklungszeit verkürzen können. Das Projekt wurde mithilfe von Github versioniert. Inhaltsverzeichnis 1 Inhaltsverzeichnis Inhaltsverzeichnis............................................................................................................................... 1 1. Einleitung und Motivation ......................................................................................................... 3 Gliederung .......................................................................................................................... 3 ProB.................................................................................................................................... 3 Projektziel .......................................................................................................................... 3 2. CSPM .......................................................................................................................................... 5 Definition ........................................................................................................................... 5 Anwendung ........................................................................................................................ 5 Aufbau eines CSP-Programms ........................................................................................... 5 Definitionen........................................................................................................................ 6 Operatoren .......................................................................................................................... 8 Nebenläufigkeit .................................................................................................................. 9 Assertions ........................................................................................................................... 9 Ein System in CSPM ......................................................................................................... 10 3. SableCC.................................................................................................................................... 12 Definition ......................................................................................................................... 12 Funktion ........................................................................................................................... 12 SableCC-Dateien .............................................................................................................. 12 Ein einfaches Beispiel in SableCC ................................................................................... 13 4. Kontinuierliche Integration ...................................................................................................... 15 Gradle ............................................................................................................................... 15 jUnit.................................................................................................................................. 16 5. Der Parser ................................................................................................................................. 18 Entwicklungsaufwand ...................................................................................................... 18 Präzedenzunterschiede ..................................................................................................... 21 Architektur ....................................................................................................................... 22 Prelexing .......................................................................................................................... 23 Diagramm zum Aufbau des CST ..................................................................................... 26 Statement-Überprüfung .................................................................................................... 26 LTL/-CTL-Formel-Überprüfung...................................................................................... 27 Renaming ......................................................................................................................... 27 Prolog-Codegenerierung .................................................................................................. 28 Positionsangaben .............................................................................................................. 30 Variablenzuweisung/Erkennung ungebundener Variablen .............................................. 31 Fehlerbehandlung ............................................................................................................. 31 Vergleich mit cspmf ......................................................................................................... 32 Befehle ............................................................................................................................. 34 2 Inhaltsverzeichnis Typechecking ................................................................................................................... 34 Performance...................................................................................................................... 35 6. Fazit und Ausblick.................................................................................................................... 37 Bewertung der aktuellen Funktionalität ........................................................................... 37 Zukünftige Entwicklung ................................................................................................... 38 Literaturverzeichnis .......................................................................................................................... 39 Abbildungsverzeichnis ..................................................................................................................... 41 Tabellenverzeichnis .......................................................................................................................... 41 Anhang ............................................................................................................................................. 42 1. Einleitung und Motivation 3 1. Einleitung und Motivation Gliederung Das erste Kapitel enthält eine Erläuterung der Motivation für die Entwicklung eines neuen CSPM-Parsers für den Model-Checker und Animator ProB. In den Kapiteln 2 bis 4 werden alle Methoden und Tools vorgestellt, die in der Arbeit zum Einsatz kommen. Kapitel 2 ist eine kurze Beschreibung und Einführung in die Struktur, Funktionsweise und Anwendungspraxis der Prozessalgebra CSPM. Kapitel 3 enthält die Grundlagen des Parsergenerators SableCC, der für die automatisierte Erzeugung der Java-Klassen verwendet wurde. In Kapitel 4 wird erläutert, wie mithilfe der Testumgebung jUnit und des Build-Management-Automatisierungs-Tools Gradle die kontinuierliche Integration erleichtert werden kann. Kapitel 5 ist eine Beschreibung der Architektur des neuen Parsers cspmj. Außerdem werden Lösungen für CSPM-Sprachenspezifische Probleme vorgestellt. Neuerungen der Funktionalität von cspmj werden im Rahmen eines Vergleichs mit dem alten CSPM-Parser cspmf eingeführt. Zum Schluss folgt in Kapitel 6 eine Bewertung des Parsers durch Nennung aller Vor- und Nachteile und eine Auflistung zukünftig relevanter Entwicklungsschritte. ProB Das 2003 von Michael Leuschel und Michael Butler vorgestellte Validierungstool ProB wurde zum Zweck der Überprüfung und Veranschaulichung von Spezifikationen der B-Methode entwickelt [LB03]. Die B-Methode ist die Theorie und Methodologie für die formale Entwicklung von Computersystemen und kommt in der Industrie häufig zur Validierung von Systemen zur Steuerung von Bahnsignalen zum Einsatz. Beispielsweise konnte die Sicherheit des Betriebssystems zur fahrerlosen Steuerung der Pariser Métro nachgewiesen werden [LB07]. Neben der B-Methode werden die Sprachen Event-B, TLA+, und Z unterstützt [Le16]. Die Integration der Sprache CSPM erfolgte erst später, mit der Absicht, CSPM und B-Spezifikationen zu kombinieren [LB05]. Ein Grund hierfür ist folgender: Eine B-Maschine kann als reaktives System betrachtet werden, das kontinuierlich Operationen unabhängig voneinander ausführt. Neben dem Vorteil der optimalen Modellierung paralleler Aktivität (gleichzeitige Ausführung von Prozessen) hat B jedoch den Nachteil, dass es sich nicht für die Modellierung von sequenzieller Aktivität (Abfolge von Prozessen in Abhängigkeit von Ereignissen) eignet. Hierfür gibt es in CSPM Operatoren, wie die sequentielle Komposition und die Unterstützung mehrerer synchroner Kommunikationsarten. Auf diesem Weg ergänzen sich B und CSPM optimal und ermöglichen eine komplementäre Spezifikation von Systemen. Eine Kompatibilität von CSPM und B untereinander, konnte durch eine Prolog-Implementierung der Interpreter beider Spezifikationssprachen und der Verwendung von Prolog-Unifikationen erreicht werden [LF08]. Projektziel Die Analyse von sicherheitskritischen Systemen ist in Zeiten zunehmender Softwarekomplexität und der von digitalen Systemen ausgehenden Verantwortung von großer Bedeutung. Das Hauptziel der Arbeit ist die Entwicklung eines neuen CSPM-Parsers in einer Java-Umgebung für die Nutzung in ProB. Für die Entwicklung des bisher integrierten Parsers cspmf, der im Rahmen der Dissertation von Marc Fontaine im Jahr 2011 vorgestellt wurde, kam die funktionale Programmiersprache Haskell zum Einsatz [Fo11]. Der Parser jeder anderen von ProB unterstützten formalen Sprache ist hauptsächlich in Java mithilfe von SableCC entwickelt worden. Der Popularitätsgrad von Haskell ist im Jahr 2016 gegenüber Java als sehr gering einzuschätzen. Dem aktuellen TIOBE Programming Community Index zufolge ist Haskell mit 0,0304% gegenüber Java etwa 65-mal mal weniger 4 1. Einleitung und Motivation populär.1 Es muss damit gerechnet werden, dass die Identifikation von Fehlern und die Weiterentwicklung von cspmf nur eingeschränkt möglich ist. Aus diesem Grund sollte ein neuer Parser in einer weit verbreiteten Programmiersprache entwickelt werden. Die Wahl fiel auf Java, um das ProB Parser-Toolset zu vereinheitlichen. Eine weitere Motivation bestand darin, den Funktionsumfang der in ProB überprüfbaren Spezifikationen zu erweitern und an den Entwicklungsstand von libcspm anzugleichen. libcspm ist der Parser des Analysetools FDR3, das ursprünglich 1991 von dem Unternehmen Formal Systems (Europe) Ltd [Gi16d] veröffentlicht und anschließend von der Universität Oxford weiterentwickelt wurde. Es diente als Leitfaden während der Entwicklung des cspm-Model-Checkers, dessen Bestandteil cspmf ist [Fo11]. Sowohl in Anlehnung an die CSPM-Dokumentation von FDR3 als auch die Übersicht der von ProB unterstützten Spezifikationen sollte cspmj als Zusammenfassung der wichtigsten Komponenten beider Parser entworfen werden. Aufgrund der vielen komplexen Eigenschaften der Sprache CSPM, die in Kapitel 5.1 genauer erklärt werden, ist der Entwicklungsaufwand als besonders hoch einzuschätzen. Zu den Projektzielen gehört die Implementierung der wesentlichen Konstrukte von CSPM, die bereits in cspmf unterstützt wurden. Dabei soll das Verhalten möglichst nahe an cspmf orientiert sein, um eine schnelle ProB-Integration zu ermöglichen. Außerdem soll der Funktionsumfang des Parsers einfach und unabhängig von FDR3 erweiterbar sein. Das Projekt wurde mithilfe von Github versioniert und kann jederzeit unter folgendem Link heruntergeladen werden: https://github.com/RobinBia/CSPMJ 1 http://www.tiobe.com/tiobe-index/ 2. CSPM 5 2. CSPM Definition CSPM ist ein maschinell interpretierbarer Dialekt der Prozessalgebra CSP (Communicating Sequential Processes) [Ro05][Ho78]. Eine Prozessalgebra ist eine Sammlung von Ansätzen zur formalen Modellierung nebenläufiger Systeme. CSPM beschreibt die Interaktion zwischen kommunizierenden Prozessen und ermöglicht ihre präzise Definition und Steuerung mithilfe mathematischer Methoden [Fr]. Anwendung Bereits in den 80er-Jahren wurde CSPM in Mikroprozessorarchitekturen wie dem INMOS T9000 Transputer eingesetzt, um unter Anderem dessen Pipeline-Befehle zu verifizieren [Ba95]. In der Industrie werden heute sowohl CSPM als auch die B-Methode zur Modellierung sicherheitskritischer Systeme eingesetzt. So entwarfen das Bremen Institute for Safe Systems und Daimler-Benz Aerospace ein Störungsmanagementsystem und eine 23000 Zeilen Code umfassende Avionikschnittstelle für die internationale Raumstation (ISS). Durch die Analyse mit CSPM konnten Fehler gefunden und Deadlocks verhindert werden [Bu97][BPS99]. Auch in der Softwareentwicklung wird CSPM eingesetzt. So half die Analyse einer Smart-Card-Architektur der Firma Altran Praxis den Schutz vor unerlaubten Zugriffen zu verifizieren [HC02]. Ein weiteres Anwendungsszenario ist die Überprüfung von kryptographischen Verfahren. Mit Hilfe von FDR konnte in dem Needham-Schroeder-Protokoll, einem Verfahren für sicheren Datenaustausch in dezentralen Netzwerken, eine Sicherheitslücke entdeckt und behoben werden [Lo96]. Aufbau eines CSP-Programms Eine CSP-Datei besteht aus einer Liste von Instruktionen. Im Folgenden werden alle möglichen Instruktionsarten aufgelistet und anhand von Beispielen veranschaulicht. Definitionen Ausdrücke Datentypen Kanäle Assertions datatype Farbe = Rot|Gruen|Blau Print-Ausdrücke Zeitabschnitte Module Includes Transparente/Externe Funktionen print x x = 100+200%3/5*4 P = a->P[]Q (\x@x+1)(3) {y|y<-{0..99},y%2==0} P[]a->Q channel c : D.{1..10} assert P :[deadlock free [F]] assert U :[has trace]: <a,b,c> Timed(OneStep) {P = a->P} module A x = 2 exports z = x endmodule include "Examples\tickets.csp" transparent diamond external chase Der folgende Abschnitt stellt eine kleine Auswahl an CSPM-Instruktionen vor, die für das Verständnis eines anschließenden Beispiels benötigt werden. Genaueres enthält die CSPM-Dokumentation von FDR3 [Gi16b], die bei der Entwicklung des Parsers als Leitfaden diente. 6 2. CSPM Definitionen Ausdrücke Ein Ausdruck kann in CSPM entweder ein Prozess-Ausdruck oder ein Nicht-Prozess-Ausdruck sein. Ersterer besteht aus Prozessatomen (z.B. STOP, SKIP, CHAOS) oder deren Verknüpfung durch Prozessoperatoren. Nicht-Prozess-Ausdrücke bestehen aus Ausdrucksatomen (z.B. Zahlen, Sequenzen, Mengen, Ereignissen), die durch arithmetische oder boolesche Operatoren verknüpft werden können. Symbol Beispiel Bedeutung/Typ m, n s a b 1 Zahl (-231,...,231-1) <> Sequenz (Folge von Ausdrücken) {} Menge (Menge von Ausdrücken) true P, Q Q[]P, STOP p e c expr _@@'c' Boolescher Wert Prozess (Typrückgabe bei Anwendung von Prozessoperator) Pattern c.1.3 Ereignis c:{1..2}.Bool Kanal s.o. Ausdruck mit variablem Typ Tabelle 1: CSPM Ausdrücke Die Symbolvariablen dieser Tabelle werden in den folgenden Erklärungen verwendet. Patterns Eine einfache Definition ist die Bindung eines Ausdrucks expr an ein Pattern p, sofern expr und p gleiche Typen besitzen. Der allgemeine Aufbau einer Definition lautet: p = expr. Patterns setzen sich dabei gegebenenfalls aus weiteren Patterns zusammen. Die folgende Auswahl enthält alle unterstützten Patterns und Patternoperatoren in der Reihenfolge ihrer Bindungsstärke von schwach (oben) nach stark (unten): Double-Pattern: Dot-Pattern: Append-Pattern: Zahlen-Pattern: Variablen-Pattern: Literal-Pattern: Tupel-Pattern: Parenthese-Pattern: Sequenz-Pattern: Mengen-Pattern: Wildcard-Pattern: p1@@p2 p1.p2 p1^p2 231,…,231-1 Bezeichner* 0.., true/false, 'c', "String" (p1,…,pn) (p) <p1,…,pn> {p} _ *Ein Bezeichner setzt sich wie folgt zusammen: Ein Buchstabe gefolgt von beliebig vielen alphanumerischen Zeichen und Unterstrichen gefolgt von beliebig vielen Prime-Zeichen, z.B. Muster1_Bezeichner2'''. 2. CSPM 7 Funktionen Eine weitere Definitionsart ist die Funktion: F(p)…(p) = e. Hierbei ist F ein Bezeichner, gefolgt von beliebig vielen Tupel- oder Parenthese-Patterns. Ein Beispiel ist: F(x)(y) = x+y. Dabei werden die Variablen-Patterns innerhalb der runden Klammern als Parameter der sogenannten Currying-Funktion aufgefasst und die Ausdrücke x und y an die Variablen-Patterns gebunden. Ein Aufruf F(2)(y) wird als Addition der Zahl 2 zu y interpretiert. Transparente und Externe Funktionen Transparente und externe Funktionen sind bereits in den Interpreter von ProB eingebaut und werden nach ihrem Aufruf nur als solche erkannt, wenn sie vorher in einer Spezifikation explizit angegeben werden: transparent <Bezeichnerliste> bzw. external <Bezeichnerliste>. Transparente Funktionen werden grundsätzlich als Prozess interpretiert. Externe Funktionen hingegen können zusätzlich im Kontext aller anderen Ausdrücke genutzt werden. Es ist allerdings anzumerken, dass sich diese aufgrund ihrer nicht semantikerhaltenden Eigenschaften nur bedingt für den Einsatz in sicherheitskritischen Systemen eignen [Gi16a]. Kanäle Ein CSP-Prozess wird ausschließlich durch die Kommunikation mit seiner Umgebung beschrieben. Um diese darzustellen, werden Ereignisse definiert, welche prozessintern sequentiell und atomar ausgeführt werden. Je mehr Ereignisse definiert werden, desto höher ist der Detailgrad zur Beschreibung der Umgebung. Ein Ereignis wird immer dann kommuniziert, wenn sich alle beteiligten Prozesse darauf einigen. Zur Deklaration und Zusammenfassung von Ereignissen und deren Typ verwendet CSPM Kanäle. Ein Kanal wird wie folgt definiert: channel c: te. Dabei ist c der Name des Kanals und te ein Typenausdruck, welcher die Typen der Kanalargumente beschreibt. Die Kommunikation über ein Ereignis findet genau dann statt, wenn sein Aufruf abgeschlossen ist, d.h. channel c:Int.Bool (Int = {-231,...,231-1}) beschreibt eine Menge von gültigen Ereignissen. In diesem Fall wäre die Menge {c.1.true, c.1.false, c.2.true, c.2.false,...} eine Zusammenfassung aller Eingaben, die unter c als Ereignis erkannt werden können. Datentypen CSPM erlaubt die Deklaration von eigenen Datentypen, um Bezeichner an größere Datenmengen zu binden und diese zu strukturieren. Die allgemeine Definition lautet datatype N = C1.te1 | C2.te2 | ... [Gi16d], wobei N der Name des zu definierenden Typs ist, Ci ein Datenkonstruktor und tei ein Typenausdruck. Dieser ist als Parameter aufzufassen und erweitert das Konstrukt C um gewisse Werte, die über den Dot-Operator zu erreichen sind. Ein Typenausdruck ist eine durch Punkte getrennte, beliebig lange Folge von n-Tupeln oder Mengen. Die folgende Anweisung definiert eine Menge von Elementen, welchen der Typ Color zugewiesen wird: datatype Color = Red.{1,2} | Green.{3,4} | Blue.{5,6}. Dies ist äquivalent mit der Definition Color = {Red.1, Red.2, Green.3, Green.4, Blue.5, Blue.6}. Includes Um mehrere Dateien zu einer Größeren zusammenzufassen, eignet sich der Aufruf von include "dateipfad". Dies kann sehr nützlich sein, wenn man bestimmte Teile eines Programms testen möchte um diese gegebenenfalls später hinzufügen zu können oder um große Dateien aufzuteilen und die Übersichtlichkeit zu verbessern. 8 2. CSPM Operatoren Prefixing Der Präfixoperator -> beschreibt das Auftreten eines Ereignisses, auf welches die Ausführung eines Prozesses folgt. Die allgemeine Definition lautet e -> P, wobei e ein Ereignis und P ein Prozess ist. Der Ausdruck e->P ist wieder ein Prozess, der beliebig lange auf das Ereignis e wartet und sich anschließend wie P verhält. Der Prozess STOP ist eine eingebaute Konstante. Er repräsentiert den Deadlock eines Prozesses, also eine Endlosschleife, welche die weitere Kommunikation von Ereignissen verhindert: P = e1->e2->STOP. P wird definiert als der Prozess, der sich so verhält wie das Konstrukt rechts vom Gleichheitszeichen. Nach der Kommunikation von e1 folgt das Ereignis e2. Anschließend wird eine Endlosschleife betreten und es finden keinerlei Ereigniskommunikationen mehr statt. Eine andere Möglichkeit der Verhinderung von endlichen Prozessen ist die Rekursion. Dabei definiert der Prozess P = e1->e2->P eine unendliche Ereignisabfolge e1->e2->e1->e2->... . Der Präfixoperator erlaubt nur die Definition von sequentiellen Folgen von Ereignissen, ohne dabei Einfluss auf den Verlauf ihrer Abarbeitung in Abhängigkeit von der Zeit und dem Umgebungszustand zu haben. Dies ist vor allem hinsichtlich der mangelnden Flexibilität der Ereignissteuerung und der verfügbaren Ressourcen nicht praktikabel. Eine Möglichkeit, die Priorität bei der Kommunikation von Ereignisse festzulegen, bieten die folgenden Auswahloperatoren. Eingabe/Ausgabe Besteht die Absicht, eine Sammlung von Prozessen zu definieren, deren Ereignisfolge sich nur atomar unterscheidet, so ist eine kürzere Schreibweise möglich: c?x:A -> P(x). Diese Instruktion beschreibt mehrere Verhaltensweisen von P(x), indem als Ereignis alle Elemente x aus der Teilmenge A (x von Typ A) des Kanals c extrahiert werden und dem Prozess als Eingabe dienen. Dazu wird x im restlichen Verlauf an die Ereignismenge aus c gebunden. Wird :A weggelassen, so sind alle Ereigniskommunikationen aus c möglich. Gleichzeitig kann die Ausgabe einer Kanaleingabe nicht nur über Prozessargumente erfolgen, sondern auch über den jeweiligen Kanal mit dem Ausgabe-Befehl (!). Ein geeignetes Beispiel hierzu ist das Folgende: P = pressButton?x -> openDoor!x -> P [Fo11]. Es beschreibt einen Prozess, der immer dann eine Tür öffnet, wenn ein Knopf gedrückt wird. Dazu empfängt P die Eingaben aus dem Kanal pressButton und gibt diese anschließend wieder über den Kanal openDoor aus. External Choice Der Nachteil, dass die Auswahl eines Prozesses immer von einem bestimmten Anfangsereignis abhängt, wird durch den External Choice Operator verhindert: P [] Q. Unterscheiden sich die Anfangsereignisse aus P und Q, so kann die äußere Umgebung, z.B. der aufrufende Prozess, über die Auswahl eines Ereignisses festlegen, welcher der Prozesse ausgeführt wird. Sind die Anfangs-Ereignisse gleich, so ist die Auswahl nichtdeterministisch und kann nur intern erfolgen. Hierzu verwendet man den Internal Choice-Operator. Internal Choice Sind die Initialereignisse beider Prozesse gleich, so tritt ein Spezialfall ein. Auf diese Weise kann der aufrufende Prozess keine eindeutige Wahl zwischen den Prozessen treffen, da die Ereigniskette unbekannt ist. Soll in diesem Fall eine Entscheidung getroffen werden, so ist dies nur während der Ereigniskommunikation der einzelnen Prozesse möglich. Man führt daher einen neuen Operator ein, die Internal Choice: P |~| Q. 2. CSPM 9 Guard Ein Prozess Q verhält sich genau dann wie P, wenn der boolesche Ausdruck b wahr ist. Ist b unwahr, so verhält sich Q wie STOP: Q = b & P. Nebenläufigkeit Um Prozesse möglichst gleichzeitig und unabhängig voneinander ausführen zu können, bietet CSPM eine Reihe von Parallelisierungsoperatoren. Generalised Parallel Führe die beiden Prozesse P und Q parallel aus. Ein Ereignis kann genau dann von einem der beiden Prozesse ausgeführt werden, wenn es nicht im Alphabet {e} enthalten ist: P [|{e}|] Q. Interleave In diesem Spezialfall von Generalised Parallel ist das Ereignisalphabet leer. Die Ausführung beider Prozesse P und Q ist unabhängig voneinander. Teilen P und Q Ereignisse, so kann nur genau einer der beiden Prozesse ein Ereignis kommunizieren: P ||| Q (≙ P [|{}|] Q) Alphabetised Parallel Während der parallelen Ausführung beider Prozesse P und Q kann die Kommunikation eines Ereignisses der Menge {e1} nur durch P , eines der Menge {e2} nur durch Q erfolgen: P [{e1}||{e2}] Q (≙ P [|{e1}^{e2}|] Q). Replicated Alphabetised Parallel Werte das Alphabet A(x) und den Prozess P(x) ∀x∈<statements> aus und führe für jeden resultierenden Prozess alphabetised Parallel aus. Die Ereignismenge für den jeweiligen Prozess P(x) ist dem Alphabet A(x) zu entnehmen: || <set-statements> @ [A(x)] P(x) (Für Statements, siehe auch Kapitel 5.6). Synchronising External Choice Dieser Operator ist eine Neuheit (seit FDR2.94) und wird bisher nicht von ProB unterstützt. Er kann als Hybrid zwischen External Choice und Generalised Parallel interpretiert werden: P [+{e}+] Q. Hier werden P und Q durch Ereignisalphabet {e} synchronisiert. Sobald ein Ereignis e in einer der beiden Prozesse nicht kommuniziert wird, verhält sich der Operator wie External Choice. Ansonsten entsteht ein Deadlock. Assertions In der Praxis ist vor allem die Sicherheit komplexerer Systeme von großem Interesse. Erwartet man, dass ein Systemabschnitt oder Prozess eine bestimmte Eigenschaft erfüllt, so kann diese zuerst mithilfe einer Assertion verifiziert werden. Auf diese Weise kann ein unkontrolliertes Terminierungsverhalten verhindert werden. Im Gegensatz zu Exceptions setzen Assertions keinen bestimmten Wert voraus, sondern ermitteln auf mathematischem Weg, ob die Eigenschaft erfüllt ist. 10 2. CSPM Refinement Assertion Refinement-Assertions sind ein fundamentaler Bestandteil bei der Konstruktion von Systemen in CSPM. Um herauszufinden, ob ein Prozess P ein M-Refinement von Q ist, kann der Ausdruck assert P [M= Q verwendet werden. Hierbei ist M einer der folgenden Refinement-Typen: T (Traces), F (Failures), FD (Failure Divergence), R (Refusals) Deadlock/Divergence-Free/Deterministic Assertion Neben Refinements können noch weitere Eigenschaften erkannt werden. Der allgemeine Aufbau einer Assertion hierbei ist: assert P :[AT [M]], wobei AT ein Assertion-Typ ist und die Angabe eines Refinement-Typs (M) optional ist. Mögliche Assertion-Typen sind: deadlock free, divergence free, deterministic. Die Assertion assert P :[deadlock free] überprüft zum Beispiel, ob P Deadlock-Frei ist. Has-Trace-Assertions Ebenfalls kann überprüft werden, ob s eine mögliche Sequenz von P ist: assert P :[has trace]: s LTL/CTL-Assertion LTL- und CTL-Assertions werden nur von ProB und nicht von FDR3 unterstützt! Die Assertion assert P |= LTL: "LTL-Formel" ist genau dann wahr, wenn die temporallogische Formel auf den Prozess P erfüllt ist. Die Temporallogik ist eine Erweiterung der Aussagenlogik, bei der es nicht um die Beschreibung von zeitlichen Abläufen geht, sondern um die Eigenschaften von Zuständen und ihr Verhalten in Abhängigkeit von bestimmten Ereignissen. Ein System in CSPM Ein klassisches Beispiel für ein sicherheitskritisches System ist das Philosophenproblem. Das Problem Fünf Philosophen (N=5) teilen sich einen Esstisch und haben Sitzplätze, die ihnen fest zugewiesen sind. Auf dem Tisch liegen fünf Gabeln, die jeweils nur von einem Philosophen gehalten werden können. Möchte ein Philosoph essen, kann er dies nur, indem er sich zuerst hinsetzt und zwei Gabeln gleichzeitig benutzt. Kann ein Philosoph keine zweite Gabel nehmen, so muss dieser warten, bis ein anderer Philosoph mit dem Essen fertig ist. Abbildung 1: Philosophen beim Essen 2. CSPM 11 Die Modellierung Zu Beginn muss gewährleistet werden, dass keine Gabel von zwei Philosophen gehalten werden kann. Dazu werden fünf Prozesse FORK(i), i∈[0;N[ erstellt, die jeweils eine Gabel repräsentieren und fünf Prozesse PHIL(i),i∈[0;N[, die jeweils einen Philosophen darstellen. Die Identitäten der Gabeln und Philosophen werden jeweils in einer dafür vorgesehenen Menge gespeichert (PHILNAMES und FORKNAMES). Die Ereignisse, die den Zustand einer Gabel beschreiben sind picksup.i.i und putsdown.i.i, wobei i.i die Information enthält, welcher Philosoph i welche Gabel i hält. Für das Aufnehmen der rechten Gabel, also der nachfolgenden Gabel i+1 muss überprüft werden, ob i+1>N-1 ist. In dem Fall handelt es sich nicht um Gabel N, sondern 0. Dies beschreiben die Ereignisse picks!i!((i+1)%N) und putsdown!i!((i+1)%N). Neben den Ereignissen für Gabeln haben die Philosophen noch zusätzlich die Möglichkeit zu sitzen und essen und aufzustehen (sits.i.i, eats.i.i, getsup.i.i) . Da es sich bei den Philosophen um Menschen handelt, ist davon auszugehen, dass sie individuelle Entscheidungen treffen und ihre Handlungen in willkürlicher Reihenfolge erfolgen. Unter der Voraussetzung, dass jeder Philosoph die linke Gabel zuerst aufnimmt und zuletzt ablegt, ermöglicht der Aufruf von PHIL(i) einen Essvorgang. Die Alphabete AlphaP(i) und AlphaF(i) beinhalten alle Ereignisse, die für die jeweiligen Gabeln und Philosophen eintreten können. Die parallele Komposition von PHIL(i) und FORK(i) ermöglicht die Ausführung beider Prozesse unter der Voraussetzung, dass nur Ereignisse aus den zugehörigen Alphabeten kommuniziert werden können. So kann ausgeschlossen werden, dass eine von dem Philosophen weiter entfernte Gabel verwendet wird. Die Auswertung beider Prozesse durch alphabetised Parallel erfolgt ∀i∈PHILNAMES und der jeweils daraus resultierenden Vereinigung beider Alphabete (union(AlphaP(i),AlphaF(i)) [Ro05]. N = 5 PHILNAMES = {0..N-1} FORKNAMES = {0..N-1} channel sits, eats, getsup:PHILNAMES channel picks, putsdown:PHILNAMES.FORKNAMES PHIL(i) = sits!i -> picks!i!i -> picks!i!((i+1)%N) -> eats!i -> putsdown!i!((i+1)%N) -> putsdown!i!i -> getsup!i -> PHIL(i) AlphaP(i) = {sits.i,picks.i.i,picks.i.(i+1)%N, eats.i,putsdown.i.i,putsdown.i.(i+1)%N,getsup.i} FORK(i) = picks!i!i -> putsdown!i!i -> FORK(i) [] picks!((i-1)%N)!i -> putsdown!((i-1)%N)!i -> FORK(i) AlphaF(i) = {picks.i.i, picks.(i-1)%N.i, putsdown.i.i, putsdown.(i-1)%N.i} SYSTEM = || i:PHILNAMES@[union(AlphaP(i),AlphaF(i))] (PHIL(i)[AlphaP(i)|| AlphaF(i)] FORK(i)) Abbildung 2: Philosophenproblem in CSPM-Code 12 3. SableCC 3. SableCC Definition SableCC ist ein LALR(1)-Parsergenerator, der 1998 im Rahmen seiner Master-Arbeit von Étienne Gagnon vorgestellt wurde [Ga98]. Funktion Unter Angabe einer Grammatik und lexikalischer Einheiten generiert SableCC (HHU-Version 3.2.9) spezifische Java-Klassen, welche den zur Grammatik gehörenden abstrakten Syntaxbaum (AST) in höherer Programmiersprache darstellen. Auf diese Weise kann die Entwicklungszeit stark reduziert werden. Unterschieden werden drei Kategorien von Funktionen: Lexer Zerlegung des Textinhalts der Eingabedatei in einzelne Wörter und Einordnung in logisch zusammenhängende Einheiten (Tokens). Für jedes Token wird eine Knoten-Klasse beginnend mit T angelegt. Parser Generierung eines AST. Dabei wird für jede Regel und Alternative der Grammatik eine Knoten-Klasse beginnend mit A generiert Analyse Klassen, die das Visitor-Pattern implementieren (AST-Visitor – DepthFirstAdapter.java) Auf diese Weise kann der AST sowohl vorwärts als auch rückwärts durchlaufen und zur Laufzeit analysiert werden. SableCC-Dateien Um die oben genannten Funktionen in höherer Programmiersprache zu erstellen, wird eine Datei benötigt, die alle notwendigen Informationen enthält. Diese wird in 7 Abschnitte unterteilt: Package (optional) Name des Projekts, bzw. Ort des Hauptverzeichnisses für oben genannte Klassen. Helpers Bestimmung von Zeichenmengen, die zur Definition von Tokens hilfreich sind. Zum Beispiel beschreibt ['A'..'Z'] die Menge aller großen Buchstaben des Alphabets. States (optional) Soll ein Token nur an einer bestimmten Stelle (z.B. in einer Teilgrammatik) als solches erkannt werden, dann kann es einem bestimmten State zugeordnet werden. In Kombination mit der Filtermethode des Lexers kann der erzeugte Tokenstream zur Laufzeit effizient manipuliert werden. Tokens Angabe aller atomaren Einheiten, die bei der lexikalischen Analyse als solche erkannt werden und in eine Tokenliste eingereiht werden. Anschließend wird diese Liste an den Parser überreicht. Ignored Tokens (optional) Angabe aller Terminalsymbole, die im Tokenstream übersprungen werden sollen. In den meisten Fällen sind dies Kommentare und Whitespace-Tokens wie Leerzeichen und 3. SableCC 13 Tabulatoren. In diesem Projekt kommt der Abschnitt nicht zum Einsatz, da die Funktionalität nicht differenziert genug für die Spezifikation von CSPM ist. Productions Definition des Concrete Syntax Tree durch Angabe einer Grammatik in EBNF-Ähnlicher Notation. Abstract Syntax Tree Angabe aller AST-Transformationen zur Erzielung einer kompakteren Darstellung des CST (Concrete Syntax Tree). Alternativregeln, die nur die Aufgabe haben auf eine neue Präzedenzstufe zu zeigen, können so weggelassen werden. Ein einfaches Beispiel in SableCC Ein einfaches Beispiel für die Angabe einer SableCC-Datei ist eine Sprache als modifizierte Untermenge von CSPM : Terminale Integerzahlen, Bezeichner beginnend mit einem Buchstaben und endend mit beliebig vielen alphanumerischen Zeichen, Präfixoperator, Parenthese, Operatoren für die Arithmetik (Addition, Subtraktion, Multiplikation, Division, Modulo). Regelwerk Alle Bezeichner sind immer Ereignisse und Prozesse zugleich. Eine Zahl ist ein Ereignis, aber kein Prozess und kann somit immer nur links vom Präfix stehen. Präzedenz Bindungsstärke von links (stark) nach rechts (schwach): Atome -> Parenthese -> Punktrechnung (*,/,%) -> Strichrechnung (+,-) -> Präfix Die folgende SableCC-Datei soll die oben spezifizierte Sprache beschreiben: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Package Beispiel; Helpers digits first_digit letter alphanum whitespace = = = = = ['0' .. '9']; [digits - '0']; (['A' .. 'Z'] | ['a' .. 'z']); (digits|letter); (' ' | 13 | 10)+; Tokens identifier number prefix addsub muldivmod par_l par_r white = = = = = = = = letter alphanum*; first_digit digits*; '->'; '+'|'-'; '*'|'/'|'%'; '('; ')'; whitespace; Ignored Tokens white; 14 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 3. SableCC Productions exp {->exp} = exp2 {->exp} = exp3 {->exp} = atom {->exp} = {prefix} exp prefix exp2 {->New exp.prefix(exp.exp,exp2.exp)} |{e2} exp2 {->exp2.exp}; {addsub} exp2 addsub exp3 {->New exp.addsub(exp2.exp,exp3.exp)} |{e3} exp3 {->exp3.exp}; {muldivmod} exp3 muldivmod atom {->New exp.muldivmod(exp3.exp,atom.exp)} |{atom} atom {->atom.exp}; {id} identifier {-> New exp.identifier(identifier)} |{num} number {-> New exp.number(number)} |{par} par_l exp par_r {-> New exp.parenthesis(exp.exp)}; Abstract Syntax Tree exp = {prefix} [exp]:exp [exp2]:exp |{addsub} [exp2]:exp [exp3]:exp |{muldivmod} [exp3]:exp [atom]:exp |{identifier} identifier |{number} number |{parenthesis} [exp]:exp; Abbildung 3: Ein einfaches SableCC Beispiel Offensichtlich erfüllt die Grammatik aus Abbildung 3 die angegebene Spezifikation nicht vollständig, denn diese Lösung ermöglicht die Gültigkeit von numerischen Werten auf der rechten Seite des Präfixoperators. Der Kompaktheit halber kommt hier das Visitor-Pattern zum Einsatz. Mithilfe eines AST-Visitors wird eine Typenanalyse durchgeführt, bei der im Knoten des Präfixoperators der zweite Ausdruck nur dann akzeptiert wird, wenn dieser nicht vom Typ Integer ist. 4. Kontinuierliche Integration 15 4. Kontinuierliche Integration Um die Qualität von Software zu gewährleisten, kam bei der Entwicklung des Parsers teilweise das Prinzip der kontinuierlichen Integration zum Einsatz. Zwei unverzichtbare Werkzeuge, um das auch permanente Integration genannte Prinzip von Java-Projekten zu ermöglichen, sind Gradle und jUnit. In diesem Projekt dient Gradle der automatisierten Kompilierung von Java-Dateien und jUnit dem Testen des Ausgabequellcodes von cspmj bzw. Eingabecode für den Prolog-Interpreter von ProB gegenüber der Ausgabe des bestehenden CSPM-Parsers cspmf. Beide Tools sind in Github versioniert und können heruntergeladen werden.2 3 Gradle Definition Gradle ist ein auf Java basierendes Build-Management-Automatisierungs-Tool, das der automatisierten Erzeugung von ausführbaren Java-Programmen und deren Verwaltung dient. Motivation und Aufbau eines Gradle-Builds Die Motivation Gradle zu nutzen, besteht darin, den Build-Prozess größerer Projekte zu beschleunigen und unabhängig von der ausführenden Plattform zu machen. Eine Build-Definition ist eine Abfolge von Aufgaben und Abhängigkeiten, die als direkt ausführbarer Code in der Datei build.gradle festgehalten werden. Während der Buildverarbeitung werden immer jeweils die Konfigurationsphase und Ausführungsphase durchlaufen. Im Konfigurations-Zyklus wird die gesamte Build-Definition durchlaufen und ein Abhängigkeitsgraph erstellt, der die Reihenfolge der zu bearbeitenden Schritte festhält. Anschließend werden die Aufgaben in der vorkonfigurierten Reihenfolge abgearbeitet [DM15]. Der Gradle-Build-Prozess des cspmj-Projekts wird mit dem Befehl gradle build über die Kommandozeile angestoßen und hat folgenden Aufbau: Herunterladen von SableCC (Version 3.2.9) und jUnit (Version 4.+) Herunterladen von plattformabhängiger cspmf-Version nach build/classes/main Generierung der Java-Klassen des CSPM-Parsers mittels SableCC Generierung der Java-Klassen des LTL- und CTL-Parsers mittels SableCC Kompilieren der Java-Dateien im Ordner src/main/java bzw. erzeugen aller .class- Dateien (javac-Befehl) Kopieren von Produktionsressourcen (.scc-Dateien) in den Ordner build/resources Generierung der ausführbaren cspmj.jar Kompilierung der Java-Dateien im Ordner src/test/java (javac-Befehl) Ausführen der jUnit-Tests Ausführen sämtlicher Verifikationsaufgaben Schlägt ein jUnit-Test fehl, erhält man ein Feedback über die Anzahl der gescheiterten Tests und deren Namen. Der Build-Prozess schlägt automatisch fehl und verursacht eine Fehlerausgabe FAILURE: Build failed with an exception. 2 3 https://github.com/junit-team/junit4 https://github.com/gradle/gradle 16 4. Kontinuierliche Integration jUnit Definition jUnit ist ein Open-Source-Framework zum Testen von Java-Programmen, das besonders für automatisierte Unit-Tests einzelner Klassen und Methoden geeignet ist [Cl06]. Motivation Im Hinblick auf das Ziel der Arbeit, der Integration eines neuen Parsers in ProB, musste die Übersetzung von CSPM nach Prolog möglichst nahe an das Verhalten des bereits bestehenden Haskell-Parsers angeglichen werden. Aus diesem Grund wurde mit dem Reverse-Engineering-Verfahren der AST-Visitor zum Generieren von Prolog-Termen mit cspmf synchronisiert. Um nachweisen zu können, dass sich die Ausgaben beider Parser bei gleichen Eingabedateien semantisch nicht unterscheiden, werden in diesem Projekt jUnit-Tests verwendet. Diese ermöglichen den Vergleich von cspmj- und cspmf-Ausgabedateien. Vor dem Übersetzen des AST wird dieser normalisiert, um die jeweiligen Ausgaben vergleichbar zu machen. Dabei werden Positionsangaben von Operatoren und Bezeichnern mit no_loc_info_available ersetzt und der Header mit Versionsinfos und dynamischen Prädikaten, sowie die Symbolliste und die Liste mit allen Kommentaren und Pragmas gelöscht. Danach erfolgt die Deaktivierung des Symbolrenamings. Auch in cspmf findet dieser Vorgang statt. Eigens dazu wurde ein neues Kommandozeilenargument implementiert. Statt des Endarguments --prologOut=dateiname.csp.pl muss hierbei --prologOutNormalised=dateiname.csp.pl verwendet werden. Einen weiteren Zweck erfüllen jUnit-Tests hinsichtlich der Überprüfung von bestimmten Abschnitten der CSPM-Grammatik. Schlägt ein Test fehl und muss der Quellcode geändert werden, so gibt ein jUnit-Test Auskunft darüber, ob durch die vorherige Änderung eine ältere Funktion verlorengegangen ist. Auf diese Weise ermöglicht automatisiertes Testen eine erhebliche Zeitersparnis. Alle Tests werden zeitgleich mit jedem Aufruf von gradle build ausgeführt. @Test public void ExpressionOperators() throws Exception // { check( "v = -(1^2)^2" +newline+"w = 0-1+2*3/4%-5" +newline+"x = #1" +newline+"z = true or false and not true" , "'bindval'('v','negate'('^'('^'('int'(1),'int'(2)),'int'(2)))" +",'no_loc_info_available')." +newline+"'bindval'('w','+'(''('int'(0),'int'(1)),'%'('/'('*'" +"('int'(2),'int'(3)),'int'(4)),'int'(-5))),'no_loc_info_available')." +newline+"'bindval'('x','#'('int'(1)),'no_loc_info_available')." +newline+"'bindval'('z','bool_or'('true','bool_and'('false'," +"'bool_not'('true'))),'no_loc_info_available')." +newline+"'symbol'('v','v','no_loc_info_available','Ident (Groundrep.)')." +newline+"'symbol'('w','w','no_loc_info_available','Ident (Groundrep.)')." +newline+"'symbol'('x','x','no_loc_info_available','Ident (Groundrep.)')." +newline+"'symbol'('z','z','no_loc_info_available','Ident (Groundrep.)')." ); } Abbildung 4: Quellcode eines jUnit Tests 4. Kontinuierliche Integration 17 Zu sehen ist ein Test, der die Korrektheit der Prolog-Codegenerierung einer CSP-Datei mit sämtlichen arithmetischen Operationen prüft. Dazu wird die Methode check(String, String) aufgerufen. Der erste String repräsentiert dabei die Eingabedatei, der zweite String ist die erwartete Ausgabe. Entspricht die Ausgabe des Parsers nach Verarbeitung des ersten Strings dem zweiten String, so ist der jUnit-Test erfolgreich verlaufen. Eine Übersicht über den Korrektheitsgrad aller Tests wird in einer html-Datei index.html angelegt. Diese befindet sich in: CSPMJ\build\reports\tests Abbildung 5: jUnit – Übersicht zum Verlauf der Tests 18 5. Der Parser 5. Der Parser Entwicklungsaufwand Verglichen mit anderen Parser-Generatoren wie Bison, ist der Entwicklungsaufwand eines Parsers mithilfe von SableCC besonders groß. Dies wird bereits anhand eines kleinen Ausschnitts der Grammatik des ursprünglichen CSPM-Parsers von FDR (Version 1 und 2) sichtbar: _proc : PAR gens AT LSQUARE set RSQUARE proc %prec | NDET gens AT proc %prec | BOX gens AT proc %prec | INTL gens AT proc %prec | SEMI gens AT proc %prec | LCOMM set RCOMM gens AT proc %prec | proc BACKSLASH set | proc INTL proc | proc LCOMM set RCOMM proc | proc LSQUARE set PAR set RSQUARE proc | proc NDET proc | proc BOX proc | proc TIMEOUT proc | proc INTR proc | proc SEMI proc | bool GUARD proc | dotted fields ARROW proc | dotted ARROW proc | STOP | SKIP| CHAOS OPEN set CLOSE; [Sc98] AT AT AT AT AT AT Abbildung 6: Ein Ausschnitt aus der CSPM-Grammatik von FDR Alle binären Operatoren tauchen in einer einzigen Regel als Alternative auf. Die Präzedenz muss dabei nicht explizit durch die Grammatik ausgedrückt werden, sondern wird hinter der jeweiligen Regel angegeben (%prec AT). Grammatikanteile, die aufgrund der Sprachenspezifikation an mehreren Stellen vorkommen müssen, können trotz Mehrdeutigkeit geparst werden. Der hier dargestellte Ausschnitt des CST ist in SableCC viel Größer. Der Grundsätzliche Aufbau des Grammatik-Kerns (siehe Abbildung 10, dunkelblau) sieht vor, für jede Präzedenzstufe eine neue Regel zu erzeugen anstatt einer Alternative. Pro Präzedenzstufe wird wiederum eine Alternative benötigt, um zur nächsten Stufe überzugehen: A B C D = = = = A BinOp B | B B BinOp C | C UnOp C | D Atom Dabei ist die Einhaltung der folgenden vier Regeln von größter Bedeutung: 1. Unäre und Binäre Operatoren dürfen nicht die gleiche Präzedenzstufe teilen. 2. Links- und Rechts-Unäre Operatoren dürfen nicht die gleiche Präzedenzstufe teilen. 3. Alle unären Operatoren müssen eine höhere Präzedenz haben als alle anderen binären Operatoren. 4. Links- und Rechts-Assoziative Binäroperatoren dürfen nicht die gleiche Präzedenzstufe teilen [Ga04]. Die Verletzung einer Regel führt entweder zur Mehrdeutigkeit der Grammatik oder zu scheinbaren Fehlern beim Parsen, die nur durch veränderte Klammersetzung zu lösen sind. Die CSPM-Sprachenspezifikation verletzt o.g. Regeln zum Teil. Die 3. Regel wird deshalb verletzt, da die unären Replicated-Operatoren in libcspm eine geringere Präzedenz haben als sämtliche binäre Operatoren [Gi16e]. Außerdem hat z.B. der Präfix-Operator eine andere Präzedenz als 5. Der Parser 19 Let-Within-Ausdrücke. Beide sind als prozesserzeugende Konstrukte aufzufassen, wobei ein Prozess jeweils ganz rechts steht. Unter der Annahme, dass der Präfix-Operator eine höhere Präzedenz als Let-Within-Ausdrücke hat, ist folgende Definition übersetzbar: A = let x=1 within c->STOP Ohne Klammersetzung ist diese Definition jedoch nicht übersetzbar: B = c->(let x=1 within STOP) Dabei handelt es sich um eine korrekte CSPM-Definition. Eine Lösung ist die Gleichbehandlung beider Konstrukte (Let-Within und Prefixing) in der Präzedenzhierarchie. Bestimmte Tokens können nur mit erheblichem Aufwand im AST-Visitor eindeutig zugeordnet werden. Beispielsweise werden Variablen-Patterns und Ausdrucksvariablen an der gleichen Stelle in der Grammatik geparst. Ihre Bedeutung, ihr Typ und Interpretation als Prologvariable hängt aber davon ab, in welchem Kontext sie und ihre Umgebung stehen. Lösung für die Mehrdeutigkeit von Generator- und Predicate-Statements in SableCC wird in Kapitel 5.6 erläutert. Die Interpretation von Zeilenumbrüchen (\n, \r, \r\n) stellt eine weitere Herausforderung dar, da Newline-Tokens verschiedene Bedeutungen haben können. In einer CSPM-Spezifikation werden Instruktionen durch Zeilenumbrüche getrennt (\r\n als Instruktionsseperator): A = c->P \r\n B = true&Q Jedoch ist das Setzen von Umbrüchen an bestimmten Stellen auch möglich, um die Übersichtlichkeit des Codes zu verbessern. In diesem Fall darf ein Newline-Token nicht als Instruktionsende interpretiert werden (\r\n als optisches Zeichen). Dazu gehören sowohl die Stellen vor und hinter binären Operatoren als auch die Stellen hinter öffnenden und vor schließenden Klammern: A = P \r\n [{e1}||{e2}] \r\n Q B = { \r\n x \r\n } Bei der Erzeugung des Prolog-Codes für den CSPM-Interpreter von ProB ist es erforderlich, einen AST-Knoten mit einer Liste von Instruktionen zu erhalten (defs). Die dafür zuständige cspmj-Teilgrammatik parst daher immer nur genau ein Newline-Token als Instruktionsseperator: start {->start} = nl* defs? {-> New start.defs([defs.def])}; defs {-> def*} = {sinlge} [decl]:def nl* {-> [decl.def]} |{multiple} [first]:def nl [rest]:defs {->[first.def,rest.def]}; Die Sprachenspezifikation erlaubt aber das Hintereinanderstehen beliebig vieler Newline-Tokens. Daher müssen alle Zeilenumbrüche bis auf einen aus dem Tokenstream während des Parsens entfernt werden. Dies wird durch das Überschreiben der Filtermethode des Lexers von SableCC erreicht. 20 5. Der Parser Leider ist es nicht immer möglich Anhand des Tokens auf die Bedeutung eines Zeilenumbruchs zu schließen. Das Newline-Token zwischen einer schließenden eckigen Klammer (]) und einem Bezeichner kann sowohl ein Instruktionsseperator als auch ein optisches Zeichen sein. Für eine korrekte Interpretation ist die Erkennung von Knoten durch das Abhören der Tokensequenz notwendig. Wird in der Lexer-Filtermethode z.B. ein Token TDlFree erkannt, dann deutet dies auf den Knoten AAssertionDef hin. Alle eckigen, schließenden Klammern, die sich in einer Assertion hinter diesem Token befinden, zeigen das Ende der Instruktion an. Kommt eine solche Klammer in einem binären Operator vor (z.B. alphabetised Parallel), kann ein nachfolgender Zeilenumbruch niemals das Instruktionsende sein. CSPM ist eine stark typisierte Sprache und die Implementierung eines Typecheckers ist ohne erheblichen Aufwand nicht möglich. Der Einsatz eines Typisierungsverfahrens wird in Zukunft den Zeitaufwand für die Vervollständigung des Typecheckers reduzieren können. Ein weiterer Unterschied zu den meisten Programmiersprachen ist, dass die Lokalität einer Definition in einem bestimmten Sichtbarkeitsbereich unerheblich ist. Beispielsweise lässt sich folgende Java-Klasse nicht kompilieren (illegal forward reference): public class Klasse { int y = x; int x = 1; } Grund dafür ist, dass die Variable x verwendet wird bevor sie definiert wird. Deshalb ist zur Identifikation von Variablen die Implementierung eines speziellen Algorithmus notwendig. Die Erkennung aller Symbole in nur einem AST-Durchlauf ist in CSPM nicht möglich. Details hierzu liefern auch Kapitel 5.8 und Kapitel 5.9. 5. Der Parser 21 Präzedenzunterschiede Wie schon im vorherigen Unterkapitel erwähnt, gibt es zwischen cspmf und libcspm Unterschiede hinsichtlich der Operatorenrangfolge. Um das SableCC-Regelwerk einzuhalten, gleichen sich cspmf und cspmj auch hier. Jedoch ist anzumerken, dass auch die Einordnung von Operatoren erfolgen muss, die seit FDR3 neu hinzugekommen sind. Aus diesem Grund wurde eine Präzedenztabelle erstellt, die erstmals auch Operatoren aus dem Bereich synchronising einordnet. Die folgende Tabelle beschreibt die Bindungsstärke aller von cspmj unterstützten Operatoren von schwach (oben) nach stark (unten). Alle rot markierten Operatoren haben in libcspm eine andere Rangfolge [Gi16e]. Rang Operator Zeichen Arität 1 Hide \ binär 2 Interleave ||| binär 3 Exception Alphabetised Parallel Generalised Parallel Linked Parallel P [| {e} |> Q P [{e1}||{e2}] Q P [{e}] Q P [e1 <-> e2] Q binär 4 Internal Choice |~| binär 5 External Choice Synchronising External Choice [] [+{e}+] binär 6 Synchronising Interrupt, Interrupt /+{e}+\, /\ binär 7 Sliding Choice/Timeout [> binär 8 Sequential Composition ; binär 9 Guard, Prefix, Lambda Let-Within If-Then-Else Replicated &, ->, \ p @ P, let defs within P if b then expr1 else expr2 [] <set-stmt> @ P linksunär 10 Nondeterministic Input Input, Output $ ?,! linksunär 11 Restriction : binär 12 Dot . binär 13 or or binär 14 and and binär 15 not not binär 16 Comparison (>, <, >=, <=, !=, ==) binär 17 Addition, Subtraction +, - binär 18 Multiplication, Division, Modulo *, /, % binär 19 Unary Minus/Negation - linksunär 20 Length # linksunär 21 Concat/Append-Pattern (^) ^ binär 22 Rename P [[e1 <- e2]] rechtsunär 23 Parenthesis, Tuple (expr),(expr1, expr2,...) - Abbildung 7: Operatorenpräzedenztabelle 22 5. Der Parser Architektur Die folgende Übersicht zeigt den Weg einer CSPM-Datei durch den Parser bis hin zur Erzeugung der zugehörigen Prolog-Ausgabe. Der Ablauf ist in einer Methode parsingRoutine(...) in der Hauptklasse CSPMparser.java zusammengefasst. Die folgenden Unterkapitel erklären den hier illustrierten Ablauf. Eingabe Importieren externer Dateien (include) Kommentarspeicherung Winkelklammersubstitution Lexing/Whitespace-Filterung Parsing Statement-Überprüfung LTL/CTL-Formel-Parsing Renaming-Analyse Renaming Prolog-Codegenerierung Erkennung ungebundener Variablen Ausgabe Fehlerbehandlung Parallele Abhandlung Folgeschritt Abbildung 8: Architektur des Parsers 5. Der Parser 23 Prelexing Die Erkennung von Tokens im SableCC-Lexer ist nur dann sinnvoll, wenn gewisse Regeln zugrunde liegen, die ihr Auftreten im Tokenstream regeln. Kommentare hingegen werden häufig in der Kategorie Ignored Tokens im Lexer aufgeführt, denn ihrer inneren Syntax liegen keine Regeln zugrunde. Ist es erforderlich, Änderungen an dem gepufferten Inhalt der CSP-Datei zur Laufzeit durchzuführen, so müssen Kommentare von der Änderungsroutine ausgeschlossen sein. Um den Inhalt von Kommentaren zu schützen, ist ihre manuelle Interpretation und Auslagerung sinnvoll. Dies geschieht in dem Parser unmittelbar nach Auslesen der Eingabedatei, um die Winkelklammersubstitution innerhalb von Kommentaren zu verhindern. Kommentarpufferung Die beiden Kommentararten Line-Comment und Multiline-Comment werden durch einen Algorithmus in der Hauptklasse CSPMparser.java (Methode saveComments(String)) in einer ArrayList<CommentInfo> gespeichert. Eine Kommentarinformation ist dabei eine Datenstruktur, die folgende Informationen über einen Kommentar speichert: Zeile und Spalte des Anfangszeichens Index des Anfangszeichens Gesamtlänge (Zeichenanzahl) Kommentarinhalt inklusive der Zeichen für die Kommentarsetzung (--, {-, -}) Multiline-Comment oder Line-Comment (boolescher Wert) Die Methode analyse(String), die im Konstruktor ausgeführt wird, identifiziert Pragmas {-# "Formel" "Kommentar" #-} mithilfe von regulären Ausdrücken (Java Regex) und verleiht der Kommentarinformation weitere Attribute zur Speicherung des Inhalts der temporallogischen LTL/CTL-Formel. Anschließend werden alle Zeichen (außer \n und \r), die mit Kommentaren assoziiert sind, durch Leerzeichen ersetzt. Auf diese Weise kann gewährleistet werden, dass im Falle eines Fehlers die Positionsangabe des fehlerverursachenden Tokens erhalten bleibt. Nach der Löschung aller Kommentare können externe Dateien eingebunden werden. Es ist erforderlich, alle Kommentare vorher zu löschen, damit include-Anweisungen innerhalb von Kommentaren nicht berücksichtigt werden. Wird eine CSP-Datei importiert, so muss der Algorithmus erneut ausgeführt werden, um auch Kommentare dieser Datei zu puffern. Winkelklammersubstitution Im Gegensatz zu libcspm unterstützt cspmf die Verwendung von Vergleichsoperatoren innerhalb von Sequenz-Ausdrücken. Eine Sequenzklammer unterscheidet sich jedoch nicht von dem Vergleichszeichen. Das parsen von <3>4> ist zum Beispiel nicht möglich, da an der Stelle hinter der 3 noch nicht klar ist, ob die Sequenz geschlossen wird, oder mit 4 verglichen werden soll. Aus diesem Grund wurde eine Klasse zur Substitution von Winkelklammern implementiert. Diese ermöglicht das Umwandeln des obigen Ausdrucks in: «3£4». Dabei gibt es vier Regeln zum Ersetzen von Zeichen: 1. 2. 3. 4. < > > < ≙ ≙ ≙ ≙ öffnende Sequenz schließende Sequenz größer als kleiner als ⇒ ⇒ ⇒ ⇒ \u00AB \u00BB \u00A3 \u20AC Alle anderen Tokens, die Winkelklammern enthalten, bleiben erhalten. ≙ ≙ ≙ ≙ « » £ € 24 5. Der Parser Zuerst wird der gesamte Dateiinhalt in einem char-Array gespeichert. Anschließend wird das Array durchlaufen und jedes einzelne Zeichen untersucht. Mithilfe von Fallunterscheidungen wird abgefangen, ob es sich bei einem gefundenen Zeichen um den Bestandteil eines der folgenden Tokens handelt: <=> <= => <-> <- -> [> <| |> Falls ja, wird das jeweilige Token übersprungen. In allen anderen Fällen, in denen eine Winkelklammer auftritt, muss substituiert werden. Folgt beispielsweise eine linke Winkelklammer hinter einem Gleichheitszeichen oder Komma (=< bzw. ,<), so kann es sich dabei niemals um einen Vergleich handeln. Steht die Klammer jedoch hinter einer Zahl oder einem Bezeichner (1<), handelt es sich immer um einen Vergleich. Mithilfe dieser Mustererkennung, die 63 solcher Regeln beinhaltet, können die meisten Winkelklammern korrekt zugewiesen werden. In seltenen Fällen kann jedoch keine Substitution erfolgen. Ein Beispiel hierfür ist eine CSP-Datei dem Inhalt A = <1,2>><3,4>. Nach Anwendung der oben genannten Regeln fehlen noch 3 Klammern: A = «1,2>><3,4» Aus diesem Grund wurde ein weiterer Algorithmus implementiert, der mithilfe der Exhaustionsmethode versucht, eine Kombination von Klammern zu finden, sodass der oben genannte Ausdruck parsbar wird. Dazu wird die erste gefundene Winkelklammer sowohl durch » als auch £ ersetzt. Anschließend wird versucht beide entstandenen Ausdrücke zu parsen. A = «1,2»><3,4» A = «1,2>><3,4» A = «1,2£><3,4» Da dies in beiden Fällen nicht möglich ist, werden die nächsten beiden Klammern ersetzt. Dabei entstehen jeweils 2 weitere Alternativen: A = «1,2»»<3,4» A = «1,2»><3,4» A = «1,2»£<3,4» A = «1,2>><3,4» A = «1,2£»<3,4» A = «1,2£><3,4» A = «1,2££<3,4» 5. Der Parser 25 Auch diesmal kann keine Definition erfolgreich geparst werden. Also wird die letzte unbekannte Klammer durch jeweils 2 Alternativen ersetzt. Es existieren nun 23 Alternativen: A = «1,2»»«3,4» A = «1,2»»<3,4» A = «1,2»»€3,4» A = «1,2»><3,4» A = «1,2»£«3,4» A = «1,2»£<3,4» A = «1,2»£€3,4» A = «1,2>><3,4» A = «1,2£»«3,4» A = «1,2£»<3,4» A = «1,2£»€3,4» A = «1,2£><3,4» A = «1,2££«3,4» A = «1,2££<3,4» A = «1,2££€3,4» Abbildung 9: Erfolgreiche Ersetzung von Winkelklammern In diesem Durchgang kann eine Definition geparst werden. Nur der Vergleich zweier Sequenzen ist korrekt. Beispiele, bei denen mehrere Pfade zu einem richtigen Ergebnis führen, können nicht Typenkorrekt sein. Der Vergleich von Atomen, welche die Ord-Zwangsbedingung [Gi16c] erfüllen, führt grundsätzlich zur Rückgabe eines booleschen Wertes, der wiederum nicht erneut vergleichbar ist. Die Ausführung der Exhaustionsmethode bezieht sich immer auf einzelne Zeilen. Liegt ein Zeilenumbruch vor und kann die aktuelle Zeile nicht geparst werden, so wird die Folgezeile dem Betrachtungsbereich hinzugezogen. Die Implementierung ist offensichtlich nicht optimal, da der Betrachtungsbereich im Falle vieler Fehler stark wachsen müsste. Dies hätte wiederum zu Folge, dass die Anzahl der zu identifizierenden Klammern so groß würde, dass eine praxisrelevante Laufzeit nicht mehr gewährleistet werden könnte. 26 5. Der Parser Diagramm zum Aufbau des CST Das folgende Bild soll eine grobe Übersicht von dem Aufbau der cspmj-Grammatik vermitteln und dient als Erklärungshilfe der folgen Kapitel. Assertions Instruktionsliste Funktion Variable Definition Print-Ausdruck Include Transparente/ Externe Funktion Zeitabschnitt Modul Pattern Ausdruck Prozessoperatoren Statements Ereignis Datentypen Kanäle Boolesche Operatoren Numerische Operatoren Typenausdruck Atome Abbildung 10: Aufbau der Grammatik von cspmj Dabei repräsentiert jedes Rechteck einen Bereich des CST, der sich zusammenfassen lässt. Die Pfeile zeigen die jeweiligen Zusammenhänge zwischen den einzelnen Teilgrammatiken an. Beispielsweise enthält eine Mengen-Comprehension sowohl Elemente aus der Teilgrammatik für Statements als auch Ausdrücke ({P(x)[]Q(x)|x<-{1..99}, x%2==0}). Dies wird durch die farbigen Pfeile kenntlich gemacht. Statement-Überprüfung Die in Comprehensions vorkommende Statement-Liste, z.B. {x | x<-{1..10}, x%2==0} besteht aus zwei verschiedenen Statement-Arten. Dazu gehören Generator-Statements Predicate-Statements p<-a oder p:a b Dabei können beide Arten an jeder Stelle einer Liste stehen. Ein Pattern eines Generator-Statements kann nicht durch die Pattern-Teilgrammatik erzeugt werden (Siehe Abbildung 10, violett). Grund hierfür ist, dass die Grammatik für Patterns und Ausdrücke zum Teil die gleichen Ausdrücke erzeugen können (z.B. x^y oder {}.<>). Die Wahl der Verzweigung fällt nicht erst vor dem Linkspfeil-Token (<-), sondern bereits bei dem ersten Token des Statements (entweder p oder b). An dieser Stelle muss die Schnittmenge der erzeugbaren Ausdrücke beider Teilgrammatiken leer sein. Dies ist aber nicht der Fall. Daher ist die Unterscheidung zwischen Predicate-Statement 5. Der Parser 27 (Ausdruck) und Generator-Statement (Pattern) nicht möglich. Um diesen Konflikt zu lösen, besitzt cspmj eine große Kerngrammatik (Siehe Abbildung 10, dunkelblau). Diese beinhaltet als Untermenge alle Regeln, die für die Erzeugung von Patterns notwendig sind. Eine Unterscheidung zwischen der Pattern- und Ausdruck-erzeugenden Teilgrammatik ist somit überflüssig. Mithilfe eines AST-Visitors werden alle Ausdruck-Knoten gesperrt, wenn ein Pattern erwartet wird. Wenn wiederum ein Ausdruck erwartet wird, werden alle Knoten gesperrt, die Patterns erzeugen. Beim Betreten eines Knotens, der sowohl für die Erzeugung von Ausdrücken, als auch Patterns zuständig ist, müssen gegebenenfalls weitere Eigenschaften überprüft werden. Ein Mengen-Pattern darf z.B. im Gegensatz zu Mengen-Ausdrücken nur ein Element enthalten. Das Betreten eines gesperrten Knotens resultiert in einer Fehlerausgabe vom Typ noPatternException. LTL/-CTL-Formel-Überprüfung Auch die Übersetzung einer LTL/-CTL-Assertion ist möglich. Im Gegensatz zu cspmf beinhaltet cspmj allerdings einen AST-Visitor, der mit Hilfe zweier SableCC-Parser die syntaktische Korrektheit angegebener Formeln beim Parsen der CSP-Datei überprüft. Somit können Fehler in den Formeln, die kein Teil der CSP-Sprache sind, in der Parsing-Phase identifiziert und in der Datei hervorgehoben werden. Der LTL-Formel folgender Assertion fehlt eine schließende Klammer, was die Ausgabe einer TreeLogicException bewirkt: assert P |= LTL: "G(X[e]". Renaming Das Renaming ermöglicht in cspmj die eindeutige Zuordnung von Variablen durch Vergabe von Indizes in nur einem AST-Durchlauf (SymbolCollector.java). Definiert man beispielsweise zwei Funktionen mit jeweils einem Argument x, so erhält das erste x den Index 1 und das zweite x den Index 2. Neben der Symbolreferenz müssen vor der Erzeugung der Prolog-Ausgabe für den ProB Interpreter weitere wichtige Informationen gesammelt werden. In einer Baum-Datenstruktur (ScopeTree.java) wird unter Anderem festgehalten, in welchem Sichtbarkeitsbereich (Scope) sich eine Variable zur Laufzeit befindet und welchen Vorgänger dieser Bereich hat. Dies ist ein Beispiel für eine Definition mit 6 Sichtbarkeitsbereichen: A(x) = let B = x within {1|y<-{2}} 0: A 1: (x) 2: let B within 3: x 4: x<-{2} 5: 1 Für jedes Variablen-Pattern wird ein Listeneintrag mit einer Symbolinformation (Objekt der Klasse Syminfo.java) erstellt. Eine Information enthält folgende Details: Knoten des Bezeichners – Liefert vor Allem Positionsangaben Symboltyp – Beschreibung der Verwendungsart, z.B. Function or Process Symbolname – Originalbezeichnung der Variable Symbolreferenzname – Name der Variable inklusive Nummer und Unterstrichvorsatz (z.B. _x2 für das zweite x, das gleichzeitig ein Pattern ist) Sichtbarkeitsbereich – Nummer des Sichtbarkeitsbereichs, in dem die Variable aufgerufen oder definiert wird Zum Schluss sind die Informationen aller Symbole der geparsten CSPM-Datei gespeichert. 28 5. Der Parser Analyse von Neudefinitionen Während der oben beschriebenen Einsammlung von Symbolen wird zeitgleich eine Renaming-Analyse durchgeführt. Unterschieden wird dabei zwischen einer horizontalen und einer vertikalen Renaming-Analyse. Die horizontale Analyse untersucht, ob ein Variablen-Pattern in einem Sichtbarkeitsbereich einer Definition mehrfach auftaucht. So führen Beispielsweise folgende Definitionen zu einer Fehlerausgabe Redefinition of Identifier x: A(x,x) = 1 A = {1|x@@x<-{1}} A(x^x) = 1 A = c?x?x -> STOP Die vertikale Renaming-Analyse untersucht hingegen, ob eine Variablenzuweisung oder Funktionsdefinition in demselben Sichtbarkeitsbereich mehrfach auftaucht. Folgende Definitionen verursachen die Fehlerausgabe Redefinition of Identifier A: B = let A = 1 A = 2 A = 1 A(x) = 2 A(x) = 1 A = 2 A = 1 A = 2 within STOP Eine Ausnahme stellt dabei die Neudefinition von Funktionen dar. Erlaubt ist das Überschreiben von Funktionen für den Fall, dass die Vorgängerinstruktion die Erstdefinition ist. Eine Datei mit folgendem CSPM-Code ist gültig: A(x) = 1 A(x) = 2 Jedoch ungültig ist dieser Code: A(x) = 1 B = 2 A(x) = 3 Bei jedem Aufruf einer eingebauten Funktion oder Konstante wird in der Symbolliste ein Eintrag mit der Information BuiltIn primitive angelegt. Eine anschließende Definition des gleichen Symbols führt zu keinem Renaming-Fehler. In diesem Fall wird der Inhalt des alten Symboleintrages ersetzt und es beginnt die Gleichbehandlung der Variable in der Renaming-Analyse. Sofern kein Fehler vorhanden ist, wird das Symbollistenobjekt an den Prolog-Codegenerator übergeben. Prolog-Codegenerierung Der Prolog-Codegenerator ist ein AST-Visitor und Objekt der Klasse PrologGenerator.java. Wie auch der Symbolsammler verfügt der Codegenerator über eine Baumdatenstruktur zur Orientierung. Darüber hinaus wird eine Hilfsklasse PrologTermOutput.java verwendet, um die Erzeugung von Prolog-Termen zu formatieren. Nach dem Aufruf einer Methode (z.B. printAtom(String)) auf ein Objekt p der Hilfsklasse wird das Argument vollautomatisch, korrekt geklammert und interpunktiert in einen Puffer geschrieben (StringWriter). Auf diese Weise ist eine fehlerarme Übersetzung jedes einzelnen Knotens in grammatisch korrekte Prolog-Terme gewährleistet. Als Strategie zur Identifikation des Verhaltens von cspmf dient das Prinzip Reverse-Engineering. Dazu wird für jeden AST-Knoten ein CSPM-Code-Beispiel erstellt und anschließend mit cspmf übersetzt. 5. Der Parser 29 Anschließend erfolgt die Rekonstruktion des Terms durch Anpassung der Grammatik (AST-Listentransformationen) und Ergänzung des jeweiligen AST-Visitor-Knotens um die oben genannten Hilfsmethodenaufrufe. Nach einem manuellen Vergleich zwischen den Ausgabedateien von cspmf und cspmj wird ein neuer jUnit-Test erzeugt oder ein bestehender ergänzt. Anhand der unten aufgeführten Methoden ist die Übersetzung der CSPM-Definition A = 1+2 nachvollziehbar: @Override (1) public void caseADefsStart( ADefsStart node) { inADefsStart(node); { List<PDef> copy; copy = new ArrayList<PDef>( node.getDef()); for(PDef e : copy) { e.apply(this); if(!currentInChannel) { p.fullstop(); } currentInChannel = false; } } [...] @Override (2) public void caseAExpressionDef( AExpressionDef node) { inAAusdruckDef(node); if(node.getExp() != null) { node.getExp().apply(this); } printSrcLoc(node); p.closeTerm(); outAExpressionDef(node); } @Override (3) public void caseAPatternExp( APatternExp node) { inAPatternExp(node); p.openTerm("bindval"); if(node.getPattern1() != null) { groundrep += 1; node.getPattern1().apply( this); groundrep -= 1; } tree.newLeaf(); if(node.getProc1() != null) { node.getProc1().apply( this); } tree.returnToParent(); outAPatternExp(node); } @Override (4) public void caseAWildcardPattern( AWildcardPattern node) { inAWildcardPattern(node); p.printVariable("_"); if(node.getWildcard() != null) { node.getWildcard().apply( this); } outAWildcardPattern(node); } @Override (5) public void caseAAdditionExp( AAdditionExp node) { inAAdditionExp(node); p.openTerm("+"); if(node.getValExp() != null) { node.getValExp().apply( this); } if(node.getValExp1() != null) { node.getValExp1().apply( this); } p.closeTerm(); outAAdditionExp(node); } @Override (6) public void caseANumberExp( ANumberExp node) { inANumberExp(node); if(node.getNumber() != null) { node.getNumber().apply( this); p.openTerm("int"); int i; i = Integer.valueOf( node.getNumber().getText()); p.printNumber(i); p.closeTerm(); } outANumberExp(node); } Abbildung 11: Sechs Methoden aus dem Prolog-Codegenerator 30 5. Der Parser Um einen korrekten Prolog-Ausdruck 'bindval'(_,+(int(1),int(2)),'src_span'(1,1,1,8,0,7)) zu erzeugen, werden die in PrologTermOutput.java vordefinierten Hilfsmethoden an entsprechender Stelle aufgerufen. Die folgenden Schritte beschreiben die Entstehung der oben genannten Prolog-Ausgabe. Dabei stehen IN und OUT für das Betreten bzw. Verlassen von Knoten: IN: ADefsStart IN: AExpressionDef IN: APatternExp Hinzufügen von Teilausdruck 'bindval'( durch Aufruf von p.openTerm("bindval"). IN: AWildcardPattern Hinzufügen von Teilausdruck _ durch Aufruf von p.printVariable("_"). OUT: AWildcardPattern IN: AAdditionExp Hinzufügen von Teilausdruck ,'+'( durch Aufruf von p.openTerm("+"). IN: ANumberExp Hinzufügen von Teilausdruck 'int'(1) durch Aufruf von p.printNumber(1) und anschließend von ) durch Aufruf von p.closeTerm(). OUT: ANumberExp IN: ANumberExp Hinzufügen von Teilausdruck ,'int'(2) durch Aufruf von p.printNumber(2) und von ) durch Aufruf von p.closeTerm(). OUT: ANumberExp OUT: APatternExp Hinzufügen von ) durch Aufruf von p.closeTerm(). Out: AAdditionExp Hinzufügen von 'src_span'(1,1,1,8,0,7) durch Aufruf von printSrcLoc(Node). Hinzufügen von ( durch Aufruf von p.closeTerm(). OUT: AExpressionDef Hinzufügen von .\r\n bzw .\n durch Aufruf von p.fullstop(). OUT: ADefsStart Positionsangaben Damit ProB den Ort eines Fehlers zurückgeben und den CSPM-Code beim animieren hervorheben kann [Le08], ist es erforderlich, die Prolog-Ausgabe mit Positionsangaben zu versehen. Dazu zählen die Positionen von Operatoren, Bezeichnern und ganzen Instruktionen. Da SableCC den Ort von Knoten im Quellcode normalerweise nicht kennt, wurde eine spezielle SableCC-Version der Heinrich-Heine-Universität verwendet. Diese erweitert die Klasse Node.java um PositionedNode.java4. Durch das Aufrufen verschiedener Methoden an dem Knoten des jeweiligen Tokens können Index, Zeile und Spalte ermittelt werden. So enthält die Positionsangabe 'src_span'(1,1,1,8,0,7) die Ortsinformation der gesamten oben beschriebenen CSPM-Definition. 4 de.hhu.stups.sablecc.patch.PositionedNode 5. Der Parser 31 Dabei haben die sechs einzelnen Argumente folgende Bedeutung für einen betrachteten Knoten node: 1. 2. 3. 4. 5. 6. Zeile des ersten Zeichens Spalte des ersten Zeichens Zeile des letzten Zeichens Spalte des letzten Zeichens Offset vom Anfang der Datei bis zum Index des ersten Zeichens des Knotens Index des letzten Zeichens - Index des ersten Zeichens Variablenzuweisung/Erkennung ungebundener Variablen Um Variablen, Funktionen oder Prozesse, die auf der rechten Seite von Definitionen auftauchen, zuordnen zu können, wurde ein Suchalgorithmus implementiert. Als Datenstrukturen kommen sowohl die Symbolliste aus dem Renaming, als auch die Baumstruktur ScopeTree.java zum Einsatz. Letztere jedoch speichert neben den Sichtbarkeitsbereichen zusätzlich diejenigen Symbole, die zur Laufzeit in dem jeweiligen Bereich definiert wurden. Die hierzu gespeicherte Information ist jeweils ein Paar, das aus Symbolname und Symbolreferenzname (Beginn mit Unterstrich bei Variablen und Nummerierung, falls Nummer >1) besteht, z.B. (x,x7). Die Zuweisung einer Variable erfolgt durch Aufruf der Methode printSymbol(String,Node), in welcher folgende drei Schritte solange wiederholt werden, bis das Symbol x gefunden oder der Sichtbarkeitsbereich 0 erreicht wurde. 1. Suche Symbol x in der Liste der definierten Variablen des aktuellen Sichtbarkeitsbereichs (ScopeTree zur Laufzeit) 2. Falls Symbol x nicht gefunden wurde, prüfe, ob die Definition erst später erfolgt. Durchsuche dazu die Symbolliste aus der Renaming-Phase nach Symbolen x, denen der aktuelle Sichtbarkeitsbereich zugeordnet ist 3. Falls x immer noch nicht gefunden wurde, kehre zum vorherigen Sichtbarkeitsbereich zurück Ist der Bezeichner nach Abbruch der o.g. Schleife bekannt, so kann der Prolog-Term ergänzt werden. Ist er jedoch unbekannt, muss untersucht werden, ob es sich bei ihm um eine eingebaute Funktion oder Konstante handelt (Abgleich mit Tabelle 3). Ist dies nicht der Fall, erfolgt eine Fehlerausgabe Unbound Identifier x. Fehlerbehandlung Alle möglichen Fehlerarten eines Übersetzungsvorgangs werden in der Methode parsingRoutine(...) der Hauptklasse CSPMparser.java abgefangen. Die Ausdifferenzierung einzelner Fehlerarten ist hinsichtlich weiterer Entwicklungsbemühungen und der Benutzerfreundlichkeit von größter Bedeutung. Aus diesem Grund wurde für alle möglichen Fehler jeweils eine Klasse angelegt, die java.lang.Exception erweitert. Die Fehlerarten, die bei der Interpretation einer CSP-Datei auftauchen können, sind: LexerException Fehler bei der Identifikation eines Tokens ParserException, IOException Verletzung syntaktischer Regeln, andere Fehler beim durchlaufen des AST RenamingException Ungültige Neudefinition in betrachtetem Sichtbarkeitsbereich 32 5. Der Parser UnboundIdentifierException Aufruf eines ungebundenen Bezeichners NoPatternException Ungültiger Ausdruck für ein Pattern in einem Generator-Statement TriangleSubstitutionException Fehler bei der Umwandlung von Winkelklammern in Sequenzklammern oder Vergleichsoperatoren IncludeFileException Fehler beim Importieren einer CSP-Datei. FileNotFoundException Die zu parsende Datei wurde nicht gefunden TreeLogicException Eine LTL- oder CTL-Assertion hat eine Formel, die syntaktische Fehler aufweist Vergleich mit cspmf Da im Rahmen der Weiterentwicklung von FDR einige syntaktische Änderungen vorgenommen wurden, gibt es zum Teil leichte Unterschiede zwischen cspmf und cspmj. Ein Beispiel dafür ist das unterschiedliche Verhalten beim Aufruf eingebauter Funktionen und Konstanten. Die Behandlung erfolgt nun stets auf die gleiche Weise. Den Aufruf eines eingebauten Wortes, das noch nicht zuvor aufgerufen wurde, interpretiert der Prolog-Codegenerator als Funktions- oder Variablen-Definition und legt einen Eintrag in der Renaming-Symbolliste mit der Information BuiltIn primitive an. Alle Symbole dieser Art können überschrieben werden. Bei einer Neudefinition wird der Listeneintrag für das entsprechende Symbol ersetzt (siehe auch Kapitel 5.8). Die folgende Liste enthält alle Wörter, deren Interpretation auf die oben genannte Weise erfolgt: Konstanten MengenFunktionen SequenzFunktionen Map-Funktionen Exceptions Prozesse DotFunktionen Bool card concat emptyMap error CHAOS extensions Char diff elem mapDelete show DIV productions Int empty head mapFromList RUN Proc inter length mapLookup SKIP Events Inter null mapMember STOP True member set mapToList WAIT False seq tail mapUpdate Seq mapUpdateMultiple set Map union Union Tabelle 2: Neue Built-ins 5. Der Parser 33 Ein Ziel der Arbeit war die Integration eines neuen CSPM-Parsers in ProB und somit die Angleichung des Verhaltens an den bestehenden ProB-Parser cspmf. Gleichzeitig sollten aber auch weitere Funktionen implementiert werden, die seit 2011 in der Spezifikation von FDR hinzugekommen und nun in der aktuellen Version FDR3.4 ausgeschrieben sind. Aus diesem Grund stellt cspmj eine Kombination aus den Funktionen beider Parser da. Unterstützt werden alle Befehle, die cspmf bereits unterstützt hat und zusätzlich dazu einige neue Operatoren, eingebaute Funktionen und Konstanten bis FDR Version 3.0. Die folgende Tabelle führt alle CSPM-Konstrukte auf, die neu implementiert wurden: Name Regel Bedeutung Map (| k1=>v1,...,kN=>vN |) Empty-Map (| |) Char-Literal/Pattern 'c' Ein Unicode-Zeichen mit einfachen Anführungsstrichen. String-Literal/Pattern "String" Eine Folge von Zeichen mit doppelten Anführungsstrichen. Has-Trace-Assertion (optional mit [M], wobei M = T|F|FD|R) assert P:[has trace [M]]:s Überprüfe ob eine Sequenz s in P vorkommt. Weise Schlüsseln ki Werte vi zu. Non-deterministic Input Non-deterministic restricted Input $p $p:a Verhalten wie bei Eingabe ?p, Auswahl erfolgt durch Internal Choice statt External Choice. P [|{e}|> Q Starte P. Wird ein Ereignis e durch P ausgeführt, so starte Q. Synchronising External Choice P [+{e}+] Q Synchronisiere P und Q durch Ereignisalphabet {e}. Gilt e∈P^e∉Qve∉P^e∈Q, so verhält sich der Operator wie []. Synchronising Interrupt P /+{e}+\ Q Verhalten wie /\ wenn e∈P^e∉Qve∉P^e∈Q. [+{e}+]<stmts>@P(x) Werte P ∀x∈<stmts> aus und setze resultierende Prozesse mit [+{e}+] zusammen. Gilt P(x)∉<stmt>,∀x, so verhält sich der Prozess wie STOP. Exception Replicated Synchronising Parallel Tabelle 3: Neue CSPM-Konstrukte Eine genauere Erklärung der Funktionalität ist der CSPM-Dokumentation von FDR zu entnehmen [Gi16b]. 34 5. Der Parser Befehle Die von Gradle generierte cspmj.jar kann über eine Kommandozeile ausgeführt werden. Folgende Befehle werden akzeptiert: java -jar cspmj.jar -parse dateiname.csp Parse eine CSP-Datei dateiname.csp und generiere eine Prolog-Datei mit dem Namen dateiname.csp.pl java -jar cspmj.jar -parse eingabe.csp --prologOut=ausgabe.csp Parse eine CSP-Datei eingabe.csp und generiere eine Prolog-Datei mit dem Namen ausgabe.csp.pl java -jar cspmj.jar -parseAll Durchsuche das aktuelle Verzeichnis und alle Unterverzeichnisse nach Dateien, welche die Endung .csp haben. Parse alle gefundenen Dateien und lege entsprechende Prolog- Dateien mit dem gleichen Anfangsnamen und der Endung .pl an. java -cp build/classes/main PerformanceTest suchpfad egebnispfad Führe cspmj und cspmf für alle Dateien mit Endung .csp in suchpfad und Unterordnern aus. Halte die Zeit fest, die jeweils zum Parsen benötigt wird und lege eine Vergleichsübersicht in dem Ordner ergebnispfad an. Typechecking Im frühen Entwicklungsstadium dieses Projekts wurde die Implementierung eines Typecheckers in Erwägung gezogen. Im weiteren Verlauf stellte sich jedoch heraus, dass die drohende Zeitknappheit und die Komplexität des Typecheckings für eine Prozessalgebra dies verhindern würde. Dennoch konnte ein Typechecker auf Basis des automatisch generierten AST von SableCC erstellt werden, bevor der CST durch AST-Transformationen kompaktifiziert wurde. Im Rahmen der Entwicklung dieses Typecheckers wurde eine Methode zur Aufschlüsselung von Datentypen entworfen. Wird zum Beispiel ein Ereignis channel c:{1}.{true} definiert, dann ist c vom Typ Int=>Bool=>Event. Der implementierte Algorithmus reduce(String) kann überprüfen, ob eine Eingabe vom Typ Dotable ist oder die Zwangsbedingung Complete erfüllt. Beispielsweise ist die Eingabe c.1.true vom Typ Event. Man schreibt auch c.1.true::Event. Der Typ Event ist atomar und enthält keine Pfeile (=>) mehr. Das bedeutet automatisch, c.1.true erfüllt die Zwangsbedingung Complete. Die Eingabe c.1 hingegen erfüllt die Zwangsbedingung nicht, da sie sich noch über Anwendung des Dot-Operators zu einem Ereignis erweitern lässt, bzw. noch Pfeile im Typ auftauchen (c.1 :: Bool => Event). Aus der Eingabe c.1 (Typ: c::Int=>Bool=>Event und 1::Int) setzt der Algorithmus einen neuen Typ zusammen (Int=>Bool=>Event.Int), dreht die Typenkette vor dem Punkt um, löscht das Paar Int.Int und dreht die Typenkette wieder zurück. Zum Schluss bleibt der Typ von c.1 übrig. Möglich ist dies mit beliebig langen Ketten von Ausdrücken, die mit Dot zusammengesetzt wurden. Das obige Beispiel zeigt, dass der entworfene Algorithmus vor allem für die Überprüfung von Typen beim Prefixing elementar wichtig ist. Zur Erweiterung des Typecheckers ist ein Neuaufbau des AST notwendig, da die meisten Knoten nicht mehr der aktuellen Version des Parsers entsprechen. Nach der Überarbeitung können einfache Typenfehler erkannt werden (Ereignisse, Datentypen, Boolesche Ausdrücke, Zahlen, Mengen, Sequenzen). Eine Voraussetzung hierfür ist, dass keine Konstrukte der funktionalen Programmierung verwendet werden. 5. Der Parser 35 Performance Um einen Nachweis über die Praxistauglichkeit von cspmj zu liefern, eignet sich nicht nur der Vergleich des Prolog-Ausgabecodes beider Parser, sondern auch ein Vergleich der Laufzeit. Aus diesem Grund wurde eine Klasse PerformanceTest.java erstellt. Der Kommandozeilenbefehl java -cp build/classes/main PerformanceTest suchordner testergebnis durchsucht den Ordner suchordner nach CSP-Dateien und führt für jede gefundene Datei filei , i∈ [0;133[ die cspmf.exe aus und erstellt ein Objekt der Klasse CSPMparser.java. Dabei wird jeweils in Sekunden auf drei Nachkommastellen genau festgehalten, wie lange die Übersetzung dauert (timecspmf(i) und timecspmj(i)). Um Schwankungen hinsichtlich der verfügbaren CPU-Ressourcen zu verhindern, wird jeder Übersetzungsvorgang zehnmal wiederholt. Anschließend entsteht ein Eintrag in einer Textdatei mit dem Namen der übersetzten Datei, dem arithmetischen Mittel der beiden Übersetzungszeiten und dem Vergleichsquotienten timecspmf (i) timecspmj (i) Der letzte Eintrag TOTAL hat die Felder (1) (2) 132 132 timecspmf (i) timecspmj (i) i=0 i=0 und den Vergleichsquotienten (1) (2) . Ursprünglich wurde jeweils die Übersetzungszeit eines Aufrufs der cspmj.exe mit einem Aufruf der cspmj.jar über die Windows-Kommandozeile verglichen. Auffällig war, dass timecspmj im Schnitt um Faktor 10 größer war. Der Grund hierfür ist jedoch nicht, dass der Parser unabhängig von einer Laufzeitvorstellung entwickelt wurde. Untersuchungen mit dem -parseAll-Argument von cspmj haben ergeben, dass nicht der Übersetzungsvorgang viel Zeit kostet, sondern der Start der Java Virtual Machine. Konzeptionelle Unterschiede zwischen Java und Haskell dürfen nicht als Nachteil von cspmj ausgelegt werden. Um möglichst objektive Vergleichswerte zu erhalten, darf nur die Zeit für die Ausführung der Hauptklasse CSPMparser.class genommen werden und nicht noch zusätzlich für das Starten der Java Virtual Machine. Eine vollständige Übersicht eines Tests mit 133 CSP-Dateien befindet sich im Anhang. Um herauszufinden, wie sich der Parser in Extremfällen verhält, eignen sich Randfallüberlegungen und die Erstellung eines Stresstests. Hierfür wurden Beispiele verwendet, die nur sehr selten bis nie in der Praxis auftauchen, aber dennoch übersetzbar sein sollten. Ein Qualitätskriterium ist ein möglichst linearer Zusammenhang zwischen Extremfallzeiten und Normalfallzeiten. Die Eigenschaften der CSPM-Dateien, deren Laufzeitergebnis in Tabelle 4 aufgelistet ist, sind wie folgt: Datei mit extremer Größe (3,4MB), monotoner Inhalt Leere Datei (0B) Datei mit durchschnittlicher Größe (21KB) kleine Datei, viele Kommentare (865B) Große Datei (70KB) scheduler1_6_0.csp EmptyFile.csp bankv1.csp McCarthy.csp Tokeneer.csp 36 5. Der Parser Dateiname timecspmf (Sekunden) timecspmj (Sekunden) Faktor (Sekunden) scheduler1_6_0.csp 22.818 Java Heap Space Error! - Tokeneer.csp 0,319 0.274 1,164 bankv1.csp 0,033 0,009 3,785 McCarthy.csp 0,02 0,001 13,39 EmptyFile.csp 0,017 0,001 31,18 Alle Dateien 3,396 0,864 3,929 Tabelle 4: Randfallbetrachtungen In den meisten Fällen ist cspmj der schnellere Parser. Auffällig ist jedoch, dass mit zunehmender Dateigröße der Zeitvorsprung geringer wird. Ein Grund hierfür könnte sein, dass die Verwendung mancher Datenstrukturen nicht effizient genug ist. Dies kann als Motivation für künftige Optimierungen angesehen werden. Eine weitere Schlussfolgerung ist, dass SableCC offensichtlich nicht für das Parsen von großen Dateien geeignet ist. So bewirkt der Übersetzungsversuch der 3 Megabyte großen scheduler1_6_0.csp einen Java-Speicherfehler beim Anlegen eines neuen Knotens. Ursächlich für eine Geschwindigkeitsreduktion ist die höchst ineffiziente Art, Sequenzklammern zu identifizieren. So kann es im schlimmsten Fall dazu kommen, dass der dafür zuständige Algorithmus bei typeninkorrekten Kettenvergleichen wie 1<2<3 ab einer gewissen Anzahl von Klammern nicht mehr terminiert. An dieser Stelle gibt es dringenden Handlungsbedarf. 6. Fazit und Ausblick 37 6. Fazit und Ausblick Bewertung der aktuellen Funktionalität Vorteile Nahezu jede CSP-Testdatei wird schneller übersetzt als mit cspmf. In manchen Fällen ist der neue Parser bis zu 30-mal schneller. Gründe hierfür sind die optimierte lexikalische Analyse durch Verkleinerung der Tokenliste (Entfernung von Kommentaren, Blank-Tokens und Newline-Tokens) und das Renaming, das im Gegensatz zu cspmf mit nur einem AST-Durchlauf auskommt. Aufgrund der hohen Anzahl an Praxisbeispielen (inkl. Randfälle: 133 Dateien), die bereits erfolgreich geparst wurden, ist die Stabilität positiv zu bewerten. Die fortschreitende Entwicklung von FDR3.4 und die nahe Orientierung an dessen Dokumentation während der Entwicklung, machen cspmj hinsichtlich der Funktionalität dem alten Parser cspmf von 2011 überlegen. Darüber hinaus ist es nun möglich LTL- und CTL-Formeln während der Parsing-Phase syntaktisch zu überprüfen. Ein Nachweis über die Unterstützung der Plattformen Linux (32 und 64 Bit), OS X und Windows, konnte mittels jUnit erbracht werden. Die Ausführung des Parsers ist nun plattformunabhängig, d.h. es werden nicht mehr vier verschiedene Versionen benötigt. Da Java eine weit verbreitete Programmiersprache ist, können Änderungen von vielen Programmierern durchgeführt werden. Die Bemühung, einen verständlichen und gut dokumentierten Java-Code zu implementieren, begünstigt diesen Vorteil zusätzlich. Nachdem erste Vergleiche der Prolog-Ausgaben beider Parser gezeigt haben, dass diese sehr ähnlich sind und sich semantisch nicht unterscheiden, ist ein unkomplizierter Austausch von cspmf möglich. Die notwendigen Änderungen am ProB-Backend sollten daher recht gering ausfallen. Nachteile Das Parsen von CSP-Dateien mit komplexen Sequenz-Ausdrücken könnte bei Anwendung der Exhaustionsmethode die Performance beeinträchtigen. Eine vollständige Angleichung an cspmf war nicht möglich, da sich die CSPM-Syntax von FDR3 in den vergangenen Jahren leicht verändert hat. So können z.B. im Gegensatz zu cspmf alle Konstanten überschrieben werden und müssen deshalb anders übersetzt werden als bisher. Ein weiterer Nachteil ist die Einschränkung, eine möglichst kompakte CSPM-Grammatik in SableCC schreiben zu müssen. Diese ist im Vergleich zur Grammatik von libcspm wesentlich komplizierter. Außerdem verhindert die Java-Implementierung von SableCC, dass sehr große CSP-Dateien geparst werden können. 38 6. Fazit und Ausblick Zukünftige Entwicklung Folgende Punkte könnten in Zukunft zu einer Verbesserung der Leistung und Funktionalität von cspmj führen oder stehen noch aus, um die Integration in ProB zu gewährleisten: Implementierung aller von libcspm unterstützten Konstrukte und Syntax-Features, die in der FDR3.4-Dokumentation ausgeschrieben sind. Dazu zählen die transparent- und external-Funktionen (nur im Backend von ProB zu berücksichtigen), Type Annotations, Assertion-Options wie die Partial Order Reduction, Modules, Parameterized Modules und Timed Sections. Entwicklung eines laufzeitfreundlichen Algorithmus, AST-Visitors oder Customized-Parsers zur Substitution von Winkelklammern. Änderungen am Back-End von ProB vornehmen und Einbau der neuen Built-ins Optimierung der Java-Implementierung durch Ersetzen von laufzeitineffizienten Methoden wie contains(String) und regulären Java-Ausdrücken Verbesserung der Lesbarkeit der Grammatik: o Komprimieren des Concrete Syntax Tree durch Zusammenfassen von Regeln und häufigere Anwendung von Quantifizierern, z.B. A = B | B C durch A = B C? ersetzen o Verständlichere Namen für Tokens und Regeln Vervollständigung des Typecheckers Implementierung einer Prolog-Übersetzung für neue Features Literaturverzeichnis 39 Literaturverzeichnis [Ba95] Barrett, G.: Model checking in practice: The T9000 Virtual Kanal Processor. IEEE Transactions on Software Engineering, 1995. [BPS99] Buth, B.; Peleska, J.; Shi, H.: Combining methods for the livelock analysis of a faulttolerant system. Technology, Proceedings of the 7th International Conference on Algebraic Methodology and Software, 1999. [Bu97] Buth, B. et al.: Deadlock analysis for a fault-tolerant system. Technology, Proceedings of the 6th International Conference on Algebraic Methodology and Software, 1997. [Cl06] Clark, M.: JUnit FAQ. http://junit.sourceforge.net/doc/faq/faq.htm, 03.08.2016. [DM15] Dockter, H.; Murdoch, A.: Gradle User Guide. https://docs.gradle.org/current/userguide/userguide.pdf. [Fo11] Fontaine, M.: A Model Checker for CSPM. Dissertation, Düsseldorf, 2011. [Fr] Freiberg, B.: Einführung in Communicating Sequential Processes. Seminararbeit, Aachen. [Ga04] Gagnon, É. M.: Specifying Binary and Unary Operator Precedence. http://www.sable.mcgill.ca/listarchives/sablecc-list/msg01208.html, 21.07.2016. [Ga98] Gagnon, É.: SABLECC, AN OBJECT-ORIENTED COMPILER FRAMEWORK. Masterarbeit, Montreal, 1998. [Gi16a] Gibson-Robinson, T.: Built-In Definitionen - FDR 3.4.0 documentation. Compression Funktionen. https://www.cs.ox.ac.uk/projects/fdr/manual/cspm/prelude.html#compressions, 08.08.2016. [Gi16b] Gibson-Robinson, T.: CSPM - FDR 3.4.0 documentation. https://www.cs.ox.ac.uk/projects/fdr/manual/cspm.html, 12.08.2016. [Gi16c] Gibson-Robinson, T.: Type System - FDR 3.4.0 documentation. http://www.cs.ox.ac.uk/projects/fdr/manual/cspm/types.html, 03.08.2016. [Gi16d] Gibson-Robinson, T.: Definitionen - FDR 3.4.0 documentation. https://www.cs.ox.ac.uk/projects/fdr/manual/cspm, 20.07.2016. [Gi16e] Gibson-Robinson, T.: Funktional Syntax - FDR 3.4.0 documentation. Binding Strength. https://www.cs.ox.ac.uk/projects/fdr/manual/cspm/syntax.html#bindingstrength, 12.08.2016. [HC02] Hall, A.; Chapman, R.: Correctness by Construction: Developing a Commercial Secure System. http://www.anthonyhall.org/c_by_c_secure_system.pdf. [Ho78] Hoare, C.: Communicating Sequential Processes, Belfast, 1978. [LB03] Leuschel, M.; Butler, M.: ProB: A Model Checker for B, Highfield, Southampton, 2003. [LB05] Leuschel, M.; Butler, M.: Combining CSP and B for Specification and Property Verification, Highfield, Southampton, 2005. [LB07] Leuschel, M.; Butler, M.: ProB: An Automated Analysis Toolset for the B Method, Düsseldorf, 2007. [Le08] Leuschel, M. et al.: Static Slicing of CSP Specifications, Düsseldorf, Valencia, 2008. [Le16] Leuschel, M.: The ProB Animator and Model Checker - Institut für Software und Programmiersprachen. 40 Literaturverzeichnis https://www3.hhu.de/stups/prob/index.php/The_ProB_Animator_and_Model_Checker , 12.08.2016. [LF08] Leuschel, M.; Fontaine, M.: Probing the Depths of CSP-M: A new fdr-compliant Validation Tool, Düsseldorf, 2008. [Lo96] Lowe, G.: Breaking and fixing the Needham-Schroeder public-key protocol using FDR. Springer-Verlag, 1996. [Ro05] Roscoe, A. W.: The Theory and Practice of Concurrency. Pearson, 2005. [Sc98] Scattergood, B.: The Semantics and Implementation of Machine-Readable CSP. Dissertation, 1998. Abbildungsverzeichnis/Tabellenverzeichnis 41 Abbildungsverzeichnis Abbildung 1: Philosophen beim Essen............................................................................................. 10 Abbildung 2: Philosophenproblem in CSPM-Code .......................................................................... 11 Abbildung 3: Ein einfaches SableCC Beispiel ................................................................................. 14 Abbildung 4: Quellcode eines jUnit Tests ....................................................................................... 16 Abbildung 5: jUnit – Übersicht zum Verlauf der Tests ................................................................... 17 Abbildung 6: Ein Ausschnitt aus der CSPM-Grammatik von FDR .................................................. 18 Abbildung 7: Operatorenpräzedenztabelle ....................................................................................... 21 Abbildung 8: Architektur des Parsers .............................................................................................. 22 Abbildung 9: Erfolgreiche Ersetzung von Winkelklammern ........................................................... 25 Abbildung 10: Aufbau der Grammatik von cspmj ........................................................................... 26 Abbildung 11: Sechs Methoden aus dem Prolog-Codegenerator .................................................... 29 Tabellenverzeichnis Tabelle 1: CSPM Ausdrücke ............................................................................................................... 6 Tabelle 2: Neue Built-ins ................................................................................................................. 32 Tabelle 3: Neue CSPM-Konstrukte ................................................................................................... 33 Tabelle 4: Randfallbetrachtungen .................................................................................................... 36 42 Anhang Anhang Die Einheit aller Zeitangaben ist Sekunden (s). Datei timecspmf altbitprotocol.csp bankv1.csp bankv2.csp BigUnionInterChannelTest.csp BigUnionInterTests.csp BLinkTest.csp Buffer.csp Buffer_hide.csp buses.csp ClosureCompTests.csp comments.csp comment_eof.csp ComplicatedChannelGuards.csp ComplicatedLinkedParallel.csp ComplicatedLinkedParallel2.csp ComplicatedSync.csp dotpattern.csp dtype.csp EmptyFile.csp emptySet.csp EnumerationTests.csp example.csp exp.csp ExpressionsNewlinesBetween.csp Fibonacci.csp FM08review.csp frogs2.csp functional_override.csp GenericBuffer1.csp hanoi.fix.csp inctest.csp inctest2.csp independent.csp Lambda.csp LambdaComplex.csp LambdaSimple.csp LetFunctionPassedOut.csp LetMultipleEquations.csp LetMultipleFuns.csp LetTests.csp LetTestsChannel.csp letwithin.csp lokal_definitions.csp mbuff.csp McCarthy.csp microwave.csp nametype_test.csp nametype_test2.csp NameWithApostrophe.csp NastyNonDet.csp nestedOps.csp newdebug.csp newlinesBetween.csp newmbuff.fix.csp occursCheck.csp oopseq.csp oopsla.csp PairMedium.csp PairSimple.csp 0.041 0.033 0.032 0.022 0.021 0.02 0.022 0.022 0.021 0.024 0.02 0.018 0.02 0.024 0.021 0.026 0.019 0.018 0.017 0.017 0.025 0.201 0.019 0.018 0.02 0.019 0.029 0.018 0.026 0.024 0.017 0.017 0.02 0.018 0.024 0.019 0.02 0.021 0.027 0.026 0.022 0.018 0.018 0.027 0.02 0.02 0.02 0.02 0.018 0.022 0.017 0.018 0.017 0.028 0.017 0.033 0.032 0.021 0.02 timecspmj Faktor 0.033 0.009 0.008 0.002 0.002 0.002 0.002 0.002 0.002 0.004 0.001 0.001 0.001 0.003 0.002 0.005 0.001 0.001 0.001 0.001 0.004 0.11 0.001 0.001 0.001 0.001 0.006 0.001 0.004 0.003 0.002 0.001 0.001 0.001 0.003 0.001 0.001 0.002 0.005 0.004 0.002 0.001 0.001 0.005 0.001 0.001 0.002 0.001 0.001 0.002 0.001 0.002 0.001 0.006 0.003 0.007 0.007 0.002 0.001 1.245 3.785 3.917 10.929 13.172 11.151 9.146 10.362 10.905 6.872 17.597 25.281 15.245 8.47 11.076 5.563 14.517 25.771 31.18 23.908 6.323 1.835 16.977 24.396 14.653 15.453 4.78 17.627 6.601 7.357 10.024 15.311 20.538 24.161 7.558 18.396 14.018 9.825 5.48 6.503 9.33 22.973 24.663 5.239 13.39 16.185 13.256 15.743 23.796 10.326 27.334 11.755 28.525 4.629 6.127 4.93 4.756 10.399 15.892 Anhang 43 Datei timecspmf ParserIssues.csp PatMatchPair.csp PatMatchTuple.csp PatMatchTupleComplex.csp peterson.csp phils.fix.csp PrimedVar.csp prioProb.csp prize.csp prologTest.csp prologTest2.csp protocol.fix.csp put12.csp ReadMe.csp RecursiveDatatype.csp ReplicatedAlphParallel.csp ReplicatedInterleave.csp ReplicatedSequential.csp ReplicatedSharing.csp RepWithGuard.csp same_identifier_error.csp SeqCompTests.csp SeqRangeTests.csp SeqTests.csp SeqType.csp SequenceComprTests2.csp Sequences.csp Sequences2.csp SequentialRouter.csp SetCompAdvanced.csp SetCompComplicated.csp SetCompComplicated2.csp SetCompTests.csp SetCompWithLambda.csp SetTests.csp simple.csp SimpleAlphaPar.csp SimpleCHAOS.csp SimpleCompLinkedPar.csp SimpleCompRenaming.csp SimpleGenGen.csp SimpleGenGenForFDR.csp SimpleGetSet.csp SimpleIfThenElse.csp SimpleInterleaveSkipTest.csp SimpleInterleaveSkipTest2.csp SimpleInterruptTimeout.csp SimpleInterruptTimeout2.csp SimpleIntTim_statespace.csp SimpleLinkedParallel.csp SimpleLinkedParallel2.csp SimplePatMatch.csp SimpleRenaming.csp SimpleRenaming2.csp SimpleRepAlphParallel.csp SimpleReplicated.csp SimpleRepLinkedParallel.csp SimpleSeqComp.csp SimpleSubsets.csp SimpleTransparent.csp speareate.csp speareate_error.csp StatementPattern.csp StrangeAgents.csp student1.csp 0.021 0.019 0.019 0.026 0.029 0.022 0.018 0.018 0.021 0.018 0.017 0.054 0.026 0.018 0.019 0.025 0.019 0.02 0.019 0.018 0.017 0.024 0.021 0.025 0.021 0.021 0.018 0.018 0.034 0.025 0.023 0.021 0.032 0.02 0.037 0.021 0.024 0.023 0.022 0.019 0.022 0.023 0.019 0.021 0.02 0.02 0.022 0.02 0.017 0.022 0.027 0.028 0.024 0.022 0.023 0.02 0.021 0.02 0.02 0.019 0.019 0.019 0.017 0.028 0.02 timecspmj Faktor 0.002 0.001 0.001 0.004 0.005 0.002 0.001 0.001 0.002 0.001 0.001 0.02 0.004 0.001 0.001 0.003 0.001 0.002 0.001 0.001 0.001 0.004 0.002 0.003 0.002 0.002 0.001 0.001 0.008 0.004 0.003 0.002 0.006 0.001 0.008 0.002 0.003 0.003 0.002 0.001 0.002 0.002 0.001 0.002 0.002 0.001 0.002 0.001 0.139 0.002 0.004 0.005 0.003 0.002 0.003 0.002 0.002 0.001 0.002 0.001 0.001 0.001 0.002 0.005 0.001 12.321 16.555 17.968 6.274 5.671 10.3 29.672 27.238 10.274 22.316 27.662 2.699 7.262 26.923 19.672 7.286 23.021 12.17 18.593 24.971 19.786 5.338 10.225 7.558 13.535 11.578 22.059 22.953 4.293 6.681 7.301 10.871 5.679 14.301 4.504 11.384 8.919 7.771 11.517 17.853 9.946 10.199 20.884 12.046 12.987 15.362 9.444 13.703 0.126 9.079 5.967 5.763 8.792 10.515 8.457 12.999 10.742 15.213 12.81 17.987 21.596 26.545 10.5 5.395 14.528 44 Anhang Datei timecspmf subtype_ex.csp subtype_nametype_ex.csp TestDotPat.csp tickets.csp Tokeneer.csp unary_minus.csp verysimple.csp VerySimpleTimeout.csp vm2.csp TOTAL 0.024 0.023 0.02 0.019 0.319 0.018 0.018 0.019 0.019 3.396 timecspmj Faktor 0.002 0.002 0.002 0.001 0.274 0.001 0.001 0.001 0.001 0.864 9.737 9.521 12.958 18.91 1.164 26.665 26.379 21.835 20.016 3.929 Testumgebung Betriebssystem: Prozessor: Arbeitsspeicher: Speicher: Windows 10 Pro 64-bit (10.0, Build 10586) Intel(R) Core(TM) i5-4570 CPU @ 3.20GHz (4 CPUs), ~3.2GHz 8192MB RAM ADATA SSD S511 120GB