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.
Einige wichtige
Begriffe werden in diesem Kapitel erläutert.
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.
Der Epilog
einer attributierten Grammatik beginnt mit dem Schlüsselwort "END",
inklusive, und erstreckt sich bis zum Ende des Dokuments.
Es gibt vier verschiedene Klassen von
Zeilenumbrüchen, die sich in Bedeutung und Priorität unterscheiden.
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.
Weiche Zeilenumbrüche entstehen durch Kalkulationen, die der Benutzer mit Parametern beeinflussen kann. In folgenden Fällen wird ein Soft Break erzeugt:
Mit einem Source
Break ist ein Zeilenumbruch gemeint, der vom Quelldokument übernommen wird.
Solche Zeilenumbrüche können mit –noBreaks deaktiviert werden.
Dies sind Zeilenwechsel, die durch semantische
Aktionen bedingt sind. Diese können selbstredend nicht deaktiviert werden, es
sei denn, semantische Aktionen werden ausgeblendet.
Beim Einzug werden zwei Arten unterschieden.
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.
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.
Wenn im Dokument die
Rede von öffnenden respektive schließenden Klammern ist, sind immer
alle drei Typen (Gruppierung, Option, Iteration) repräsentativ gemeint.
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.
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.
Ü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).
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.
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.
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".
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".
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".
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".
Syntax: -name production
Syntax: -n production
Um nur eine einzelne Produktion auszugeben,
kann diese so spezifiziert werden.
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".
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.
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 ] ).
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 }.
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?".
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(); .)
.
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
)
.
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(); .)
.
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!".
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".
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*) .
Syntax: -altParOpen text
Syntax: -apo text
Hiermit kann der Text für öffnende redundante
Klammern von Alternativenlisten eingestellt werden.
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
.>.
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".
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".
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".
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"
.
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 )
.
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.
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 )
].
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.
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.
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.
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.
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.
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.
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.
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; }
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
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.
Dieses Kapitel enthält eine kurz gehaltene Dokumentation der Implementierung des Pritty Printers.
Im Folgenden seien die Klassen des Printy-Projekts kurz erläutert.
Einige der verwendeten Algorithmen sollen im Folgenden diskutiert werden.
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.
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.
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.
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.
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.
Zusammenfassend kann
bemerkt werden, dass die Arbeit am Printy für ein zehnstündiges
Projektpraktikum aufwandsangemessen war.
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.
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.