Kurzbeschreibung der Aufgabenmodellierungssprache Tombola Holger Uhr 26. September 2003 Inhaltsverzeichnis 1 Einleitung 1 2 Tokens 1 3 Produktionsregeln 3.1 Die Startdeklaration . 3.2 Klassendeklarationen . 3.3 Aufgabendeklarationen 3.4 Unteraufgaben . . . . 3.5 Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Die Bedienung des Simulators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 2 3 5 6 8 1 Einleitung Die Sprache Tombola dient der Spezifikation von Aufgabenmodellen, die in einem Simulator ausgeführt werden können. Die Aufgabenmodelle werden durch eine Vielzahl temporaler Relationen hierarchisch verfeinert. Tombola besitzt weiterhin ein reichhaltiges Objektmodell, mit dessen Hilfe die Ausführung von Aufgaben an Bedingungen über Objekte geknüpft werden kann sowie durch Nachwirkungen von Aufgaben Objekte verändert werden können. Der Aufbau von Tombola wird im folgenden durch eine nähere Beschreibung der Produktionsregeln ihrer Grammatik erläutert. (Wenn diese Regeln manchmal unnötig komplex erscheinen, liegt das daran, dass sie direkt aus der Eingabe für den ParserGenerator von Tombola übernommen und nicht vereinfacht wurden.) Zum Schluss wird die Bedienung des Simulator-Prototyps kurz erläutert. 1 2 Tokens Die Produktionsregeln basisieren auf einer Reihe von Tokens, die entweder Terminalsymbole sind und in der Produktionsregel wörtlich angegeben werden, oder als komplexe Tokens durch die folgenden regulären Ausdrücke spezifiziert werden. Die in den Produktionsregeln auftauchenden komplexen Tokens haben dabei folgende Bedeutung: hINTEGER_LITERALi, hBOOLEAN_LITERALi und hSTRING_LITERALi stehen jeweils für Literale der angegebenen Datentypen und sind aufgebaut wie ihre Java-Äquivalente: hINTEGER_LITERALi ::= 0 | [1-9] ([0-9])* hBOOLEAN_LITERALi ::= false | true hSTRING_LITERALi ::= " ( ( [",\,\n,\r] ) | ( \ ( [n,t,b,r,f,\,’,"] | [0-7] ( [0-7] )? | [0-3] [0-7] [0-7] ) ) )* " Das Token hIDi steht für einen Bezeichner in Tombola und hat folgenden Aufbau: hIDi ::= hLETTERi (h#LETTERi|h#DIGIT i)* h#LETTERi ::= [ a-z, ä, ö, ü, ß, A-Z, Ä, Ö, Ü, _ ] h#DIGIT i ::= [ 0-9 ] Tombola erlaubt zwischen allen Tokens beliebig viele »Whitespaces« (Leerzeichen, Tabulatoren und Zeilenumbrüche) sowie beide aus Java bekannten Arten von Kommentaren. 3 Produktionsregeln Eine Tombola-Eingabedatei besteht aus einer Startdeklaration und einer Folge von Klassendeklarationen und Aufgabendeklarationen. hInputi ::= hStartDeclarationi ; (hClassDeclarationi | hTaskDeclarationi)* 3.1 Die Startdeklaration Die Startdeklaration legt die oberste Aufgabe der auszuführenden Hierarchie fest. Dazu wird der Name der Hauptaufgabenklasse angegeben und eine (optional leere) Liste von Ausdrücken als Parameter, die der Hauptaufgabe beim Start übergeben werden. Als Parameter bei der Startdeklaration sind nur Ausdrücke, in denen keine Variable und keine new-Operatoren auftauchen, zulässig. Zu den zulässigen Ausdrücken mehr in Abschnitt 3.5 2 hStartDeclarationi ::= start hIDi hExternalTaskInvocationi hExternalTaskInvocationi ::= ( hActualParameterListi ) hActualParameterListi ::= [ hExpressioni ( , hExpressioni )* ] 3.2 Klassendeklarationen Neben den »primitiven« Datentypen int, boolean und String kann man in Tombola eigene Klassen definieren, zu denen Objekte als Instanzen gebildet werden können. Eine Klassendefinition besteht aus dem Klassennamen (der eindeutig sein muss), einer eventuellen Oberklasse, die wie in Java mit dem Schlüselwort extends angegeben wird, und einer Liste von Instanzvariablen. Eine Instanzvariable wird durch ihren Typ, ihren Namen und einen optionalen Defaultwert beschrieben. Als Typen sind wiederum die primitiven Datentypen und selbst definierte Klassen sowie Arrays über Klassentypen (durch [] gekennzeichnet) zulässig. Wird kein Defaultwert angegeben, sind die Felder bei neu erzeugten Objekten jeweils mit dem typabhängigen Defaultwert initialisiert (0 bei int, false bei boolean sowie null bei String und den Klassentypen). Tombola hat, wie Java, nur einfache Vererbung. Mit dem instanceof-Operator (siehe Abschnitt 3.5) kann man testen, ob ein Objekt einer bestimmten Klasse angehört. hClassDeclarationi ::= class hIDi [ extends hIDi ] { ObjVariableDeclList } hObjVariableDeclListi ::= ( hTypei hIDi [ = hExpressioni ] ; )* hTypei ::= hPrimitiveTypei | hIDi [ [ ] ] hPrimitiveTypei ::= int | boolean | String 3.3 Aufgabendeklarationen Eine Aufgabendeklaration besteht aus dem Namen der Aufgabenklasse, gefolgt von der (optional leeren) Liste der formalen Parameter (die wiederum aus dem Parametertyp und dem Namen bestehen) und der Aufgabenspezifizierung. hTaskDeclarationi ::= hIDi ( [ hFormalParameterListi ] ) hTaskSpecificationi hFormalParameterListi ::= hFormalParameter i ( , hFormalParameter i )* hFormalParameter i ::= hTypei hIDi 3 hTaskSpecificationi ::=- ( - - => { autostart hOrExpressioni SEQ SER ALT OPT PAR SIM => { prompt : ( hOrExpressioni ) - } autostop hTaskInitializerListi ) ATOM ( hSubtaskListi ) foreach hIDi hIDi ( hIDi ) ( hSubtaskInvoci LOOP ( hOrExpressioni ) ( hSubtaskInvoci ) hEffectListi } - ) - Abbildung 1: Eisenbahndiagramm der Produktionsregel für Aufgabenspezifierung Die komplexeste aller Produktionsregeln beschreibt die Aufgabenspezifizierung. Um den Überblick zu erleichtern, wird diese Regel in Abbildung 1 als Eisenbahndiagramm angegeben. Am Anfang einer Aufgabenspezifizierung kann, mit dem Schlüsselwort prompt markiert, optional ein Prompt-Ausdruck angegeben werden, dessen ausgewertete Form (die vom Typ String sein muss) dann bei den Dialogen im Simulator an Stelle des Aufgabennamens verwendet wird. Danach folgt eine optionale Vorbedingung für die Aufgabenausführung. Nur wenn diese erfüllt ist, kann die Aufgabe begonnen werden. In der dann folgenden optionalen Aufgabeninitialisierungsliste können aufgabeninterne Variablen definiert und mit Defaultwerten versehen werden. Der Gültigkeitsbereich von aufgabeninternen Variablen reicht bis zum Ende der Effektliste der Aufgabe. Falls eine Aufgabe so bald wie möglich selber starten soll, kann dies mit dem Schlüsselwort autostart angegeben werden. (Falls gleichzeitig mehrere Aufgaben automatisch gestartet werden können, ist nicht definiert, welche als erste startet.) Umgekehrt spezifiziert das Schlüsselwort autostop, dass eine Aufgabe sich so bald wie möglich selbst beendet. Dann hat man die Wahl, entweder eine der Standard-Temporalrelationen zu wählen oder eine freie Schleifenaufgabe (mit LOOP gekennzeichnet). Die Standard-Temporalrelationen haben folgende Bedeutung: ATOM Aufgaben dieses Typs haben keine Unteraufgaben. SEQ Die Unteraufgaben werden in der angegebenen Reihenfolge nacheinander ausgeführt. SER Die Unteraufgaben werden in einer beliebigen Reihenfolge nacheinander ausgeführt. ALT Genau eine der Unteraufgaben wird ausgeführt. 4 OPT Eine oder keine der Unteraufgaben wird ausgeführt. PAR Alle Unteraufgaben werden, beliebig miteinander verschränkt, ausgeführt. SIM Alle Unteraufgaben werden simultan ausgeführt, d. h., zuerst werden alle Aufgaben in beliebiger Reihenfolge gestartet und dann wiederum in beliebiger Reihenfolge beendet. Nach Angabe einer nicht-atomaren Temporalrelation hat man zwei Möglichkeiten, die Unteraufgaben festzulegen: Entweder gibt man eine Liste von Unteraufgaben an, oder man spezifiziert mit Hilfe des Schlüsselwortes foreach eine Schleife über alle Elemente eines Arrays. Dazu müssen der Elementtyp des Arrays, ein Bezeichner für die Schleifenvariable und das Array, über das iteriert wird, angegeben werden, gefolgt von einem Unteraufgabenaufruf. Dieser Aufruf wird dann für alle Arrayelemente durchgeführt. Die Schleifenvariable ist bei jeder Iteration eine Referenz auf das entsprechende Element des Arrays, d. h., man kann sie sowohl zum Lesen des Elementwertes benutzen, als auch zum Setzen des Wertes. Dies ist insbesondere unverzichtbar, wenn man ein neu erzeugtes Array in einer Schleife mit Objektreferenzen füllen will. Ob und in welcher Reihenfolge die einzelnen Unteraufgabenaufrufe geschehen, richtet sich natürlich nach der gewählten temporalen Relation. Bei der freien Schleife wird eine Bedingung angegeben, die vor jedem Schleifendurchlauf ausgewertet wird. Trifft die Bedingung zu, wird die angegebene Unteraufgabe aufgerufen. Am Ende der Aufgabenspezifikation kann noch eine Effektliste stehen. Die Anweisungen in dieser Liste werden direkt nach der Beendigung der Aufgabe ausgeführt. hTaskSpecificationi ::= : [ hSTRING_LITERALi ] [ ( hOrExpressioni ) => ] [ { hTaskInitializerListi } ] [ autostart ] [ autostop ] ( ATOM | ((SEQ | SER | ALT | OPT | PAR | SIM ) ( hSubtaskListi ) | foreach hIDi hIDi ( hIDi ) ( hSubtaskInvoci ) | LOOP ( hOrExpressioni ) ( hSubtaskInvoci ) )) [ => { hEffectListi } ] hTaskInitializerListi ::= ( Type hIDi [ = Expression ] ; )+ hSubtaskListi ::= hSubtaskInvoci ( , hSubtaskInvoci )* hEffectListi ::= ( hAssignmenti ; )+ 3.4 Unteraufgaben Es gibt zwei Möglichkeiten, eine Unteraufgabe anzugeben: Externe Unteraufgaben (bereits im Abschnitt 3.1 erwähnt) werden getrennt von der Oberaufgabe definiert. Der beim Aufruf angegebene Bezeichner bestimmt die aufgerufene Aufgabenklasse. Man kann einer externen Unteraufgabe über eine Parameterliste Werte übergeben. (Eine Rückgabe von Werten an die Oberaufgabe gibt es 5 in dem Sinne nicht; jedoch kann die externe Unteraufgabe in ihrer Effektliste Felder von übergebenen Objekten verändern.) Eine externe Unteraufgabe hat einen eigenen Gültigkeitsbereich für Variablen (Variablen aus der Oberaufgabe sind also hier nicht zugänglich) und kann auch von verschiedenen Oberaufgaben aus aufgerufen werden. Interne Unteraufgaben werden im Körper der Oberaufgabendefinition spezifiziert. Ihr Gültigkeitsbereich ist eine Erweiterung des Oberaufgabengültigkeitsbereichs, so dass alle Variablen aus der Oberaufgabe weiterhin zugänglich sind. Sie eignen sich für Unteraufgaben, die ihrer Natur nach nur als Teile einer ganz bestimmten Oberaufgabe Sinn machen. Der beim Aufruf angegebene Bezeichner hat hier nur kommentierenden Character. Der vollständige Name einer internen Unteraufgabenklasse ergibt sich, wenn man dem Namen der internen Unteraufgabenklasse den vollständigen Namen der Oberaufgabe, gefolgt von einem Punkt, voranstellt. Unteraufgabenaufrufe können beliebig tief geschachtelt werden. hSubtaskInvoci ::= hIDi [ hExternalTaskInvocationi | hTaskSpecificationi ] 3.5 Ausdrücke Die in Tombola möglichen algebraischen und logischen Ausdrücke und die Zuweisung orientieren sich wiederum an ihren Java-Äquivalenten. Die Addition ist ebenfalls überladen, so dass Strings konkateniert werden können. Ist nur einer der beiden Operanden ein String, wird er mit einer String-Repräsentation des anderen Operanden verknüpft. Die logischen Operatoren || und && ermöglichen die aus Java bekannte verkürzte Auswertung. hExpressioni ::= hAssignmenti | hOrExpressioni hAssignmenti ::= hNamei = hExpressioni hOrExpressioni ::= hAndExpressioni ( || hAndExpressioni )* hAndExpressioni ::= hExclusiveOrExpressioni ( && hExclusiveOrExpressioni )* hExclusiveOrExpressioni ::= hEqualityExpressioni ( ^ hEqualityExpressioni )* hEqualityExpressioni ::= hInstanceOfExpressioni ( == hInstanceOfExpressioni> | != hInstanceOfExpressioni» )* Mit Hilfe des instanceof-Operators kann, wie in Java, festgestellt werden, ob das Objekt, auf das die Referenz links vom Operator verweist, zu der rechts stehenden Klasse oder einer ihrer Oberklassen gehört. 6 hInstanceOfExpressioni ::= RelationalExpression [ instanceof hIDi ] hRelationalExpressioni ::= hAdditiveExpressioni ( < hAdditiveExpressioni | > hAdditiveExpressioni | <= hAdditiveExpressioni | >= hAdditiveExpressioni )* hAdditiveExpressioni ::= hMultiplicativeExpressioni ( + hMultiplicativeExpressioni | hMultiplicativeExpressioni )* hMultiplicativeExpressioni ::= hUnaryExpressioni ( * hUnaryExpressioni | / hUnaryExpressioni | % hUnaryExpressioni )* hUnaryExpressioni ::= [ + ] hPrimaryExpressioni | - hPrimaryExpressioni | ! hPrimaryExpressioni hPrimaryExpressioni ::= hLiteral i | hNamei | hCreationExpressioni | hInteractiveInputExpressioni | ( hExpressioni ) hLiteral i ::= hIntegerLiteral i | hBooleanLiteral i | hStringLiteral i | hObjectLiteral i hIntegerLiteral i ::= hINTEGER_LITERALi hBooleanLiteral i ::= hBOOLEAN_LITERALi hStringLiteral i ::= hSTRING_LITERALi hObjectLiteral i ::= null Ein Feld eines Objektes, das in einer Variablen abgelegt ist, wird wie in Java angesprochen, indem der Feldname, durch einen . getrennt, hinter den Variablennamen gehängt wird. hNamei ::= hIDi ( . hIDi )* Neue Objekte einer Klasse werden wie in Java durch den new-Operator, gefolgt vom Klassennamen erzeugt. Bei der Erzeugung von Arrays folgt noch ein Ausdruck, der die Größe des Arrays angibt. Arrays können (zumindest momentan) nur über Klassentypen erzeugt werden. Die Elemente von neu erzeugten Arrays sind mit dem Wert null vorbelegt. 7 hCreationExpressioni ::= new hIDi [ [ hAdditiveExpressioni ] ] Um interaktive Eingaben in das Aufgabenmodell einfließen zu lassen, kann man Eingabefenster erzeugen, mit denen eine ganze Zahl, ein Wahrheitswert oder ein String eingelesen werden kann. Als Parameter kann dabei ein Ausdruck übergeben werden, der dem Benutzer als Prompt bei der Eingabe angezeigt wird. hInteractiveInputExpressioni ::= hInteractiveIntegerInputExpressioni | hInteractiveBooleanInputExpressioni | hInteractiveStringInputExpressioni hInteractiveIntegerInputExpressioni ::= INT_INPUT ( [ hExpressioni ] ) hInteractiveBooleanInputExpressioni ::= BOOLEAN_INPUT ( [ hExpressioni ] ) hInteractiveStringInputExpressioni ::= STRING_INPUT ( [ hExpressioni ] ) 4 Die Bedienung des Simulators Der Simulator ist für Release-Zwecke in einer JAR-Datei abgelegt. Man startet ihn mit dem Aufruf java -jar Tombola.jar hTombola-Dateii. Voraussetzung dafür ist ein Java-Interpreter der Version 1.3 (oder höher). Der Simulator liest dann die angegebene Datei ein und beginnt, falls er keine Fehler feststellt, mit der Simulation. Die Simulation besteht aus einer Abfolge einzelner Schritte. Jeder Schritt besteht entweder aus der Auswahl einer Aufgabe, die fortschreiten (also beginnen oder enden) kann, oder dem Ausfüllen eines Eingabeelements. Alle Aufgaben, die momentan fortschreiten können, werden in einem Auswahlfeld angezeigt, zusammen mit der Art des möglichen Fortschreitens. Der Benutzer kann dann die gewünschte Aufgabe auswählen, so dass diese im nächsten Schritt fortschreitet. Steht nur eine Aufgabe zum Fortschreiten zur Verfügung, wird lediglich ein Dialogfenster angezeigt, in dem das Fortschreiten bestätigt werden kann. Kann keine Aufgabe fortschreiten (weil noch eine oder mehrere Aufgaben ihre Effektliste abarbeiten), so wird gar kein Aufgabenfenster angezeigt. Werden mehrere Fenster angezeigt (Eingabefenster für die Effektlisten sich beendender Aufgaben und evtl. das Aufgabenauswahlfenster), so werden diese anfangs alle in der Bildschirmmitte positioniert, so dass sie sich gegenseitig verdecken. Es kann sein, dass das Aufgabenauswahlfenster manchmal kurz aufzuflackern scheint. Dies geschieht immer dann, wenn eine Aufgabe, deren Effektliste abgearbeitet wurde, beendet wird. Da sich die Liste der Aufgaben, die fortschreiten können, dadurch verändern kann, wird das Aufgabenfenster zu diesem Zeitpunkt immer vom Bildschirm entfernt und neu erzeugt. 8