<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>xscDevBlog - LastSharp &#38; Co. &#187; HowTo</title>
	<atom:link href="http://dev.xscheme.de/category/codeschnipsel/howto/feed/" rel="self" type="application/rss+xml" />
	<link>http://dev.xscheme.de</link>
	<description>Der xscheme-DevelopmentBlog</description>
	<lastBuildDate>Sun, 23 May 2010 11:40:10 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Wie entwickle ich meine eigene Scriptsprache? (Teil 3: Syntax)</title>
		<link>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-3/</link>
		<comments>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-3/#comments</comments>
		<pubDate>Sun, 30 Aug 2009 11:26:44 +0000</pubDate>
		<dc:creator>WordPress</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Theorie]]></category>
		<category><![CDATA[eigene programmiersprache]]></category>
		<category><![CDATA[eigene scriptsprache]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=952</guid>
		<description><![CDATA[Nun, wie weit sind wir bisher? Wir haben einen Lexer, der unsere Eingabe in Einzelteile spaltet, und wir können einfache Ausdrücke in eine verwertbare Form bringen. Was wir bisher noch nicht (bewusst) gemacht haben, ist, einen Ausdruck zu analysieren und zu prüfen, ob er sinnvoll ist oder nur unzusammenhängend aneinandergereihte Token enthält.
Zur Erinnerung: sowohl &#8220;1+2+3&#8243; [...]]]></description>
			<content:encoded><![CDATA[<p>Nun, wie weit sind wir <a href="http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/">bisher</a>? Wir haben einen <a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/">Lexer</a>, der unsere Eingabe in Einzelteile spaltet, und wir können einfache Ausdrücke <a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/">in eine verwertbare Form bringen</a>. Was wir bisher noch nicht (bewusst) gemacht haben, ist, einen Ausdruck zu analysieren und zu prüfen, ob er sinnvoll ist oder nur unzusammenhängend aneinandergereihte Token enthält.</p>
<p>Zur Erinnerung: sowohl &#8220;1+2+3&#8243; als auch &#8220;1+(&#8221; wären Zeichenfolgen, die der Lexer akzeptieren und ohne zu meckern zerkleinern würde. Das ist auch in Ordnung so, da die <strong>syntaktische Prüfung</strong> ohnehin eher Aufgabe des Parsers ist. Aber was genau gilt es denn dabei zu beachten?</p>
<h2>&#8220;Syntaxfragen&#8221;</h2>
<ul>
<li>Sind genau so viele öffnende wie schließende Klammern vorhanden und passen diese zusammen?</li>
<li>Stimmt die Zahl der Parameter, mit der eine Funktion aufgerufen wird?</li>
<li>Stimmen die Parametertypen, mit denen eine Funktion aufgerufen wird? (&#8220;+&#8221; arbeitet normalerweise nur auf Zahlen, der Punkt z.B. in PHP auf Strings, etc&#8230;)</li>
<li>Befinden sich Operatoren an den richtigen Positionen? (Die Fakultät einer Zahl wird z.B. durch Anhängen eines Ausrufezeichens gekennzeichnet: &#8220;3!&#8221;)</li>
<li>Darf eine Funktion geschachtelt vorkommen? (z.B. wäre &#8220;echo(echo(1))&#8221; eher sinnlos, wenn &#8220;echo&#8221; die Ausgabefunktion ist)</li>
<li>Darf eine Funktion/ein Konstrukt in einer untergeordneten Umgebung vorkommen? (Das wäre z.B. der Fall, wenn man Funktionsdefinitionen innerhalb von Funktionsdefinitionen zulässt.)</li>
</ul>
<p>Neben der Syntax gibt es noch weitere Punkte, die eine Programmier-/Scriptsprache ausmachen. Eine Auflistung findet man z.B. <a href="http://www.pilgerer.org/pw/ProgrammierSprachen">hier</a>.</p>
<p><span id="more-952"></span></p>
<h2>Einiges ist schon erledigt</h2>
<p>Der im vorhergehenden Teil präsentierte Shunting-Yard-Algorithmus nimmt uns die Überprüfung der Klammern, sowie der Position der Parameter bereits ab. Des weiteren ist es unumgänglich zur Erstellung des Syntaxbaumes die Anzahl der Parameter zu kennen, die eine bestimmte Funktion benötigt. Wir können also unsere Liste auf die Hälfte kürzen.</p>
<h2>Einiges noch nicht</h2>
<p>Der Syntaxbaum selbst macht die Syntaxprüfung recht simpel. Aber zuerst müssen wir für jede Funktion, jeden Operator und jedes Terminalsymbol festlegen, welche syntaxktischen Eigenschaften es hat. Das wird letztlich mittels einer Klasse <em>SyntaxDefinition</em> realisiert werden, die die entsprechenden Daten kapselt.</p>
<p>Wichtig ist hierbei: wir betrachten all die genannten Datentypen als Funktionen, auch die Terminalsymbole. Jede Funktion hat einen Rückgabetyp und beliebig viele Parametertypvarianten. Eine &#8220;Zahl&#8221; wäre also eine Funktion mit dem Parametertyp &#8220;Zahl&#8221; und dem Rückgabetyp &#8220;Zahl&#8221;.</p>
<p>Eine beispielhafte Aufstellung für die Syntax einer Sprache wäre die folgende:</p>
<ul>
<li>Terminalsymbol <em>Integer</em>:
<ul>
<li>Eingabe: Integer</li>
<li>Rückgabe: Integer</li>
<li>Verschachtelung erlaubt</li>
<li>Unterordnung erlaubt</li>
</ul>
</li>
<li>Terminalsymbol <em>String</em>:
<ul>
<li>Eingabe: String</li>
<li>Rückgabe: String</li>
<li>Verschachtelung erlaubt</li>
<li>Unterordnung erlaubt</li>
</ul>
</li>
<li>Terminalsymbol <em>Identifier</em>:
<ul>
<li>Eingabe: Identifier</li>
<li>Rückgabe: Identifier</li>
<li>Verschachtelung erlaubt</li>
<li>Unterordnung erlaubt</li>
</ul>
</li>
<li>Funktion <em>DefConstant</em>:
<ul>
<li>Eingabe: (Identifier, Integer) oder (Identifier, String)</li>
<li>Rückgabe: keine</li>
<li>Verschachtelung nicht erlaubt</li>
<li>Unterordnung erlaubt</li>
</ul>
</li>
<li>Operator <em>Plus</em>:
<ul>
<li>Eingabe: (Integer, Integer)</li>
<li>Rückgabe: Integer</li>
<li>Verschachtelung erlaubt</li>
<li>Unterordnung erlaubt</li>
</ul>
</li>
<li>&#8230;</li>
</ul>
<p>Es gibt aber auch Funktionen, deren Rückgabetyp erst zum Ausführungszeitpunkt feststeht. Bestes Beispiel ist hier die Auswertung von Variablen, die ja Werte verschiedenster Typen enthalten können.</p>
<p>Wie würde also nun die Überprüfung der Typkorrektheit und der Verschachtelung ablaufen? Wie bereits erwähnt nutzen wir hierfür den Syntaxbaum und testen jeden einzelnen Knoten unter Berücksichtigung der Kindknoten:</p>
<pre>               operator : +
              /            \
        integer : 1     operator : *
                       /            \
                  integer : 2   function : echo
                                     |
                                constant : e</pre>
<p>Für unseren Test entspräche das dem folgenden Baum (Notation: &#8220;Rückgabe / Verschachtelung erlaubt?&#8221;):</p>
<pre>               integer / ja
              /            \
        integer / ja    integer / ja
                       /            \
                  integer / ja  void / nein
                                     |
                                runtime / ja</pre>
<p>Die Wurzel (das Plus) erwartet zwei Parameter des Typs &#8220;integer&#8221;. Eine Überprüfung der Kindknoten zeigt, dass diese genau diesen Rückgabetyp besitzen &#8211; also alles in Ordnung , auch die Verschachtelung.<br />
Betrachtet man den rechten Ast weiter, sieht man zwei Probleme: die Multiplikation benötigt zwei &#8220;integer&#8221;-Parameter, erhält aber &#8220;integer&#8221; und &#8220;void&#8221; (<em>void</em> ist der Ausdruck für &#8220;keine Rückgabe&#8221;); und die &#8220;echo&#8221;-Funktion kann nicht geschachtelt auftreten, befindet sich aber auf Ebene 2.</p>
<p>Einen dieser Fehler sollte der Parser letztlich ausgeben und abbrechen!</p>
<h2>Einiges muss warten</h2>
<p>Ob ein Ausdruck in einer untergeordneten Umgebung auftritt, kann ein reiner Parser nicht wissen. Man könnte ihm zwar diesbezügliche Informationen zukommen lassen (und sollte das auch, wenn man vorhat, einen Compiler o.Ä. zu schreiben), der einfachste Ort für diese Überprüfung ist jedoch die Auswertung. (Das ist auch scriptsprachentauglich.)</p>
<p>Was die Parametertypen angeht, die erst bei der Ausführung feststehen, kann man entweder mit regulären Ausdrücken arbeiten und mit deren Hilfe die Teilergebnisse untersuchen, bevor man die übergeordnete Funktion aufruft, oder man überlässt das ganz der jeweiligen Funktion selbst. Geschmacksache.</p>
<h2>Spezielle Konstrukte</h2>
<p>In jeder Sprache gibt es bestimmte Schlüsselwörter, die spezielle Konstrukte beschreiben/einleiten. Gemeint ist soetwas wie:</p>
<pre>def a = 2</pre>
<p>Der Shunting-Yard-Algorithmus kommt aber nur mit Operatoren oder mit Funktionen der Form &#8220;f(p1, p2, &#8230;)&#8221; klar.<strong> </strong></p>
<h3><strong>Kein großes Problem?</strong></h3>
<p>Gut, das wäre jetzt kein großes Problem für das oben stehende Beispiel: man definiert &#8220;def&#8221; und &#8220;=&#8221; als Operatoren, wobei &#8220;def&#8221; ein Präfix-Operator mit genau einem Parameter ist und eine höhere Priorität als &#8220;=&#8221; hat. Der Operator &#8220;def&#8221; legt eine leere Variable mit dem übergebenen Namen an und liefert diesen Namen als Rückgabewert.<br />
&#8220;=&#8221; wiederum ist ein Infix-Operator mit zwei Parametern, das die (existierende!) Variable des übergebenen Namens mit dem übergebenen Wert füllt.</p>
<p>Der Syntaxbaum für oben genannten Ausdruck wäre dann:</p>
<pre>              operator : =
              /          \
     operator : def   integer : 2
             |
     identifier : a</pre>
<p>Und schon hätten wir das  gewünschte Verhalten, allerdings auf Kosten der Übersichtlichkeit in der Implementierung, sowie eines sehr großen Freiraums in der Grammatik. Immerhin wäre so etwas dann auch erlaubt:</p>
<pre>(((((def a))))) = 2
def b = def a</pre>
<p>Ein Verhindern von Verschachtelungen ist hier nicht mehr möglich.</p>
<h3><strong>Bessere Lösung</strong></h3>
<p>Wir brauchen einen Mechanismus, der aus &#8220;def a = 2&#8243; so etwas macht wie &#8220;def(a, 2)&#8221;, also einen regulären Funktionsaufruf. Es handelt sich hierbei um eine Mustererkennung auf Basis bereits gelesener Token, d.h. die eigentliche Umwandlung findet nach dem erstmaligen Einlesen der Eingabesequenz durch den Lexer statt:</p>
<pre>keyword : def
identifier : a
operator : =
integer : 2</pre>
<p>wird zu</p>
<pre>function : def
open : (
identifier : a
comma : ,
integer : 2
close : )</pre>
<p>In meinen Augen bietet sich hierfür wieder  der in <a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/">Teil 1</a> unter Alternative 4 vorgestellte Algorithmus an, der z.B. mit einem Automaten arbeiten könnte, der die folgende Akzeptanzfunktion besitzt (Vorsicht: Pseudocode!):</p>
<pre>private int tokenNumber = -1;
public bool accept(Token t)
{
    tokenNumber++;
    return
        (tokenNumber == 0 &amp;&amp; "t ist vom Typ 'keyword' und hat den Wert 'def'") ||
        (tokenNumber == 1 &amp;&amp; "t ist vom Typ 'identifier'") ||
        (tokenNumber == 2 &amp;&amp; "t ist vom Typ 'operator' und hat den Wert '='") ||
        tokenNumber &gt; 2;
}</pre>
<p>Gleichzeitig müssten irgendwo die relevanten Token gesichert werden, damit die Umwandlung in eine Funktion später auch reibungslos vonstatten gehen kann.</p>
<p>Ich würde sagen, damit haben wir eine vernünftige Lösung gefunden.</p>
<h2>Fazit</h2>
<p>Wir können nun unseren Syntaxbaum auf syntaktische Merkmale hin untersuchen und spezielle Ausdrücke und Konstrukte berücksichtigen. Langsam sollten wir uns also an die Ausführung eines Ausdrucks machen.</p>
<p>Ein in diesem Artikel häufig verwendetes Wort war &#8220;Umgebung&#8221;. Es handelt sich dabei um den Speicher für Variablen, Funktionen, etc&#8230; Ohne diesen ist die Entwicklung einer Scriptsprache relativ witzlos, weswegen wir an genau dieser Stelle weitermachen werden.</p>
<h2>Inhalt</h2>
<ol>
<li><a href="../2009/07/eigene-programmiersprache-scriptsprach/">Einführung: Ein Abenteuer in Teilen</a></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/">Der Lexer</a></li>
<li><a href="../2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/">Grundlagen des Parsens</a></li>
<li><strong>Syntax</strong></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Wie entwickle ich meine eigene Scriptsprache? (Teil 2: Grundlagen des Parsens)</title>
		<link>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/</link>
		<comments>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/#comments</comments>
		<pubDate>Sat, 15 Aug 2009 13:51:46 +0000</pubDate>
		<dc:creator>WordPress</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Theorie]]></category>
		<category><![CDATA[eigene programmiersprache]]></category>
		<category><![CDATA[eigene scriptsprache]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=905</guid>
		<description><![CDATA[Einen wichtigen Schritt haben wir an dieser Stelle bereits hinter uns: Der hier beschriebene Lexer verwandelt eine Eingabesequenz wie &#8220;1+3*(4-2)&#8221;  in eine Liste von Tokens mit Typ und Wert:
zahl        :    1
operator    :    +
zahl       [...]]]></description>
			<content:encoded><![CDATA[<p>Einen wichtigen Schritt haben wir an dieser Stelle bereits hinter uns: Der <a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/">hier beschriebene</a> Lexer verwandelt eine Eingabesequenz wie &#8220;1+3*(4-2)&#8221;  in eine Liste von Tokens mit Typ und Wert:</p>
<pre>zahl        :    1
operator    :    +
zahl        :    3
operator    :    *
open        :    (
zahl        :    4
operator    :    -
zahl        :    2
close       :    )</pre>
<p>Damit haben wir eine maschinenlesbare Repräsentation des gewünschten Ausdrucks &#8211; allerdings keine Garantie dafür, dass dieser syntaktisch korrekt ist. Ebensowenig kann eine Maschine, die diese Liste nun vorgelegt bekommt, &#8220;einfach mal so&#8221; den Wert des Ausdrucks berechnen, da ihr Informationen zur Auswertungsreihenfolge fehlen. (Wenn 1+3 zuerst berechnet wird, lautet das Ergebnis 8, wenn 3*(4-2) zuerst berechnet wird, lautet es 7.)</p>
<p>Wir brauchen also eine Darstellung unseres Ausdrucks, die bezüglich der Reihenfolge der Auswertung eindeutig ist und eine Maschine, die diese erstellt  &#8211; und wie könnte es anders sein: auch dieses Problem wurde bereits einmal gelöst.</p>
<p>Wandeln wir also auf den Spuren von <a href="http://de.wikipedia.org/wiki/Edsger_Wybe_Dijkstra">Edsger W. Dijkstra</a>.</p>
<p><span id="more-905"></span></p>
<h2>Reverse Polish Notation</h2>
<p>Der polnische Mathematiker Jan Łukasiewicz entwickelte etwa 1920 die sog. <a href="http://en.wikipedia.org/wiki/Polish_notation">Polnische Notation</a> für arithmetische Ausdrücke, die sich dadurch auszeichnete, dass jedem Operator (&#8220;+&#8221;, &#8220;-&#8221;, &#8230;) direkt seine Operanden (Zahlen oder weitere arithmetische Ausdrücke) folgten. Durch diese Präfixnotation ergibt sich die Möglichkeit, auf Klammern, Kommas, etc&#8230; verzichten zu können, wenn man weiß, wie viele Operanden ein Operator benötigt:</p>
<pre>1+2+3         ==&gt;        + 1 + 2 3           (auch: + + 1 2 3)
1+3*(4-2)     ==&gt;        + 1 * 3 - 4 2
(3+4)*(4*3-1) ==&gt;        * + 3 4 - * 4 3 1</pre>
<p>Anhand des letzten Beispiels will ich die Auswertung so eines Ausdrucks beschreiben. Das Prinzip ist: &#8220;Werte den Ausdruck, der nur Zahlen als Operanden hat, als nächstes aus!&#8221;</p>
<pre>
<pre>   * + 3 4 - * 4 3 1        |   Das + bezieht sich auf 3 und 4!
=  * 7     - * 4 3 1        |   Das * bezieht sich auf 4 und 3!
=  * 7     - 12    1        |   Das - bezieht sich auf 12 und 1!
=  * 7     11               |   Das * bezieht sich auf 7 und 11!
=  77</pre>
</pre>
<p>Wie bereits erwähnt,<strong> funktioniert so eine Auswertung nur, wenn man weiß, wie viele Operanden/Parameter ein Operator/eine Funktion benötigt</strong>. Deswegen haben wir uns bei der Entwicklung des Lexers auch die Mühe gemacht, diese Information immer irgendwie bei der Hand zu haben.</p>
<p>Was Dijkstra nun gemacht hat, war, aus der Präfix- eine Postfixnotation zu machen, die sog. <a href="http://en.wikipedia.org/wiki/Reverse_Polish_notation">Reverse Polish Notation (RPN)</a>. Hier stehen nun alle Operanden <em>vor</em> dem Operator zu dem sie gehören:</p>
<pre>1+2+3         ==&gt;        1 2 3 + +          (auch: 1 2 + 3 +)
1+3*(4-2)     ==&gt;        1 3 4 2 - * +
(3+4)*(4*3-1) ==&gt;        3 4 + 4 3 * 1 - *</pre>
<p>Sowohl <em>Polish Notation</em> als auch <em>Reverse Polish Notation</em> beschreiben (unter der Voraussetzung, dass man die Anzahl der Parameter/Operanden kennt) eine <strong>eindeutige Auswertungsreihenfolge</strong>. Bei der letztlichen Auswertung ist die RPN aber ihrem Vorfahren in Bezug auf Speicherverbrauch und Verständlichkeit dann doch voraus. Die Regel: &#8220;Wenn ein Operator auftaucht, werte ihn aus!&#8221;</p>
<p>Wir benötigen hierbei einen <a href="http://de.wikipedia.org/wiki/Stapelspeicher">Stack</a>, der die Zwischenergebnisse speichert und der diese bei Auftreten eines Operators zur Berechnung zur Verfügung stellt.</p>
<pre>
<pre>----------------------------------------------------------------------------------
Eingabe            | Stack    |  Kommentar
----------------------------------------------------------------------------------
3 4 + 4 3 * 1 - *  |          |  Beginn des Algorithmus
4 + 4 3 * 1 - *    | 3        |  Zahl (3): auf den Stack!
+ 4 3 * 1 - *      | 3 4      |  Zahl (4): auf den Stack!
4 3 * 1 - *        | 7        |  Operator (+): hole 3 und 4, berechne, speichere
3 * 1 - *          | 7 4      |  Zahl (4): auf den Stack!
* 1 - *            | 7 4 3    |  Zahl (3): auf den Stack!
1 - *              | 7 12     |  Operator (*): hole 4 und 3, berechne, speichere
- *                | 7 12 1   |  Zahl (1): auf den Stack!
*                  | 7 11     |  Operator (-): hole 12 und 1, berechne, speichere
                   | 77       |  Operator (*): hole 7 und 11, berechne, speichere
                   | 77       |  Eingabe leer, Ergebnis auf Stack.</pre>
</pre>
<p>Dies umzusetzen ist kein großes Problem und spricht in jedem Fall für die <em>Reverse Polish Notation</em> als Darstellung eines auszuwertenden Ausdrucks. Und dann wäre da noch die kleine, aber feine Tatsache, dass es einen Algorithmus gibt, der die <strong>Umwandlung</strong> &#8220;normaler&#8221; (Infix-)Ausdrücke (à la &#8220;1+2*f(3)&#8221;) in diese Notation vornimmt.</p>
<h2>Der Shunting-Yard-Algorithmus</h2>
<p>Der <a href="http://en.wikipedia.org/wiki/Shunting_yard_algorithm">Shunting-Yard-Algorithmus</a> (zu deutsch: <em>Rangierbahnhofalgorithmus</em>) ist eine ebenfalls von Dijkstra entwickelte Methode zur Umwandlung von Infix-Ausdrücken in Postfix-Notation. Sein Name kommt daher, dass seine Funktionsweise den Abläufen in einem Rangierbahnhof entspricht: alle terminalen Symbole (z.B. Zahlen, Variablen, &#8230;) sind Waren und Güter, die von Zügen (den nichtterminalen Symbolen wie Operatoren und Funktionen) mitgenommen werden müssen.</p>
<p>Die folgende Animation zeigt (vereinfacht) das Prinzip des Algorithmus. Er basiert darauf, dass in der Zugwarteschlange immer der Zug mit der höchsten Priorität (in der Realität z.B. die Geschwindigkeit) ganz vorne steht. Will sich ein langsamerer Zug einreihen, müssen also zuerst alle schnelleren Züge losgefahren sein. (Jeder Zug kann beliebig viele Waren transportieren und nimmt beim Abfahren alle verfügbaren mit! Es kann also auch passieren, dass ein Zug leer losfährt.)</p>
<p>Übertragen auf die <em>Reverse Polish Notation</em>: <strong>Die Operationen mit der niedrigsten Priorität werden zuletzt ausgeführt, entsprechen also den langsamsten Zügen</strong>. Mit ein wenig Überlegung sieht man, dass der Algorithmus also genau das Ergebnis liefern wird, was wir haben wollen!</p>
<p style="text-align: center;"><img class="size-full wp-image-911 aligncenter" style="border: 1px solid #bbbbbb; padding: 1em;" title="shuntingyard" src="http://dev.xscheme.de/wp-content/uploads/2009/08/shuntingyard.gif" alt="shuntingyard" width="400" height="400" /></p>
<p style="text-align: left;">Die Waren-Schlange kann übrigens in der letztlichen Implementierung entfallen, wenn man alle Waren einfach direkt zum Ausgang schiebt und immer nur den Zug voranstellt, der sie mitnehmen soll.</p>
<p style="text-align: left;">Hinzu kommt noch die Behandlung von Klammern und Trennsymbolen (z.B Kommas). Ich möchte den Algorithmus hier nicht im Detail aufschreiben, da der zugehörige Wikipedia-Artikel (englisch) eine <a href="http://en.wikipedia.org/wiki/Shunting_yard_algorithm#The_algorithm_in_detail">genaue Beschreibung</a> enthält.</p>
<p style="text-align: left;">Wichtig ist:</p>
<ul>
<li>Der Algorithmus kann Operatoren, Terminalsymbole, Klammern, Trennzeichen und Funktionen verarbeiten, d.h. wenn wir spezielle Konstrukte haben (z.B. &#8220;def x=a*(b-1)&#8221;) müssen wir diese erst in Funktionsform bringen. (z.B. &#8220;definition(x, a*(b-1))&#8221;)</li>
<li>Der Algorithmus hat eine Laufzeit in <strong>O(n)</strong>, d.h. er ist vergleichsweise effizient.</li>
</ul>
<h2>Reverse Polish Notation vs. Syntaxbaum</h2>
<p>Eine weitere Möglichkeit, die Ausführungsreihenfolge eindeutig festzulegen, ist das Erstellen eines Syntaxbaumes. Für den Ausdruck &#8220;1+2*sqrt(e)&#8221; hat dieser z.B. die folgende Form:</p>
<pre>               operator : +
              /            \
        integer : 1     operator : *
                       /            \
                  integer : 2   function : sqrt
                                     |
                                constant : e</pre>
<p>Die Auswertung erfolgt von unten nach oben, d.h. bei der letztlichen Berechnung würden folgende Schritte ausgeführt:</p>
<pre>e                 =&gt; 2.71
sqrt(2.71)        =&gt; 1.65
2                 =&gt; 2
2 * 1.65          =&gt; 3.3
1                 =&gt; 1
1 + 3.3           =&gt; 4.3</pre>
<p><strong>Das besondere ist, das jeder Syntaxbaum sehr einfach in <em>Reverse Polish Notation</em> umgewandelt werden kann und jede RPN ebenso einfach in einen Syntaxbaum. </strong></p>
<p>Für erstere Umwandlung durchläuft man den Baum als <em>Post-Order-Traversierung</em>, d.h. man schreibt ausgehend von der Wurzel zuerst die <em>Post-Order-Notation</em> der Kindknoten auf und hängt anschließend den Wert der Wurzel daran an.</p>
<p>Für die entgegensetzte Transformation tut man so, als würde man die RPN-Notation auswerten (siehe oben!), aber anstatt auf dem Stack Zwischenergebnisse zu speichern, sichert man dort die Teilbäume:</p>
<pre>
<pre>-----------------------------------------------------------------------------------
Eingabe            | Stack           |  Kommentar
-----------------------------------------------------------------------------------
3 4 + 4 3 * 1 - *  |                     |  Beginn des Algorithmus
4 + 4 3 * 1 - *    | 3                   |  Zahl (3): <span style="text-decoration: underline;">Blatt</span> auf den Stack!
+ 4 3 * 1 - *      | 3 4                 |  Zahl (4): Blatt auf den Stack!
4 3 * 1 - *        | +(3,4)              |  Operator (+): hole Blätter, bilde Baum
3 * 1 - *          | +(3,4) 4            |  Zahl (4): Blatt auf den Stack!
* 1 - *            | +(3,4) 4 3          |  Zahl (3): Blatt auf den Stack!
1 - *              | +(3,4) *(4,3)       |  Operator (*): hole Blätter, bilde Baum
- *                | +(3,4) *(4,3) 1     |  Zahl (1): Blatt auf den Stack!
*                  | +(3,4) -(*(4,3), 1) |  Operator (-): hole Blatt/Teilbaum, bilde Baum
[...]

Ergebnis: *(+(3,4), -(*(4,3), 1))</pre>
</pre>
<p>Ich hoffe die Baumnotation &#8220;Wurzel(Teilbaum1, Teilbaum2, &#8230;)&#8221; ist verständlich.</p>
<p>Es gibt hierzu noch eine weitere wichtige Anmerkung:<br />
<strong>Jeder Algorithmus, der die <em>Reverse Polish Notation</em> eines Ausdrucks erstellen kann, kann so modifiziert werden, dass er einen Syntaxbaum erstellt und jeder Algorithmus, der einen Syntaxbaum erstellt, kann auch die RPN eines Ausdrucks erstellen!</strong></p>
<p>Beide Darstellungen sind also gleichwertig in der Funktionalität, der Syntaxbaum benötigt jedoch mehr Speicherplatz, außerdem ist eine Auswertung des Baumes auf rekursivem Weg ebenfalls nicht gerade resourcenschonend.</p>
<h2>Fazit</h2>
<p>Auch wenn die <em>Reverse Polish Notation</em> eines Ausdrucks Verarbeitungsvorteile mit sich bringt, erschwert sie die Untersuchung der Eingabe auf syntaktische Korrektheit (vor allem Typkorrektheit!). Unser Parser muss also intern einen Syntaxbaum erstellen, diesen überprüfen und anschließend die RPN des Ausdrucks ausgeben. Damit dürften wir auf der sicheren Seite sein.</p>
<p>Nur: Was ist eigentlich Syntax?</p>
<h2>Inhalt</h2>
<ol>
<li><a href="http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/">Einführung: Ein Abenteuer in Teilen</a></li>
<li><a href="../2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/">Der Lexer</a></li>
<li><strong>Grundlagen des Parsens</strong></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-3/">Syntax</a><strong><br />
</strong></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Wie entwickle ich meine eigene Scriptsprache? (Teil 1: Der Lexer)</title>
		<link>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/</link>
		<comments>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/#comments</comments>
		<pubDate>Tue, 11 Aug 2009 18:55:07 +0000</pubDate>
		<dc:creator>WordPress</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Theorie]]></category>
		<category><![CDATA[eigene programmiersprache]]></category>
		<category><![CDATA[eigene scriptsprache]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=889</guid>
		<description><![CDATA[Die Aufgabe, die ich mir hier gestellt habe, eine eigene Script- oder Programmiersprache zu entwickeln, bringt so ihre Probleme mit sich. Wie und wo fange ich an? Ist das nicht zu viel für mich? Und sollte ich nicht auf bereits vorhandene Bibliotheken zurückgreifen, anstatt alles von Grund auf neu zu entwickeln?
Vor allem die letzte Frage [...]]]></description>
			<content:encoded><![CDATA[<p>Die Aufgabe, die ich mir <a href="http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/">hier</a> gestellt habe, eine <strong>eigene Script- oder Programmiersprache</strong> zu entwickeln, bringt so ihre Probleme mit sich. Wie und wo fange ich an? Ist das nicht zu viel für mich? Und sollte ich nicht auf bereits vorhandene Bibliotheken zurückgreifen, anstatt alles von Grund auf neu zu entwickeln?</p>
<p>Vor allem die letzte Frage kann einem zu schaffen machen. Warum etwas möglicherweise nicht gut funktionierendes entwickeln, wenn es doch überall schon tausendfach durchdachte Lösungen gibt? Immerhin lautet doch eine der obersten Regeln in der Softwareentwicklung:</p>
<blockquote><p>Don&#8217;t reinvent the wheel!</p></blockquote>
<p>Nun, ich bin Informatikstudent. Ich will wissen, wie die Dinge &#8220;unter der Haube&#8221; aussehen, ich will lernen. Und wenn alle nur noch auf vorgefertige Bibliotheken zurückgreifen, weiß doch bald keiner mehr, was dem Zauber eigentlich zugrunde liegt&#8230; <a href="http://www.codinghorror.com/blog/archives/001145.html">Dementsprechend</a>:</p>
<blockquote><p><strong>Don&#8217;t reinvent the wheel, unless you plan on learning more about wheels!</strong></p></blockquote>
<p>Diese Anleitung soll jedem helfen, der interessiert daran ist, was im Inneren eines Interpreters oder Compilers eigentlich (ungefähr so) abläuft. Ich selbst habe gerade erst das zweite Semester hinter mir, d.h. allzu theoretisch wird es hier nicht und jeder mit ein wenig logischem Denken und genug Engagement sollte es hinbekommen, meinen Ausführungen zu folgen.</p>
<p>Lasset uns beginnen. <img src='http://dev.xscheme.de/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p><span id="more-889"></span></p>
<h2>Der Lexer</h2>
<p>Ein <a href="http://de.wikipedia.org/wiki/Lexikalischer_Scanner">lexikalischer Scanner</a> (kurz: <em>Lexer</em>) ist eine &#8220;Maschine&#8221;, die eine Zeichenfolge als Eingabe erhält und diese in kleinere Teile spaltet, denen eine bestimmte Bedeutung/ein bestimmter Typ zugrunde liegt.</p>
<p>Ein Taschenrechner, beispielsweise, kennt Zahlen, Operatoren und Klammern. Gibt man dem Lexer dieses Taschenrechners nun die Folge &#8220;1+2*(4-3)&#8221; zur Verarbeitung, wird daraus die folgende (oder eine ähnliche) Liste:</p>
<pre>zahl        :    1
operator    :    +
zahl        :    2
operator    :    *
open        :    (
zahl        :    4
operator    :    -
zahl        :    3
close       :    )</pre>
<p>Die einzelnen Zeilen nennt man <em>Tokens</em>. Sie bestehen aus einem Typ (&#8220;zahl&#8221;, &#8220;operator&#8221;, &#8230;) und einem Wert (&#8220;1&#8243;, &#8220;+&#8221;, &#8230;) und kapseln somit alle Informationen, die man benötigt, um damit weiterzuarbeiten.</p>
<p>Wichtig ist folgendes: der Lexer überprüft nicht den &#8220;Sinn&#8221; der Eingabe, d.h. auch ein nicht wohlgeformter Ausdruck wie &#8220;1+)(*2&#8243; würde von ihm brav und folgsam in eine Liste von Tokens umgewandelt werden. Die <em>semantische</em> Überprüfung erfolgt an anderer Stelle: dem <em>Parser</em>.</p>
<h2>Prinzip</h2>
<p>Wie schreibe ich einen Lexer? Das ist keine allzu schwere Sache, die Frage ist nur, wie effizient er letztlich ist. Ich werde hier verschiedene Ansätze darstellen, die jeweils Vor- und Nachteile haben.</p>
<h3><strong>Ansatz 1: Reguläre Ausdrücke auf den Anfang der Eingabesequenz anwenden<br />
</strong>(~ nicht naiv, aber auch nicht so der Hammer)<strong><br />
</strong></h3>
<ul>
<li>Der Lexer verwaltet eine Reihe von <strong>Datentypen</strong>, die durch <a href="http://de.wikipedia.org/wiki/Regul%C3%A4re_Ausdr%C3%BCcke">reguläre Ausdrücke</a> (Testen kann man z.B. <a href="http://www.regex-tester.de/regex.html">hier</a>) eindeutig definiert sind. Also z.B.:
<pre>zahl   =&gt;    [0-9]+
id     =&gt;    [a-zA-Z]+
open   =&gt;    \(
close  =&gt;    \)
comma  =&gt;    ,
op     =&gt;    [+\-]
...</pre>
</li>
<li></li>
<li>Auf Basis dieser Typen erfolgt nun die <strong>Umwandlung einer Eingabezeichenfolge in Tokens</strong>:
<ul>
<li>Erstelle eine leere Ausgabeliste.</li>
<li>Während die Zeichenfolge nicht leer ist:
<ol>
<li>Vergleiche den Anfang der Zeichenfolge mit den regulären Ausdrücken aller verfügbaren Datentypen, bis du einen Treffer landest.</li>
<li>Kein Treffer: Die Zeichenfolge enthält einen unbekannten Datentyp!</li>
<li>Ansonsten:
<ul>
<li>Schneide den gefundenen Ausdruck von der Eingabezeichenfolge ab.</li>
<li>Erstelle ein Token des gefundenen Typs und hänge es an die Ausgabeliste an.</li>
</ul>
</li>
<li>Gib die Ausgabeliste aus.</li>
</ol>
</li>
</ul>
</li>
<li>Ein <strong>Token</strong> enthält (wie bereits erwähnt) den Typ und Wert eines Ausdrucks. Des weiteren könnte es sich anbieten, auch jeweils Verweise zum vorhergehenden und nachfolgenden Token bereitzustellen, die ja Einfluss auf den <em>realen</em> Typ eines Tokens haben können.<br />
(Ein Plus benötigt z.B. normalerweise zwei Parameter, wenn aber vor dem Plus ein anderer Operator und danach eine Zahl steht, ist diese Zahl der einzige Parameter und das Plus ist ein Vorzeichen. Die Methode, die solche Sachen überprüft, sollte in der Implementierung der Datentypen auftauchen!)</li>
</ul>
<p>Der Vorteil dieser Methode ist die<strong> Erweiterbarkeit</strong>. Wenn man einen neuen Typen hinzufügen will, erstellt man einfach den dazugehörigen Ausdruck. Den Rest übernimmt der Lexer.</p>
<p>Ein gewaltiger Nachteil ist die Zuverlässigkeit: oft gibt es Mehrdeutigkeiten bei regulären Ausdrücken und der Lexer weiß nicht, welcher gerade der richtige ist. Außerdem lässt die Geschwindigkeit zu wünschen übrig: Im schlimmsten Fall (die gesamte Eingabe besteht aus n Tokens des gleichen Typs und dieser Typ ist der letzte, der überpürft wird) beträgt  bei einer Gesamtzahl von m Datentypen die Laufzeit: O(n*m*O(regulärer Ausdruck)) also vermutlich irgendetwas im Bereich <strong>O(m*n<sup>2</sup>)</strong></p>
<h3><strong>Ansatz 2: Spezialisierung auf bekannte Eingabetypen</strong><br />
(~ naiver Ansatz)</h3>
<p>Wenn man den Lexer nicht für beliebige Datentypen und -formate ausrichtet, sondern von vornherein weiß, wie viele es davon gibt und wie sie aussehen, kann man die Realisierung einfacher/verständlicher machen:</p>
<ol>
<li>Initialisiere einen Zähler i mit 0 und eine leere Liste für die Ausgabe.</li>
<li>Während i kleiner ist als die Länge der Eingabe:
<ul>
<li>Überprüfe das i-te Zeichen.</li>
<li>Wenn es eine Zahl ist, erhöhe i so lange um eins, bis das i-te Zeichen keine Zahl mehr ist und erstelle aus den dabei &#8220;überstrichenen&#8221; Zeichen ein Token des Typs &#8220;integer&#8221;. (Erstellt Token für Zahlen.)</li>
<li>Wenn es ein Buchstabe ist, erhöhe i so lange um eins, bis das i-te Zeichen weder Zahl noch Buchstabe ist und erstelle aus den dabei überstrichenen Zeichen ein Token des Typs &#8220;identifier&#8221;. (Erstellt Token für Variablen)</li>
<li>Wenn es ein Anführungszeichen ist, erhöhe i so lange um eins, bis das i-te Zeichen ebenfalls ein Anführungszeichen und das (i-1)-te Zeichen kein Backslash ist [...] (Erstellt Token für Strings)</li>
<li>usw&#8230;</li>
<li>Hänge das erstellte Token an die Ausgabeliste an.</li>
</ul>
</li>
<li>Gib die Ausgabeliste aus.</li>
</ol>
<p>Der Vorteil ist ganz klar die Geschwindigkeit: Jedes Zeichen der Eingabesequenz wird höchstens zweimal überprüft, d.h. die Laufzeit liegt in <strong>O(n)</strong>.</p>
<p>Nachteile sind hier die umständliche Erweiterbarkeit, sowie der meist aufgeblähte Code.</p>
<h3><strong>Ansatz 3: Endlicher Automat<br />
</strong>(~ professionell und kompliziert (?))</h3>
<p>Ansatz 2 war schon nichts anderes als die prinzipielle Funktionsweise eines <a href="http://de.wikipedia.org/wiki/Endlicher_Automat">endlichen Automaten</a>: Ausgehend von einem aktuellen Zustand (z.B. &#8220;Kein Zeichen überprüft&#8221;, &#8220;Stringbeginn entdeckt&#8221;) und einer Eingabe (z.B. das nächste Zeichen) geht der Automat in einen neuen Zustand (z.B. &#8220;Unerlaubtes Zeichen&#8221;, &#8220;Stringinhalt&#8221;) über. Wenn irgendwann ein Ausgabezustand erreicht wird, erhält man das gerade gelesene Token.</p>
<p>Wenn wir beispielsweise nur die Schlüsselwörter &#8220;echo&#8221; und &#8220;echtheit&#8221; hätten, ergäbe sich folgender Automat. (Startsymbol: S, Endsymbol E):</p>
<pre>                                         "echo" -----&gt; E("echo")
    e          c           h          o /
S -----&gt; "e" -----&gt; "ec" -----&gt; "ech"
                                      t \         h              e
                                         "echt" -----&gt; "echth" -----&gt; ... -----&gt; E("echtheit")</pre>
<p>Wenn ein Zustandsübergang nicht möglich ist, hat man es mit einer unerlaubten Sequenz zu tun. Ein detaillierteres Beispiel zur Modellierung eines endlichen Automaten findet man <a href="http://www.htw-dresden.de/~beck/Compiler/doc/lex.html">hier</a>.</p>
<p>Das besondere an endlichen Automaten ist, dass jeder reguläre Ausdruck in so einen Automaten verwandelt werden kann. Eine detaillierte Beschreibung für Interessierte gibt es <a href="http://www.informatik.uni-bremen.de/agbs/lehre/ws0607/uegen/folien-lex-analyse-2x2.pdf">hier (Uni Bremen)</a>.</p>
<p>Vor- und Nachteile entsprechen Ansatz 2, die Laufzeit beträgt ebenfalls <strong>O(n)</strong>. Endliche Automaten werden von fast allen Lexer-/Parser-Generatoren (auf Basis einer Grammatik) erstellt, sind also der meistverbreitete Ansatz zum Erstellen eines Lexers.</p>
<h3><strong>Ansatz 4: &#8220;Akzeptanztest&#8221; auf Basis vieler kleiner Typ-Automaten</strong><br />
( ~ Symbiose von Ansatz 1 und 3)</h3>
<p>Ich stand also nun vor dem Problem, zwischen Effizienz und Erweiterbarkeit abzuwägen &#8211; genauer gesagt: mich für eines von beiden entscheiden zu müssen &#8211; und das passte mir nicht wirklich. Also, zur Ablenkung mal weg vom Schreibblock und Computer, zum Lidl einkaufen und Flaschen zurückbringen. Was man halt so tut.</p>
<p>Und da war er: ein <strong>Pfandflaschen-Rückgabe-Automat,</strong> der nichts anderes tat, als tagein, tagaus Flaschen anzunehmen und zu überprüfen. Passte die Flasche, gab es einen Zettel, der bares Geld wert war, passte sie nicht, eine Fehlermeldung.</p>
<p><a href="http://dev.xscheme.de/wp-content/uploads/2009/08/miniautomat.png"><img class="alignleft size-full wp-image-936" style="border: 1px solid #bbbbbb; padding: 1em; margin-right: 1em; margin-bottom: 1em;" title="miniautomat" src="http://dev.xscheme.de/wp-content/uploads/2009/08/miniautomat.png" alt="miniautomat" width="111" height="158" /></a>Warum erzähle ich das? Nun, dieser Automat war die Lösung für mein Problem! <strong><br />
Man braucht viele kleine, auf einen bestimmten Typ (von Token, von Flaschen, &#8230;) spezialisierte Automaten, die anhand der bereits zuvor eingegebenen Daten (gelesene Zeichen, akzeptierte Flaschen, &#8230;) sagen, ob sie eine Eingabe (das nächste Zeichen, die nächste Flasche, &#8230;) akzeptieren. Oder eben nicht.</strong></p>
<p>Das Beispiel aus Ansatz 3  würde hierbei so realisiert: Sowohl der &#8220;echo&#8221;-Automat, als auch der &#8220;echtheit&#8221;-Automat würden die Eingaben &#8220;e&#8221;, &#8220;c&#8221; und &#8220;h&#8221; (in dieser Reihenfolge) akzeptieren, aber wenn nun ein &#8220;o&#8221; kommt, wird sich der &#8220;echtheit&#8221;-Automat aufregen. Für die weitere Überprüfung kann er also ignoriert werden. Und erst wenn auch der &#8220;echo&#8221;-Automat nicht mehr weitermachen will (also nach besagtem &#8220;o&#8221;), hat man sein Token gefunden und kann es über die Ausgabefunktion des Automaten auslesen.</p>
<p><strong>Allgemein:</strong> Man habe eine Menge A von Typ-Automaten und eine Eingabesequenz &lt;s<sub>0</sub>, s<sub>1</sub>, s<sub>2</sub>, s<sub>3</sub>, &#8230;, s<sub>n</sub>&gt;</p>
<ol>
<li>Versetze alle Automaten in A in ihren <strong>Ausgangszustand</strong>. (<em>Reset</em>)</li>
<li>Finde alle Automaten in A, <strong>die s<sub>0</sub> akzeptieren</strong> und bilde aus ihnen die Menge A<sub>0</sub></li>
<li>Initialisiere i mit 0. Während 0 &lt;= i &lt; n:
<ul>
<li>Wenn |A<sub>i</sub>| = 0: brich Schleife ab.</li>
<li>Ansonsten: Finde alle Automaten in A<sub>i</sub>, die s<sub>i+1</sub> akzeptieren und bilde aus ihnen die Menge A<sub>i+1</sub></li>
<li>Erhöhe i um 1.</li>
</ul>
</li>
<li>Wenn i=0: Ungültiger Ausdruck.</li>
<li>Ansonsten: Finde einen  <strong>passenden Typ</strong> aus A<sub>i-1</sub> bis A<sub>0</sub>, der sich in einem Ausgabezustand befindet (der Index der entsprechenden Automatenmenge sei j), und erstelle ein Token mit dem Wert &lt;s<sub>0</sub>, s<sub>1</sub>,&#8230;, s<sub>j</sub>&gt; und dem gefundenen Typen.</li>
<li>Fahre mit der restlichen Eingabesequenz fort.</li>
</ol>
<p>Betrachten wir die Laufzeit: Schritt 1 liegt in O(|A|), Schritt 2 ebenfalls. Schritt 3 benötigt im schlimmsten Fall (die Zeichenfolge wird von allen Automaten komplett akzeptiert) eine Laufzeit von O(n*|A|). Schritt 5 kann ebenfalls eine Laufzeit in O(n*|A|) erreichen, wenn kein einziger Automat in einem Ausgabezustand ist, die restlichen Schritte haben einen konstanten Zeitbedarf O(1).</p>
<p>Diese Methode ermittelt also den nächsten Token mit einer Laufzeit von O((2n+2)*|A|+1), also <strong>O(n*|A|)</strong>. Gleichzeitig ist <strong>Erweiterbarkeit</strong> gewährleistet, da die Typ-Automaten sehr leicht zu implementieren sind und der Lexer sie nicht von vornherein kennen muss.</p>
<p><strong>Zuverlässigkeit</strong> ist nur in Schritt 5 fraglich: Was ist der &#8220;passende Typ&#8221; zu einem Token? Hier könnte man ein hierarchisches System einführen, das z.B. festlegt: &#8220;Wenn die Wahl zwischen dem Typ Funktion und dem Typ Bezeichner getroffen werden muss, nimm die Funktion!&#8221;</p>
<p>Die Funktion eines Integer-Automats, die überprüft, ob ein Zeichen akzeptiert wird, könnte z.B. so aussehen:</p>
<pre>public bool Accept(char c)
{
    switch (char)
    {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            return true;
        default:
            return false;
    }
}</pre>
<p>Damit kann man leben, oder? Schwieriger wird es erst bei solchen Sachen wie Strings, wo z.B. beachtet werden muss, dass ein gültiges Zeichen, das nach dem Schließen des Strings kommt, natürlich nicht mehr akzeptiert werden darf. (Man müsste also im Automaten selbst weitere Zustandsdaten, z.B. einen boolschen Wert <em>stringClosed</em> oder so, speichern.)</p>
<h3><strong>Fazit</strong></h3>
<p>Um eine größtmögliche Erweiterbarkeit zu gewährleisten und weil ich stolz auf die Idee bin und überprüfen will, wie sie sich umsetzen lässt, werden wir Ansatz 4 implementieren. Ich frage mich allerdings, was &#8220;Pfandflaschenalgorithmus&#8221; auf Englisch heißt&#8230;</p>
<p><strong>Update (30.08.2009): </strong><br />
Eine bessere Analogie für den Algorithmus wäre statt dem Pfandflaschenautomaten wohl ein Rennen oder ein Wettbewerb: derjenige, der am weitesten kommt (&#8220;die meisten Eingaben akzeptiert), ist der Sieger &#8211; außer er bricht dann zusammen, fängt an zu heulen, etc&#8230; (&#8220;außer er befindet sich nicht in einem Ausgabezustand&#8221;) In dem Fall gewinnt der zweite Platz (der aber identisch mit dem ersten sein kann), außer auch dieser kommt mit dem Druck nicht klar. Und so weiter.</p>
<h2>Implementierung</h2>
<p style="text-align: left;"><a href="http://dev.xscheme.de/wp-content/uploads/2009/08/lexer.png"><img class="alignleft size-medium wp-image-940" style="border: 1px solid #bbbbbb; padding: 1em; margin-right: 1em; margin-bottom: 1em;" title="lexer" src="http://dev.xscheme.de/wp-content/uploads/2009/08/lexer-300x124.png" alt="lexer" width="300" height="124" /></a>Bei der Implementierung des Lexers sollten gleich alle verfügbaren Datentypen (Operator, Trennzeichen, Terminalausdruck, &#8230;) in eigenen Klassen gekapselt werden. Das macht die spätere Verwendung einfacher.</p>
<p style="text-align: left;">Zwei Sachen sollten hier näher erläutert werden. Zuerst die Methode <strong><em>signification </em></strong>der Klasse <em>SyntaxType</em>: sie ermittelt anhand eines konkreten Tokens den &#8220;<strong>wirklichen Typ</strong>&#8221; dieses Tokens. Man könnte z.B. einen Grundtyp <em>Identifier</em> definieren (als Unterklasse von <em>Terminal</em>), der alle Buchstabenfolgen (keine Zahlen, keine Sonderzeichen) repräsentiert. Die <em>signification</em>-Methode könnte dann ermitteln ob der Identifier eine Variable, ein Listenname, eine Konstante, etc&#8230; ist und dies zurückliefern. Oder eben oben genanntes Beispiel mit dem Pluszeichen, das abhängig vom Kontext ein Operator oder ein Vorzeichen sein kann.</p>
<p style="text-align: left;">Zweitens das Interface <strong><em>IStateMachine</em></strong>: Es kapselt die für Ansatz 4 (s.o.) benötigten Operationen. (Die Methode <em>IsOutputState</em> fehlt, sollte aber nicht vergessen werden!)</p>
<p style="text-align: left;"><strong>Update (12.08.2009):</strong><br />
Datentypen, die spezielle, angepasste Methoden <em>signification</em> benötigen, müssen als Unterklassen der jeweiligen Typen (hauptsächlich <em>Terminal</em> vermutlich) definiert werden und die Methoden überschreiben. Nur, falls das noch nicht klar war.</p>
<h2 style="text-align: left;">Gesamtfazit</h2>
<p style="text-align: left;">Nun kommen wir leicht an die einzelnen Bestandteile eines Ausdrucks, aber uns fehlen noch die Mittel, damit richtig umzugehen. Der nächste Schritt ist es also, Regeln für unsere verschiedenen Ausdrücke zu erstellen (Welche Art von Parametern benötigt eine Funktion/ein Operator/ein Pattern? Darf eine Funktion verschachtelt ausgeführt werden? Etc&#8230;) und anhand dieser Regeln einen gegebenen Ausdruck zu überprüfen und in eine einfach weiterzuverarbeitende Form zu bringen.</p>
<p style="text-align: left;">Hier kommt der sog. <em>Syntaxbaum</em> ins Spiel. Und der <em>Parser</em>, der ihn erstellt.</p>
<h2>Inhalt</h2>
<ol>
<li><a href="http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/">Einführung: Ein Abenteuer in Teilen</a></li>
<li><strong>Der Lexer</strong></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/">Grundlagen des Parsens</a></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-3/">Syntax</a><strong><br />
</strong></li>
</ol>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 3149px; width: 1px; height: 1px;">
<ul>
<li>Schneide den gefundenen Ausdruck von der Eingabezeichenfolge ab.</li>
<li>Wenn der Treffer ein Datenmuster ist, wende den Lexer auf alle Unterwerte an und hänge nacheinander ein Pattern-Token, eine öffnende Klammer, die verarbeiteten Unterwerte durch Kommas getrennt und eine schließende Klammer an die Ausgabeliste an.</li>
<li>Wenn der Treffer ein normaler Datentyp ist, erstelle ein Token dieses Typs und hänge es an die Ausgabeliste an.</li>
</ul>
</div>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Wie entwickle ich meine eigene Scriptsprache? Ein Abenteuer in Teilen.</title>
		<link>http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/</link>
		<comments>http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/#comments</comments>
		<pubDate>Tue, 28 Jul 2009 18:19:55 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Theorie]]></category>
		<category><![CDATA[anleitung]]></category>
		<category><![CDATA[eigene programmiersprache]]></category>
		<category><![CDATA[eigene scriptsprache]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=793</guid>
		<description><![CDATA[In diesem Artikel, dem ersten von vielen, geht es um die Entwicklung einer eigenen Script- oder Programmiersprache. Das sei nur für die erwähnt, die sich Angesichts des Vorgeplänkels der nächsten zwei Absätze fragen: &#8220;Was wird das denn jetzt?&#8221; und dann wieder verschwinden wollen&#8230;
Vorspiel
Neben LastSharp und LeSharp, den beiden Tools, die für eine breite Masse gedacht [...]]]></description>
			<content:encoded><![CDATA[<p><em>In diesem Artikel, dem ersten von vielen, geht es um die Entwicklung einer eigenen Script- oder Programmiersprache. Das sei nur für die erwähnt, die sich Angesichts des Vorgeplänkels der nächsten zwei Absätze fragen: &#8220;Was wird </em>das<em> denn jetzt?</em>&#8221; <em>und dann wieder verschwinden wollen&#8230;</em></p>
<h2>Vorspiel</h2>
<p>Neben LastSharp und LeSharp, den beiden Tools, die für eine breite Masse gedacht und deshalb auch zumindest ein bisschen nützlich sind, arbeite ich noch an zwei eher theoretischen Projekten: UVD (das im Moment ruht) und Lapicon (Loose API Connection Language).</p>
<p>Letzteres ist eine Scriptsprache zum Senden und Verarbeiten von REST-Requests, also zum Verwenden verschiedenster APIs, z.b. dem von Last.FM. Kein großer Leckerbissen für den Otto-Normal-Benutzer also und auch für Entwickler, die sich mit dem Thema beschäftigen, ist die Sprache nicht der Inbegriff von Eleganz. Deshalb mein Entschluss: <strong>Lapicon muss von Grund auf neu entwickelt werden</strong>. Das bedeutet: neue Funktionen, mehr Komfort, bessere Performance, usw&#8230; Alles in allem keine triviale Aufgabe.</p>
<p>Und dann kam mir die Idee, meinen Versuch zu protokollieren und somit allen, die daran interessiert sind, eine <strong>eigene Programmier- oder Scriptsprache zu entwickeln</strong>, ein Stückchen weiterzuhelfen. Eine kleine Reise, ein kleines Abenteuer also.<br />
<span id="more-793"></span></p>
<h2>Ein Hinweis</h2>
<p>Das Konzept, das hier entwickelt wird, eignet sich für Sprachen, die &#8211; so will ich es jetzt aus Ermangelung eines Fachbegriffs mal nennen &#8211; &#8220;blockbasiert&#8221; sind, also so etwa wie <a href="http://de.wikipedia.org/wiki/Pascal_(Programmiersprache)#Hallo_Welt">Pascal</a> oder <a href="http://de.wikipedia.org/wiki/Very_High_Speed_Integrated_Circuit_Hardware_Description_Language#Skelett_eines_VHDL-_Bausteines">VHDL</a>. D.h. man hat (z.B. bei Funktionen) immer einen Start- und einen Endblock, wobei der Endblock überflüssig werden kann, wenn man Sachen wie Zeileneinrückung einbezieht. (Ein Konzept, dass ich, während ich mich in Ruby eingearbeitet habe, bei <a href="http://en.wikipedia.org/wiki/Haml">HAML</a> und SASS kennen- und schätzen gelernt habe.)</p>
<p>Des weiteren werden wir zur Erkennung verschiedener Anweisungen mit <a href="http://de.wikipedia.org/wiki/Regul%C3%A4rer_Ausdruck">Regular Expressions</a> arbeiten, weshalb man sich dort auskennen sollte. Ein Tutorial gibt es z.B. <a href="http://www.regular-expressions.info/tutorial.html">hier</a> (englisch).</p>
<h2>Reisevorbereitung</h2>
<p>Bevor wir uns überhaupt mit der Sprache selbst beschäftigen, sollten wir folgendes tun:</p>
<ol>
<li><strong>Wir müssen uns überlegen, welche Komponenten wir entwickeln müssen, um Ausdrücke zu erkennen und zu verarbeiten.</strong><br />
Die Schlagworte hier sind <em>Lexer</em> und <em>Parser</em>. (Ich hatte anfangs ein anderes Konzept vorgesehen, das aber einen Haufen Schwächen hatte, weshalb ich jetzt doch wieder auf das klassische Prinzip zurückkomme. Ich werde mich bemühen, das so gut wie möglich darzustellen&#8230;)</p>
<p>Der <a href="http://de.wikipedia.org/wiki/Lexikalischer_Scanner">Lexer</a> nimmt einen Ausdruck und zerlegt ihn in sog. <em>Token</em>, d.h. in kleine Teile, die einen Typ und einen Wert besitzen. Man nehme beispielsweise den Ausdruck &#8220;1+2*sqrt(e)&#8221;. Ein Lexer (auch <em>Tokenizer</em> genannt) könnte daraus eine Liste der folgenden Gestalt fabrizieren:</p>
<pre>integer  : 1
operator : +
integer  : 2
operator : *
id       : sqrt
open     : (
id       : e
close    : )</pre>
<p>Der <a href="http://de.wikipedia.org/wiki/Parser">Parser</a> nimmt nun diese Liste und erstellt daraus einen <em>Syntaxbaum</em>, d.h. eine Repräsentation des Ausdrucks als Baumstruktur. Hier wird vor allem darauf geachtet, dass verschiedene Operatoren verschiedene Prioritäten haben können, dass es Verschachtelungen durch Klammern gibt, etc&#8230; Ein Syntaxbaum für unser Beispiel könnte nach dem Parsen z.B. so aussehen:</p>
<pre>               operator : +
              /            \
        integer : 1     operator : *
                       /            \
                  integer : 2   function : sqrt
                                     |
                                constant : e</pre>
</li>
<li><strong>Wir müssen festlegen, was die Sprache können soll.<br />
</strong>Welche Datenstrukturen sollen zugänglich sein? Kann man Funktionen definieren? Gibt es arithmetische Operationen?<strong> </strong>Schleifen? Man sollte sich eine Liste machen. Für unsere Beispielsprache könnte das so aussehen:</p>
<ul>
<li>Es gibt zwei Datentypen: Zahlen und Strings.</li>
<li>Es gibt Variablen, die aber nur Zahlen enthalten können.</li>
<li>Es gibt eine Ausgabefunktion, die Strings und Zahlen ausgibt.</li>
<li>Es gibt eine Ausgabefunktion, die Zeilenumbrüche erzeugt.</li>
<li>Es gibt eine Eingabefunktion, die Zahlen einliest und in einer Variablen speichert.</li>
<li>Es gibt die arithmetischen Operationen Addition (+), Subtraktion (-), Multiplikation (*), Division (/), die auf ganzen Zahlen arbeiten.</li>
<li>Es gibt Klammern, mit denen man die Ausführungsreihenfolge beeinflussen kann.</li>
<li>Es gibt eine Schleife, die einen Befehl genau n-mal (n ist gegeben) ausführt.</li>
<li>Es gibt drei Funktionen, die jeweils überprüfen, ob eine Zahl größer, kleiner oder gleich 0 ist und bei zutreffendem Ergebnis, einen Fehler ausgeben.</li>
</ul>
<p>Was wir also entwickeln werden, ist ein Taschenrechner.</li>
<li><strong>Wir müssen uns Gedanken dazu machen, wie der Interpreter aufgebaut ist und arbeitet.</strong><br />
Wie genau wird unser Programmcode verarbeitet? Wie arbeitet der Interpreter intern?An dieser Stelle wird klar, warum ich mich dafür entschieden habe, eine &#8220;blockbasierte&#8221; (also mit expliziten Blockanfängen und -enden) Sprache zu entwickeln: es ermöglicht die Ausführung eines Programms Zeile für Zeile. (Zwar wäre es kein unerträglicher Aufwand, auch so etwas wie Klammerstrukturen, ähnlich zu C#, unterstützen zu lassen, aber es geht auch so &#8211; und macht außerdem den Quelltext strukturierter, wie man z.B. an <a href="http://de.wikipedia.org/wiki/Ruby_(Programmiersprache)#Klassenbasierte_Objektorientierung">Ruby</a> sieht.)</p>
<p>Das andere Konzept, das den Interpreter zu einem nützlichen Interpreter macht, habe ich bei der Mikroprogrammierung näher kennengelernt. Man hat einen Instruktionszeiger (<em>Instruction Pointer</em>, <em>IP</em>), der auf die aktuelle Instruktion zeigt und einen <a href="http://de.wikipedia.org/wiki/Stapelspeicher">Stack</a>, der den Wert des Zeigers zwischenspeichern kann. Jede Anweisung sagt dem Interpreter nach ihrer Ausführung, was er nun weiter machen soll. Zyklus:</p>
<ol>
<li>Hole die Anweisung (<em>Statement</em>), auf die der IP gerade zeigt. Ist keine Anweisung vorhanden, brich ab.</li>
<li>Übergib die Anweisung an den Verarbeiter (<em>Processor</em>) und speichere das Ergebnis.</li>
<li>Lies aus dem Ergebnis die folgenden Teile aus: Zeiger-Offset, Zeiger-Aktion und Stack-Aktion.</li>
<li>Wenn die Stack-Aktion &#8220;PUSH&#8221; ist, lege den IP auf den Stack.</li>
<li>Wenn die Zeiger-Aktion &#8220;NULL&#8221; ist, setze den IP auf 0, ist sie &#8220;STACK&#8221;, setze den IP auf die Position, die zuoberst auf dem Stack liegt. Bei &#8220;CONTINUE&#8221; erhöhe den IP um 1.</li>
<li>Wenn die Stack-Aktion &#8220;POP&#8221; ist, nimm das oberste Element vom Stack.</li>
<li>Addiere den Zeiger-Offset auf den Instruktionszeiger.</li>
<li>Gehe zu 1.</li>
</ol>
<p><strong>Beispiel: Schleife, die mindestens einmal ausgeführt wird</strong><br />
(Notation: Zeiger-Offset, Zeiger-Aktion, Stack-Aktion)</p>
<blockquote><p>Schleifenbeginn: 0 / CONTINUE / PUSH<br />
Schleifenrumpf: beliebig<br />
Schleifenbedingung erfüllt: 1 / STACK / NONE<br />
Schleifenbedingung nicht erfüllt: 0 / CONTINUE / POP</p></blockquote>
<p><strong>Beispiel 2: Funktionsaufruf</strong></p>
<blockquote><p>Aufruf: &lt;Adresse+1&gt; / NULL / PUSH<br />
Rumpf: beliebig<br />
Rücksprung: 1 / STACK / POP</p></blockquote>
<p>Der <em>Processor</em> ist das, wo die eigentliche Ausführung  eines Befehls stattfindet. Er tut folgendes:</p>
<ol>
<li>Erstelle mithilfe von Lexer und Parser (siehe oben) den Syntaxbaum des Befehls.</li>
<li>Wenn der Baum Kindknoten hat, werte zuerst die jeweiligen Teilbäume aus. (<em>Top-Down-Methode</em>)</li>
<li>Schaue, ob für den Typ des Wurzelknotens eine Verarbeitungsroutine (<em>Evaluator</em>) existiert. Wenn ja, rufe sie mit den in 2. ermittelten Daten auf und gib ihr Ergebnis zurück, wenn nicht, gib eine Fehlermeldung aus und brich die Gesamtausführung ab.</li>
</ol>
</li>
<li><strong>Und wie werden die programminternen Daten gespeichert?<br />
</strong>Wir benötigen eine Umgebung, in der ein Programm läuft. Diese Umgebung hat vor allem die Aufgabe, Variablen, Objekte, Funktionen, Listen, Arrays, etc&#8230; zu verwalten, also eine große Menge an Daten zu speichern und zugreifbar zu machen. Wenn man von vornherein weiß, was auf einen zukommt (wenn man also einen Interpreter für eine ganz bestimmte und ganz genau definierte Sprache schreibt), kann man es sich einfach machen:</p>
<ul>
<li>einen Speicher für Variablen,</li>
<li>einen Speicher für Funktionen,</li>
<li>einen Speicher für Listen,</li>
<li>&#8230;</li>
</ul>
<p>Die Realisierung als <a href="http://de.wikipedia.org/wiki/Hashtable">Hashtabelle</a> (Wörterbuch, <em>Dictionary</em>) würde sich für jeden einzelnen dieser Speicher anbieten. Eine Hashtabelle ist eine Liste von Schlüssel-Wert-Paaren, wo jeder Wert schnell über seinen Schlüssel adressiert werden kann &#8211; in obigem Fall wären folgende Zuordnungen sinnvoll:</p>
<ul>
<li>[Variablenname =&gt; Variablenwert]</li>
<li>[Funktionsname =&gt; (Funktionsbeginn (Zeiger), Funktionsende (Zeiger))]</li>
<li>[Listenname =&gt; Liste]</li>
<li>&#8230;</li>
</ul>
<p>In meinem Studium wird mir beigebracht, alles so abstrakt wie möglich zu halten, weswegen ich die nun folgende Lösung vorschlage. Hierbei benötigen wir nur zwei Hashtabellen (plus eine geschachtelte) und haben zum einen den Vorteil, dass wir beliebige Daten speichern können, zum anderen, dass die Überprüfung auf doppelte Bezeichner (Variablen-, Funktions- und Listennamen sollten ja nur einmal vorkommen!) sehr einfach wird:</p>
<ul>
<li>Wir verwenden eine Tabelle, um jedem Bezeichner einen Typ zuzuweisen. ([Bezeichner =&gt; Typ])</li>
<li>Wir verwenden eine weitere Tabelle, um jedem Typ eine weitere Hashtabelle zuzuweisen, die wiederum jeden Bezeichner mit seinem Wert verknüpft. ([Typ =&gt; [Bezeichner =&gt; Wert]])</li>
</ul>
<p>Mit entsprechenden Operationen auf diesen beiden Tabellen haben wir nun unsere Umgebung (<em>Environment</em>).</li>
</ol>
<p>Nachdem wir das alles beantwortet haben, können wir loslegen. Aber Halt! Einen wichtigen Punkt haben wir noch nicht angesprochen: Wie verwende ich Regular Expressions so, dass ich zum einen leicht feststellen kann, welchen Anweisungstyp ich vor mir habe, und zum anderen einfach an die gewünschten Daten komme?</p>
<p>Das ist der Punkt, an dem wir unsere praktische Arbeit beginnen werden.</p>
<h2>Inhalt</h2>
<ol>
<li><strong>Einführung: Ein Abenteuer in Teilen</strong></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-1/">Der Lexer</a></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-2/">Grundlagen des Parsens</a></li>
<li><a href="http://dev.xscheme.de/2009/08/wie-entwickle-ich-meine-eigene-scriptsprache-teil-3/">Syntax</a><strong><br />
</strong></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/07/eigene-programmiersprache-scriptsprach/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Zwischenstand: Universal Version Description (UVD) / Entwickler gesucht</title>
		<link>http://dev.xscheme.de/2009/05/zwischenstand-universal-version-description-uvd-entwickler-gesucht/</link>
		<comments>http://dev.xscheme.de/2009/05/zwischenstand-universal-version-description-uvd-entwickler-gesucht/#comments</comments>
		<pubDate>Wed, 13 May 2009 14:17:12 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[Batch]]></category>
		<category><![CDATA[Codeschnipsel]]></category>
		<category><![CDATA[Fundstücke]]></category>
		<category><![CDATA[General]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Lapicon]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Theorie]]></category>
		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=755</guid>
		<description><![CDATA[Ich möchte an dieser Stelle einmal auf den aktuellen Stand des hier begonnenen Projekts verweisen, der sich wie folgt darstellt: Ich habe das UVD-Format weiter ausgearbeitet und angepasst und damit begonnen, eine C#-Bibliothek zu schreiben, die dessen Verarbeitung dient.
Als Beispiel sei die UVD von LastSharp gegeben, die wir im folgenden Codebeispiel mithilfe einer XSL-Transformation (DescriptionPage.xsl) [...]]]></description>
			<content:encoded><![CDATA[<p>Ich möchte an dieser Stelle einmal auf den aktuellen Stand des <a href="http://dev.xscheme.de/2009/05/universal-version-description-uvd/">hier</a> begonnenen Projekts verweisen, der sich wie folgt darstellt: Ich habe das UVD-Format weiter ausgearbeitet und angepasst und damit begonnen, eine C#-Bibliothek zu schreiben, die dessen Verarbeitung dient.</p>
<p>Als Beispiel sei die <a href="http://dev.xscheme.de/uvd/lastsharp.uvd.xml">UVD von LastSharp</a> gegeben, die wir im folgenden Codebeispiel mithilfe einer XSL-Transformation (<a href="http://dev.xscheme.de/uvd/DescriptionPage.xsl">DescriptionPage.xsl</a>) in eine HTML-Datei verwandeln. Das soll illustrieren, wie einfach die Verwendung von UVD sein kann, wenn entsprechende Bibliotheken vorhanden sind:</p>
<pre class="c-sharp:collapse" name="code">UVD lastsharp = UVD.Create("http://dev.xscheme.de/uvd/lastsharp.uvd.xml");
lastsharp.Save("c:/lastsharp.htm", "http://dev.xscheme.de/uvd/DescriptionPage.xsl");</pre>
<p>Nun haben wir eine Datei &#8220;lastsharp.htm&#8221;, die <a href="http://dev.xscheme.de/uvd/lastsharp.htm">hier</a> eingesehen werden kann und Beschreibungen in verschiedenen Sprachen und Längen bietet, Downloadlinks, etc&#8230; Außerdem sind (wie man bei der Ansicht des Quelltextes feststellen kann) auch die META-Tags zu Schlüsselwörtern und Beschreibungen bereits gesetzt, sodass die Seite auch für Suchmaschinen verwendbar ist.</p>
<p><span id="more-755"></span>Das UVD-Objekt (s. Code) bietet noch weitere Möglichkeiten, aber ich möchte nun auf den Update-Mechanismus zu sprechen kommen, den man mithilfe von UVD implementieren kann. Eine UVD kann einen Verweis auf die vorhergehende Version, sowie Informationen zum inkrementellen Update (lösche Datei X, aktualisiere Datei Y, &#8230;) enthalten, sodass man durch Untersuchen der Versionsgeschichte jede Version auf den neuesten Stand bringen kann.</p>
<p>Wir verwenden die folgenden (sinnlosen) Dateien zur Demonstration: <a href="http://dev.xscheme.de/uvd/updatetest1.xml">Version 1.2</a> &gt;&gt; <a href="http://dev.xscheme.de/uvd/updatetest2.xml">Version 1.1</a> &gt;&gt; <a href="http://dev.xscheme.de/uvd/updatetest3.xml">Version 1.0</a>. Mithilfe der UVDs läuft ein Update von Version 1.0 auf 1.2 folgendermaßen ab:</p>
<ol>
<li>Extrahiere aus der UVD zu Version 1.2 die URL zu Version 1.1!</li>
<li>Speichere die Update-Informationen in der UVD zu Version 1.1!</li>
<li>Führe nacheinander die Update-Schritte (1.0 &gt;&gt; 1.1) und (1.1 &gt;&gt; 1.2) aus!</li>
</ol>
<p>Die Bibliothek, an der ich gerade arbeite, verwendet hierfür sog. Update-Profile, die festlegen, welche Sprache und welches Betriebssystem für das Update berücksichtigt werden soll. Code:</p>
<pre class="c-sharp:collapse" name="code">UVD version12 = UVD.Create("http://dev.xscheme.de/uvd/updatetest1.xml");
UVDVersion currentVs = new UVDVersion(1, 0); // aktuelle Version
UpdateProfile profile = new UpdateProfile(); // OS und Sprache egal
Update u = profile.CreateUpdate(currentVs, version12);
foreach(UpdateSegment s in u)
{
    Console.WriteLine(s.ToString());
}
u.Execute("c:/program/"); // Programmverzeichnis muss angegeben werden!</pre>
<p>Das liefert folgende Ausgabe:</p>
<pre>[Add: file3.txt] http://www.download.de
[Execute: file3.txt]
[Remove: file4.txt]
[Add: file1.txt] http://www.download.de
[Update: file2.txt] http://www.download.de
[Execute: file3.txt]</pre>
<p>Anschließend werden genau diese Schritte ausgeführt. Bei der Erstellung des Updates wird im übrigen mehr oder weniger intelligent vorgegangen: wenn eine Datei in Version 1.2 gelöscht wird, wird sie in Version 1.1 gar nicht erst hinzugefügt. Und warum eine Datei hinzufügen, wenn sie irgendwann später überschrieben wird? Dieses Verhalten lässt sich über das Update-Profil festlegen. Wenn deaktiviert, lautet die Ausgabe:</p>
<pre><span style="text-decoration: underline;">[Add: file4.txt] http://www.download.de</span>
<span style="text-decoration: underline;">[Add: file2.txt] http://www.download.de</span>
[Add: file3.txt] http://www.download.de
[Execute: file3.txt]
<span style="text-decoration: underline;">[Remove: file4.txt]</span>
[Add: file1.txt] http://www.download.de
<span style="text-decoration: underline;">[Update: file2.txt] http://www.download.de</span>
[Execute: file3.txt]</pre>
<p>Soviel dazu. Was jetzt noch wichtig ist, ist die Übertragung der Bibliothek auf andere Programmiersprachen. <strong>Deswegen suche ich Entwickler, die mit mir gemeinsam UVD auf Java, PHP, etc&#8230; portieren.</strong> Bei Interesse einfach eine E-Mail schreiben an <a href="mailto:Yannick_Scherer@gmx.net">Yannick_Scherer@gmx.net</a>! Aber man sollte sich bewusst sein, dass das einiges an Arbeit bedeutet.</p>
<p>Mehr habe ich vorerst nicht vorzuweisen&#8230; Außer vielleicht das ultimative &#8220;einzeilige&#8221; Update (mit einer sinnvollen Datei funktioniert es dann auch&#8230;):</p>
<pre class="c-sharp:collapse" name="code">new UpdateProfile()
       .CreateUpdate(new UVDVersion(1, 0), UVD.Create("http://dev.xscheme.de/uvd/updatetest1.xml"))
       .Execute("c:/program/");</pre>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/05/zwischenstand-universal-version-description-uvd-entwickler-gesucht/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Vom Gedankenspiel zum Versuch: Universal Version Description (UVD)</title>
		<link>http://dev.xscheme.de/2009/05/universal-version-description-uvd/</link>
		<comments>http://dev.xscheme.de/2009/05/universal-version-description-uvd/#comments</comments>
		<pubDate>Fri, 01 May 2009 22:24:43 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Projekte]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[Theorie]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[pad]]></category>
		<category><![CDATA[portable application description]]></category>
		<category><![CDATA[universal version description]]></category>
		<category><![CDATA[update check]]></category>
		<category><![CDATA[uvd]]></category>
		<category><![CDATA[version check]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=713</guid>
		<description><![CDATA[Das Problem der Updates
Ich bin in meinem vorletzten Artikel zum Thema Update-Check bereits darauf eingegangen: Versionsänderungen sind nicht immer leicht zu verfolgen und die Überprüfung auf Updates erfordert meist einen nicht zu unterschätzenden Programmier- und Verwaltungsaufwand. Weiterhin bedeutet es für den Entwickler selbst auch, dass er auf mehreren Hochzeiten tanzen muss: er stellt meist eine [...]]]></description>
			<content:encoded><![CDATA[<h2>Das Problem der Updates</h2>
<p>Ich bin in meinem <a href="http://dev.xscheme.de/2009/04/gedankenspiel-update-notification-server/">vorletzten Artikel zum Thema Update-Check</a> bereits darauf eingegangen: Versionsänderungen sind nicht immer leicht zu verfolgen und die Überprüfung auf Updates erfordert meist einen nicht zu unterschätzenden Programmier- und Verwaltungsaufwand. Weiterhin bedeutet es für den Entwickler selbst auch, dass er auf mehreren Hochzeiten tanzen muss: er stellt meist eine eigene Downloadseite bereit, die Links zu verschiedenen Versionen enthält, er aktualisiert gleichzeitig die Dateien, die für die Versionsüberprüfung zuständig sind, er kümmert sich um die Daten, die in irgendwelchen Softwareverzeichnissen (heise, chip.de, etc&#8230;) stehen, usw., usw&#8230; Als verantwortungsvoller Entwickler mit der Ambition, sein Programm unter die Leute zu bringen, hat man richtiggehend die Pflicht, diese Schritte durchzuführen &#8211; und das regelmäßig und mit äußerster Genauigkeit.</p>
<p>Hier nun also das (absolut logische und vernünftige) Konzept, das die meisten Beispiele, die man im Web zu dem Thema &#8220;Update-Check&#8221; findet, implementieren:</p>
<p style="text-align: center;"><img class="alignnone size-full wp-image-715" title="Allgemeines Konzept zum Update-Check" src="http://dev.xscheme.de/wp-content/uploads/2009/05/unserver.png" alt="Allgemeines Konzept zum Update-Check" width="600" height="300" /></p>
<p>Augenscheinliche Probleme hierbei:</p>
<ul>
<li> Die Verwaltung der Programmdaten läuft (beim Otto-Normal-Programmierer) meist von Hand ab, d.h. man bearbeitet die Update-Datei (die nicht, wie im Beispiel, im XML-Format vorliegen kann), lädt sie von Hand auf den <em>eigenen</em> Webserver und ist aus Kompatibilitätsgründen meist an ein (demnach schwer erweiterbares) Format und an einen bestimmten Dateinamen gebunden.</li>
<li>Je nachdem, wie detailliert die Update-Informationen sind (nur Versionsnummer vs. Versionsnummer, Änderungsdatum, Changelog, &#8230;), kann das Bearbeiten der Versionsdatei eine Heidenarbeit bedeuten.</li>
<li>Der Entwickler muss das Programm erst einmal dazu bringen, einen funktionierenden Versions-Check durchzuführen.</li>
<li>Und was passiert bei Programmen, die mehrsprachig funktionieren und beworben werden?</li>
</ul>
<p>Die Idee im erwähnten Artikel war es also, den &#8220;Update Notification Server&#8221; nicht mehr als Eigentum des Entwicklers zu sehen, sondern als über ein API zugängliche Plattform im Internet, die Verwaltungsfunktionen komfortabel bereitstellt. Gleichzeitig wären verschiedene Bibliotheken für das API nötig, um dem Programmierer die Implementierung der Versionsprüfung fast gänzlich abzunehmen.</p>
<p>Was folgte, war ein Einspruch.</p>
<p><span id="more-713"></span></p>
<p>Es sei nicht garantierbar, dass der verantwortliche zentrale Server auch in zwei Jahren noch erreichbar sein würde, wurde argumentiert. Man könne das bei einem eigenen Server viel eher kontrollieren &#8211; und außerdem habe heutzutage ohnehin jeder Entwickler zumindest ein bisschen Webspace.</p>
<p>Dem kann man nicht widersprechen. Zwar würde eine zentrale Plattform den Komfort steigern, allerdings unvermeidbar auch das Risiko. Was also tun?</p>
<h2>Unvereinbarkeit</h2>
<p>Ein großes Problem bei den vielen Versionsprüfungsimplementierungen (was für ein Wort!) im Web ist die Unvereinbarkeit. Die meisten basieren auf verschiedenen Dateiformaten, die wiederum unterschiedlichen Informationsgehalt bieten. Eine reine Textdatei mit der Versionsnummer mag dem <a href="http://www.aeroxp.org/board/index.php?showtopic=11508">einen</a> reichen, der <a href="http://themech.net/2008/05/adding-check-for-update-option-in-csharp/">andere</a> benötigt aber vllt. auch schon den Downloadlink für die aktuelle Version dazu. Oder gar den Namen des Programms und Zusatzmeldungen. <a href="http://beta.unclassified.de/code/dotnet/updatecheck/">Kann passieren</a>.</p>
<p>Wenn einem schließlich keine der Lösungen des World Wide Webs zusagt, dann bleibt nichts anderes zu tun, als eine bestehende Lösung anzupassen. Oder <em>from scratch</em>, also komplett neu zu beginnen. Das wiederum kostet Zeit und Energie, Schweiß und Kaffee, von den Nerven gar nicht erst zu sprechen. Was also tun? (Zweimal haben wir uns das jetzt schon gefragt!)</p>
<p>Eine Lösung, die es allen recht macht, wird es vermutlich nie geben, aber das heißt nicht, dass man nicht versuchen darf, in ihre Nähe zu kommen. Was benötigt wird, ist ein <strong>standardisiertes Dateiformat für Softwarebeschreibungen</strong>.</p>
<h2>Standards</h2>
<p>Es gibt so etwas bereits unter dem Namen <strong>Portable Application Description (PAD)</strong>, nachzulesen beispielsweise in <a href="http://de.wikipedia.org/wiki/Portable_Application_Description">Wikipedia</a> oder der <a href="http://www.asp-shareware.org/pad/">Association of Shareware  Professionals</a>, anzusehen u.a. <a href="http://download.agilita.de/pad_file.xml">hier</a>. Der Vorteil: das <em>ist</em> in der Tat ein Standard; der Nachteil: er wurde für den Vertrieb von <a href="http://de.wikipedia.org/wiki/Shareware">Shareware</a> entworfen. Außerdem wird ein alternatives XML-Namensraumkonzept verwendet, was sich für einen solchen Standard meiner Meinung nach nicht gehört.</p>
<p><img class="alignright size-full wp-image-717" style="margin-left: 1em; margin-bottom: 0.5em;" title="Portable Application Description" src="http://dev.xscheme.de/wp-content/uploads/2009/05/pad.png" alt="Portable Application Description" width="300" height="310" />Aber ganz allgemein hat das Format in meinen Augen signifikante Schwächen:</p>
<ul>
<li>Erweiterbarkeit: Man darf genau <em>einen</em> Screenshot angeben, <em>eine</em> Informations-URL, usw&#8230;</li>
<li>Es findet keine Unterscheidung nach unterstützen Betriebsystemen statt. Angenommen, es gäbe einen Installer, der das Programm auf den neueren Windows-Versionen einrichtet, sowie einen, der es sogar auf Windows 95 zum Laufen bringt: PAD bietet keine Möglichkeit, diese Dateien zu unterscheiden bzw. überhaupt erst vernünftig gemeinsam anzubieten.</li>
<li>Wir leben in einer multilingualen Welt. Die meisten Programme sind für mehrere Sprachen ausgelegt, doch würde man versuchen, Informations/Download-URLs nach Sprache anzubieten, würde man an PAD absolut <em>insane</em> werden.</li>
<li>Überhaupt fehlt so etwas wie Hinweise auf Support im Web in PAD. Man kann Telefonnummern angeben, aber das Leben und die Lebenshilfe findet heutzutage nun einmal zu größeren Teilen im Internet statt als noch früher. Und E-Mail-Adressen bedeuten bloß Wartezeit.</li>
<li>Man kann in PAD genau einen Entwickler angeben. Was macht man bei heutzutage weit verbreiteten Entwickler-Teams?</li>
<li>Das Format hat eine Struktur, die das Verständnis manchmal erschwert. Aber es wird ja von Computern verarbeitet, also ist das akzeptabel. Andererseits: auch Suchmaschinencrawler sind Computerprogramme, und PAD (ein Standard!) wird von denen so gut wie ignoriert!</li>
<li>Was Nutzer oftmals interessiert, ist die Entwicklung eines Programms: Was gibt es neues? Was wurde entfernt, was verbessert?</li>
</ul>
<p>Es ist vielleicht etwas hochgegriffen, aber für die Softwarebeschreibung der heutigen Zeit muss ein neuer Standard erarbeitet werden. Und hier wird aus dem Gedankenspiel der Versuch.</p>
<h2>Was macht Software aus?</h2>
<p>Um welche Punkte müsste sich ein neues Format kümmern, was sind seine Prämissen und Ziele? Und was genau will ich eigentlich? Ein Brainstorming:</p>
<ul>
<li>Sprachenunterstützung</li>
<li>Änderungsverfolgung</li>
<li>Autorenliste</li>
<li>Firmenkontaktdaten</li>
<li>Lizenzinformationen</li>
<li>Suchmaschinen-Metadaten</li>
<li>Installationsanweisungen</li>
<li>Abhängigkeiten: Was brauche ich, damit das Programm läuft?</li>
<li>Einfacher Zugang zu Support-Quellen (Foren, Anleitungen, FAQ)</li>
</ul>
<p>Es entwickelt sich vor meinem inneren Auge also langsam eine XML-Struktur, die das alles mit sich bringt: <strong>XML Application Description (XAD)</strong>, veranschaulicht durch ein <a href="http://dev.xscheme.de/xad/lastsharp.xad.xml">Beispiel</a> (Rechtsklick &gt;&gt; Quelltext), das verwendete <a href="http://dev.xscheme.de/xad/xad.xsl">XSLT-Stylesheet</a> und die <a href="http://dev.xscheme.de/xad/xad.dtd">DTD</a> (das Dokument ist <a href="http://www.validome.org/grammar/validate/?lang=ge&amp;viewSourceCode=1&amp;grammarTyp=DTD&amp;url=http://dev.xscheme.de/xad/xad.dtd">valide</a>, ich weiß nicht, warum Firefox und der IE da rumspinnen).</p>
<h2>Ein Gedanke</h2>
<p>Dann kam mir ein Gedanke: <strong>nicht nur Programme</strong> unterliegen Änderungen, auch Dokumente, Filme, Serien lassen sich einem Versionskonzept unterordnen. Zwei Beispiele: die FAZ vom 30.04.2009 könnte ebensogut die Versionsnummer 2009.119 (der 30.04. ist der 119. Tag des Jahres 2009) tragen, und die Special Extended Edition von &#8220;Herr der Ringe: Die zwei Türme&#8221; könnte auch &#8220;Herr der Ringe 2.1&#8243; heißen (wenn man keinen Wert auf Gewinn legen würde). Mit ein wenig Phantasie überträgt man das ganze auf Autos, CPUs, kurz gesagt: alles, was sich irgendwie als bildlicher <strong>Entwicklungsstrom</strong> darstellen lässt.</p>
<p style="text-align: center;"><img class="size-full wp-image-718 aligncenter" title="strom" src="http://dev.xscheme.de/wp-content/uploads/2009/05/strom.png" alt="strom" width="550" height="230" /></p>
<p>Wichtig hierbei ist die Möglichkeit, stets auf die früheren Versionen eines Objektes zugreifen zu können, weil im Idealfall Verweise auf diese vorhanden sind.</p>
<p>Wenn man also das oben erwähnte Format weiterentwickelt, verallgemeinert und anpasst, dann hat man ein mächtiges Werkzeug für weit mehr als bloß Software. Und hier wären wir.</p>
<h2>Universal Version Description (UVD)</h2>
<p>Da die Verwendung nicht mehr nur auf Programme beschränkt ist, ist es wichtig, anzugeben, was genau in einem Dokument beschrieben wird. Die Nomenklatur hierfür wäre idealerweise etwas wie &#8220;Kategories:Unterkategorie&#8221; oder &#8220;Kategorie&#8221; allein. Beispiele:</p>
<ul>
<li>media:tv-episode</li>
<li>software:utility</li>
<li>paper:specification</li>
<li>paper:mathematics</li>
<li>&#8230;</li>
</ul>
<p>Folgendes wäre ein Beispiel für das kleinstmögliche UVD-Dokument:</p>
<pre>&lt;?xml version="1.0"?&gt;
&lt;!DOCTYPE uvd SYSTEM "http://dev.xscheme.de/uvd/uvd-1.dtd"&gt;
&lt;uvd version="1.0.0" type="software:audio"&gt;
  &lt;general&gt;
    &lt;name&gt;WinAmp&lt;/name&gt;
  &lt;/general&gt;
&lt;/uvd&gt;</pre>
<p>Nicht sehr aussagekräftig, aber jetzt bauen wir noch Informations-Links (in verschiedenen Sprachen), sowie Beschreibungstexte (in verschiedenen Sprachen und Längen; die unterstützen Längenangaben sind 100, 255, 2000 und &#8220;any&#8221;) ein:</p>
<pre>&lt;?xml version="1.0"?&gt;
&lt;!DOCTYPE uvd SYSTEM "http://dev.xscheme.de/uvd/uvd-1.dtd"&gt;
&lt;uvd version="1.0.0" type="software:audio"&gt;
  &lt;general&gt;
    &lt;name&gt;WinAmp&lt;/name&gt;
    &lt;urls&gt;
      &lt;url lang="en"&gt;http://www.winamp.com/&lt;/url&gt;
      &lt;url lang="de"&gt;http://de.winamp.com/&lt;/url&gt;
    &lt;/urls&gt;
    &lt;descriptions&gt;
      &lt;description lang="en" maxlength="255"&gt;
        WinAmp is a multimedia player, supporting (...)
      &lt;/description&gt;
      ...
    &lt;/descriptions&gt;
  &lt;/general&gt;
&lt;/uvd&gt;</pre>
<p>Deutsch und Englisch vorhanden, alles andere beliebig nachtragbar. Weitere Informationen wären: Lizenz, Keywords, Screenshots, etc&#8230;Anschließend könnte in einem neuen Abschnitt &#8220;release&#8221; die aktuelle Version (inkl. Downloadlinks nach Sprache und OS) beschrieben, in &#8220;contributors&#8221; alle Beteiligten aufgelistet und in &#8220;company&#8221; Informationen zum Firmenkontakt bereitgestellt werden.</p>
<p>Eine komplette Dokumentation des Formats würde den Rahmen aber noch mehr sprengen, deswegen wird hier auf die folgenden Dokumente verwiesen:</p>
<ul>
<li>&#8220;Spezifikation&#8221; zu UVD: <a href="http://dev.xscheme.de/uvd/Universal%20Version%20Description.txt">http://dev.xscheme.de/uvd/Universal%20Version%20Description.txt</a></li>
<li>DTD: <a href="http://dev.xscheme.de/uvd/uvd-1.dtd">http://dev.xscheme.de/uvd/uvd-1.dtd</a> (<a href="http://www.validome.org/grammar/validate/?lang=ge&amp;viewSourceCode=1&amp;grammarTyp=DTD&amp;url=http://dev.xscheme.de/uvd/uvd-1.dtd">Validate</a>)</li>
<li>Beispiele:
<ul>
<li><a href="http://dev.xscheme.de/uvd/release-example.uvd.xml">Programm-Release</a></li>
<li><a href="http://dev.xscheme.de/uvd/writer-example.uvd.xml">Dokument</a></li>
<li><a href="http://dev.xscheme.de/uvd/episode-example.uvd.xml">TV-Episode</a></li>
</ul>
</li>
</ul>
<h2>Fazit</h2>
<p>Meistens ist der eigene Blick von Selbstherrlichkeit getrübt, wenn man etwas in den eigenen Augen neues und tolles geschaffen hat. Deswegen brauche ich Meinungen hierzu.</p>
<p>Um das anfangs erwähnte Argument noch einmal aufzugreifen, man könne seinen eigenen Webspace besser kontrollieren, will ich hier erwähnen, dass ein UVD-Dokument natürlich überall liegen kann. Was bisher fehlt, sind Programme zur Verwaltung und Erstellung, sowie Bibliotheken zur Verarbeitung.</p>
<p>Vielleicht überschätze ich auch den Nutzen des ganzen. Kein Grund allerdings, den Stein nicht ins Rollen zu bringen.</p>
<h2>Update (02.05.2009)</h2>
<p>Ich will an dieser Stelle noch demonstrieren, wie sich das Format für einfache Versionsprüfung, sowie Update-Verarbeitung eignet.</p>
<p>Für einen normalen Versionscheck würde schon so etwas reichen:</p>
<pre class="xml:collapse" name="code">&lt;uvd version="1.0.0" type="software:ripper"&gt;
  &lt;general&gt;
    &lt;name&gt;LastSharp&lt;/name&gt;
  &lt;/general&gt;
  &lt;release&gt;
    &lt;date&gt;
      &lt;day&gt;20&lt;/day&gt;
      &lt;month&gt;4&lt;/month&gt;
      &lt;year&gt;2009&lt;/year&gt;
    &lt;/date&gt;
    &lt;version&gt;
      &lt;major&gt;0&lt;/major&gt;
      &lt;minor&gt;4&lt;/minor&gt;
      &lt;build&gt;1&lt;/build&gt;
    &lt;/version&gt;
  &lt;/release&gt;
&lt;/uvd&gt;</pre>
<p>Und folgende Datei könnte von einem Updater automatisch verarbeitet werden:</p>
<pre class="xml:collapse" name="code">&lt;uvd version="1.0.0" type="software:ripper"&gt;
  &lt;general&gt;
    &lt;name&gt;LastSharp&lt;/name&gt;
  &lt;/general&gt;
  &lt;release&gt;
    &lt;date&gt;
      &lt;day&gt;20&lt;/day&gt;
      &lt;month&gt;4&lt;/month&gt;
      &lt;year&gt;2009&lt;/year&gt;
    &lt;/date&gt;
    &lt;version&gt;
      &lt;major&gt;0&lt;/major&gt;
      &lt;minor&gt;4&lt;/minor&gt;
      &lt;build&gt;1&lt;/build&gt;
    &lt;/version&gt;
    &lt;files&gt;
      &lt;file name="LastUtility" os="any" type="lib" filename="LastUtility.dll"
            update-action="update" update-path=".inc" direct-download="no"&gt;
        &lt;urls&gt;
          &lt;url lang="all"&gt;http://download.server.tld/LastUtility.dll&lt;/url&gt;
        &lt;/urls&gt;
      &lt;/file&gt;
      &lt;file name="LastSharpExe" os="any" type="exe" filename="LastSharp.exe"
            update-action="update" direct-download="no"&gt;
        &lt;urls&gt;
          &lt;url lang="all"&gt;http://download.server.tld/LastSharp.exe&lt;/url&gt;
        &lt;/urls&gt;
      &lt;/file&gt;
      &lt;file name="SettingsFile" os="any" type="xml" filename="settings.xml"
            update-action="remove" /&gt;
      &lt;file name="LastSharpFull" os="any" type="package" filename="LastSharp041.zip"
            update-action="ignore"&gt;
        &lt;urls&gt;
          &lt;url lang="all"&gt;http://download.server.tld/LastSharp041.zip&lt;/url&gt;
        &lt;/urls&gt;
        &lt;descriptions&gt;
          &lt;description lang="de" maxlength="any"&gt;
            Das vollständige Paket mit allen Dateien von LastSharp 0.4.1.
          &lt;/description&gt;
        &lt;/descriptions&gt;
      &lt;/file&gt;
    &lt;/files&gt;
  &lt;/release&gt;
&lt;/uvd&gt;</pre>
<p>Wenn man genau hinsieht, erkennt man die Anweisungen an den Updater:</p>
<ol>
<li>Lade die Datei &#8220;LastUtility.dll&#8221; von der angegebenen URL herunter und überschreibe ihre Entsprechung im Unterverzeichnis &#8220;.inc&#8221;.</li>
<li>Tue dasselbe mit der Datei &#8220;LastSharp.exe&#8221;, allerdings diesmal im Hauptverzeichnis. (&#8220;update-path&#8221; fehlt)</li>
<li>Lösche die Datei &#8220;settings.xml&#8221; im Hauptverzeichnis.</li>
</ol>
<p>Der letzte &#8220;file&#8221;-Eintrag enthält das Gesamtpaket, das beim automatischen Update ignoriert wird. (&#8220;update-action=&#8217;ignore&#8217;&#8221;) Gleichzeitig wird es aber dem User im Gegensatz zu den Dateien zuvor direkt zugänglich gemacht. (&#8220;direct-download=&#8217;no&#8217;&#8221; soll verhindern, dass evtl. Clients die so markierten Links dem User zeigen)</p>
<p>Wenn man jetzt bedenkt, dass ein wichtiger Punkt von UVD die Verlinkung mit den früheren Versionen ist (Der &#8220;release&#8221;-Tag bietet das Attribut &#8220;previous&#8221;, das auf das entsprechende UVD-Dokument verlinkt!), sieht man schnell, dass man mit diesem Format <strong>jede beliebige</strong> Version aktualisieren kann, wenn man sich in der Versionsgeschichte &#8220;zurückhangelt&#8221;.</p>
<p>Ich hoffe, diese kleine Demonstration hat noch einmal verdeutlicht, worin ich u.a. den Nutzen des Formats sehe.</p>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/05/universal-version-description-uvd/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Wie verwende ich Lapicon in meinem eigenen Programm? (.NET)</title>
		<link>http://dev.xscheme.de/2009/04/lapicon-in-eigenem-programm-verwenden/</link>
		<comments>http://dev.xscheme.de/2009/04/lapicon-in-eigenem-programm-verwenden/#comments</comments>
		<pubDate>Sun, 26 Apr 2009 19:43:55 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Lapicon]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[rest]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=677</guid>
		<description><![CDATA[Lapicon (Loose API Connection Language) kann (ab Version 1.0.10) auf einfache Art und Weise im eigenen Programm verwendet werden.
Um zu lernen, wie das geht, entwickeln wir hier (mit C#, aber eine Übertragung auf andere .NET-Sprachen dürfte jeder einigermaßen versierte Programmierer hinbekommen) ein Mini-Programm, das in einem Fenster zwei Eingabefelder für &#8220;Interpret&#8221; und &#8220;Titel&#8221; bereitstellt und [...]]]></description>
			<content:encoded><![CDATA[<p><strong><a href="http://dev.xscheme.de/lapicon">Lapicon</a></strong> (Loose API Connection Language) kann (ab Version 1.0.10) auf einfache Art und Weise im eigenen Programm verwendet werden.</p>
<p>Um zu lernen, wie das geht, entwickeln wir hier (mit C#, aber eine Übertragung auf andere .NET-Sprachen dürfte jeder einigermaßen versierte Programmierer hinbekommen) ein Mini-Programm, das in einem Fenster zwei Eingabefelder für &#8220;Interpret&#8221; und &#8220;Titel&#8221; bereitstellt und das zugehörige Album findet. Ich gebe zu, keine bahnbrechende Idee, aber zur Demonstration reicht&#8217;s.  <span id="more-677"></span></p>
<p>Zuallererst erstellen wir ein neues Projekt vom Typ &#8220;Windows Forms Anwendung&#8221; mit beliebigem Namen und erstellen einen Verweis auf Lapicon.  Das geht folgendermaßen:</p>
<ol>
<li>Rechtsklick auf &#8220;Veweise&#8221; im Projektmappen-Explorer, dann Klick auf &#8220;Verweis hinzufügen&#8230;&#8221;:<img class="size-full wp-image-678 alignnone" title="Lapicon Tutorial: Verweis hinzufügen" src="http://dev.xscheme.de/wp-content/uploads/2009/04/lapiconassemblytut1.png" alt="Lapicon Tutorial: Verweis hinzufügen" width="343" height="189" /></li>
<li>Unter &#8220;Durchsuchen&#8221; die Datei &#8220;Lapicon.exe&#8221; suchen und auswählen. Klick auf &#8220;OK&#8221;.<img class="alignnone size-full wp-image-679" title="Lapicon Tutorial: Lapicon.exe einbinden" src="http://dev.xscheme.de/wp-content/uploads/2009/04/lapiconassemblytut2.png" alt="Lapicon Tutorial: Lapicon.exe einbinden" width="504" height="232" /></li>
</ol>
<p>Jetzt kann die Programmentwicklung losgehen. Wir erstellen ein Fenster, das in etwa so aussieht:</p>
<p style="text-align: center;"><img class="size-full wp-image-680 aligncenter" title="Lapicon Tutorial: Fenster" src="http://dev.xscheme.de/wp-content/uploads/2009/04/lapiconassemblytut3.png" alt="Lapicon Tutorial: Fenster" width="322" height="160" /></p>
<p>Jetzt geht es an die Implementierung der Funktionen. Alle Lapicon-Funktionen werden durch das Objekt &#8220;LapiconProcessor&#8221; im Namespace &#8220;Lapicon&#8221; bereitgestellt. Da &#8220;LapiconProcessor&#8221; ein ziemlich langer Name ist und Programmierer von Haus aus faul sind, fügen wir folgendes Statement an den Anfang des Codes unseres Fensters (Rechtsklick auf das Fenster &gt;&gt; &#8220;Code anzeigen&#8221;) ein:</p>
<pre>using L = Lapicon.LapiconProcessor;</pre>
<p>Damit kürzen wir das Objekt &#8220;LapiconProcessor&#8221; als &#8220;L&#8221; ab und können ab sofort in unserem Code darauf zugreifen. Zuallererst ist unsere Anwendung ja auf das Last.FM-API ausgelegt, d.h. beim Laden der Anwendung würde es sich anbieten, Lapicon für dieses API zu konfigurieren. Hierfür verwenden wir das &#8220;Load&#8221;-Ereignis des Fensters, indem wir an irgendeine freie Stelle (im Fensterentwurf) doppelklicken. Anschließend ergänzen wir den so erstellten Code folgendermaßen:</p>
<pre>private void AlbumFinderForm_Load(object sender, EventArgs e)
{
<span style="color: #ff9900;"><span style="color: #000000;">   // Lapicon: Initialisierung für Last.FM (entspricht L.Process("#lastfm"))</span></span>
<span style="color: #ff9900;">   L.LastFMInit();<span style="color: #000000;"> </span></span>
}</pre>
<p>Jetzt müssen wir noch festlegen, was passiert, wenn wir auf den Button klicken. Also erstellen wir mit einem Doppelklick darauf das &#8220;Click&#8221;-Event und verwenden die verschiedenen Lapicon-Funktionen, um eine Anfrage an Last.FM zu senden und das Ergebnis in einer MessageBox anzuzeigen. Die wichtigsten Lapicon-Funktionen sind:</p>
<ul>
<li><strong>Process(&#8220;statement1&#8243;, &#8220;statement2&#8243;, &#8230;) </strong>führt die angegebenen Lapicon-Befehle nacheinander aus.</li>
<li><strong>Var(&#8220;var&#8221;)</strong> liefert den Wert einer Variablen.</li>
<li><strong>Var(&#8220;var&#8221;, &#8220;wert&#8221;)</strong> speichert &#8220;wert&#8221; in der Variable &#8220;var&#8221;.</li>
<li><strong>VarExists(&#8220;var&#8221;)</strong> liefert &#8220;true&#8221;, wenn eine Variable existiert.</li>
<li><strong>List(&#8220;var&#8221;)</strong> liefert eine Liste.</li>
<li><strong>ListCreate(&#8220;var&#8221;)</strong> erstellt eine Liste.</li>
<li><strong>List(&#8220;var&#8221;, &#8220;wert&#8221;)</strong> fügt &#8220;wert&#8221; zur Liste hinzu.</li>
<li><strong>ListExists(&#8220;var&#8221;)</strong> liefert &#8220;true&#8221;, wenn eine Liste existiert.</li>
</ul>
<p>Mehr braucht man meistens nicht. Unser Click-Event wird demnach folgendermaßen aussehen:</p>
<pre class="c-sharp" name="code">private void btFindAlbum_Click(object sender, EventArgs e)
{
    // Interpret und Titel
    string artist = txtArtist.Text.Trim();
    string track = txtTrack.Text.Trim();

    // Wenn eins von beidem leer ist: nicht weitermachen
    if (artist == "" || track == "") return;

    // Ansonsten: Album (und richtig geschriebene Infos) holen
    L.Process(
        "[track.getInfo : track=" + track + "&amp;artist=" + artist + "]",
        "store track/album/title in album",
        "store track/artist/name in artist",
        "store track/name in track"
    );

    // Ausgabe oder Fehler
    if (L.Var("album") == "")
    {
        MessageBox.Show(
            "Kein Album zu diesen Eingaben gefunden!",
            "Kein Album",
            MessageBoxButtons.OK,
            MessageBoxIcon.Exclamation);
    }
    else
    {
        MessageBox.Show(
            "Gefundene Informationen:\n\n" +
               "   " + L.Var("artist") + " - " + L.Var("track") + "\n" +
               "   Album: " + L.Var("album"),
            "Album gefunden!",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information);
    }
}</pre>
<p>Das war auch schon alles. Mit &#8220;Process&#8221; wird die Anfrage abgeschickt und die nötigen Daten gesichert, mit &#8220;Var&#8221; erfolgt die Verwendung der Daten. Und schon ist das Programm <em>good to go</em>:</p>
<p><img class="alignnone size-full wp-image-681" title="Lapicon Tutorial: Fertiges Programm" src="http://dev.xscheme.de/wp-content/uploads/2009/04/lapiconassemblytut4.png" alt="Lapicon Tutorial: Fertiges Programm" width="486" height="305" /></p>
<p>Viel einfacher geht es wirklich nicht mehr. Ich hoffe, damit allen, die Interesse an Lapicon haben, ein Stück weiter geholfen zu haben.</p>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/04/lapicon-in-eigenem-programm-verwenden/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Wie baue ich mir ein Script mit Lapicon?</title>
		<link>http://dev.xscheme.de/2009/04/kurzanleitung-zu-lapicon/</link>
		<comments>http://dev.xscheme.de/2009/04/kurzanleitung-zu-lapicon/#comments</comments>
		<pubDate>Sat, 25 Apr 2009 00:06:07 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[Lapicon]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[lastfm]]></category>
		<category><![CDATA[rest]]></category>
		<category><![CDATA[webservice]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=635</guid>
		<description><![CDATA[Lapicon (Loose API Connection Language) ist ein Interpreter für eine Scriptsprache, die auf den ersten Blick Ähnlichkeiten mit dem Windows-Eigenen Batch hat und dafür gedacht ist, mit Webservern in Verbindung zu treten, die ihre Daten als XML bereitstellen und REST-Anfragen über HTTP-GET unterstützen. Soweit die Theorie.
Praktisch gesehen ist Lapicon eine einfache Möglichkeit, Webservices zu verwenden. [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://dev.xscheme.de/lapicon"><strong>Lapicon</strong></a> (Loose API Connection Language) ist ein Interpreter für eine Scriptsprache, die auf den ersten Blick Ähnlichkeiten mit dem Windows-Eigenen <em>Batch</em> hat und dafür gedacht ist, mit Webservern in Verbindung zu treten, die ihre Daten als XML bereitstellen und REST-Anfragen über HTTP-GET unterstützen. Soweit die Theorie.</p>
<p>Praktisch gesehen ist Lapicon eine einfache Möglichkeit, <strong>Webservices zu verwenden</strong>. Und geeignete gibt es (wie die Google-Suche nach &#8220;REST-API&#8221; schon bald zeigt) einige:</p>
<ul>
<li>Last.FM (http://www.last.fm/api)</li>
<li>Flickr (http://www.flickr.com/services/api/)</li>
<li>Twitter (http://apiwiki.twitter.com/Twitter-API-Documentation)</li>
<li>Wuala (http://www.wuala.com/de/api)</li>
<li>&#8230;</li>
</ul>
<p>Aber wie geht man nun vor, wenn man Lapicon für irgendeines dieser APIs verwenden will? Diese Frage will ich in diesem Artikel beantworten, anhand zweier Beispiele: den Skripts DownloadAlbumAfterSearch.lpc und Lyrics.lpc aus dem Paket <strong>DarkSharpScripts</strong>, das ab Version 1.0.9 von Lapicon im Verzeichnis &#8220;packages&#8221; zu finden ist. Das erste Script sucht anhand einer Eingabe alle in Frage kommenden Alben und lässt den User eines auswählen, das anschließend komplett von Last.FM heruntergeladen wird (wenn möglich). Und das zweite findet mithilfe der <a href="http://lyricwiki.org/Main_Page">LyricWiki</a> (noch eine Seite, die REST-Anfragen unterstützt!) den Songtext zu einem vom User eingegebenen Musiktitel.</p>
<p><span id="more-635"></span><strong></strong></p>
<p><strong>Schritt 0: Den einfachen Scriptstart ermöglichen</strong></p>
<p>Damit man möglichst einfach Lapicon-Scripts starten kann, sollte man eine Datei mit der Endung &#8220;.lpc&#8221; anlegen und unter Windows darauf doppelklicken und anschließend über &#8220;Programm aus Liste auswählen&#8221; &gt;&gt; &#8220;Durchsuchen&#8230;&#8221; die Lapicon.exe als Standardprogramm festlegen. Dann lassen sich Scripts per Doppelklick starten.</p>
<p><strong>Schritt 1: Grundeinstellungen</strong></p>
<p>Zuallererst muss Lapicon mitgeteilt werden, wohin es Anfragen schicken soll, wie diese Anfragen aussehen sollen und wie es mit den Antworten umzugehen hat. Man stelle sich folgende Fragen:</p>
<ol>
<li>Wie lautet die URL, an die die Anfragen gehen sollen? (Findet man meistens auf der API-Seite.)</li>
<li>Brauche ich irgendwelche Daten für die Anfrage, die immer vorhanden sein müssen? (Beispielsweise bei Last.FM: ein API-Schlüssel.)</li>
<li>Wie ist das Format der Anfrage-URL? &#8220;URL?Parameter&#8221; oder &#8220;URL/Parameter1/Parameter2&#8243;?</li>
<li>Wie sieht die Antwort aus? Welcher XML-Knoten ist die Wurzel? Sind irgendwelche Namespaces enthalten?</li>
<li>Werden mir Informationen bereitgestellt, ob Fehler aufgetreten sind, und wo finde ich sie?</li>
</ol>
<p>Jetzt geht es ans Konfigurieren von Lapicon, hier am Beispiel Last.FM:</p>
<pre>// s.o. Punkt 1: die Basis-URL
#base http://ws.audioscrobbler.com/2.0/

// s.o. Punkt 2: API-Key muss immer mitgesendet werden
#static api_key ce59c153ff5e3003c74df5b618aeccac

// s.o. Punkt 3: Die Anfrage hat das Format "URL?Parameter" (ansonsten: #noquery)
#query

// s.o. Punkt 4: Wurzel ist der "lfm"-Knoten; wir brauchen die Namespaces "xspf" und "lastfm"
#rootnode
#namespace xspf http://xspf.org/ns/0/
#namespace lastfm http://www.audioscrobbler.net/dtd/xspf-lastfm

// s.o. Punkt 5: wenn kein Fehler aufgetreten ist, steht im Attribut "status" des "lfm"-Knotens "ok"
#errornode lfm/@status
#noerrorvalue ok</pre>
<p>Für &#8220;#rootnode&#8221; und &#8220;#errornode&#8221;, sowie später für Anfragen, wird Kenntnis von XPath benötigt. Es sei an dieser Stelle auf den <a href="http://de.wikipedia.org/wiki/XPATH">Wikipedia-Artikel zum Thema</a> verwiesen.</p>
<p>Da Last.FM als eines der Hauptanwendungsfelder für Lapicon gedacht war, lässt sich das oben geschriebene abkürzen:</p>
<pre>#lastfm</pre>
<p>Und schon ist Lapicon bereit dafür, zu arbeiten.</p>
<p><strong>Schritt 2: Wie soll das Programm ablaufen?</strong></p>
<p>Es ist immer wichtig, sich klarzumachen, was man jetzt eigentlich vorhat. Was also soll unser Script machen? Welche Daten brauchen wir dafür? Welche Anfragen müssen wir senden und welche Teile der Antworten verwenden?</p>
<p>Wir schreiben ein Script, das Alben sucht und anschließend komplett herunterlädt, also soll es folgende Schritte ausführen:</p>
<ol>
<li>Eingabe des Suchstrings</li>
<li>Verwendung der Funktion <a href="http://www.last.fm/api/show/?service=357">album.search</a> des Last.FM-APIs um eine Suche durchzuführen. Aus der Antwort lesen wir die Titel, Interpreten und IDs der einzelnen Alben aus.</li>
<li>Ausgabe einer nummerierten Liste mit Interpreten und Albumtitel</li>
<li>Eingabe der Nummer des gewünschten Albums</li>
<li>Verwendung der Funktion <a href="http://www.last.fm/api/show/?service=271">playlist.fetch</a>, um an die Tracks des Albums zu kommen. (Mit zwei nicht dokumentierten Parametern.) Wir verwenden die URLs und die Titel der Tracks.</li>
<li>Erstellen des Ausgabeverzeichnisses nach dem Muster &#8220;Interpret/Album&#8221;</li>
<li>Schleife, die einen Track nach dem anderen herunterlädt.</li>
<li>Ende.</li>
</ol>
<p><strong>Schritt 3: Implementierung</strong></p>
<p>Das Originalscript setzt zuerst die Last.FM-Einstellungen fest, dann lädt es die DarkSharpScripts-Konfigurationsdatei, die im selben Verzeichnis liegt:</p>
<pre>// Use Last.FM
#lastfm

// Include
inc(&lt;script directory&gt;/Config.DarkSharpScripts.lpc)</pre>
<p>Anschließend folgt die Eingabe des Suchstrings und eine Prüfung, ob auch wirklich etwas eingegeben wurde:</p>
<pre>// Search
def query &lt;&lt; Suchen
echo

[if &lt;query&gt; is not empty]</pre>
<p>Die Zeile &#8220;def query &lt;&lt; Suchen&#8221; bewirkt, dass der User eine Eingabezeile mit der Beschriftung &#8220;[in] Suchen:&#8221; vor sich hat. Drückt er die Enter-Taste wird alles eingegebene in der Variable &#8220;query&#8221; gespeichert. Um auf den Wert dieses Variable wiederum zuzugreifen, muss man sie in spitze Klammern setzen: &#8220;&lt;query&gt;&#8221;. Alles, was jetzt folgt, wird demnach nur ausgeführt, wenn der Wert von &#8220;query&#8221; nicht leer ist:</p>
<pre>   [album.search : album=&lt;query&gt;&amp;limit=&lt;DS_SearchLimit&gt;]*
   store* results/albummatches/album/name in albumTitles
   store* results/albummatches/album/artist in albumCreators
   store* results/albummatches/album/id in albumIDs

   // Choose
   f:displayAlbum n
      echo [&lt;n&gt;] &lt;albumCreators[&lt;n&gt;]&gt; - &lt;albumTitles[&lt;n&gt;]&gt;
   f:end
   forall* albumTitles index t: displayAlbum(&lt;t&gt;)
   echo</pre>
<p>Das Konstrukt in eckigen Klammern ist die Anfrage an Last.FM. Vor dem Doppelpunkt steht die Funktion, die aufgerufen werden soll, dahinter ihre Argumente &#8211; in diesem Fall &#8220;album&#8221; und &#8220;limit&#8221; mit den Werten der Variablen &#8220;query&#8221; und &#8220;DS_SearchLimit&#8221; (letztere aus der Konfigurationsdatei; sie legt fest, wie viele Suchergebnisse maximal angezeigt werden). Die Sternchen signalisieren, dass nun nicht ein einzelner Wert gespeichert werden soll, sondern eine Reihe von Werten in einer Liste. &#8220;store* results/albummatches/album/name in albumTitles&#8221; beispielsweise speichert alle Werte, die über diesen XPath (&#8220;results/&#8230;&#8221;) zu erreichen sind, in der Liste &#8220;albumTitles&#8221;.</p>
<p>Was dann folgt, ist eine Funktion, die genau einen Wert (die Variable &#8220;n&#8221;) als Argument erhält. Sie gibt anhand der beiden Listen &#8220;albumTitles&#8221; und &#8220;albumCreators&#8221; das n-te Album (wobei n bei 0 beginnt) aus. Hier sieht man den Zugriff auf einzelne Listenelemente: &lt;albumTitles[0]&gt; wäre z.B. der Titel des ersten gefundenen Albums, falls vorhanden.</p>
<p>Diese Funktion wird nun für jedes Element der Liste &#8220;albumTitles&#8221; aufgerufen, immer mit dem aktuellen Index als Argument. (Neben der gezeigten Version der &#8220;forall*&#8221;-Schleife gibt es noch eine weitere, die nicht den Index, sondern den Wert als Variable liefert: &#8220;forall* list as element: &#8230;&#8221;)</p>
<p>Jetzt kann der User eingeben, welches Album er haben will, anschließend wird die Playlist mit den einzelnen Titel geladen:</p>
<pre>   def nr &lt;&lt; Nummer des Albums
   echo

   // Album Info
   def albumID = &lt;albumIDs[&lt;nr&gt;]&gt;
   def albumName = file(&lt;albumTitles[&lt;nr&gt;]&gt;)
   def albumCreator = file(&lt;albumCreators[&lt;nr&gt;]&gt;)

   // Playlist Info
   [playlist.fetch : playlistURL=lastfm://playlist/album/&lt;albumID&gt;&amp;streaming=1&amp;fod=1]*
   store* xspf:playlist/xspf:trackList/xspf:track/xspf:title in trackTitles
   store* xspf:playlist/xspf:trackList/xspf:track/xspf:location in trackLocation</pre>
<p>Die Zeile &#8220;def albumName = file(&#8230;)&#8221; speichert in der Variable &#8220;albumName&#8221; einen bereinigten, für Dateinamen geeigneten String. Jetzt haben wir die URLs und Titel der Tracks, die wir herunterladen wollen. Und genau das macht der folgende Abschnitt &#8211; wieder nach dem Prinzip: &#8220;Lade die Datei mit dem Index &#8220;index&#8221; herunter!&#8221;</p>
<pre>   [if &lt;trackLocation length&gt; != 0]

      // Directory
      def dir = &lt;DS_AlbumsPath&gt;&lt;albumCreator&gt;/&lt;albumName&gt;
      mkdir &lt;dir&gt;

      // Download Function
      f:dl index
         def title = file(&lt;trackTitles[&lt;index&gt;]&gt;)
         def nindex
         nindex ::= &lt;index&gt;+1
         echo Downloading(&lt;nindex&gt;/&lt;trackLocation length&gt;) &lt;albumCreator&gt; - &lt;title&gt;...
         def fn = &lt;dir&gt;/&lt;nindex&gt; - &lt;albumCreator&gt; - &lt;title&gt;.mp3
         lpc(&lt;script directory&gt;/ResolveAndDownload.lpc) with &lt;trackLocation[&lt;index&gt;]&gt;|&lt;fn&gt;
         mp3Gain(&lt;fn&gt;)
      f:end

      // Download
      forall* trackTitles as t: dl(&lt;trackTitles counter&gt;)
      //
      wait

   [else]

      wait Album nicht herunterladbar

   [endif]

[else]

   wait Keine Sucheingabe

[endif]</pre>
<p>Wir sehen einiges Neues hier: die Funktion &#8220;mkdir&#8221; erstellt ein Verzeichnis, die Zuweisung mit &#8220;::=&#8221; löst eine Berechnung aus (damit &#8220;nindex&#8221; um 1 größer ist als &#8220;index&#8221;) und dann das Konstrukt &#8220;lpc(&#8230;) with &#8230;&#8221;. Hier geschieht folgendes: Lapicon ruft das Script ResolveAndDownload.lpc auf, das im selben Verzeichnis liegt, und zwar mit den <span style="text-decoration: underline;">Kommandozeilenargumenten</span> &lt;trackLocation[&lt;index&gt;]&gt; und &lt;fn&gt;. Dieses kleine Hilfsscript macht nichts anderes, als die übergebene URL (das erste Argument) mithilfe von HTTP aufzulösen und dann herunterzuladen. Das zweite Argument ist der Name der heruntergeladenen Datei. Es sollte sich also eigentlich von selbst erschließen:</p>
<pre>// Data
def url = &lt;cmd 1&gt;
def file = &lt;cmd 2&gt;

// Resolve
def rurl

#error off
rurl = resolve &lt;url&gt;
#error on

[if &lt;rurl&gt; is not empty]

   down &lt;rurl&gt; &gt;&gt; &lt;file&gt;

[else]

   echo Resolving URL "&lt;url&gt;" failed...
   echo
   wait Taste drücken zum Beenden
   exit

[endif]</pre>
<p>Die Funktion &#8220;mp3Gain&#8221;, die nach dem &#8220;lpc&#8221;-Statement kommt, ist in der Konfigurationsdatei definiert und ruft ein Hilfsprogramm auf, das wiederum mit der mp3Gain.exe,  die im selben Verzeichnis liegt, die Musikdatei normalisiert:</p>
<pre>// MP3-Gain
f:mp3Gain file

   [if &lt;file&gt; is not empty]

      echo Normalisieren...
      shell: &lt;script directory&gt;/ExecuteMp3Gain.bat | "&lt;file&gt;"

   [endif]

f:end</pre>
<p>Eine genaue Übersicht über alle von Lapicon unterstützen Funktionen findet sich in der Datei Lapicon.txt oder auf SourceForge: <a href="http://lastsharp.svn.sourceforge.net/viewvc/lastsharp/branches/Lapicon/Lapicon.txt?view=markup&amp;pathrev=65">http://lastsharp.svn.sourceforge.net/viewvc/lastsharp/branches/Lapicon/Lapicon.txt?view=markup&amp;pathrev=65</a></p>
<p><strong>Beispiel 2: Lyrics.lpc</strong></p>
<pre>// -------------------------------------------------------------------
// DarkSharp.Lyrics
//   Downloads lyrics
//   Copyright (c) 2009 Yannick Scherer
// -------------------------------------------------------------------
// Lyricwiki
#base http://lyricwiki.org/api.php
#rootnode LyricsResult
#static func getSong
#static fmt xml

// Config
inc(&lt;script directory&gt;/Config.DarkSharpScripts.lpc)

// Input
def artist &lt;&lt; Interpret
def track &lt;&lt; Titel

// Get Lyrics from Lyricwiki
[artist=&lt;artist&gt;&amp;song=&lt;track&gt;] store lyrics in lyrics

[if &lt;lyrics&gt; is not empty]
   // Clean
   artist = file(&lt;artist&gt;)
   track = file(&lt;track&gt;)
   // Save
   mkdir &lt;DS_LyricsPath&gt;
   echo crlf(&lt;lyrics&gt;) &gt;&gt;&gt; &lt;DS_LyricsPath&gt;&lt;artist&gt; - &lt;track&gt;.txt
[else]
   echo Keine Lyrics gefunden!
[endif]

echo
wait</pre>
<p>Anmerkungen hierzu: man kann Anfragen auch in einer Zeile und/oder ohne Angabe der Funktion schreiben. Letzteres ist immer dann notwendig, wenn der Parameter, der die Funktion enthält, nicht &#8220;method&#8221; heißt.</p>
<p>Des weiteren sieht man eine Dateiausgabe: zuerst wird mit &#8220;artist = file(&lt;artist&gt;)&#8221; der Interpret und anschließend der Titel bereinigt, dann wird ein Verzeichnis erstellt und anschließend eine Ausgabe in eine Datei getätigt. Das Einschließen der Lyrics in &#8220;crlf(&#8230;)&#8221; bewirkt, dass Zeilenumbrüche richtig umgesetzt werden; das Verwenden von &#8220;&gt;&gt;&gt;&#8221; statt &#8220;&gt;&gt;&#8221; zwingt Lapicon, eine evtl. vorhandene Datei zu überschreiben, anstatt an deren Ende etwas anzuhängen.</p>
<p>Das Nachvollziehen der Grundeinstellungen bleibt jedem selbst überlassen.</p>
<p>Ich hoffe, damit konnte ich ein wenig helfen, wenn es um die Arbeit mit Lapicon geht!</p>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/04/kurzanleitung-zu-lapicon/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Inside Last.FM: LastSharp und die eigene Playlist</title>
		<link>http://dev.xscheme.de/2009/04/inside-lastfm-lastsharp-und-die-eigene-playlist/</link>
		<comments>http://dev.xscheme.de/2009/04/inside-lastfm-lastsharp-und-die-eigene-playlist/#comments</comments>
		<pubDate>Tue, 21 Apr 2009 18:46:40 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[HowTo]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=598</guid>
		<description><![CDATA[Wenn man versucht, als Nicht-Abonnent mit LastSharp die eigene Playlist herunterzuladen, gelingt das meistens noch mit dem ersten Titel, bevor folgende Meldung erscheint:
Es ist ein Problem beim TuneIn aufgetreten:
There is not enough content provided by Last.FM to play the station. If this is your playlist, fill it up. Otherwise (or if this message keeps appearing), [...]]]></description>
			<content:encoded><![CDATA[<p>Wenn man versucht, als Nicht-Abonnent mit LastSharp die eigene Playlist herunterzuladen, gelingt das meistens noch mit dem ersten Titel, bevor folgende Meldung erscheint:</p>
<p style="padding-left: 30px;">Es ist ein Problem beim TuneIn aufgetreten:<br />
There is not enough content provided by Last.FM to play the station. If this is your playlist, fill it up. Otherwise (or if this message keeps appearing), wait or try another station.</p>
<p><strong>Das ist kein Problem, das von LastSharp verursacht wird</strong>, sondern hat mit Last.FM bzw. dessen Verhalten bei Playlistabfragen zu tun. Im Folgenden will ich versuchen, meine Erkenntnisse zusammenzufassen.</p>
<p><span id="more-598"></span></p>
<p>Man betrachte die <a href="http://www.lastfm.de/help/faq?category=Musik+h%C3%B6ren+auf+Last.fm">Last.FM-FAQ zum Thema &#8220;Musik hören auf  Last.FM&#8221;</a>:</p>
<blockquote><p><strong>Du kannst Playlisten anhören, wenn du ein Abonnent bist.</strong> Um eine Playlist anzuhören &#8211; deine eigene oder die eines anderen Benutzers &#8211; gehe zu der Seite der jeweiligen Playlist (zum Beispiel in deiner eigenen Musiksammlung) und klicke dort den Button &#8220;Spiele diese Playlist&#8221;. Playlisten werden immer mit zufälliger Titelreihenfolge wiedergegeben, und sie müssen mindestens 45 Titel von 15 Künstlern enthalten, bevor du sie spielen kannst.</p>
<p><strong><em>Beachte: Playlisten können derzeit nur auf der Website, nicht im Softwareplayer wiedergegeben werden.</em></strong></p>
<p><strong>Falls du kein Abonnent bist, kannst trotzdem deine eigenen Playlisten erstellen, aber du kannst sie dir nicht als solche anhören.</strong> Deine neueste Playlist wird in dem Player auf deiner Profilseite erscheinen, wo die Titel von dir oder anderen Leuten, die dein Profil besuchen, einzeln gespielt oder angespielt werden können.</p></blockquote>
<p>Zur Illustration: das Last.FM-Widget verwendet folgende URL, um an die Playlist zu kommen:</p>
<p style="padding-left: 30px;">lastfm://playlist/[PlaylistID]/shuffle</p>
<p>Verwendet man diese URL als Nicht-Abonnent, erhält man die Meldung, dass diese Radiostation nur für Abonnenten zugänglich ist. (Fehlercode 5)</p>
<p>In LastSharp wird also bei Nicht-Abonnenten das &#8220;/shuffle&#8221; am Ende weggelassen, was Last.FM dazu bringt, dennoch die richtige Playlist zu senden. Allerdings erscheint schon bald der o.g. Fehler, wenn man mit dem Herunterladen beginnt &#8211; wahrscheinlich bemerkt Last.FM, dass da etwas im Gange ist, was eigentlich nicht erlaubt wäre. Stellt sich die Frage, warum man es nicht von vornherein unterbindet.</p>
<p>Antwort siehe oben: &#8220;Deine neueste Playlist wird in dem Player auf deiner Profilseite erscheinen, wo die Titel von dir oder anderen Leuten, die dein Profil besuchen, einzeln gespielt oder angespielt werden können.&#8221; Man kann also als Nicht-Abonennt nicht gezielt die eigene Playlist anhören, sondern nur, wenn sie auf der eigenen Profilseite erscheint. Und wie macht das Widget das dann? So: <a href="http://dev.xscheme.de/2009/03/inside-lastfm-bestimmten-titel-direkt-downloaden/">Inside Last.FM: Bestimmten Titel direkt downloaden</a></p>
<p>Ich stelle also die Frage: Sollte man die Widget-Methode in LastSharp implementieren, damit auch Nicht-Abonnenten ihre Playlist herunterladen können &#8211; auf die Gefahr hin, LastSharp ein wenig fragwürdiger erscheinen zu lassen, was Legalität angeht?</p>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/04/inside-lastfm-lastsharp-und-die-eigene-playlist/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Inside Last.FM: Bestimmten Titel direkt downloaden</title>
		<link>http://dev.xscheme.de/2009/03/inside-lastfm-bestimmten-titel-direkt-downloaden/</link>
		<comments>http://dev.xscheme.de/2009/03/inside-lastfm-bestimmten-titel-direkt-downloaden/#comments</comments>
		<pubDate>Thu, 05 Mar 2009 22:36:49 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[HowTo]]></category>
		<category><![CDATA[bestimmten titel herunterladen]]></category>
		<category><![CDATA[lastfm]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=540</guid>
		<description><![CDATA[Bevor ich dieses Thema weiter ausführe, will ich sagen, dass ich ungeheure Skrupel davor habe, in irgendeinem Programm jemals dieses Feature zu implementieren. Theoretisch ist es möglich, praktisch wird die Grauzone, die Last.FM ohnehin umgibt, um ein Vielfaches dunkler&#8230;
Es ist möglich, Titel vollständig, ganz gezielt und ohne Anmeldung von Last.FM herunterzuladen!
Hierfür verwendet man zwei Funktionen [...]]]></description>
			<content:encoded><![CDATA[<p>Bevor ich dieses Thema weiter ausführe, will ich sagen, dass ich ungeheure Skrupel davor habe, in irgendeinem Programm jemals dieses Feature zu implementieren. Theoretisch ist es möglich, praktisch wird die Grauzone, die Last.FM ohnehin umgibt, um ein Vielfaches dunkler&#8230;</p>
<p><strong>Es ist möglich, Titel <span style="text-decoration: underline;">vollständig</span>, <span style="text-decoration: underline;">ganz gezielt</span> und <span style="text-decoration: underline;">ohne Anmeldung</span> von Last.FM herunterzuladen!</strong></p>
<p><span id="more-540"></span>Hierfür verwendet man zwei Funktionen des APIs: <a href="http://www.last.fm/api/show?service=356">track.getInfo</a> und <a href="http://www.last.fm/api/show?service=271">playlist.fetch</a>. Die erste holt die Last.FM-ID des Titels, die zweite findet die Download-URL. Und zwar folgendermaßen:</p>
<p>Ausgehend von Interpret und Titel rufen wir via <a href="http://de.wikipedia.org/wiki/Representational_State_Transfer">REST</a> die Funktion track.getInfo auf:</p>
<p><span style="font-size: 8pt;">http://ws.audioscrobbler.com/2.0/?method=track.getinfo&amp;api_key=<strong>[APIKey]</strong>&amp;artist=<strong>[Interpret]</strong>&amp;track=<strong>[Titel]</strong></span></p>
<p>Also z.B. &#8220;The Ballad of the RAA&#8221; von &#8220;The Rural Alberta Advantage&#8221; mit dem auf der API-Seite angegebenen Key:</p>
<p><span style="font-size: 8pt;">http://ws.audioscrobbler.com/2.0/?method=track.getinfo&amp;api_key=b25b959554ed76058ac220b7b2e0a026&amp;artist=the+rural+alberta+advantage&amp;track=the+ballad+of+the+raa</span></p>
<p>Wir erhalten folgende Antwort (XML):</p>
<pre><span class="pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
&lt;<span class="start-tag">lfm</span><span class="attribute-name"> status</span>=<span class="attribute-value">"ok"</span>&gt;
&lt;<span class="start-tag">track</span>&gt;
    <span style="color: #ff9900;">&lt;<span class="start-tag">id</span>&gt;170204242&lt;/<span class="end-tag">id</span>&gt;
</span>    &lt;<span class="start-tag">name</span>&gt;The Ballad of The RAA&lt;/<span class="end-tag">name</span>&gt;
    &lt;<span class="start-tag">mbid</span>&gt;&lt;/<span class="end-tag">mbid</span>&gt;
    &lt;<span class="start-tag">url</span>&gt;http://www.last.fm/music/The+Rural+Alberta+Advantage/_/The+Ballad+of+The+RAA&lt;/<span class="end-tag">url</span>&gt;
    &lt;<span class="start-tag">duration</span>&gt;207000&lt;/<span class="end-tag">duration</span>&gt;
    <span style="color: #ff9900;">&lt;<span class="start-tag">streamable</span><span class="attribute-name"> fulltrack</span>=<span class="attribute-value">"1"</span>&gt;1&lt;/<span class="end-tag">streamable</span>&gt; </span>
    &lt;<span class="start-tag">listeners</span>&gt;2310&lt;/<span class="end-tag">listeners</span>&gt;
    &lt;<span class="start-tag">playcount</span>&gt;12794&lt;/<span class="end-tag">playcount</span>&gt;
    &lt;<span class="start-tag">artist</span>&gt;
        &lt;<span class="start-tag">name</span>&gt;The Rural Alberta Advantage&lt;/<span class="end-tag">name</span>&gt;
        &lt;<span class="start-tag">mbid</span>&gt;&lt;/<span class="end-tag">mbid</span>&gt;
        &lt;<span class="start-tag">url</span>&gt;http://www.last.fm/music/The+Rural+Alberta+Advantage&lt;/<span class="end-tag">url</span>&gt;
    &lt;/<span class="end-tag">artist</span>&gt;
    ...
&lt;/<span class="end-tag">track</span>&gt;
&lt;/<span class="end-tag">lfm</span>&gt;</pre>
<p>Uns interessieren die beiden hervorgehobenen Stellen: die erste liefert die gesuchte ID, die zweite sagt uns, ob der Track in voller Länge anhörbar ist. <strong>Nur wenn diese Zeile genau so (fulltrack=&#8221;1&#8243;) aussieht, funktioniert die hier beschriebene Methode!</strong></p>
<p>Jetzt rufen wir playlist.fetch mithilfe dieser ID auf, indem wir die Playlist &#8220;lastfm://playlist/track/[ID]&#8221; verlangen:</p>
<p><span style="font-size: 8pt;">http://ws.audioscrobbler.com/2.0/?method=playlist.fetch&amp;api_key=<strong>[APIKey]</strong>&amp;playlistURL=lastfm://playlist/track/<strong>[ID]</strong>&amp;streaming=1&amp;fod=1&amp;api_sig=<strong>[Signatur]</strong>&amp;sk=<strong>[Session]</strong></span></p>
<div style="border: solid 2px #bbbbbb; background-color: #fbfbfb; padding: 1em; margin-top: 1em; margin-bottom: 1em;">Seit Ende Juli 2009 werden (im Gegensatz zu vorher) die Parameter <em>api_sig</em> (eine korrekte API-Signatur)  und <em>sk</em> (ein zuvor über <a href="http://www.lastfm.de/api/show?service=266">auth.getMobileSession</a> geholter SessionKey) benötigt. Das wurde vermutlich gemacht, damit niemand mehr den Beispiel-API-Key der Dokumentationsseite für diesen Zweck missbrauchen kann. Eine solche Signatur wird folgendermaßen erstellt:</p>
<ol>
<li>Alle Parameter (auch <em>api_key</em>!) alphabetisch ordnen.</li>
<li>Parameternamen und -werte  ohne Trennzeichen hintereinanderschreiben.</li>
<li>API-Secret anhängen.</li>
<li>MD5-Hash bilden.</li>
</ol>
<p>Also bei uns bekäme man die Signatur allgemein mit (ohne Zeilenumbruch):</p>
<pre>MD5(<span style="font-size: 8pt;">api_key<strong><strong>[APIKey]</strong></strong>fod1methodplaylist.fetchplaylistURLlastfm://playlist/track/<strong><strong>[ID]</strong></strong>
sk<strong><strong>[Session]</strong></strong></span><span style="font-size: 8pt;">streaming1<strong>[APISecret]</strong>)</span></pre>
<p>Wenn ich das so betrachte, ist die Signatur für die meisten derjenigen, die diese Methode hier angewandt haben nicht ohne weiteres zu erstellen und erfüllt somit wohl ihren Zweck.</p>
<p>Man bräuchte, um erfolgreich zu sein, einen Account bei Last.FM und einen API-Schlüssel. Beides ist kostenlos verfügbar, der Aufwand erhöht sich durch das Erstellen der Signatur und dem vorherigen Aufruf von auth.getMobileSession aber ungemein.</p></div>
<p>Der Parameter &#8220;streaming=1&#8243; ist wichtig, da nur so auch der Pfad zur entsprechenden Datei übermittelt wird; &#8220;fod=1&#8243; erweitert anscheinend unsere Zugriffsberechtigung von &#8220;Vorschau&#8221; auf &#8220;Ganzer Titel, falls verfügbar&#8221;. Für unseren Titel senden wir also</p>
<p><span style="font-size: 8pt;">http://ws.audioscrobbler.com/2.0/?method=playlist.fetch&amp;api_key=</span><span style="font-size: 8pt;">b25b959554ed76058ac220b7b2e0a026</span><span style="font-size: 8pt;">&amp;playlistURL=lastfm://playlist/track/<span style="color: #ff9900;">170204242</span>&amp;streaming=1&amp;fod=1&amp;api_sig=&#8230;&amp;sk=&#8230;</span></p>
<p>und erhalten als Antwort so etwas wie:</p>
<pre><span class="pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
&lt;<span class="start-tag">lfm</span><span class="attribute-name"> status</span>=<span class="attribute-value">"ok"</span>&gt;
&lt;<span class="start-tag">playlist</span><span class="attribute-name"> version</span>=<span class="attribute-value">"1" </span><span class="attribute-name">xmlns</span>=<span class="attribute-value">"http://xspf.org/ns/0/"</span>&gt;
&lt;<span class="start-tag">title</span>&gt;The Ballad of The RAA&lt;/<span class="end-tag">title</span>&gt;
&lt;<span class="start-tag">creator</span>&gt;lastfm&lt;/<span class="end-tag">creator</span>&gt;
&lt;<span class="start-tag">link</span><span class="attribute-name"> rel</span>=<span class="attribute-value">"http://www.lastfm/allflp"</span>&gt;&lt;/<span class="end-tag">link</span>&gt;
&lt;<span class="start-tag">trackList</span>&gt;
    &lt;<span class="start-tag">track</span>&gt;
        <span style="color: #ff9900;">&lt;<span class="start-tag">location</span>&gt;http://play.last.fm/anon/84c5974616cab2aadc698096893ee9d7.mp3&lt;/<span class="end-tag">location</span>&gt;</span>
        &lt;<span class="start-tag">id</span>&gt;170204242&lt;/<span class="end-tag">id</span>&gt;
        &lt;<span class="start-tag">title</span>&gt;The Ballad of The RAA&lt;/<span class="end-tag">title</span>&gt;
        &lt;<span class="start-tag">album</span>&gt;&lt;/<span class="end-tag">album</span>&gt;
        &lt;<span class="start-tag">creator</span>&gt;The Rural Alberta Advantage&lt;/<span class="end-tag">creator</span>&gt;
        &lt;<span class="start-tag">duration</span>&gt;207000&lt;/<span class="end-tag">duration</span>&gt;
        &lt;<span class="start-tag">extension</span><span class="attribute-name"> application</span>=<span class="attribute-value">"http://www.last.fm"</span>&gt;
            &lt;<span class="start-tag">fullduration</span>&gt;207000&lt;/<span class="end-tag">fullduration</span>&gt;
            &lt;<span class="start-tag">trackauth</span>&gt;9c1a1&lt;/<span class="end-tag">trackauth</span>&gt;
            &lt;<span class="start-tag">artistpage</span>&gt;http://www.last.fm/music/The+Rural+Alberta+Advantage&lt;/<span class="end-tag">artistpage</span>&gt;
            &lt;<span class="start-tag">albumpage</span>&gt;&lt;/<span class="end-tag">albumpage</span>&gt;
            &lt;<span class="start-tag">trackpage</span>&gt;

http://www.last.fm/music/The+Rural+Alberta+Advantage/_/The+Ballad+of+The+RAA

            &lt;/<span class="end-tag">trackpage</span>&gt;
            &lt;<span class="start-tag">buyTrackURL</span>&gt;...&lt;/<span class="end-tag">buyTrackURL</span>&gt;
            &lt;<span class="start-tag">buyAlbumURL</span>&gt;...&lt;/<span class="end-tag">buyAlbumURL</span>&gt;
            &lt;<span class="start-tag">freeTrackURL</span>&gt;&lt;/<span class="end-tag">freeTrackURL</span>&gt;
            &lt;<span class="start-tag">fodlimit</span>&gt;&lt;/<span class="end-tag">fodlimit</span>&gt;
            &lt;<span class="start-tag">license</span>&gt;arp&lt;/<span class="end-tag">license</span>&gt;
        &lt;/<span class="end-tag">extension</span>&gt;
        &lt;<span class="start-tag">image</span>&gt;http://userserve-ak.last.fm/serve/126s/19603485.jpg&lt;/<span class="end-tag">image</span>&gt;
    &lt;/<span class="end-tag">track</span>&gt;
&lt;/<span class="end-tag">trackList</span>&gt;
&lt;/<span class="end-tag">playlist</span>&gt;
&lt;/<span class="end-tag">lfm</span>&gt;</pre>
<p>Und voilà, die hervorgehobene Zeile zeigt uns, wo wir die gesuchte Datei finden. Ist der Track nicht vollständig abspielbar erhalten wir immerhin die 30-Sekunden-Vorschau, aber wer will die schon&#8230;</p>
<p>Nun, das sind genug Informationen, dass jeder, der ein wenig Ahnung von Programmierung hat, sich einen eigenen Client zusammenbasteln kann, der gezielt bestimmte Titel herunterlädt. Auch eine Website ließe sich ohne weitere Probleme aufbauen, die zu den Eingaben &#8220;Interpret&#8221; und &#8220;Titel&#8221; die entsprechende URL findet.</p>
<p>Ich brauche Meinungen dazu&#8230;</p>
<p>P.S.:  <a href="http://www.last.fm/music/The+Rural+Alberta+Advantage">The Rural Alberta Advantage</a> ist eine Indie-Band aus Kanada, die meiner Meinung nach Unterstützung verdient hat. Dementsprechend habe ich mir auch auf der <a href="http://www.theraa.com/">Website der Band</a> deren Album &#8220;Hometowns&#8221; gekauft. Sehr zu empfehlen!</p>
<p><strong>Update (22.04.2009):</strong> Diese Methode habe ich als eines der Beispiel-Scripts für <a href="http://dev.xscheme.de/2009/04/release-lapicon-105/">Lapicon</a> implementiert.</p>
<p><strong>Update (05.08.2009):</strong><br />
Last.FM hat einige Änderungen durchgeführt, die die Verwendung dieser Methode für jeden, der sie von Hand anwendet, sehr erschweren. Eine Implementierung in einem Programm wäre aber ohne weiteres möglich. (Es gibt meines Wissens auch einige Programme, die bisher auf diesem Weg an Musik gekommen sind. Die werden bestimmt angepasst. Nur bevor jemand mich darum bittet, sowas zu schreiben^^)</p>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2009/03/inside-lastfm-bestimmten-titel-direkt-downloaden/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
	</channel>
</rss>
