Ein Parser für CSPM in Java

Werbung
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
Herunterladen