StartseiteSitemapDownloadsHilfeImpressumPOV-Ray-Zauberwürfel Chat


Startseite

Syntaktische Analyse

Zielvorstellung

Der Parser erzeugt zu einem lexikalisch und auch syntaktisch korrekten Tinyray−Quellprogramm einen entsprechenden Syntaxbaum. Sobald der vollständige Syntaxbaum erstellt ist, wird der Scanner als auch der Parser nicht mehr benötigt. Alles Weitere geschieht dann mit dem Syntaxbaum als Grundlage.


Der Syntaxbaum zum vorliegenden, korrekten Tinyray-Quellprogramm …

two.tinyray

Tinyray {
  Camera { [1.0, 1.0, 5.0]; [-0.13, 0.0, 0.0]; [0.0, 1.0, 0.0] }
  Ambience { [1.0, 0.3, 0.7] }
  Background { [0.6, 0.0, 0.0] }
  Fog { [0.3, 0.5, 0.0]; 0.12 }
  Sun { [1.0, 1.0, 1.0]; [1.0, 1.0, 0.5] }
  Sphere { [-0.85, 0.0, 0.0]; 1.0; [0.8, 0.5, 0.0]; [0.2, 0.5, 0.2] }
  Sphere { [0.85, 0.45, 0.45]; 0.6; [0.8, 0.0, 0.0]; [0.4, 0.0, 0.4]; 0.4; 30 }
}
… sieht beispielsweise wie folgt aus.
Beispiel eines Syntaxbaumes

Dieser Syntaxbaum ist von links nach rechts zu lesen; links befindet sich die Wurzel namens Tinyray und rechts befinden sich die Blätter. Die ovalen Knoten repräsentieren die Nichtterminale der Tinyray−Grammatik und die rechteckigen Knoten repräsentieren die für die weitere Verarbeitung relevanten Terminalsymbole.
Der vorliegende Syntaxbaum ist ein sogenannter abstrakter Syntaxbaum (AST), „abstrakt” — weil die unbedeutenden, überflüssigen Token bzw. Produktionen fehlen, beispielsweise sind die Klammern und Kommata im abstrakten Syntaxbaum nicht zu sehen. Kommentare würden in einem abstrakten Syntaxbaum ebenfalls nicht erscheinen.
Dieser Syntaxbaum wurde mit dem Tinyray−DotCode erzeugt — ein recht nützliches Feature von Tinyray, da freut sich der Compilerbauer! :−)

Scannen vs. Parsen

Beim Scannen ging es ja hauptsächlich darum, Tokens quasi Terminalsymbole T in einem Quellprogramm zu erkennen. Beim Parsen geht es vielmehr darum, Nichtterminale N in einem Tokenstrom zu erkennen. Die atomaren Bestandteile eines Nichtterminals sind Terminalsymbole, die der Scanner liefert. Deshalb benötigt der Parser den Scanner, um aus dem Tokenstrom des Scanners die Nichtterminale zu erkennen. Da aber Nichtterminale in anderen Nichtterminalen enthalten sind, ergibt sich aus dem Parse−Vorgang eine Baumstruktur von Nichtterminalen und Terminalen, wohingegen der Scanner nur eine listenartige Struktur von Terminalen liefert.

Übungsvorschlag

Um den Parse−Vorgang etwas besser nachvollziehen zu können, sollten Sie sich in der folgenden Grammatik TG das Startsymbol S, welches die Zusammensetzung aller nur möglichen Tinyray−Programme definiert, herauspicken. Und beginnen Sie dann in Gedanken oder auf einem Blatt Papier sich durch die Produktionen P zu navigieren und merken Sie sich dabei nur die Terminalsymbole, die Sie auf dem Weg finden, wie zum Beispiel:
  1. Wegen dem Startsymbol S stoße ich auf das Nichtterminal Tinyray und notiere meine ersten Terminale
  2. TINYRAY LBRACE
  3. Jetzt könnte ich den geklammerten Ausdruck
    1. (Global | Defaults | Geo | Sun)*
    beliebig oft wiederholen oder einfach nur überspringen. Ich könnte jetzt zum Beispiel mit dem Nichtterminal Sun fortsetzen, die folgenden beiden Terminale würden SUN LBRACE lauten. Aus Bequemlichkeit überspringe ich aber diesen Ausdruck und stoße dabei auf das letzte Terminalsymbol des Nichtterminals Tinyray.
  4. RBRACE
  5. Und erhalte im Gesamten drei Terminale.
  6. TINYRAY LBRACE RBRACE
  7. Wenn ich diese Terminale T rücktransformiere, erhalte ich das kürzest mögliche Tinyray−Programm.
  8. Tinyray { }
Suchen Sie nicht nach der Bedeutung von dem, was dabei herauskommt! Es geht lediglich um die Aneinanderreihung einzelner Terminale gemäß gegebener Grammatik. Beispielsweise kann nach TINYRAY nur ein LBRACE folgen, wohingegen die Aneinanderreihung „TINYRAY SUN” nicht von dieser Grammatik akzeptiert werden würde.

Tinyray−Grammatik

Tinyray-Grammatik TG = (N, T, P, S) mit

N = {
    TinyrayGlobalDefaultsGeoSunCamera,
    BackgroundAmbienceFogBoundingTriangle,
    SpherePlaneParametersVector
},

T = {
    TINYRAY    := 'Tinyray',        CAMERA   := 'Camera',
    BOUNDING   := 'Bounding',       SPHERE   := 'Sphere',
    TRIANGLE   := 'Triangle',       PLANE    := 'Plane',
    AMBIENCE   := 'Ambience',       SUN      := 'Sun',
    BACKGROUND := 'Background',     FOG      := 'Fog',
    DEFAULTS   := 'Defaults',

    LBRACE     := '{',              RBRACE   := '}',
    LBRACKET   := '[',              RBRACKET := ']',
    SEP        := ','|';',

    REAL       := ['+'|'-'] ((('0'-'9')+) | (('0'-'9')*'.'('0'-'9')+))
                  [('E'|'e') ['+'|'-'] ('0'-'9')+]
},

P = {
    Tinyray    ::= TINYRAY LBRACE (Global | Defaults | Geo | Sun)* RBRACE,

    Global     ::= Camera | Background | Ambience | Fog,
    Defaults   ::= DEFAULTS LBRACE Parameters RBRACE,
    Geo        ::= Bounding | Triangle | Sphere | Plane,
    Sun        ::= SUN LBRACE Vector SEP Vector RBRACE,

    Camera     ::= CAMERA LBRACE Vector SEP Vector [ SEP Vector ] RBRACE,
    Background ::= BACKGROUND LBRACE Vector RBRACE,
    Ambience   ::= AMBIENCE LBRACE Vector RBRACE,
    Fog        ::= FOG LBRACE Vector SEP REAL RBRACE,

    Bounding   ::= BOUNDING LBRACE (Bounding | Triangle |
                       Sphere | Defaults)* RBRACE,
    Triangle   ::= TRIANGLE LBRACE Vector SEP Vector
                       SEP Vector [ SEP Parameters ] RBRACE,
    Sphere     ::= SPHERE LBRACE Vector SEP REAL [ SEP Parameters ] RBRACE,
    Plane      ::= PLANE LBRACE Vector SEP Vector
                       SEP Vector [ SEP Parameters ] RBRACE,

    Parameters ::= Vector [ SEP Vector [ SEP REAL [ SEP REAL [
                       SEP REAL ] ] ] ],

    Vector     ::= LBRACKET REAL SEP REAL SEP REAL RBRACKET
} und

S = Tinyray.

Bestimmung von Firstmengen

Ohne Firstmengen ist praktisch kein Parsen möglich. Was nun Firstmengen sind, möchte ich anhand der Tinyray−Grammatik erläutern.
Wenn Sie sich mal die Startproduktion Tinyray ansehen, sehen Sie, dass diese mit dem Terminalsymbol TINYRAY gefolgt vom LBRACE−Terminal beginnt. Es gibt keine andere Möglichkeit die Produktion Tinyray zu beginnen, also ist das erste Symbol der Produktion Tinyray das Terminalsymbol TINYRAY. Die Firstmenge dazu sieht dann wie folgt aus:
  1. First(Tinyray) = { TINYRAY }
Mit der Produktion Vector verhält es sich analog mit dem Ergebnis:
  1. First(Vector) = { LBRACKET }
Der Vector beginnt also immer mit dem Terminalsymbol LBRACKET.
Folgende Produktionen sind ähnlich einfach gelagert:
  1. First(Defaults) = { DEFAULTS }
  2. First(Sun) = { SUN }
  3. First(Camera) = { CAMERA }
  4. First(Background) = { BACKGROUND }
  5. First(Ambience) = { AMBIENCE }
  6. First(Fog) = { FOG }
  7. First(Bounding) = { BOUNDING }
  8. First(Triangle) = { TRIANGLE }
  9. First(Sphere) = { SPHERE }
  10. First(Plane) = { PLANE }
Die Produktion Parameters beginnt aber mit dem Nichtterminal Vector, demnach wäre also die Produktion Vector in der Firstmenge von Parameters, doch ganz so ist es nicht. In einer Firstmenge können sich nur Terminalsymbole befinden, da aber bekannt ist, mit welchem Terminalsymbol die Produktion Vector beginnt, kann die Firstmenge von Vector übernommen werden:
  1. First(Parameters) = First(Vector) = { LBRACKET }
Irgendwie logisch — wenn also Parameters mit einem Vector beginnt und Vector mit einem LBRACKET beginnt, dann beginnt natürlich auch Parameters mit einem LBRACKET (Transitivität).


Preisfrage: Wie lautet nun die Firstmenge von Global?


Die Produktion Global könnte mit der Produktion Camera beginnen, könnte aber auch mit den Produktionen Background, Ambience oder Fog beginnen; hat also vier Möglichkeiten. Die Firstmenge von Global setzt sich deshalb wie folgt zusammen:
  1. First(Global)
    = First(Camera) v First(Background) v First(Ambience) v First(Fog)
    = { CAMERA } v { BACKGROUND } v { AMBIENCE } v { FOG }
    = { CAMERA, BACKGROUND, AMBIENCE, FOG }
Aus diesem Grunde spricht man von einer Firstmenge einer Produktion und nicht von einem Firstterminal. Die Produktion Global kann also mit den Terminalsymbolen CAMERA oder BACKGROUND oder AMBIENCE oder FOG beginnen.
Mit Geo verhält es sich analog:
  1. First(Geo)
    = First(Bounding) v First(Triangle) v First(Sphere) v First(Plane)
    = { BOUNDING } v { TRIANGLE } v { SPHERE } v { PLANE }
    = { BOUNDING, TRIANGLE, SPHERE, PLANE }
Die Produktion Geo kann also mit den Terminalsymbolen BOUNDING oder TRIANGLE oder SPHERE oder PLANE beginnen.
Nun wissen Sie also, was Firstmengen überhaupt sind und wie man sie bestimmen kann. Doch wie die Firstmengen den Parse−Vorgang unterstützen können, habe ich noch gar nicht erwähnt. Lesen Sie hierzu im Folgenden die Bedeutung der Firstmengen.

Bedeutung der Firstmengen

Der Parser erhält vom Scanner nur Tokens, also Terminalsymbole der Grammatik. Der Scanner kennt nämlich keine Nichtterminale. Es ist die Aufgabe des Parsers, aus dem Tokenstrom eines Scanners einen Syntaxbaum gemäß den Produktionen einer Grammatik aufzubauen.
Woher weiß also der Parser, welche Produktion der Grammatik greifen muss, wenn er ein Token vom Scanner erhält?
Da es nun für jede Produktion eine Firstmenge gibt, muss man eigentlich nur noch nachsehen in welcher Firstmenge sich das vom Scanner erhaltene Token bzw. Terminalsymbol liegt und erhält somit die passende Produktion. Und die Produktion entspricht einem Teilbaum des Syntaxbaumes. Der Syntaxbaum kann Dank der Firstmengen erstellt werden.

Terminale mit Sonderstatus

Es gibt drei Terminalsymbole, die in der Tinyray−Grammatik TG nicht zu sehen sind, die aber vom Tinyray−Scanner als Token zurückgegeben werden können. Diese Terminalsymbole heißen wie folgt:
  1. COMMENT
    Ein Kommentar kann praktisch an jeder Stelle in einem Tinyray−Quellprogramm vorkommen. Wenn also dieses Terminalsymbol korrekter Weise in die Grammatik TG aufgenommen werden würde, wäre diese Grammatik um ein Vielfaches größer — schlimmer noch — gar unlesbar!
  2. UNKNOWN
    Es gibt praktisch kein unbekanntes Token in einem korrekten Tinyray−Quellprogramm. Deshalb liegt UNKNOWN nicht in dieser Grammatik. Erhält der Parser dieses Token, kommt es zu einer Exception, der Parse−Vorgang wird abgebrochen.
  3. EOS
    Das Ende eines korrekten Tinyray−Programms kann nur nach Abarbeitung der Startproduktion Tinyray erreicht werden. Deshalb ist es vielleicht übersichtlicher, dieses Token nicht in die Grammatik aufzunehmen.



Implementierung

Zur Realisierung des Tinyray−Parsers werden die Klassen aus der lexikalischen Analyse sowie zusätzliche drei Dateien der syntaktischen Analyse benötigt:
  1. ParseException.java
    Bei einem syntaktisch sowohl auch bei einem lexikalisch inkorrekten Tinyray−Quellprogramm wird eine Exception (hier: ParseException) geworfen.
  2. ParseTreeKit.java
    Hierbei handelt es sich um ein Interface, welches die Schnittstelle für eine knotenerzeugende Klasse definiert.
  3. Parser.java
    Ist der Parser instantiiert, kann mit der Methode parse() der Syntaxbaum erzeugt werden. Die Rückgabe von parse() ist der Wurzelknoten des Syntaxbaumes. Der Rückgabetyp wird von einer Implementierung des ParseTreeKit bestimmt.

ParseException.java

package tinyray.frontend;

/**
 * Die Klasse ParseException ist eine Exception, welche nur von
 * einem Parser während eines Parse-Vorgangs geworfen werden sollte.
 */

public class ParseException extends Exception {

    /**
     * Fest vorgegebene Serialisierungskonstante,
     * welche hier eigentlich nichts zur Sache tut, jedoch
     * aus Versionierungsgründen angegeben werden muss.
     */

    public final static long serialVersionUID = 12345678L;

    /**
     * Initialisiert eine ParseException mit einer Nachricht und
     * dem Parser, in welchen die Exception ausgelöst wird.
     * @param message Die Nachricht.
     * @param parser Der Parser.
     */

    public ParseException(String message, Parser<?, ?> parser) {
        super(message + " {" + parser + "}");
    }

    /**
     * Initialisert eine ParseException mit einer bereits anderen
     * geworfenen Instanz wie zum Beispiel eine IOException.
     * @param cause Die bereits geworfene Instanz.
     */

    public ParseException(Throwable cause) {
        super(cause);
    }
}

ParseTreeKit.java

package tinyray.frontend;

import java.util.List;

/**
 * Das Interface ParseTreeKit stellt eine "abstrakte Fabrik" (Design-Pattern)
 * dar, welche vom Parser benötigt wird, um jeden einzelnen Knoten des
 * Syntaxbaumes zu erzeugen. Benötigt der Parser einen Knoten für den
 * Syntaxbaum, dann instantiiert er diesen Knoten nicht selbst, sondern
 * lässt ihn sich von einem ParseTreeKit erzeugen. Die Idee dabei ist,
 * dass ein und derselbe Parser zu einem Tinyray-Programm unter Verwendung
 * unterschiedlicher ParseTreeKits auch unterschiedliche Syntaxbäume erzeugen
 * kann. Es können zum Beispiel auch im Nachhinein auch weitere ParseTreeKits
 * geschrieben werden, ohne dabei in den Code des Parsers eingreifen zu
 * müssen.
 * Derzeit existieren zwei Implementierungen des vorliegenen ParseTreeKit:
 * der RaytracerKit und der LanguageKit.
 * @param <N> Die Basisklasse für einen Knoten des Syntaxbaumes.
 * @param <R> Die Basisklasse für die Wurzel des Syntaxbaumes.
 */

public interface ParseTreeKit<N, R extends N> {

    /**
     * Erzeugt die Wurzel des Syntaxbaumes, das Tinyray-Programm.
     * @param arguments Knotenliste: Beliebig viele Global-, Defaults-,
     *                               Geo- und Sun-Knoten.
     * @return Das Tinyray-Programm als abstrakter Syntaxbaum.
     */

    public R getTinyray(List<N> arguments);

    /**
     * Erzeugt einen Camera-Knoten.
     * @param arguments Knotenliste: Zwei bis drei Vector-Knoten.
     * @return Der Camera-Knoten.
     */

    public N getCamera(List<N> arguments);

    /**
     * Erzeugt einen Background-Knoten.
     * @param arguments Knotenliste: Ein Vector-Knoten.
     * @return Der Background-Knoten.
     */

    public N getBackground(List<N> arguments);

    /**
     * Erzeugt einen Ambience-Knoten.
     * @param arguments Knotenliste: Ein Vector-Knoten.
     * @return Der Ambience-Knoten.
     */

    public N getAmbience(List<N> arguments);

    /**
     * Erzeugt einen Fog-Knoten.
     * @param arguments Knotenliste: Ein Vector- und ein Real-Knoten.
     * @return Der Fog-Knoten.
     */

    public N getFog(List<N> arguments);

    /**
     * Erzeugt einen Defaults-Knoten, die Default-Parameter.
     * @param arguments Knotenliste: Ein Parameters-Knoten.
     * @return Der Defaults-Knoten.
     */

    public N getDefaults(List<N> arguments);

    /**
     * Erzeugt einen Bounding-Knoten.
     * @param arguments Knotenliste: Beliebig viele Bounding-, Triangle-,
     *                               Sphere- und Defaults-Knoten.
     * @return Der Bounding-Knoten.
     */

    public N getBounding(List<N> arguments);

    /**
     * Erzeugt einen Triangle-Knoten.
     * @param arguments Knotenliste: Drei Vector-Knoten, optional gefolgt von
     *                               einem Parameters-Knoten.
     * @return Der Triangle-Knoten.
     */

    public N getTriangle(List<N> arguments);

    /**
     * Erzeugt einen Sphere-Knoten.
     * @param arguments Knotenliste: Ein Vector- und ein Real-Knoten, optional
     *                               gefolgt von einem Parameters-Knoten.
     * @return Die Sphere-Knoten.
     */

    public N getSphere(List<N> arguments);

    /**
     * Erzeugt einen Plane-Knoten.
     * @param arguments Knotenliste: Drei Vector-Knoten, optional gefolgt von
     *                               einem Parameters-Knoten.
     * @return Der Plane-Knoten.
     */

    public N getPlane(List<N> arguments);

    /**
     * Erzeugt einen Sun-Knoten (Die Sonne).
     * @param arguments Knotenliste: Zwei Vector-Knoten.
     * @return Der Sun-Knoten.
     */

    public N getSun(List<N> arguments);

    /**
     * Erzeugt einen Parameters-Knoten.
     * @param arguments Knotenliste: Ein Vector-Knoten und optional einen
     *                               weiteren Vector-Knoten, optional gefolgt
     *                               von einem Real-Knoten, optional gefolgt
     *                               von einem Real-Knoten, optional gefolgt
     *                               von einem Real-Knoten.
     * @return Der Parameters-Knoten.
     */

    public N getParameters(List<N> arguments);

    /**
     * Erzeugt einen Vector-Knoten.
     * @param arguments Knotenliste: Drei Vector-Knoten.
     * @return Der Vector-Knoten.
     */

    public N getVector(List<N> arguments);

    /**
     * Erzeugt einen Real-Knoten.
     * @param real Eine Fließkommazahl als String.
     * @return Der Real-Knoten.
     */

    public N getReal(String real);
}

Parser.java

package tinyray.frontend;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Die Klasse Parser erzeut gemäß der Tinyray-Grammatik mit Hilfe
 * eines Scanners und einer Fabrik (ParseTreeKit) einen entsprechenden
 * Syntaxbaum. Jede Produktion der Grammatik wird durch eine Methode
 * realisiert und bringt einen Teilbaum des Syntaxbaumes hervor.
 * Dem ParseTreeKit wird überlassen, welche Knoten er für den Syntaxbaum
 * letztendlich produzieren wird.
 *
 * Die Produktionen der Tinyray-Grammatik:
 *
 * Tinyray    ::= TINYRAY LBRACE (Global | Defaults | Geo | Sun)* RBRACE .
 *
 * Global     ::= Camera | Background | Ambience | Fog .
 * Defaults   ::= DEFAULTS LBRACE Parameters RBRACE .
 * Geo        ::= Bounding | Triangle | Sphere | Plane .
 * Sun        ::= SUN LBRACE Vector SEP Vector RBRACE .
 *
 * Camera     ::= CAMERA LBRACE Vector SEP Vector [ SEP Vector ] RBRACE .
 * Background ::= BACKGROUND LBRACE Vector RBRACE .
 * Ambience   ::= AMBIENCE LBRACE Vector RBRACE .
 * Fog        ::= FOG LBRACE Vector SEP REAL RBRACE .
 *
 * Bounding   ::= BOUNDING LBRACE (Bounding | Triangle |
 *                  Sphere | Defaults)* RBRACE .
 * Triangle   ::= TRIANGLE LBRACE Vector SEP Vector
 *                  SEP Vector [ SEP Parameters ] RBRACE .
 * Sphere     ::= SPHERE LBRACE Vector SEP REAL [ SEP Parameters ] RBRACE .
 * Plane      ::= PLANE LBRACE Vector SEP Vector
 *                  SEP Vector [ SEP Parameters ] RBRACE .
 *
 * Parameters ::= Vector [ SEP Vector [ SEP REAL [ SEP REAL [
 *                  SEP REAL ] ] ] ] .
 * Vector     ::= LBRACKET REAL SEP REAL SEP REAL RBRACKET .
 *
 * Kommentare (COMMENT) werden stets geskippt und
 * unbekannte Tokens (UNKNOWN) lösen eine ParseException aus.
 *
 * @param <N> Die Basisklasse für einen Knoten des Syntaxbaumes.
 * @param <R> Die Basisklasse für die Wurzel des Syntaxbaumes.
 */

public class Parser<N, R extends N> {

    // Der Scanner.
    private Scanner scanner;

    // Der Knotenerzeuger, die Knotenerzeugung wird vom Parse-Vorgang
    // getrennt, so verfügt der Entwickler über mehr Spielraum.
    private ParseTreeKit<N, R> kit;

    /**
     * Initialisiert den Parser mit einem Scanner und einer Fabrik.
     * @param scanner Der Scanner.
     * @param kit Die Fabrik.
     */

    public Parser(Scanner scanner, ParseTreeKit<N, R> kit) {
        this.scanner = scanner;
        this.kit = kit;
    }

    /**
     * Stößt den Parse-Vorgang an und gibt einen Syntaxbaum zurück.
     * Falls das zu parsende Tinyray-Programm Grammatikfehler enthalten
     * sollte, so wird jedoch eine ParseException geworfen.
     * @return Der Syntaxbaum.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    public R parse() throws ParseException {

        // Liest das erste Token ein.
        this.tryNextToken();

        if (this.hasFirstOfTinyray()) {
            return this.parseTinyray();
        } else {
            throw new ParseException("Tinyray erwartet", this);
        }
    }

    /* Produktionen */

    /**
     * Diese Methode gibt die Wurzel des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Tinyray ::= TINYRAY LBRACE (Global | Defaults | Geo | Sun)* RBRACE .
     * @return Die Wurzel des Syntaxbaumes.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private R parseTinyray() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.TINYRAY);
        this.testAndNextToken(Token.Type.LBRACE);

        // Global | Defaults | Geo | Sun (beliebig oft)
        while (true) {
            if (this.hasFirstOfGlobal()) {
                arguments.add(this.parseGlobal());
            } else if (this.hasFirstOfDefaults()) {
                arguments.add(this.parseDefaults());
            } else if (this.hasFirstOfGeo()) {
                arguments.add(this.parseGeo());
            } else if (this.hasFirstOfSun()) {
                arguments.add(this.parseSun());
            } else {
                break;
            }
        }

        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getTinyray(arguments);
    }

    /**
     * Diese Methode gibt einen Global-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Global ::= Camera | Background | Ambience | Fog .
     * @return Der Global-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseGlobal() throws ParseException {

        if (this.hasFirstOfCamera()) {
            return this.parseCamera();
        } else if (this.hasFirstOfBackground()) {
            return this.parseBackground();
        } else if (this.hasFirstOfAmbience()) {
            return this.parseAmbience();
        } else if (this.hasFirstOfFog()) {
            return this.parseFog();
        } else {
            String message = "Camera, Background, Ambience oder Fog erwartet";
            throw new ParseException(message, this);
        }
    }

    /**
     * Diese Methode gibt einen Defaults-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Defaults ::= DEFAULTS LBRACE Parameters RBRACE .
     * @return Der Defaults-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseDefaults() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.DEFAULTS);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseParameters());
        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getDefaults(arguments);
    }

    /**
     * Diese Methode gibt einen Geo-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Geo ::= Bounding | Triangle | Sphere | Plane .
     * @return Der Geo-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseGeo() throws ParseException {

        if (this.hasFirstOfBounding()) {
            return this.parseBounding();
        } else if (this.hasFirstOfTriangle()) {
            return this.parseTriangle();
        } else if (this.hasFirstOfSphere()) {
            return this.parseSphere();
        } else if (this.hasFirstOfPlane()) {
            return this.parsePlane();
        } else {
            String message = "Bounding, Triangle, Sphere oder Plane erwartet";
            throw new ParseException(message, this);
        }
    }

    /**
     * Diese Methode gibt einen Sun-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Sun ::= SUN LBRACE Vector SEP Vector RBRACE .
     * @return Der Sun-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseSun() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.SUN);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getSun(arguments);
    }

    /**
     * Diese Methode gibt einen Camera-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Camera ::= CAMERA LBRACE Vector SEP Vector [ SEP Vector ] RBRACE .
     * @return Der Camera-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseCamera() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.CAMERA);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseVector());

        // 3. Vektor (optional)
        if (this.getToken().getType() == Token.Type.SEP) {
            this.tryNextToken();
            arguments.add(this.parseVector());
        }

        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getCamera(arguments);
    }

    /**
     * Diese Methode gibt einen Background-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Background ::= BACKGROUND LBRACE Vector RBRACE .
     * @return Der Background-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseBackground() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.BACKGROUND);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getBackground(arguments);
    }

    /**
     * Diese Methode gibt einen Ambience-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Ambience ::= AMBIENCE LBRACE Vector RBRACE .
     * @return Der Ambience-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseAmbience() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.AMBIENCE);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getAmbience(arguments);
    }

    /**
     * Diese Methode gibt einen Fog-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Fog ::= FOG LBRACE Vector SEP REAL RBRACE .
     * @return Der Fog-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseFog() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.FOG);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseReal());
        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getFog(arguments);
    }

    /**
     * Diese Methode gibt einen Bounding-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Bounding ::= BOUNDING LBRACE (Bounding | Triangle |
     *                  Sphere | Defaults)* RBRACE .
     * @return Der Bounding-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseBounding() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.BOUNDING);
        this.testAndNextToken(Token.Type.LBRACE);

        // Bounding | Triangle | Sphere | Defaults (beliebig oft)
        while (true) {
            if (this.hasFirstOfBounding()) {
                arguments.add(this.parseBounding());
            } else if (this.hasFirstOfTriangle()) {
                arguments.add(this.parseTriangle());
            } else if (this.hasFirstOfSphere()) {
                arguments.add(this.parseSphere());
            } else if (this.hasFirstOfDefaults()) {
                arguments.add(this.parseDefaults());
            } else {
                break;
            }
        }

        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getBounding(arguments);
    }

    /**
     * Diese Methode gibt einen Triangle-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Triangle ::= TRIANGLE LBRACE Vector SEP Vector
     *                  SEP Vector [ SEP Parameters ] RBRACE .
     * @return Der Triangle-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseTriangle() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.TRIANGLE);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseVector());

        // SEP Parameters (optional)
        if (this.getToken().getType() == Token.Type.SEP) {
            this.tryNextToken();
            arguments.add(this.parseParameters());
        }

        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getTriangle(arguments);
    }

    /**
     * Diese Methode gibt einen Sphere-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Sphere ::= SPHERE LBRACE Vector SEP REAL [ SEP Parameters ] RBRACE .
     * @return Der Sphere-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseSphere() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.SPHERE);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseReal());

        // SEP Parameters (optional)
        if (this.getToken().getType() == Token.Type.SEP) {
            this.tryNextToken();
            arguments.add(this.parseParameters());
        }

        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getSphere(arguments);
    }

    /**
     * Diese Methode gibt einen Plane-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Plane ::= PLANE LBRACE Vector SEP Vector
     *               SEP Vector [ SEP Parameters ] RBRACE .
     * @return Der Plane-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parsePlane() throws ParseException {

        ArrayList<N> arguments = new ArrayList<N>();

        this.testAndNextToken(Token.Type.PLANE);
        this.testAndNextToken(Token.Type.LBRACE);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseVector());
        this.testAndNextToken(Token.Type.SEP);
        arguments.add(this.parseVector());

        // SEP Parameters (optional)
        if (this.getToken().getType() == Token.Type.SEP) {
            this.tryNextToken();
            arguments.add(this.parseParameters());
        }

        this.testAndNextToken(Token.Type.RBRACE);

        return this.kit.getPlane(arguments);
    }

    /**
     * Diese Methode gibt einen Parameters-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Parameters ::= Vector [ SEP Vector [ SEP REAL
     *                    [ SEP REAL [ SEP REAL ] ] ] ] .
     * @return Der Parameters-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseParameters() throws ParseException {

        if (this.hasFirstOfParameters()) {

            ArrayList<N> arguments = new ArrayList<N>();

            arguments.add(this.parseVector());

            // SEP Vector [ SEP REAL [ SEP REAL [ SEP REAL ] ] ] (optional)
            if (this.getToken().getType() == Token.Type.SEP) {
                this.tryNextToken();
                arguments.add(this.parseVector());

                // SEP REAL [ SEP REAL [ SEP REAL ] ] (optional)
                if (this.getToken().getType() == Token.Type.SEP) {
                    this.tryNextToken();
                    arguments.add(this.parseReal());

                    // SEP REAL [ SEP REAL ] (optional)
                    if (this.getToken().getType() == Token.Type.SEP) {
                        this.tryNextToken();
                        arguments.add(this.parseReal());

                        // SEP REAL (optional)
                        if (this.getToken().getType() == Token.Type.SEP) {
                            this.tryNextToken();
                            arguments.add(this.parseReal());
                        }
                    }
                }
            }

            return this.kit.getParameters(arguments);

        } else {
            throw new ParseException("Parameters erwartet", this);
        }
    }

    /**
     * Diese Methode gibt einen Vector-Knoten des Syntaxbaumes zurück
     * und realisiert folgende Produktion:
     * Vector ::= LBRACKET REAL SEP REAL SEP REAL RBRACKET .
     * @return Der Vector-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseVector() throws ParseException {

        if (this.hasFirstOfVector()) {

            ArrayList<N> arguments = new ArrayList<N>();

            this.testAndNextToken(Token.Type.LBRACKET);
            arguments.add(this.parseReal());
            this.testAndNextToken(Token.Type.SEP);
            arguments.add(this.parseReal());
            this.testAndNextToken(Token.Type.SEP);
            arguments.add(this.parseReal());
            this.testAndNextToken(Token.Type.RBRACKET);

            return this.kit.getVector(arguments);

        } else {
            throw new ParseException("Vector erwartet", this);
        }
    }

    /**
     * Diese Methode gibt einen Real-Knoten des Syntaxbaumes zurück.
     * @return Der Real-Knoten.
     * @throws ParseException Die ParseException bei Grammatikfehler.
     */

    private N parseReal() throws ParseException {

        Token real = this.testAndNextToken(Token.Type.REAL);
        return this.kit.getReal(real.getValue());
    }

    /* First-Mengen */

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Tinyray-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfTinyray() {
        return this.getToken().getType() == Token.Type.TINYRAY;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Global-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfGlobal() {
        return (this.hasFirstOfCamera())
            || (this.hasFirstOfBackground())
            || (this.hasFirstOfAmbience())
            || (this.hasFirstOfFog());
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Background-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfBackground() {
        return this.getToken().getType() == Token.Type.BACKGROUND;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Ambience-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfAmbience() {
        return this.getToken().getType() == Token.Type.AMBIENCE;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Fog-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfFog() {
        return this.getToken().getType() == Token.Type.FOG;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Defaults-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfDefaults() {
        return this.getToken().getType() == Token.Type.DEFAULTS;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Camera-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfCamera() {
        return this.getToken().getType() == Token.Type.CAMERA;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Geo-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfGeo() {
        return (this.hasFirstOfBounding())
            || (this.hasFirstOfTriangle())
            || (this.hasFirstOfSphere())
            || (this.hasFirstOfPlane());
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Bounding-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfBounding() {
        return this.getToken().getType() == Token.Type.BOUNDING;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Triangle-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfTriangle() {
        return this.getToken().getType() == Token.Type.TRIANGLE;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Sphere-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfSphere() {
        return this.getToken().getType() == Token.Type.SPHERE;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Plane-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfPlane() {
        return this.getToken().getType() == Token.Type.PLANE;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Sun-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfSun() {
        return this.getToken().getType() == Token.Type.SUN;
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Parameters-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfParameters() {
        return this.hasFirstOfVector();
    }

    /**
     * Prüft, ob das zuletzt gelesene Token in der Firstmenge
     * der Vector-Produktion enthalten ist.
     * @return true, das Token befindet sich in der Menge.
     */

    private boolean hasFirstOfVector() {
        return this.getToken().getType() == Token.Type.LBRACKET;
    }

    /* Hilfsmethoden */

    /**
     * Gibt das zuletzt gelesene Token des Scanners zurück.
     * @return Das zuletzt gelesene Token.
     */

    private Token getToken() {
        return this.scanner.getToken();
    }

    /**
     * Linear-rekursive Methode, die das nächstgültige Token zurückgibt.
     * Kommentare werden solange übersprungen (geskippt) bis ein gültiges
     * Token anliegt. Sobald ein unbekanntes Token erkannt wird, wird eine
     * ParseException geworfen. Eine IOException wird als ParseException
     * geworfen (vielleicht nicht ganz korrekt).
     * @throws ParseException Die ParseException.
     */

    private void tryNextToken() throws ParseException {
        try {
            this.scanner.nextToken();
        } catch (IOException ioe) {
            throw new ParseException(ioe);
        }
        if (this.getToken().getType() == Token.Type.UNKNOWN) {
            throw new ParseException("Unbekanntes Token", this);
        } else if (this.getToken().getType() == Token.Type.COMMENT) {
            this.tryNextToken();
        }
    }

    /**
     * Prüft, ob der aktuell anliegende Token vom gegebenen Tokentyp ist
     * und veranlasst den Scanner zum nächstgültigen Token zu lesen
     * (siehe tryNextToken).
     * Ist das aktuell anliegende Token nicht vom gegebenen Tokentyp,
     * so wird eine ParseException worfen.
     * @param type Der gegebene Tokentyp.
     * @return Das aktuell anliegende Token.
     * @throws ParseException Die ParseException.
     */

    private Token testAndNextToken(Token.Type type) throws ParseException {
        if (this.getToken().getType() == type) {
            Token result = this.getToken();
            this.tryNextToken();
            return result;
        } else {
            throw new ParseException(type + " erwartet", this);
        }
    }

    /**
     * Gibt die textuelle Repräsentation des Scanners zurück,
     * dabei interessiert eigentlich nur das aktuell anliegende,
     * also zuletzt gelesene Token.
     */

    public String toString() {
        return this.getToken().toString();
    }
}



Anwendungsbeispiel

Um ein kleines Anwendungsbeispiel des Parsers zeigen zu können, benötigt man zu allererst eine Implementierung des abstrakten ParseTreeKit (Interface). Eine prägnante Implementierung des ParseTreeKit, welche zudem auch die Struktur des Syntaxbaumes gut hervorhebt, heißt im Folgenden XMLTreeKit. Der XMLTreeKit erzeugt die Knoten des Syntaxbaumes als String-Listen „List<String>”, welche jeweils XML−Code−Fragmente enthalten. Das Anwendungsbeispiel heißt XMLTree und gibt den Syntaxbaum als XML-Baum aus.
Keine Angst! Es ist der einzige XML-Part zum Tinyray-Projekt. :−)
Dieses rudimentäre Beispiel soll Sie auf weitere Implementierungen des ParseTreeKit vorbereiten, die da heißen: Tinyray−LanguageKit und Tinyray−RaytracerKit.

XMLTree.java

package tinyray.examples;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;

import tinyray.frontend.ParseException;
import tinyray.frontend.ParseTreeKit;
import tinyray.frontend.Parser;
import tinyray.frontend.Scanner;


public class XMLTree {

    public static class XMLTreeKit
            implements ParseTreeKit<List<String>, List<String>> {

        public List<String> getTinyray(List<List<String>> arguments) {
            return tag("Tinyray", arguments);
        }

        public List<String> getCamera(List<List<String>> arguments) {
            return tag("Camera", arguments);
        }

        public List<String> getBackground(List<List<String>> arguments) {
            return tag("Background", arguments);
        }

        public List<String> getAmbience(List<List<String>> arguments) {
            return tag("Ambience", arguments);
        }

        public List<String> getFog(List<List<String>> arguments) {
            return tag("Fog", arguments);
        }

        public List<String> getDefaults(List<List<String>> arguments) {
            return tag("Defaults", arguments);
        }

        public List<String> getBounding(List<List<String>> arguments) {
            return tag("Bounding", arguments);
        }

        public List<String> getTriangle(List<List<String>> arguments) {
            return tag("Triangle", arguments);
        }

        public List<String> getSphere(List<List<String>> arguments) {
            return tag("Sphere", arguments);
        }

        public List<String> getPlane(List<List<String>> arguments) {
            return tag("Plane", arguments);
        }

        public List<String> getSun(List<List<String>> arguments) {
            return tag("Sun", arguments);
        }

        public List<String> getParameters(List<List<String>> arguments) {
            return tag("Parameters", arguments);
        }

        public List<String> getVector(List<List<String>> arguments) {
            return tag("Vector", arguments);
        }

        public List<String> getReal(String real) {
            ArrayList<String> result = new ArrayList<String>();
            result.add("<Real>" + Double.parseDouble(real) + "</Real>");
            return result;
        }

        private List<String> tag(String name, List<List<String>> arguments) {
            ArrayList<String> result = new ArrayList<String>();
            result.add("<" + name + ">");
            for (int i = 0; i < arguments.size(); i++) {
                for (int j = 0; j < arguments.get(i).size(); j++) {
                    result.add("\t" + arguments.get(i).get(j));
                }
            }
            result.add("</" + name + ">");
            return result;
        }
    }

    public static void main(String[] arguments) {

        if (arguments.length > 0) {

            try {

                // 1. Der Scanner erhält den Tinyray-Code als Stream.
                Scanner scanner =
                    new Scanner(new FileInputStream(arguments[0]));

                // 2. Den ParseTreeKit erzeugen.
                XMLTreeKit kit = new XMLTreeKit();

                // 3. Den Parser mit Scanner- und Kit-Instanz erzeugen.
                Parser<List<String>, List<String>> parser =
                    new Parser<List<String>, List<String>>(scanner, kit);

                // 4. Parsen!
                List<String> tinyray = parser.parse();

                // 5. Die Ausgabe des Syntaxbaumes (hier: XML-Baum).
                System.out.print("<?xml version=\"1.0\"");
                System.out.println(" encoding=\"ISO-8859-1\"?>");
                for (int i = 0; i < tinyray.size(); i++) {
                    System.out.println(tinyray.get(i));
                }

            } catch (ParseException e) {
                System.err.println(e.getMessage());
            } catch (FileNotFoundException e) {
                System.err.println(e.getMessage());
            }
        } else {
            System.err.println("Tinyray-Programm-Datei angeben.");
        }
    }
}
Ein kurzes Tinyray-Quellprogrämmchen …

hello−world.tinyray

// Hallo Welt!

Tinyray {
    Sphere {
        [0, 0, 0]; // Wo liegt die Kugel?
        2.0        // Welchen Radius besitzt die Kugel?
    }
    Sun {
        [1, 1, 1]; // In welcher Richtung befindet sich die Sonne?
        [1, 1, 1]  // Welche Farbe hat die Sonne?
    }
}
… kann Dank XMLTreeKit auch recht kurz und ausdrucksstark als Syntaxbaum in XML−Format ausgegeben werden.

java XMLTree hello−world.tinyray

<?xml version="1.0" encoding="ISO-8859-1"?>
<Tinyray>
    <Sphere>
        <Vector>
            <Real>0.0</Real>
            <Real>0.0</Real>
            <Real>0.0</Real>
        </Vector>
        <Real>2.0</Real>
    </Sphere>
    <Sun>
        <Vector>
            <Real>1.0</Real>
            <Real>1.0</Real>
            <Real>1.0</Real>
        </Vector>
        <Vector>
            <Real>1.0</Real>
            <Real>1.0</Real>
            <Real>1.0</Real>
        </Vector>
    </Sun>
</Tinyray>
Jedoch ein etwas längerer Tinyray-Code …

pyramid.tinyray

Tinyray {
    Camera { [0.0, 0.0, 9.0]; [1.6, 1.1, 0.0]; [0.0, 1.0, 0.0] }
    Ambience { [0.4, 0.6, 0.0] }
    Background { [0.0, 0.4, 0.7] }
    Fog { [0.3, 0.3, 0.8]; 0.065 }

    Sun { [1.0, 1.0, 1.0]; [1.0, 1.0, 1.0] }
    Sun { [-0.866, -0.5, 0.05]; [0.8, 0.8, 0.4] }
    Sun { [-1.0, 0.2, 1.0]; [0.5, 1.0, 1.0] }

    Defaults { [0.4, 0.4, 0.4]; [0.0, 0.0, 0.0]; 0.2; 10.0; 0.6 }
    Plane { [4.0, 0.0, 0.0]; [0.0, 4.0, 0.0]; [0.0, 0.0, -2.0] }

    Defaults { [0.7, 0.0, 0.0]; [0.0, 0.1, 0.0]; 1.2; 9.0 }
    Triangle { [0.0, 0.0, 1.5]; [0.0, 1.0, 0.0]; [-0.866, -0.5, 0.0] }

    Defaults { [0.7, 0.0, 0.0]; [0.3, 0.1, 0.0] }
    Triangle { [0.0, 0.0, 1.5]; [-0.866, -0.5, 0.0]; [0.866, -0.5, 0.0] }

    Defaults { [0.7, 0.0, 0.0]; [0.6, 0.1, 0.0] }
    Triangle { [0.0, 0.0, 1.5]; [0.866, -0.5, 0.0]; [0.0, 1.0, 0.0] }

    Defaults { [0.7, 0.0, 0.0]; [0.9, 0.1, 0.0] }
    Triangle { [0.0, 1.0, 0.0]; [-0.866, -0.5, 0.0]; [0.866, -0.5, 0.0] }
}
… fabriziert schon extrem langen und unüberschaubaren XML-Code.

java XMLTree pyramid.tinyray

<?xml version="1.0" encoding="ISO-8859-1"?>
<Tinyray>
    <Camera>
        <Vector>
            <Real>0.0</Real>
            <Real>0.0</Real>
            <Real>9.0</Real>
        </Vector>
        <Vector>
            <Real>1.6</Real>
            <Real>1.1</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>0.0</Real>
            <Real>1.0</Real>
            <Real>0.0</Real>
        </Vector>
    </Camera>
    <Ambience>
        <Vector>
            <Real>0.4</Real>
            <Real>0.6</Real>
            <Real>0.0</Real>
        </Vector>
    </Ambience>
    <Background>
        <Vector>
            <Real>0.0</Real>
            <Real>0.4</Real>
            <Real>0.7</Real>
        </Vector>
    </Background>
    <Fog>
        <Vector>
            <Real>0.3</Real>
            <Real>0.3</Real>
            <Real>0.8</Real>
        </Vector>
        <Real>0.065</Real>
    </Fog>
    <Sun>
        <Vector>
            <Real>1.0</Real>
            <Real>1.0</Real>
            <Real>1.0</Real>
        </Vector>
        <Vector>
            <Real>1.0</Real>
            <Real>1.0</Real>
            <Real>1.0</Real>
        </Vector>
    </Sun>
    <Sun>
        <Vector>
            <Real>-0.866</Real>
            <Real>-0.5</Real>
            <Real>0.05</Real>
        </Vector>
        <Vector>
            <Real>0.8</Real>
            <Real>0.8</Real>
            <Real>0.4</Real>
        </Vector>
    </Sun>
    <Sun>
        <Vector>
            <Real>-1.0</Real>
            <Real>0.2</Real>
            <Real>1.0</Real>
        </Vector>
        <Vector>
            <Real>0.5</Real>
            <Real>1.0</Real>
            <Real>1.0</Real>
        </Vector>
    </Sun>
    <Defaults>
        <Parameters>
            <Vector>
                <Real>0.4</Real>
                <Real>0.4</Real>
                <Real>0.4</Real>
            </Vector>
            <Vector>
                <Real>0.0</Real>
                <Real>0.0</Real>
                <Real>0.0</Real>
            </Vector>
            <Real>0.2</Real>
            <Real>10.0</Real>
            <Real>0.6</Real>
        </Parameters>
    </Defaults>
    <Plane>
        <Vector>
            <Real>4.0</Real>
            <Real>0.0</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>0.0</Real>
            <Real>4.0</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>0.0</Real>
            <Real>0.0</Real>
            <Real>-2.0</Real>
        </Vector>
    </Plane>
    <Defaults>
        <Parameters>
            <Vector>
                <Real>0.7</Real>
                <Real>0.0</Real>
                <Real>0.0</Real>
            </Vector>
            <Vector>
                <Real>0.0</Real>
                <Real>0.1</Real>
                <Real>0.0</Real>
            </Vector>
            <Real>1.2</Real>
            <Real>9.0</Real>
        </Parameters>
    </Defaults>
    <Triangle>
        <Vector>
            <Real>0.0</Real>
            <Real>0.0</Real>
            <Real>1.5</Real>
        </Vector>
        <Vector>
            <Real>0.0</Real>
            <Real>1.0</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>-0.866</Real>
            <Real>-0.5</Real>
            <Real>0.0</Real>
        </Vector>
    </Triangle>
    <Defaults>
        <Parameters>
            <Vector>
                <Real>0.7</Real>
                <Real>0.0</Real>
                <Real>0.0</Real>
            </Vector>
            <Vector>
                <Real>0.3</Real>
                <Real>0.1</Real>
                <Real>0.0</Real>
            </Vector>
        </Parameters>
    </Defaults>
    <Triangle>
        <Vector>
            <Real>0.0</Real>
            <Real>0.0</Real>
            <Real>1.5</Real>
        </Vector>
        <Vector>
            <Real>-0.866</Real>
            <Real>-0.5</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>0.866</Real>
            <Real>-0.5</Real>
            <Real>0.0</Real>
        </Vector>
    </Triangle>
    <Defaults>
        <Parameters>
            <Vector>
                <Real>0.7</Real>
                <Real>0.0</Real>
                <Real>0.0</Real>
            </Vector>
            <Vector>
                <Real>0.6</Real>
                <Real>0.1</Real>
                <Real>0.0</Real>
            </Vector>
        </Parameters>
    </Defaults>
    <Triangle>
        <Vector>
            <Real>0.0</Real>
            <Real>0.0</Real>
            <Real>1.5</Real>
        </Vector>
        <Vector>
            <Real>0.866</Real>
            <Real>-0.5</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>0.0</Real>
            <Real>1.0</Real>
            <Real>0.0</Real>
        </Vector>
    </Triangle>
    <Defaults>
        <Parameters>
            <Vector>
                <Real>0.7</Real>
                <Real>0.0</Real>
                <Real>0.0</Real>
            </Vector>
            <Vector>
                <Real>0.9</Real>
                <Real>0.1</Real>
                <Real>0.0</Real>
            </Vector>
        </Parameters>
    </Defaults>
    <Triangle>
        <Vector>
            <Real>0.0</Real>
            <Real>1.0</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>-0.866</Real>
            <Real>-0.5</Real>
            <Real>0.0</Real>
        </Vector>
        <Vector>
            <Real>0.866</Real>
            <Real>-0.5</Real>
            <Real>0.0</Real>
        </Vector>
    </Triangle>
</Tinyray>
Bei längerem Code sollte man vielleicht die grafische Darstellung von Tinyray−DotCode der XML-Darstellung vorziehen.
Klarer als ein Syntaxbaum ist ein Graph.


Es ist aber auch möglich, die grafische Darstellung etwas kompakter zu machen.
Überschaubarer wird der Syntaxbaum, wenn man sich eine Ebene einsparen kann.





Info

stefan−baur.de / Tinyray−Parser
  • besucht am Dienstag, den 9. März 2010 um 23:49 Uhr
  • geändert am Sonntag, den 15. März 2009 von Stefan K. Baur
  • ähnliche Seiten:






Startseite

Copyright © 2004-2009 Stefan K. Baur − Druck20042005200620072008200920102011