Technik, Gothic und Anderes

Technik ist Spiel, Gothic ist ernst und Zeit hat man zuviel

Archiv für 'Coding' Kategorie


Selbst einen Flickr Screensaver in VB.net programmieren

Geschrieben von skaldrom am 15. January 2008

ScreensaverJa, ich lebe in einer dunklen Welt: in diese Welt sind die memory_limits noch auf 8MB, viele Pixel auf dem Monitor sind untot, Slashdot ist wegen zuviel Arbeit nicht mehr in der History meiner Browserleiste und aus hier nicht näher zu spezifizierenden Gründen muss ich mit Visual Basic 2005 Express Edition arbeiten. Und das in einer VMWare Umgebung unter Linux. Da kommt Freude auf!

Nundenn, da sucht man sich doch einfach mal ein nettes Projekt um den Einstieg etwas zu erleichtern. Meine Wahl fiel auf einen Bildschirmschoner, der in regelmässigen Abständen Bilder von flickr darstellen soll.

Benötigte Dinge

Um möglichst schmerzfrei auf Flicker zugreifen zu können, benutze ich die FlickrNet API Library von Codeplex. Diese Library ist relativ gut dokumentiert und hat einen irgendwie offiziellen Touch.

Ausserdem benutze ich das Screensaver Starter Kit, das bei Visual Basic dabei ist um das grundlegende Gerüst zu erzeugen. Es kann angewählt werden, wenn ein neues Projekt erstellt wird.

Lustige Details und Probleme

Generell kann man mal ganz viel aus dem generierten Code löschen. Interessierte seien auf das beiliegende Projekt verwiesen.

FlickrNet API Library

Zuerst muss die FlickerNet.dll in das Projekt kopiert werden. Neuere Versionen haben bei mir Probleme gemacht, was aber gar nichts heissen muss. Damit alle diese Fehler ausgeschlossen werden können, habe ich eine aus einem funktionierenden Beispiel herauskopiert. Bei My Project → Verweise muss diese DLL hinzugefügt und der Namespace importiert werden.
Bei Flickr muss noch einen API-Key gelöst werden und dann steht dem Einsatz nichts mehr im Wege.

Bildschirmschoner

Ein Screensaver ist nichts anderes als ein grosses Form ohne Fensterränder, das sich immer nach vorne zwängt und den Mauszeiger abschaltet. Ein Bildschirmschoner sollte drei Modi unterstützen:

  • Darstellen
  • Preview beim Windows-Konfigurationsdialog
  • Einstellungen

Die Hauptarbeit, nämlich die Darstellung des eigentlichen Screensavers wird im ScreenSaverForm gemacht, Die Einstellungen werden über das OptionsForm abgehandelt.

Mit der folgenden Funktion wird die URL von ca. 100 Fotos im Feld allPhotos gespeichert:

''' <summary>
    ''' Lädt die URLs von einigen Bilder zum gegebenen Tag von Flickr in ein Feld
    ''' </summary>
    Private Sub LoadBackgroundImage()
        Dim f As Flickr = New Flickr(apikey)

        Dim options As PhotoSearchOptions = New PhotoSearchOptions()
        options.Tags = My.Settings.Tag
        Dim bgPhotos As Photos = f.PhotosSearch(options)

        allPhotos = bgPhotos.PhotoCollection
        currentImageIndex = 0

    End Sub

Ein Timer erhöht den Index des aktuell darzustellenden Bildes:

Private Sub backgroundChangeTimerTick(ByVal sender As Object, ByVal e As EventArgs) Handles backgroundChangeTimer.Tick

        ' Hintergrundbild ändern und nächstes Bild verwenden.
        currentImageIndex = currentImageIndex + 1
        Refresh()
    End Sub

Dieses Codesnipped lädt ein Bild mit einem Index herunter uns stellt es dar:

Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)
        If allPhotos.Length > 0 Then
            currentImageIndex = currentImageIndex Mod allPhotos.Length
            ' Aktuelles Hintergrundbild gestreckt zeichnen, sodass es den gesamten Bildschirm ausfüllt
            Dim f As Flickr = New Flickr(apikey)
            Dim currentImage As Image
            currentImage = Image.FromStream(f.DownloadPicture(allPhotos.Item(currentImageIndex).MediumUrl))
            e.Graphics.DrawImage(currentImage, 0, 0, Size.Width, Size.Height)
        Else
            myError = "No Photos for this Tag"
        End If

    End Sub

Weiter werden Tastatureingaben und Mausbewegungen abgefangen. Wenn der Screensaver nicht im Previewmodus ist, beendet er sich bei einer Benutzerreaktion.

Das Preview

Beim Screen Saver Starter Kit fehlt das Preview. Irgendwo haben die Hersteller geschrieben, das sei Absicht, damit die Entwickler das erweitern könnten. <laut>Haha!</laut>. Der Grund wurde mir dann schon klar. Bei einem Aufruf im Preview-Modus wird ein Windowshandle des Elternfensters mitgegeben. Da muss man sich als Kind eintragen und das geht mit dem super .net nicht so einfach. Dank VB-Helper und msdner (leider offline: http://www.msdner.com/dev-archive/6/10-32-68963.shtm) habe ich dann eine Lösung gefunden und implementieren können.

Der grösste Teil dieses magischen Codes steht in MyEvents.vb. Um das zu sehen muss man im Menü Projekt → Alle Dateien anzeigen auswählen und den Ast My Project aufklappen. Dort drin befindet sich die Unterscheidung der Modis, etc:

Public Declare Function GetClientRect Lib "user32" (ByVal hwnd As Integer, ByRef lpRect As RECT) As Integer
        Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer) As Integer
        Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, ByVal dwNewInteger As Integer) As Integer
        Public Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
        Public Declare Function SetParent Lib "user32" (ByVal hWndChild As Integer, ByVal hWndNewParent As Integer) As Integer
        Public Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
        Public Structure RECT
            Public left As Integer
            Public top As Integer
            Public right As Integer
            Public bottom As Integer
        End Structure
        Public Class Win32Corners
            Public Const SWP_NOACTIVATE As Object = &H10
            Public Const SWP_NOZORDER As Object = &H4
            Public Const SWP_SHOWWINDOW As Object = &H40
            Public Const GWL_STYLE As Object = -16
            Public Const WS_CHILD As Object = &H40000000
            Public Const GWL_HWNDPARENT As Object = -8
            Public Const HWND_TOP As Object = 0
        End Class

        Private Sub SetForm(ByRef f As ScreenSaverForm, ByRef arg As Long)
            Dim style As Integer
            Dim previewHandle As Integer = Int32.Parse(CType(arg, String))
            Dim r As New RECT
            GetClientRect(previewHandle, r)
            With f
                .WindowState = FormWindowState.Normal
                .FormBorderStyle = FormBorderStyle.None
                .Width = r.right
                .Height = r.bottom
            End With
            style = GetWindowLong(f.Handle.ToInt32, Win32Corners.GWL_STYLE)
            style = style Or Win32Corners.WS_CHILD
            SetWindowLong(f.Handle.ToInt32, Win32Corners.GWL_STYLE, style)
            SetParent(f.Handle.ToInt32, previewHandle)
            SetWindowLong(f.Handle.ToInt32, Win32Corners.GWL_HWNDPARENT, previewHandle)
            SetWindowPos(f.Handle.ToInt32, 0, r.left, 0, r.right, r.bottom, Win32Corners.SWP_NOACTIVATE Or Win32Corners.SWP_NOZORDER Or Win32Corners.SWP_SHOWWINDOW)
        End Sub

        Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As ApplicationServices.StartupEventArgs) Handles Me.Startup
            'Dunno why this is needed
            My.Settings.RunsAsPreview = False
            If e.CommandLine.Count > 0 Then
                ' Aus zwei Zeichen bestehendes Befehlszeilenargument abrufen
                Dim arg As String = e.CommandLine(0).ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim().Substring(0, 2)
                Select Case arg
                    Case "/c"
                        ' Dialogfeld "Optionen" anzeigen
                        Me.MainForm = My.Forms.OptionsForm
                    Case "/p"
                        My.Settings.RunsAsPreview = True
                        Me.MainForm = My.Forms.ScreenSaverForm
                        SetForm(Me.MainForm, e.CommandLine(1))
                    Case "/s"
                        My.Settings.RunsAsPreview = False
                        Me.MainForm = My.Forms.ScreenSaverForm
                    Case Else
                        MessageBox.Show("Ungültiges Befehlszeilenargument:" + arg, "Ungültiges Befehlszeilenargument", MessageBoxButtons.OK, MessageBoxIcon.Error)
                End Select
            Else
                ' Wenn keine Argumente übergeben wurden, Bildschirmschoner anzeigen
                Me.MainForm = My.Forms.ScreenSaverForm
            End If

        End Sub

        'OnInitialize wird für die erweiterte Anpassung des eigenen Anwendungsmodells (MyApplication) verwendet.
        'Der Startcode für Ihre spezielle Anwendung sollte in einem Startereignishandler positioniert werden.
        <Global.System.Diagnostics.DebuggerStepThrough()> _
        Protected Overrides Function OnInitialize(ByVal commandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String)) As Boolean
            Return MyBase.OnInitialize(commandLineArgs)
        End Function

Installation

Im bin/Release/ Verzeichnis gibt es nach dem Erstellen des Projektes mehrere Dateien. Damit die Geschichte läuft, muss man FlickrSaver.exe in FlickrSaver.scr und FlickrSaver.exe.config muss in FlickrSaver.scr.config umbenamsen. Danach können die 3 Dateien:

  • FlickrSaver.scr
  • FlickrSaver.scr.config
  • FlickerNet.dll

ins c:\WIndows\system32 Verzeichnis verschoben und als Bildschirmschoner aufgerufen werden.

Viel Spass mit dem Code

Teile und geniesse:
  • Technorati
  • del.icio.us
  • MisterWong
  • Digg
  • StumbleUpon
  • blogmarks
  • Furl
  • Simpy
  • Spurl
  • YahooMyWeb

Ähnliche Artikel

Eingeordnet in Coding | Keine Kommentare »

Drupal mit neuen Google AdSense Werbeblöcken (Serverbasiert)

Geschrieben von admin am 16. November 2007

Neues AdSense


(Affilliate Link :-) ) sind neuerdings Serverbasiert. Das heisst: Aussehen, Inhalt, etc kann grösstenteils von der AdSense-Seite aus gemanaged und muss nicht mehr mühsam von Hand auf jeder Seite eingepflegt, geftpt und gecheckt werden. Das geniale Drupal AdSense Modul beherrscht diese neue Variante leider (noch) nicht von Haus aus. Mit einem kleinen Patch geht das aber.

Vorgehen

  1. Die Datei adsense.module mit diesem Patch so patchen, wie in diesem Beitrag beschrieben.
  2. Auf der Google AdSense Seite AdSense-Setup → Anzeigen Verwalten aufrufen und eine neue Anzeige designen.
  3. Den Code anzeigen lassen und die Slot-Nummer notieren. Es sollte etwas sein wie 2666876213.
  4. In Dupal als Administrator Administer → Site configuration → Google AdSense aufrufen und Custom Channels aufklappen.
  5. Die Slotnummer in einen Channel schreiben.
  6. Diesen Channel beim Block für die Anzeige einsetzen.
  7. Warten… Und sich freuen…
Teile und geniesse:
  • Technorati
  • del.icio.us
  • MisterWong
  • Digg
  • StumbleUpon
  • blogmarks
  • Furl
  • Simpy
  • Spurl
  • YahooMyWeb

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel, Web | Keine Kommentare »

PHP: NHL-Spielerstatistiken in Excel ausgeben für Fantasy-Leagues

Geschrieben von admin am 15. November 2007

Das Problem

HockeyIch habe mir sagen lassen, dass es auch hier bei uns Leute gibt die in einer Fantasy League mitspielen. Irgendwie werden da virtuelle aber real existierende Sportler gezogen und verkauft und nach einem speziellen Algorithmus werden Punkte verteilt, die abhängig von den tatsächlichen Resultaten sind, die dieser Spieler erzielt. Nun, auf jeden Fall wird da viel mit Statistiken gewurstelt. Ein Fantasy League-Mitglied ist an mich herangetreten, weil er die online publizierten Resultate der Skaters und Goalies der NHL in einem Excel haben wollte.

Zuerst dachte ich, das sei ein Fall für Dapper. War es aber nicht, da auf der Webpage reine Text-Exporte waren.

Die Erfahrung hat gezeigt, dass die Form der Ausgabe auf diesen Seiten oftmals ändert und man sich auf so wenig wie möglich verlassen sollte.

Die Felder dieser Tabelle haben keine Trennzeichen, sondern eine feste Breite und sind links- oder rechtsbündig formatiert.

Einige Punkte der Lösung

Download der Webseite in eine Variable

Zuerst muss man mal an die Seite rankommen. In PHP den Inhalt einer URL in einen String laden ist in der Theorie relativ einfach. In der Praxis jedoch ist aus Sicherheitsgründen der Zugriff auf URLs für die file* Funktionen oftmals eingeschränkt. Nunja, dann wird halt ein cURL-Fallback mit eingebaut, so dass wir auf jeden Fall in $page ein Array aus den Zeilen der HTML-Datei haben:

if(!($page = @file($url))) {
    $ch = curl_init();
    curl_setopt ($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_USERAGENT, 'User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070723 Iceweasel/2.0.0.6 (Debian-2.0.0.6-0etch1+lenny1)');
    curl_setopt ($ch, CURLOPT_HEADER, 0);
    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec ($ch);
    curl_close ($ch);
    $page=explode("\n",$result);
  }

Überschriften auslesen

Ich lese die Tabellenübberschriften mit einem regulären Ausdruck aus (man beachte dass die Sternchen sich der Formatierung (links/rechts) anpassen), aus dem danach die Spaltenbreiten gelesen werden können. Aus den Spaltenbreiten wiederum kann ein regulärer Ausdruck gebaut werden über den auf die Werte zugegriffen werden kann.

Beispielsweise steht auf der Webpage:

NAME                                   POS GP   G    A PTS SOG +/- PIM PPG SHG

daraus lese ich mit folgendem Regexp die Feldbreiten:

'@^(NAME *)(POS *)(GP)( *G)( *A)( *PTS)( *SOG)( *\\+/\\-)( *PIM)( *PPG)( *SHG)$@'

Damit baue ich dynamisch einen neuen regulären Ausdruck, der die Tabellendaten auslesen kann:

/^(.{39})(.{4})(.{2})(.{4})(.{5})(.{4})(.{4})(.{4})(.{4})(.{4})(.{4})$/

Codiert sieht es so aus. $tline im Codebeispiel beinhaltet eine getrimmte Zeile aus dem HTML.

// Überschriften und Feldgrössen
if(preg_match('@^(NAME *)(POS *)(GP)( *G)( *A)( *PTS)( *SOG)( *\\+/\\-)( *PIM)( *PPG)( *SHG)$@',$tline,$matches)) {
  if(!$inresults) {
    // Nach den Überschriften kommen die Tabelleneinträge
    $inresults=true;
    // Überschriften auslesen
    $headercaptions=preg_split('/ +/', $tline, -1, PREG_SPLIT_NO_EMPTY);
    // Überschriften ausgeben
    echo "\r\n".join("\t",$headercaptions)."\r\n";
  }
  // Wie breit ist ein Feld (in Zeichen)
  $sizes=array();
  for($i=1;$i<count($matches);$i++) {
    $sizes[$i-1]=strlen($matches[$i]);
  }

  // Suchmuster für Einträge aufbauen
  $searchpattern="";
  foreach($sizes as $size) {
    $searchpattern.='(.{'.$size.'})';
  }
  //print $searchpattern."<br />";
  $searchpattern='/^'.$searchpattern.'$/';
  continue;
}
// Tabellenzeilen auslesen
if($inresults && preg_match($searchpattern, $tline, $matches)) {
   $row=array();
   for($i=1; $i<count($matches); $i++) {
     $row[]=trim($matches[$i]);
   }
   // Und ausgeben
   echo join("\t",$row)."\r\n";
   continue;
}

Ausgabe als Excel

Hier bescheisse ich… Ich gebe nur eine tabulatorseparierte Liste aus. Excel schnallts, OpenOffice leider nicht (hat da wer eine gescheitere Lösung? HTML-Tabellen?)…

Zuerst die Headers:

header("Content-type: application/vnd.ms-excel");
header('Content-Disposition: inline; filename="'.$dateiname.'.xls"');

Einzelne Zellen werden mittels Tabulator (\t) voneinander getrennt, Zeilenenden werden durch “\r\n” markiert:

echo join("\t",$row)."\r\n";

Die Lösung

Die komplette Lösung kann von der Homepage heruntergeladen werden und eine Demo gibts ebenda…

Teile und geniesse:
  • Technorati
  • del.icio.us
  • MisterWong
  • Digg
  • StumbleUpon
  • blogmarks
  • Furl
  • Simpy
  • Spurl
  • YahooMyWeb

Ähnliche Artikel

Eingeordnet in Webapplikationen | Keine Kommentare »

Einfache Animationen auf der Konsole mit PHP (1-Dimensional)

Geschrieben von skaldrom am 7. November 2007

AnimationWenn man mit PHP Konsolenapplikationen schreibt, wäre es manchmal schön, eine pfundige Ausgabe mit etwas Bewegung erzeugen zu können. Solche Kleinstanimationen eignen sich auch für Aufgaben beim Erlernen von PHP, denn sie sind sehr unaufwändig und es tut sich trotzdem was.

Diese Ausgaben sind Eindimensional (auf einer Linie) und funktionieren unter Linux und Windows. Ich warte sehnsüchtig auf die PHP-Languagebindings für die aalib oder libcaca :-)…

Der Trick ist der Carriage Return (CR), der den Cursor an den Anfang der Zeile setzt, aber nicht vertikal weiterscrollt. in PHP wird dieses Zeichen mit \r codiert. Grundsätzlich ist der Ablauf wie folgt:

Bilderfolge in ein Array rendern
Solange man Lust hat:
    Zeige nächstes Bild
    Warte
  }
}

Unter Linux können zur weiteren Effektsteigerungen auch noch ANSI-Escapesequenzen verwendet werden. Mit diesen kryptischen Dingern kann man den einzelnen Zeichen Vorder- und Hintergrundfarbe verpassen und verwirrendes einschalten wie etwa ein Blinken. ESCAPE ist in PHP beispielsweise 33 in oktal und wird mittels \033 dargestellt.

Nun, zeig mal Code!

<?php
// Render Movie
$frames=array();
// 10 images for this movie
for($i=0;$i<10;$i++) {
    // The background consits of blue underscores
    $frame=array_fill(0, 10, "\033[34m_\033[0m");
    // One actor is a red star
    $frame[$i]="\033[31m*\033[0m";
    // The other a green dot
    $frame[9-$i]="\033[32m.\033[0m";
    // Join Background and actors
    $frames[]=join("",$frame);
}

// Output
for($i=0;$i<20;$i++) {
  foreach($frames as $frame) {
        // Rewind cursor and show frame
        print "\r".$frame;
        // Sleep a bit, because nights are hard these times...
        usleep(100000)
  }
}
print "\n";
?>
Teile und geniesse:
  • Technorati
  • del.icio.us
  • MisterWong
  • Digg
  • StumbleUpon
  • blogmarks
  • Furl
  • Simpy
  • Spurl
  • YahooMyWeb

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel | Keine Kommentare »

Erstellen eines Timers in Xoops

Geschrieben von skaldrom am 1. November 2007

Xoops ist ein geniales CMS, auch wenn es in letzter Zeit durch administrative Querelen nicht sehr gute Schlagzeilen gemacht hat. Wir benutzen es in einer erweiterten Version als Intranet.

Nun braucht ein Modul plötzlich unendlich lange um sich darzustellen. Das hindert unseren rasanten Arbeitsfluss und darum muss darum korrigiert werden. Statt rumzufrickeln lohnt es sich, die Sache seriös anzugehen.

Xoops bietet eine sehr gute Debugfunktion an: Adminsitration → Systemeinstellungen → Voreinstellungen → Allgemeine Einstellungen → Debug-Modus kann auf PHP-Debug gesetzt werden. Danach werden unten an der Seite viele Infos dargestellt, unter Anderem SQL-Befehle und Timer.

Xoops Debug

Genau solche Timer kann man selber einfach erstellen. Zuerst muss der XoopsLogger eingebunden werden:

include_once(XOOPS_ROOT_PATH . '/class/logger.php');

Um einen Timer zu starten:

$xoopsLogger->startTime( 'absence_count_query' );

Um ihn wieder zu stoppen:

$xoopsLogger->stopTime( 'absence_count_query' );

Diese Timer wird nun - wie oben ersichtlich - wunderschön in die Debugfunktion eingereiht.

Teile und geniesse:
  • Technorati
  • del.icio.us
  • MisterWong
  • Digg
  • StumbleUpon
  • blogmarks
  • Furl
  • Simpy
  • Spurl
  • YahooMyWeb

Ähnliche Artikel

Eingeordnet in Theorie und Schnipsel, Web | 1 Kommentar »

Eine eigene Programmiersprache erschaffen? Lexer und Parser in PHP!

Geschrieben von skaldrom am 25. October 2007

Ich will Gott sein!

Paper SnippetsWer träumt nicht davon: Eine eigene Programmiersprache, denn der Syntax und das Wort sind Ausdruck! Einen Schritt weiter gehen und vom Programmiersprachenanwender zum Programmiersprachenerschaffer aufsteigen! Auch dies ist nun möglich in PHP, doch leider nur mit minimaler Dokumentation.

Ich gehe davon aus, dass die Leserin/Der Leser Grundlagen im Compilerbau besitzt. Ansonsten müsste man sich diese noch aneignen. Compilerbau ist rekursiv und somit göttlich! *schwärm*.

Das Beispiel

Ich möchte einen Interpreter bauen für normale, arithmetische Ausdrücke wie 9*(8+6)-2/(-4). Klar könnte man diesen einfach durch eval hauen, aber erstens macht das nicht so viel Spass und zweitens ist dieses Beispiel sowas wie das Hello World des Compilerbaus.

Als BNF habe ich folgendes vor:

<Expression> ::= '+' <Expression> // Unäres + (Vorzeichen)
<Expression> ::= '-' <Expression> // Unäres - (Vorzeichen)
<Expression> ::= <Term>
<Expression> ::= <Expression> '+' <Term>
<Expression> ::= <Expression> '-' <Term>
<Term> ::=  <Factor>
<Term> ::= <Term> '*' <Factor>
<Term> ::= <Term> '/' <Factor>
<Factor> ::=  NUMBER // Ok, hier hab ich abgekürzt ;) Soll der Lexer erledigen...
<Factor> ::= '(' <Expression> ')'

Ein Problem stellen die unären Plus- und Minusoperatoren dar. Sie besitzen das gleiche Symbol, nicht aber die gleiche Priorität wie ihre binären Kollegen. Lemon erlaubt keine änderung der Priorität nach Regeln und so funzen jetzt halt auch Konstrukte wie —+++—+1…

Die Werkzeuge

Ich verwende PHP_LexerGenerator, der kompatibel zum re2c Format ist und den PHP_ParserGenerator der in grossen Teilen dem LEMON Parser Generator entspricht. Heruntergeladen werden können sie mittels folgenden Zeilen, die allerdings unter Umständen noch eine Anpassungen der Versionsnummer brauchen:

pear install channel://pear.php.net/PHP_ParserGenerator-0.1.5
pear install channel://pear.php.net/PHP_LexerGenerator-0.3.4

Als einziges, weiteres Beispiel für diese Libs ausserhalb der Doku habe ich nur dieses Drupalmodul gefunden.

Die Realisierung

Der Lexer

Für den Lexer muss eine plex Datei erstellt werden. Sie beinhaltet den Rumpf des Lexerobjekts und die lexikalischen Regeln:

TermLexer.plex

<?php
class TermLexer
{
    private $data;
    public $counter;
    public $token;
    public $value;
    public $node;
    public $line;
    private $state = 1;

    function __construct($data)
    {
        $this->data = $data;
        $this->counter = 0;
        $this->line = 1;
    }

/*!lex2php
%input $this->data
%counter $this->counter
%token $this->token
%value $this->value
%line $this->line
number = /[0-9]+(\.[0-9]+)?/
multiplication = /\*/

division = @/@
plus = /\+/
minus = /\-/
openP = /\(/
closeP = /\)/
other = /./
*/
/*!lex2php
%statename START
multiplication {
  $this->token = TermParser::TP_MULTIPLICATION;
  //echo "multiplication: ".$this->value."\n";
}
division {
  $this->token = TermParser::TP_DIVISION;
  //echo "division: ".$this->value."\n";
}
plus{
  $this->token = TermParser::TP_PLUS;
  //echo "plus: ".$this->value."\n";
}
minus{
  $this->token = TermParser::TP_MINUS;
  //echo "plus: ".$this->value."\n";
}
openP {
  $this->token = TermParser::TP_OPENP;
  //echo "openP: ".$this->value."\n";
}
closeP {
  $this->token = TermParser::TP_CLOSEP;
  //echo "closeP: ".$this->value."\n";
}
number {
  $this->token = TermParser::TP_NUMBER;
  //echo "number: ".$this->value."\n";
}
other {
  return false;
}
*/

}

So richtig laufen tuts aber noch nicht, da er Konstanten des Parsers benötigt. Die stehen erst zur Verfügung wenn wir den Parser auch gebaut haben.

Der Parser

Der Parser wird vom Lexer mit Tokens gefüttert. Die Notation im Lemon-Style unterscheidet sich von Lex und Yacc, ist aber durchaus logisch. Hier der ganze Code, mal hingeknallt:

TermParser.y

/* This is an example for a Parser in PHP */
%name TP_
%declare_class {class TermParser}
%include_class
{
    // states whether the parse was successful or not
    public $successful = true;
    public $retvalue = 0;
    private $lex;
    private $internalError = false;

    function __construct($lex) {
        $this->lex = $lex;
    }
}

%token_prefix TP_

%parse_accept
{
    $this->successful = !$this->internalError;
    $this->internalError = false;
    $this->retvalue = $this->_retvalue;
    echo "WORKED!!\n\n";
}

%syntax_error
{
    $this->internalError = true;
    echo "Syntax Error on line " . $this->lex->line . ": token '" .
        $this->lex->value . "' count ".$this->lex->counter." while parsing rule: ";
    foreach ($this->yystack as $entry) {
        echo $this->tokenName($entry->major) . '->';
    }
    foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
        $expect[] = self::$yyTokenName[$token];
    }
    echo "\n"
    throw new Exception('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN. '), expected one of: ' . implode(',', $expect));
}

%left PLUS MINUS.
%left MULTIPLICATION DIVISION.

start(res)       ::= expression(expr). { res = expr; }

/* Unary minus or plus */
expression(res)  ::= PLUS expression(e). { res = +e; }
expression(res)  ::= MINUS expression(e). { res = -e; }

/* The common stuff */
expression(res)  ::= term(t). { res = t; }
expression(res)  ::= expression(e1) PLUS term(t2). { res = e1+t2; }
expression(res)  ::= expression(e1) MINUS term(t2). { res = e1-t2; }

term(res)        ::= factor(f). { res = f; }
term(res)        ::= term(t1) MULTIPLICATION factor(f2). { res = t1*f2; }
term(res)        ::= term(t1) DIVISION factor(f2). { res = t1/f2; }

factor(res)      ::= NUMBER(n). { res = n; }
factor(res)      ::= OPENP expression(e) CLOSEP. { res = e; }

Generieren der eigentlichen Parser und Lexer

Damit Parser und Lexer generiert werden und Terme gefüttert werden können, sei hier ein kleines Beispiel gegeben:

<?php
// Create Parser
passthru('php /usr/share/php/PHP/ParserGenerator/cli.php TermParser.y');

// Create Lexer
require_once 'PHP/LexerGenerator.php';
$lex = new PHP_LexerGenerator('TermLexer.plex');

# Test
include_once("TermParser.php");
include_once("TermLexer.php");

$teststr='9*(8+6)-2/(-4)';

$lex = new TermLexer($teststr);
// $lex = new TermLexer(file_get_contents($lexerfile));

echo "-------------------------------\n";
echo " Parsing $teststr\n";
echo "-------------------------------\n";
$parser = new TermParser($lex);
while ($lex->yylex())  {
    echo "Parsing  {$lex->token} Token {$lex->value} \n";
    $parser->doParse($lex->token, $lex->value);
}
$parser->doParse(0, 0);

print "Returnvalue: ".$parser->retvalue."\n";
?>

Und nu?

Nun viel Spass… Ich bin auf der Suche nach der EBNF für RegExps, wenn die jemand hat :) …. Ihr könnt den ganzen Code hier auch downloaden…

Teile und geniesse:
  • Technorati
  • del.icio.us
  • MisterWong
  • Digg
  • StumbleUpon
  • blogmarks
  • Furl
  • Simpy
  • Spurl