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.
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:
- Wegen dem Startsymbol S stoße ich auf das Nichtterminal Tinyray und notiere meine ersten Terminale
TINYRAY LBRACE
- Jetzt könnte ich den geklammerten Ausdruck
- (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.
RBRACE
- Und erhalte im Gesamten drei Terminale.
TINYRAY LBRACE RBRACE
- Wenn ich diese Terminale T rücktransformiere, erhalte ich das kürzest mögliche
Tinyray−Programm.
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 = {
Tinyray, Global, Defaults, Geo, Sun, Camera,
Background, Ambience, Fog, Bounding, Triangle,
Sphere, Plane, Parameters, Vector
},
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:
First(Tinyray) = { TINYRAY }
Mit der Produktion
Vector
verhält es sich analog mit dem Ergebnis:
First(Vector) = { LBRACKET }
Der Vector beginnt also immer mit dem Terminalsymbol
LBRACKET.
Folgende Produktionen sind ähnlich einfach gelagert:
First(Defaults) = { DEFAULTS }
First(Sun) = { SUN }
First(Camera) = { CAMERA }
First(Background) = { BACKGROUND }
First(Ambience) = { AMBIENCE }
First(Fog) = { FOG }
First(Bounding) = { BOUNDING }
First(Triangle) = { TRIANGLE }
First(Sphere) = { SPHERE }
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:
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:
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:
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:
- 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!
- 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.
- 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.
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.
Es ist aber auch möglich, die grafische Darstellung etwas kompakter zu machen.