4. Beispiel: Attributierte Grammatik

Angabe

In HTML (Hypertext Markup Language) gibt es Konstrukte, um verschachtelte Listen zu definieren. Das Kommando <OL> (ordered list) leitet eine numerierte Liste ein, <UL> (unordered list) eine Liste mit *. Ein Listeneintrag beginnt mit <LI> (list item) und eine ganze Liste wird mit </OL> bzw. </UL> beendet. Auf dem Bildschirm wird der Text entsprechend seiner logischen Struktur eingerückt dargestellt.

Die Eingabe von

Das sind Listen:
<OL><LI> Text eins
<LI> Text zwei <UL>
<LI> erste Zeile
<LI> zweite Zeile
</UL>
<LI> Text drei
</OL>
Hier geht's weiter.

bewirkt die Bildschirmdarstellung


Das sind Listen:

  1. Text eins
  2. Text zwei
  3. Text drei

Hier geht's weiter.


Anmerkung: Diese Darstellung hängt natürlich von ihrem Browser ab. Bei der Lösung wird jedoch bei jedem Listenelement das Zeichen * verwendet. Jede Liste ist um tabs(1) eingerückt.

Erweitern Sie die nachfolgend gezeigte Grammatik um Attribute für die Darstellung von Listen in einem HTML-Text. Unterscheiden Sie numerierte von nicht numerierten Items (Is). Der Text soll im synthetisierten Attribut S.x geliefert werden. Die Funktion tabs(n) liefert n Tabulatoren. Ein newline wird durch "\n" dargestellt. Das Attribut chars.x enthält die Textstücke zwischen den Kommandos. Verwenden Sie den Operator ||, um Zeichenketten zusammenzuhängen.

S     -> Html 
Html  -> Text Html | Text
Text  -> chars | List
List  -> <UL> Is </UL> | <OL> Is </OL>
Is    -> Item Is | Item
Item  -> <LI> Html

Lösung

S    -> Html           S.x := Html.x
                       Html.i := 0            /* noch keine Einrückung */ 


Html -> Text Html1     Html.x := Text.x || "\n" || Html1.x
                       Text.i := Html.i
                       Html1.i := Html.i  


Html -> Text           Html.x := Text.x
                       Text.i := Html.i  


Text -> chars          Text.x := chars.x 


Text -> List           Text.x := List.x
                       List.i := Text.i + 1   /* einrücken */  


List -> <UL> Is </UL>  Is.i := List.i; List.x := Is.x 
                       Is.n := 1
                       Is.o := false          /* keine Numerierung */  


List -> <OL> Is </OL>  Is.i := List.i; List.x := Is.x 
                       Is.n := 1
                       Is.o := true           /* mit Numerierung */  


Is   -> Item Is1       Item.i := Is.i
                       Is1.i := Is.i 
                       Is1.n := Is.n + 1      /* nächste Nummer */ 
                       Item.n := Is.n
                       Item.o := Is.o
                       Is1.o := Is.o 
                       Is.x := Item.x || "\n" || Is1.x  


Is   -> Item           Item.i := Is.i
                       Item.n := Is.n 
                       Item.o := Is.o
                       Is.x := Item.x  


Item -> <LI> Html      Html.i := Item.i 
                       IF Item.o = true       /* d.h., numerierte Liste */
                       THEN Item.x := tabs(Item.i) || Item.n || ". " || Html.x 
                       ELSE Item.x := tabs(Item.i) || "* " || Html.x  

Erklärung

Erklärungen von attributierten Grammatiken sind immer schwierig, weil es beliebig viele Möglichkeiten gibt, eine Grammatik zu attributieren. In diesem Zusammenhang verweise ich zuallererst auf den Appendix des letzten Skriptums.

Fragestellung

Was ist eigentlich zu tun? Wo liegt das (eigentliche) Problem?

Es macht einen großen Unterschied aus, ob man eine bestehende Grammatik attributieren oder eine ganz neue Grammatik erstellen soll. Im ersten Fall muß man unbedingt das Gesamtkonzept erfassen und die Intention der Grammatik begreifen.

Folgende Anforderungen stellen sich:

  1. Eine Liste muß eingerückt werden. Alle Listeneinträge beginnen an der selben Tabulatorposition.
  2. Wo wirkt sich die Unterscheidung der numerierten von den nicht numerierten Listen aus?
  3. Ein Zähler wird benötigt, um die Listenelemente zu zählen.
  4. Welche Elemente soll ich durch "\n" trennen?
  5. Der ganze erzeugte Text muß im Attribut x synthetisiert werden.

Analyse der Grammatik

Sehen wir uns die Grammatik einmal ganz genau an. Die ersten beiden Produktionen

S    -> Html 
Html -> Text Html | Text 

sind noch sehr einfach zu verstehen. Das Startsymbol S ist ein HTML-Text und ein HTML-Text ist eine Liste von Textelementen.

Anmerkung: Die Produktion

List -> Item List | Item

ist sehr häufig bei Listen zu sehen. Äquivalent wäre die Produktion

List -> List Item | Item

die ist aber linksrekursiv. Bitte beachten Sie dazu die Ableitungsbäume der Abbildung 4.3 in Kapitel 4.1 des Skriptums.

Aus der nächsten Produktion sehen wir, daß ein Textelement aus Textstücken (chars) oder aus Listen (List) bestehen kann.

Text -> chars | List 

Irgendwo hier muß also die Einrückung passieren weil ja die ganze Liste eingerückt dargestellt werden soll.

Eine Liste wiederum besteht aus Listeneinträgen (Is für Items) und kann entweder numeriert sein oder auch nicht.

List -> <UL> Is </UL> | <OL> Is </OL> 

Hier ist eine gute Stelle, um die beiden Listentypen voneinander zu unterscheiden. Wo sich diese Unterscheidung später vielleicht auswirken wird ist hier egal.

Die Listeneinträge sind einfach aneinandergereiht und bestehen selbst wieder aus HTML-Text.

Is   -> Item Is | Item 
Item -> <LI> Html 

Das heißt aber auch, daß alle bis dahin angesammelten Einrückungen an den HTML-Text weitergegeben werden müssen. An dieser Stelle wird sich auch die Unterscheidung der Listentypen auswirken, ob ein * oder eine Nummer erzeugt wird.

Attributierung

Die Synthetisierung des Ergebnistextes im Attribut x hat in unserer Liste zwar die niedrigste Priorität, sie zieht sich jedoch naturgemäß durch alle Attributierungsregeln hindurch. Wir nehmen also an, daß jedes Nichtterminal ein synthetisiertes Attribut x hat in das wir (hemmungslos) Zeichen hineinschreiben bzw. den schon erzeugten Text daraus lesen.

Anmerkung: Im weiteren werden alle Attributierungsregeln die nur "Durchreichefunktion" haben weggelassen.

Gehen wir systematisch an Hand unserer Anforderungsliste vor.

  1. Listeneinrückung
  2. Unterscheidung in numerierte und nicht numerierte Listen
  3. Zähler
  4. Trennung durch "\n"

Listeneinrückung

Wir brauchen offensichtlich ein Attribut, daß die derzeitige Einrückung anzeigt. Der Name des Attributs ist i (der Buchstabe i steht für indent). Bei jeder neuen Liste wird sein Wert um eins erhöht. Die Stelle, wo das passiert haben wir schon bei der Analyse der Grammatik gesehen.

Die zweite Regel von

Text -> chars | List 

erhält die Attributierungsregel

List.i := Text.i + 1

d.h., eine Liste wird um eine Tabulatorposition mehr eingerückt als der restliche Text.

An dieser Stelle wollen wir kurz innehalten, um uns das Wesen der Attributierung vor Augen zu halten. Jede einzelne Grammatikproduktion wird unabhängig von den anderen attributiert. Uns interessiert also nicht der globale Zusammenhang - den sollten wir zumindest im Hinterkopf behalten - sondern nur die lokale Auswirkung. Wir erzeugen nur Information, die an anderer Stelle gebraucht wird. Die Daten, die wir dafür brauchen, müssen wir also woanders erzeugen.

In unserem Fall ist die neue Tabulatorposition die erzeugte Information. Dabei müssen wir auf die alte Tabulatorposition zurückgreifen. Alleine durch diese Definition sehen wir, daß das Attribut i vererbt wird.

Irgendwo müssen wir das Attribut i auch initialisieren. Das geschieht zweckmäßigerweise gleich am Beginn, nämlich in der Grammatikregel

S -> Html

durch die Attributierungsregel Html.i := 0, d.h., die Einrückung jedes Html-Textes wird auf die nullte Tabulatorposition gesetzt.

Anmerkung: Es ist nicht notwendig, den Text wieder "auszurücken", d.h., die Einrückung rückgängig zu machen. Die Attribute sind lokale Größen die durch die Grammatik automatisch die richtigen Werte zugewiesen bekommen.

Listentypen

Die Unterscheidung von numerierten und nicht numerierte Listen ist sehr einfach. Schon bei der Analyse der Grammatik haben wir die entsprechenden Grammatikregeln lokalisiert.

List -> <UL> Is </UL>
List -> <OL> Is </OL> 

Wir müssen den Items der Listen irgendwie anzeigen, daß sie zu einer numerierten bzw. nicht numerierten Liste gehören. Diese Unterscheidung wird später gebraucht, weil ja entweder eine Zahl oder ein * ausgegeben werden soll. Wir verwenden dazu ein Flag, das entweder auf true oder auf false gesetzt wird.

List -> <UL> Is </UL>    Is.o := false
List -> <OL> Is </OL>    Is.o := true

Das Flag wirkt sich dann aus wenn wir zu einem Listeneintrag kommen, also erst in der Grammatikregel

Item -> <LI> Html

An dieser Stelle müssen wir, abhängig vom Wert des Flags, entweder eine Nummer oder einen * ausgeben. Damit auch alles am richtigen Platz steht erzeugen wir noch die richtige Einrückung und geben außerdem den entsprechenden Text aus.

IF Item.o = true
THEN Item.x := tabs(Item.i) || >Nummer< || ". " || Html.x 
ELSE Item.x := tabs(Item.i) || "* " || Html.x  

Das Attribut i muß demnach mindestens bis zu dieser Stelle in der Grammatik vererbt werden. (Auf das Attribut x wollen wir nicht näher eingehen. Es enthält den auszugebenden Text und wird auch dementsprechend verwendet.)

Für Nummer fehlen uns jedoch noch die entsprechenden Regeln.

Zähler

Bei der Analyse der Grammatik haben wir schon gesehen, wie eine Liste aufgebaut ist. Die Listeneinträge sind einfach aneinander gereiht.

Is -> Item Is
Is -> Item 

Eine Liste von Items besteht also aus einem Item gefolgt von weiteren Items oder aus einem einzigen Item. Wir verwenden das Attribut n, um die Position in der Liste anzuzeigen. Dieser Zähler muß also bei jedem weiteren Element um eins erhöht werden. Die Attributierungsregeln sind daher

Is -> Item Is1      Item.n := Is.n
                    Is1.n := Is.n + 1

Is -> Item          Item.n := Is.n

Auch in diesem Fall greifen wir auf vererbte Information zurück. Irgendwo "vorher" (weiter oben im Ableitungsbaum) müssen wir das Attribut Is.n initialisieren. Das geschieht, wenn wir zum ersten Mal auf eine Liste stoßen, in

List -> <UL> Is </UL> | <OL> Is </OL> 

Die Attributierungsregel lautet ganz einfach Is.n := 1.

Trenner

Zuerst müssen wir uns überlegen, welche Elemente eigentlich von welchen Elementen getrennt werden. Nocheinmal zur Erinnerung: Ein HTML-Text besteht aus einer Liste von Textelementen. Ein Textelement ist entweder ein Textstück oder eine Liste.

Html -> Text Html | Text
Text -> chars | List

Das ergibt folgende Kombinationsmöglichkeiten:

Sowohl ein Text als auch eine Liste fängt immer in einer neuen Zeile an, d.h. hier müssen wir das Trennzeichen einfügen. Die Variante ein Text gefolgt von einem Text gibt es nicht, weil chars den gesamten Text zwischen zwei Listen beinhaltet.

Die obige Überlegung führt sofort zu

Html -> Text Html1      Html.x := Text.x || "\n" || Html1.x

Jetzt fehlt nur noch die Trennung der Listeneinträge. Jeder Listeneintrag fängt in einer neuen Zeile an. Aufgrund unserer früheren Attributierungsregel wissen wir, daß die Tabulatorposition stimmt, nur das Trennzeichen fehlt noch. Wir wenden das gleiche Schema an und erhalten

Is -> Item Is1          Is.x := Item.x || "\n" || Is1.x

Nach dem Zusammensetzen der einzelnen Bausteine und dem Hinzufügen der Durchreicheregeln erhalten wir die endgültige Lösung.


Alex's University Page / Alex's Home Page / alex@complang.tuwien.ac.at
TEL +43 1 58801-4463, FAX +43 1 5057838