Printy

Pritty Printer für attributierte Grammatiken

Philipp Grasböck

 

Einführung. 1

Begriffsbestimmungen. 1

Prolog. 1

Epilog. 1

Umbruch. 1

Einzug. 2

Öffnende/schließende Klammer 2

Öffnendes/schließendes Symbol 3

Überlanges Symbol 3

Verschachtelungstiefe. 3

Konsolenschnittstelle. 3

Kommandozeilenoptionen. 3

Konfigurationssprache. 9

Bibliothek. 13

Dokumentation. 13

Klassen. 14

Algorithmen. 14

Frame Files. 15

Resumee. 15

Plattform.. 15

Erweiterbarkeit 15

 

Einführung

Der Printy akzeptiert als Eingabe eine ATG-Datei und erzeugt (im Standardmodus) eine ATG-Datei, die nach gewissen Heuristiken und umfangreichen Parametern formatiert wurde. Die im Standardmodus erzeugte ATG ist zur Quelle sprachäquivalent. Semantische Aktionen werden immer rechts ausgegeben. Falls sie im Quelldokument links stehen, werden sie bis zur Semantikspalte verschoben. Der Text vor der ersten und nach der letzten Produktion wird 1:1 in das Zieldokument kopiert. Zeilenumbrüche werden aus dem Quelldokument übernommen. Darüber hinaus ist das Verhalten konfigurierbar.

Begriffsbestimmungen

Einige wichtige Begriffe werden in diesem Kapitel erläutert.

Prolog

Der Prolog einer attributierten Grammatik umfasst den Text vom Anfang des Dokuments bis zum Beginn der ersten Produktion, inklusive allfälliger Kommentare vor der ersten Produktion. Der Prolog wird 1:1 im Zieldokument ausgegeben, mit Ausnahme von Leerzeichen am Zeilenende, die, wie bei allen mehrzeiligen Texten, abgeschnitten werden.

Epilog

Der Epilog einer attributierten Grammatik beginnt mit dem Schlüsselwort "END", inklusive, und erstreckt sich bis zum Ende des Dokuments.

Umbruch

Es gibt vier verschiedene Klassen von Zeilenumbrüchen, die sich in Bedeutung und Priorität unterscheiden.

Hard Break

Harte Zeilenumbrüche sind jene, die vom Benutzer explizit gefordert werden durch Aufrufe von Config.PutBreak() oder des break-Befehls. Sie können per Übergang von Symbol auf Symbol definiert werden und haben Vorrang gegenüber Soft Breaks.

Soft Break

Weiche Zeilenumbrüche entstehen durch Kalkulationen, die der Benutzer mit Parametern beeinflussen kann. In folgenden Fällen wird ein Soft Break erzeugt:

Source Break

Mit einem Source Break ist ein Zeilenumbruch gemeint, der vom Quelldokument übernommen wird. Solche Zeilenumbrüche können mit noBreaks deaktiviert werden.

Semantic Break

Dies sind Zeilenwechsel, die durch semantische Aktionen bedingt sind. Diese können selbstredend nicht deaktiviert werden, es sei denn, semantische Aktionen werden ausgeblendet.

Einzug

Beim Einzug werden zwei Arten unterschieden.

Absoluter Einzug

Absolute Einzüge werden von bestimmten Symbolen geöffnet und geschlossen, zum Beispiel öffnende Klammern respektive schließende Klammern.

Alle Symbole, die unter einem solchen öffnenden Symbol stehen, werden danach links ausgerichtet.

Relativer Einzug

Der relative Einzug wird zum absoluten Einzug hinzugerechnet. Er entspricht dem Abstand eines Symbols vom direkt davor liegenden öffnenden Symbol. Ein Alternativentrennzeichen '|' hat normalerweise einen relativen Einzug von 0 Zeichen, weil es direkt unter '(', '[' oder '{' stehen soll. Ein Literal hat per default einen relativen Einzug von 2 Zeichen, was der konventionellen Einrückung entspricht.

Der relative Einzug wird eingestellt, indem man Config.INDENT als Symbol verwendet, also zum Beispiel "space INDENT Production 2;" setzt den relativen Einzug für eine Produktion auf 2 Zeichen.

Öffnende/schließende Klammer

Wenn im Dokument die Rede von öffnenden respektive schließenden Klammern ist, sind immer alle drei Typen (Gruppierung, Option, Iteration) repräsentativ gemeint.

Öffnendes/schließendes Symbol

Ein öffnendes Symbol (normalerweise eine öffnende Klammer oder '=') öffnet einen absoluten Einzug. Alle Symbole, die in den Textzeilen darunter stehen, werden danach links ausgerichtet, bis ein schließendes Symbol (schließende Klammer oder '.' per default) kommt.

Überlanges Symbol

Ein überlanges Symbol ist ein Symbol, das zu lang ist, um in einer Zeile die Spaltenbreite einzuhalten. Solche Symbole führen zwingend zu einer Überschreitung der Spaltenbreite.

Verschachtelungstiefe

Überall dort, wo als Argument eine Verschachtelungstiefe akzeptiert wird, kann diesem auch ein negativer Wert übergeben werden. In diesem Fall gilt der Schwellenwert nicht als maximaler, sondern dessen Betrag als minimaler Wert. (Beispiel: 2 bedeutet bis inklusive der 2. Ebene; -2 bedeutet ab exklusive der 2. Ebene).

Konsolenschnittstelle

Das Printy-Projekt besteht aus einer Bibliothek (namespace Printy.Library) sowie einem Tool für die Kommandozeile (namespace Printy.Tool). Letzteres wird im Folgenden genauer erläutert.

Kommandozeilenoptionen

Synopsis: Printy infile outfile {option}

Printy kann mit Hilfe von Befehlszeilenoptionen weitgehend konfiguriert werden; falls eine noch exaktere Kontrolle über Abstands-, Umbruch- und Einzugssteuerung gewünscht ist, kann dies mit Hilfe der Konfigurationssprache bewerkstelligt werden. Der Parser für diese Sprache wurde ebenfalls mit Coco erzeugt.

Eine Option kann mit "­–" oder "/" eingeleitet werden; im gesamten Dokument wird die erste Möglichkeit repräsentativ für beide verwendet.

config

Syntax: -config [filename]

Syntax: -c [filename]

Mit diesem Parameter kann auf eine Konfigurationsdatei verwiesen werden. Die Konfigurationssprache wird in einem eigenen Kapitel behandelt.

Standardwert ist "config.txt".

index

Syntax: -index [filename]

Syntax: -i [filename]

Dies gibt den Dateinamen eines zu erzeugenden Index-Dokuments an. Dieses enthält ein Frameset, das links eine sortierte Liste von Produktionen und rechts die Grammatik enthält.

Standardwert ist "index.html".

overview

Syntax: -overview [filename]

Syntax: -o [filename]

Dies gibt den Dateinamen eines zu erzeugendes Übersicht-Dokuments an. Dieses enthält die sortierte Liste von Produktionen, die Links in das rechte Fenster darstellen.

Standardwert ist "overview.html".

style

Syntax: -style [filename]

Syntax: -s [filename]

Hiermit kann auf eine CSS-Datei verwiesen werden. Es wird ein entsprechender link-tag generiert.

Standardwert ist "style.css".

name

Syntax: -name production

Syntax: -n production

Um nur eine einzelne Produktion auszugeben, kann diese so spezifiziert werden.

ebnf

Syntax: -ebnf [true | false]

Syntax: -e [+ | -]

Diese Option entfernt Attribute, semantische Aktionen, LL(1) Konfliktauflöser sowie Prolog und Epilog, um als Ergebnis die EBNF-Grammatik auszugeben.

Falls die angegebene Ausgabedatei die Endung ".ebnf" (case insensitive) hat, ist diese Option automatisch aktiv.

 

P<out int x> (. x = 500; .) = IF (condition) "P" "Q" | "P".

Printy in.atg out.atg -ebnf

P = "P" "Q" | "P".

html

Syntax: -html [true | false]

Syntax: -h [+ | -]

Diese Option legt fest, ob ein XHTML-Dokument erzeugt werden soll.

Falls die angegebene Ausgabedatei die Endung ".html" (case insensitive) hat, ist diese Option automatisch aktiv.

altPar

Syntax: -altPar [level]

Syntax: -ap [level]

Mit diesem Parameter kann die redundante Klammerung von Alternativenlisten gesteuert werden. Wenn das Argument weggelassen wird, werden alle Vorkommen von Alternativenlisten in der Grammatik geklammert. Andernfalls wird nur bis zur angegebenen Verschachtelungstiefe geklammert. Der Wert 2 zum Beispiel bedeutet, dass nur dort Klammern eingefügt werden, wo noch keine sind, da die Ebene 2 jener der Produktionen ist.

 

P = a | b | [ x | y | z ].

Printy in.atg out.atg -altPar

P = ( a | b | [ ( x | y | z ) ] ).

Printy in.atg out.atg -altPar 2

P = ( a | b | [ x | y | z ] ).

indentWidth

Syntax: -indentWidth value

Syntax: -iw value

Dieser Wert gibt jene Breite an, innerhalb welcher öffnende Symbole erlaubt sind. Jenseits dieser Breite werden sie in die nächste Zeile umgebrochen. Mit diesem Parameter kann vermieden werden, dass allzu große Einzüge entstehen, die die Spaltenbreite überschreiten.

 

12345678901234567890

P = "Hello World" | { a b c }.

Printy in.atg out.atg -indentWidth 20

P = "Hello World" | { a b c }.

Printy in.atg out.atg -indentWidth 19

P = "Hello World" |

    { a b c }.

colWidth

Syntax: -colWidth value

Syntax: -cw value

Legt die Breite (in Zeichen) der linken Spalte fest. Diese wird im Normalfall nicht überschritten, es sei denn, es kommen einzelne überlange Symbole vor, die dem Programm keine andere Wahl lassen, oder der Benutzer hat dies durch Hard Breaks ausdrücklich so festgelegt.

 

12345678901234567890123456789012345678901234567

P = "Hello" "World" "how" "are" "you" "today?".

Printy in.atg out.atg -colWidth 47

P = "Hello" "World" "how" "are" "you" "today?".

Printy in.atg out.atg -colWidth 46

P = "Hello" "World" "how" "are" "you" "today?"

  .

Printy in.atg out.atg -colWidth 45

P = "Hello" "World" "how" "are" "you"

    "today?".

colSpace

Syntax: -colSpace value

Syntax: -cs value

Abstand (in Zeichen) der Grammatikspalte von der Semantikspalte. Diese Variable darf alle möglichen Werte annehmen; bei Werten >= 0 ist garantiert, dass die Semantikspalte links ausgerichtet ist. Bei einem negativen Spaltenabstand werden dort, wo Platz ist, die semantischen Aktionen weiter links ausgegeben. Dies ist jedoch nicht der empfohlene Weg, um Spaltenüberschneidungen zu realisieren; siehe colOverlap.

 

12345678901234567890

P = a (. CheckA(); .) | b  (. CheckB(); .) .

Printy in.atg out.atg -cw 5 –colSpace 0

P = a(. CheckA(); .)

  | b(. CheckB(); .)

  .

Printy in.atg out.atg -cw 5 -colSpace 3

P = a   (. CheckA(); .)

  | b   (. CheckB(); .)

  .

 

autoBlock

Syntax: -autoBlock [true | false]

Syntax: -ab [+ | -]

Die Wirkung dieser Option ist, dass schließende Symbole immer unter öffnenden Symbolen ausgegeben werden. Mit diesem Parameter wird eine intelligente Umbruchheuristik aktiviert, die die Festlegung von Hard Breaks mit PutBreak() ergänzen  kann.

Die autoBlock Option empfiehlt sich in Kombination mit altPar und brSeparator.

 

P = a | b | c.

Printy in.atg out.atg -ap -bs

P = ( a

    | b

    | c ).

Printy in.atg out.atg -ap -bs -autoBlock

P = ( a

    | b

    | c

    )

  .

autoFit

Syntax: -autoFit [true | false]

Syntax: -af [+ | -]

Dieser Parameter legt fest, ob die Breite der linken Spalte automatisch bestimmt wird. Falls true, gilt colWidth als maximale Breite der linken Spalte, falls false, gilt colWidth als fixe Breite der linken Spalte; es sei denn, dass ein überlanges Symbol oder ein Hard Break vorkommt.

 

123456789012345678901234

P = a (. CheckA(); .) | b (. CheckB(); .) | (. CheckOther(); .).

Printy in.atg out.atg -cw 20 -cs 4

P = a                   (. CheckA(); .)

  | b                   (. CheckB(); .)

  |                     (. CheckOther(); .)

  .

Printy in.atg out.atg -cw 20 -cs 4 -autoFit

P = a    (. CheckA(); .)

  | b    (. CheckB(); .)

  |      (. CheckOther(); .)

  .

noBreaks

Syntax: -noBreaks [true | false]

Syntax: -nb [+ | -]

Diese Option deaktiviert, falls true, so genannte Source Breaks. Auf diese Weise kann die Gestaltung von Zeilenumbrüchen vom Quelldokument unabhängig gemacht werden.

 

P = "Hello"

"World!"

.

Printy in.atg out.atg

P = "Hello"

    "World!"

  .

Printy in.atg out.atg -noBreaks

P = "Hello" "World!".

spaceChar

Syntax: -spaceChar char

Syntax: -sc char

Dies legt das Abstandszeichen fest. So ist es möglich, die Spaltenbreiten leichter zu überprüfen.

 

P<out int x> (. x = 5; .) = "P".

Printy in.atg out.atg -cw 20

P<out int x>            (. x = 5; .)

= "P".

Printy in.atg out.atg -cw 20 -spaceChar "-"

P<out int x>------------(. x = 5; .)

=-"P".

lineNum

Syntax: -lineNum text

Syntax: -ln text

Dies ist der Formatstring für Zeilennummern. Standardmäßig werden keine Zeilennummern ausgegeben. Das Ergebnis entspricht dem Aufruf von n.ToString(lineNum), wobei n die entsprechende Zeilennummer darstellt. Zeilennummern werden immer linksbündig ausgegeben.

 

P = "Hello" | "World" | "Line" | "Number" | "Demo".

Printy in.atg out.atg -bs -ab -lineNum "(*00*) "

(*01*) P = "Hello"

(*02*)   | "World"

(*03*)   | "Line"

(*04*)   | "Number"

(*05*)   | "Demo"

(*06*)   .

altParOpen

Syntax: -altParOpen text

Syntax: -apo text

Hiermit kann der Text für öffnende redundante Klammern von Alternativenlisten eingestellt werden.

altParClose

Syntax: -altParClose text

Syntax: -apc text

Hiermit kann der Text für schließende redundante Klammern von Alternativenlisten eingestellt werden.

 

P = a | b | c.

Printy in.atg out.atg -ap

P = ( a | b | c ).

Printy in.atg out.atg -ap -altParOpen "<." -altParClose ".>"

P = <. a | b | c .>.

colOverlap

Syntax: -colOverlap [true | false]

Syntax: -co [+ | -]

Falls dieser Parameter auf true gesetzt wird, ist es möglich, dass sich die Spalten überlappen. Dies sieht so aus, dass längere Zeilen, die keine Semantik enthalten, nicht in die Berechnung der Spaltenbreite miteinbezogen werden. Somit kann einer allzu weit rechts beginnenden Semantikspalte bei umfangreichen Grammatiken vorgebeugt werden.

 

P = "Lange Zeichenkette Nr. 1" | "Kurz" (. Aktion(); .) | "Lange Zeichenkette Nr. 2".

Printy in.atg out.atg -cw 40 -bs -af -cs 2

P = "Lange Zeichenkette Nr. 1"

  | "Kurz"                       (. Aktion(); .)

  | "Lange Zeichenkette Nr. 2".

Printy in.atg out.atg -cw 40 -bs -af -cs 2 -colOverlap

P = "Lange Zeichenkette Nr. 1"

  | "Kurz"  (. Aktion(); .)

  | "Lange Zeichenkette Nr. 2".

brProduction

Syntax: -brProduction [true | false]

Syntax: -bp [+ | -]

Legt fest, dass vor einer Produktion, falls notwendig, ein Zeilenwechsel erfolgt; mit anderen Worten, dass eine Produktion immer Beginn einer Zeile ist.

 

/* Kommentar */ P = "Produktion".

Printy in.atg out.atg –brProduction

/* Kommentar */

P = "Produktion".

brAssign

Syntax: -brAssign [true | false]

Syntax: -ba [+ | -]

Legt fest, dass vor einem Zuweisungszeichen '=' ein Zeilenwechsel erfolgt.

 

P = "Produktion".

Printy in.atg out.atg –brAssign

P

= "Produktion".

brDelimiter

Syntax: -brDelimiter [true | false]

Syntax: -bd [+ | -]

Legt fest, dass vor einem Delimiterzeichen '.' ein Zeilenwechsel erfolgt.

 

P = "Produktion".

Printy in.atg out.atg –brDelimiter

P = "Produktion"

  .

brSeparator

Syntax: -brSeparator [level]

Syntax: -bs [level]

Legt fest, dass vor einem Alternativentrennzeichen '|' ein Zeilenwechsel erfolgt. Das optionale Argument gibt die Verschachtelungstiefe an, bis zu jener ein Umbruch erfolgen soll.

 

P = a | b | ( x | y | z ).

Printy in.atg out.atg -ab -brSeparator

P = a

  | b

  | ( x

    | y

    | z

    )

  .

Printy in.atg out.atg -ab -brSeparator 2

P = a

  | b

  | ( x | y | z )

  .

brOpen

Syntax: -brOpen [level]

Syntax: -bo [level]

Legt fest, dass vor einer öffnenden Klammer ein Zeilenwechsel erfolgt. Das optionale Argument gibt die Verschachtelungstiefe an, bis zu jener ein Umbruch erfolgen soll.

brClose

Syntax: -brClose [level]

Syntax: -bc [level]

Legt fest, dass vor einer schließenden Klammer ein Zeilenwechsel erfolgt. Das optionale Argument gibt die Verschachtelungstiefe an, bis zu jener ein Umbruch erfolgen soll.

Der empfohlene Weg, um einen Blockstil zu erreichen, ist autoBlock in Kombination mit
brSeparator.

 

P = [ a | b | ( x | y | z ) ].

Printy in.atg out.atg -breakOpen -breakClose

P =

    [ a | b |

      ( x | y | z

      )

    ].

Printy in.atg out.atg -breakOpen 2 -breakClose 2

P =

    [ a | b | ( x | y | z )

    ].

Konfigurationssprache

Einige Einstellungen an der Konfiguration sind zu komplex für Kommandozeilenoptionen, deshalb wurde für die umfangreicheren Parameter eine eigene Sprache entwickelt, die mit einem Coco generierten Parser gelesen wird. Sie ist eine einfache Liste von Formatierungsregeln, die wie Anweisungen durch ein ';' getrennt sind. Diese sind in einer Textdatei abgelegt, die mit der config Option interpretiert wird.

Im Folgenden seien die einzelnen Anweisungen an Hand von Beispielen erläutert.

space

Syntax: "space" SymbolAllIndent SymbolAll value

Entsprechung: Config.PutSpace()

Diese Anweisung setzt den Abstand zwischen zwei Symbolen.

 

space Symbol Attribute 1;

Dieser Aufruf hat den Effekt, dass zwischen einem Symbol und dessen Attributsliste ein Abstand von einem Zeichen steht.

 

space Open ALL 2;

Dieses Beispiel bewirkt, dass einer öffnenden Klammer immer ein Abstand von zwei Zeichen folgt.

break

Syntax: break SymbolAll SymbolAll (value | "always" | "never" | "default")

Entsprechung: Config.PutBreak()

Mit dieser Anweisung kann das Zeilenumbruchverhalten exakt gesteuert werden. Die Umbrüche zwischen Symbolen werden per Übergang von Symbol zu Symbol definiert.

 

break Symbol Attribute never;

Dies bewirkt, dass die Attributsliste eines Symbols niemals in die nächste Zeile umgebrochen wird.

 

break ALL Delimiter always;

Diese Anwendung demonstriert, wie es möglich ist, zu erzwingen, dass ein '.' immer in die nächste Zeile umgebrochen wird. Dies entspricht der Option brDelimiter.

 

break ALL Separator always;

Diese Anweisung erzeugt einen Blockstil, in dem Alternativen untereinander stehen. Dies entspricht der Option brSeparator ohne Argument.

 

break ALL Separator 2;

Diese Anweisung erzeugt auch einen Blockstil, jedoch nur für Alternativen, die auf Produktionsebene (Ebene 2) vorkommen, also nicht für geklammerte Alternativen. Dies entspricht der Option brSeparator mit Argumentwert 2.

open

Syntax: "open" SymbolAll;

Syntax: "open" "[" {Symbol} "]";

Entsprechung: Config.openSet

Die Menge der öffnenden Symbole wird mit diesem Befehl bestimmt. Die erste Syntax fügt der Menge ein Element hinzu, die zweite definiert die Menge an Hand einer Auflistung von Elementen.

Die vordefinierte Einstellung versteht öffnende Klammern (Open) und das Zuweisungssymbol (Assign) als öffnende Symbole.

close

Syntax: "close" SymbolAll;

Syntax: "close" "[" {Symbol} "]";

Entsprechung: Config.closeSet

Analog zu open spezifiziert man hiermit die schließenden Symbole. Voreinstellung ist [schließende Klammer (Close), '.' (Delimiter)].

 

open [Open];

close [Close];

Diese Befehle haben den Effekt, dass '=' und '.' nicht mehr als öffnende/schließende Symbole gelten. Der sichtbare Effekt ist jener, dass Text nicht mehr nach '=' links ausgerichtet wird, und dass das '.' nicht mehr unter dem '.' ausgegeben wird.

hide

Syntax: "hide" SymbolAll;

Syntax: "hide" "[" {Symbol} "]";

Entsprechung: Config.hideSet

Falls es gewünscht ist, dass bestimmte Symbole, wie zum Beispiel Kommentare, ausgeblendet werden, kann dies hiermit umgesetzt werden. Die Option ebnf verwendet diesen Befehl und versteckt Attribute, Aktionen, Konfliktauflöser sowie Prolog und Epilog.

 

hide OuterComment;

Dieses Beispiel bewirkt, dass Kommentare zwischen Produktionen ausgeblendet werden. Kommentare innerhalb von Produktionen bleiben erhalten.

 

hide [Prolog, Epilog, Action, Resolver, Attribute];

Dies bewirkt, dass alle ATG-Erweiterungen der EBNF ausgeblendet werden, sodass nur die EBNF übrig bleibt. Die Option ebnf funktioniert auf diese Weise.

overlap

Syntax: "overlap" SymbolAll;

Syntax: "overlap" "[" {Symbol} "]";

Entsprechung: Config.overlapSet

Diese Menge enthält jene Symbole, die mit der rechten Spalte überlappen dürfen. Falls die rechte Spalte mit keinen Symbolen überlappen soll, ist diese Menge als Leermenge ("[]") festzulegen.

 

overlap [Prolog, Epilog, OuterComment];

Auf diese Weise teilt man dem Programm mit, dass die angegebenen Symbole sich mit der Semantikspalte überschneiden dürfen. Bei diesen macht es Sinn, da im Prolog und Epilog keine (formatierbaren) semantischen Aktionen vorkommen und es bei Kommentaren zwischen Produktionen nicht auffällt, wenn sie länger sind. Wäre OuterComment nicht Element der Menge, dann würde sich die Semantikspalte weiter rechts befinden, falls längere Kommentare im Text vorkommen, was zu einer unerwünschten Optik führen kann.

 

overlap ALL;

Nun ist es allen Symbolen erlaubt, sich mit der Semantikspalte zu überschneiden. Dies hat den Vorteil, dass sich die Semantikspalte so weit links wie möglich befindet (falls autoFit ebenfalls aktiviert ist), kann jedoch etwas irreführend aussehen. Davon abgesehen ist gewährleistet, dass sich innerhalb einer Zeile keine Symbole überschneiden. Dies entspricht der Option colOverlap.

style

Syntax: "style" SymbolAll;

Syntax: "style" "[" {Symbol} "]";

Entsprechung: HyperConfig.styleSet

Diese Menge enthält jene Symbole, für welche CSS-Stilklassen erzeugt werden. Die entsprechende Klasse hat denselben Namen wie das Symbol, also zum Beispiel "Production".

 

style Production;

style Symbol;

style Literal;

Diese Zeilen legen fest, dass für die angegebenen Symbole CSS-Stilklassen erzeugt werden.

Mit folgenden CSS-Statements können die Symbole im Text formatiert werden:

.Production { font-weight: bold; }

.Symbol { font-style: italic; }

.Literal { background-color: Lime; }

 

Falls es eine Produktion namens "MyProduction" gibt, kann diese über die ID-Formatierung angesprochen werden:

#MyProduction { background-color: Green; }

 

Links (also Nonterminalsymbole) auf die Produktion können ebenfalls über eine Klasse formatiert werden. Dies gilt sowohl für Links innerhalb der XHTML-ATG als auch in der Overview-Frame, falls eine generiert wurde.

.ref_MyProduction { font-weight: bold; }

class

Syntax: "class" <className> [SymbolAll] <text> {<text>}

Entsprechung: HyperConfig.PutStyleClass()

Mit diesem Befehl kann ein CSS-Klassenname für bestimmte Zeichenketten definiert werden. Falls zum Beispiel erwünscht ist, das spezielle Fangsymbol syntaktisch hervorzuheben, ist dies hiermit möglich.

 

class <SyncClass> Literal <SYNC>;

Dieser Befehl erzeugt die CSS-Klasse "SyncClass" und formatiert damit alle Literale mit dem Text "SYNC".

 

 

SymbolAll

Symbol

 

=

=

 

  ( "ALL"

  ( "Production"

Produktion

  | Symbol

  | "Symbol"

Terminal- oder Nonterminalsymbol

  )

  | "Literal"

Literal (string | char | "WEAK" | "SYNC" | "ANY")

.

  | "Open"

öffnende Klammer

 

  | "Close"

schließende Klammer

SymbolAllIndent

  | "Assign"

Zuweisungszeichen '='

=

  | "Separator"

Alternativentrennzeichen '|'

  ( "INDENT"

  | "Delimiter"

Begrenzungszeichen '.'

  | SymbolAll

  | "InnerComment"

Kommentar innerhalb einer Produktion

  )

  | "OuterComment"

Kommentar zwischen Produktionen

.

  | "Attribute"

Attributsliste <…>

 

  | "Resolver"

Konfliktauflöser IF (…)

 

  | "Prolog"

Prolog

 

  | "Epilog"

Epilog

 

  | "Action"

semantische Aktion (. … .)

 

  | "LineNumer"

Zeilennummer

 

  )

 

 

.

 

Symbol-Klassen

Bibliothek

Die einfachste Möglichkeit, das Programm zu konfigurieren, sind die Command Line Switches. Für komplexere Einstellungen steht die Konfigurationssprache als Ergänzung zur Verfügung. Für den Fall, dass eine programmatische Kontrolle erwünscht ist, findet sich im Folgenden eine kurze Anleitung für die Verwendung der Bibliothek.

Das folgende selbsterklärende Code-Stück demonstriert die Verwendung der Printy-Klassen. Es erzeugt ein Programm, das als Argumente das Quelldokument und das Zieldokument, in dieser Reihenfolge, erwartet.

 

using Printy.Library;

using System.IO;

 

namespace MyNamespace

{

  class MyClass

  {

    static void Main(string[] argv)

    {

      Parser parser = new Parser(new Scanner(argv[0]));

      parser.Parse();

      Printer printer = new Printer();

      Config config = new Config();

      /* Konfigurationseinstellungen hier */

      TextBuilder builder = new TextBuilder(parser.grammar,config);

      TextWriter writer = new StreamWriter(argv[1]);

      printer.Print(builder);

      builder.WriteConentTo(writer);

      writer.Close();

    }

  }

}

 

Dort, wo sich der Kommentar befindet, können Änderungen an der Konfiguration vorgenommen werden:

 

/* Konfigurationseinstellungen hier */

config.PutBreak(Config.Symbol,Config.Attribute,Config.BREAK_NEVER);

config.PutSpace(Config.Open,Config.Close,0);

config.hideSet += Config.OuterComment;

config.lineNum = "/*000*/ ";

config.altPar = 2;

config.autoBlock = true;

 

Um statt einer ATG ein XHTML-Dokument zu erzeugen, muss lediglich der Konstruktoraufruf geändert werden:

 

TextBuilder builder = new HyperTextBuilder(parser.grammar,config);

 

Weiterführende Informationen zu den Klassen können den Dokumentationskommentaren der Quelltexte entnommen werden.

Dokumentation

Dieses Kapitel enthält eine kurz gehaltene Dokumentation der Implementierung des Pritty Printers.

Klassen

Im Folgenden seien die Klassen des Printy-Projekts kurz erläutert.

Bibliothek

Konsolenschnittstelle

Algorithmen

Einige der verwendeten Algorithmen sollen im Folgenden diskutiert werden.

Einzugssteuerung

Es gibt absolute und relative Einzüge.

Absolute Einzüge werden mit einem Stack verwaltet und von öffnenden Symbol geöffnet.

Relative Einzüge werden in der Abstands-Matrix (s.u.) gespeichert und können mit Config.PutSpace() manipuliert werden.

Abstandssteuerung

Die Abstände zwischen Symbolen werden per Übergang von Symbol auf Symbol definiert. Gespeichert werden die Werte in einer Matrix (Config.spaceMatrix).

Mit der Methode Config.PutSpace() kann sowohl ein Abstandswert als auch der relative Einzug für ein Symbol (Config.INDENT) festgelegt werden.

Umbruchsteuerung

Die Umbrüche werden ebenfalls per Übergang definiert (Config.breakMatrix). Die Matrix enthält die Verschachtelungstiefe, bis zu jener Umbrüche für die entsprechende Kombination eingefügt respektive verhindert (Config.BREAK_NEVER) werden.

Mit obiger Konstante ist es möglich, einen unerwünschten Umbruch zwischen zwei Symbolen, beispielsweise einem Nonterminalsymbol und dessen Attributsliste, zu vermeiden. In diesem Fall wird einfach der Zeilenumbruch nach der Attributsliste erfolgen.

Ablaufsteuerung

Die Ablaufsteuerung übernimmt die Printer Klasse. Sie durchwandert den Syntaxgraphen, dessen Wurzel ein Grammar Objekt ist. Die verschiedenen Knoten haben unterschiedliche Klassen, die durch ein type Feld bestimmt werden; die Felder des Node Klasse werden nach dem Schema einer flachen Objekthierarchie mit verschiedenen Bedeutungen verwendet.

Frame Files

Die Frame Files Parser.frame und Scanner.frame wurden von den Coco-Vorlagen übernommen und mit einigen Ergänzungen versehen. Zunächst hat die Klasse Token zwei neue Felder, comment und lines. comment ist ein Zeiger auf ein Kommentar-Token; dieser wird in der semantischen Aktion der Kommentar-Pragmas gesetzt. lines speichert die Anzahl der Zeilen für mehrzeilige Kommentare. Dies ist dort von Vorteil, wo man den Text in Zeilen zerlegen muss. Um die Anzahl der Zeilen zu ermitteln, muss man auf die aktuelle Zeile des Scanners zugreifen können. Zu diesem Zweck wurde das Property Line im Scanner eingeführt. In der Parser.frame gibt es zwei neue Methoden, RegisterLines() und RegisterComment(). RegisterLines() ist dafür zuständig, die höchste Zeilenanzahl im Grammar-Objekt zu speichern; RegisterComment() nimmt die lineare Verkettung von Kommentar-Tokens über den comment-Zeiger vor.

Resumee

Zusammenfassend kann bemerkt werden, dass die Arbeit am Printy für ein zehnstündiges Projektpraktikum aufwandsangemessen war.

Plattform

Die Programmiersprache C#, die ich während der Entwicklung des Programms genauer kennen lernen durfte, scheint eine sehr elegante Sprache mit vielen guten Ansätzen (und ein paar Schönheitsfehlern) zu sein. Um zwei der Schönheitsfehler zu nennen, wäre da die Undurchsichtigkeit des uniformen Typsystems (es gibt keine Klassen für geboxte Strukturen) und das Fehlen von anonymen Klassen. Der Umfang der .NET-Klassenbibliothek ist vorbildlich, obwohl ich eine Entsprechung der Java-Properties vermisse. An der Laufzeitumgebung fiel mir auf, dass es nicht möglich ist, eine DLL in einer EXE zu referenzieren, wenn sie den gleichen Dateinamen hat; der Versuch führt zu einer TypeLoadException.

Erweiterbarkeit

Bei der Gestaltung der Bibliotheksklassen wurde auf Erweiterbarkeit, Lesbarkeit und gute Dokumentation großen Wert gelegt. So sollte es kein Problem sein, beispielsweise eine Ableitung von TextBuilder zu schreiben, die bestimmte weiterführende Aktionen mit dem Präprozessor erledigt. Für den normalen Funktionsumfang dürfte dies jedoch nicht notwendig sein, da das Command Line Tool alle Möglichkeiten der Konfiguration einräumt.