MySQL Dumps und Restores in Java Applikationen

25. Januar 2012 Keine Kommentare

Eine auf den ersten Blick triviale aber letztendlich doch nicht so einfach zu loesende Aufgabe ist das Erstellen bzw. Zurueckspielen von MySQL Dumps aus einer Java Anwendung heraus. Wer sich schon einmal die Zaehne daran ausgebissen hat, findet hier eine moegliche Loesung.

Erzeugen eines Dumps

Wir starten mysqldump in einem neuen Prozess und leiten STDOUT in einen Stream um, der uns die Dump Daten in die gewuenschte Datei schreibt. Die Parameter max_allowed_packet und net_buffer_length koennen je nach Bedarf angepasst werden, sollten aber relativ klein gehalten werden, da sonst unter Umstaenden beim spaeteren Zurueckspielen des Dumps Speicherprobleme auftreten koennen.

ProcessBuilder builder = new ProcessBuilder(
  new String[] {
    "mysqldump",
    "--max_allowed_packet=16777216",
    "--net_buffer_length=16777216",
    "--opt",
    "--routines",
    "--triggers",
    "--lock-all-tables",
    "-uusername",
    "-ppassword",
    "database"
  }
);
 
builder.redirectErrorStream(true);
Process process = builder.start();
 
InputStream processStdout = process.getInputStream();
OutputStream processStdin = process.getOutputStream();
BufferedInputStream reader = new BufferedInputStream(processStdout);
FileOutputStream fileOut = new FileOutputStream(filename);
 
byte[] buffer = new byte[1024];
int length = 0;
while((length = reader.read(buffer)) != -1) {
  fileOut.write(buffer, 0, length);
}
 
processStdout.close();
processStdin.close();
fileOut.close();
 
try {
  process.waitFor();
} catch(InterruptedException e) {}

Einspielen eines Dumps

Hier verfahren wir aehnlich wie beim Erzeugen des Dumps. Es ist NICHT zuverlaessig moeglich, den Dump mittels der ueblichen File Redirection oder mit einem “source dumpfile.sql” zurueckzuspielen, da MySQL dann bei einer Warning im Dumpfile abbricht. Aus diesem Grund senden wir den Dump direkt an STDIN des Prozesses. Um den Prozess beenden zu koennen, schicken wir noch ein “exit\n” hinterher.

ProcessBuilder builder = new ProcessBuilder(
  new String[] {
    "mysql",
    "-uusername",
    "-ppassword",
    "--batch",
    "database"
  }
);
 
builder.redirectErrorStream(true);
Process process = builder.start();
 
OutputStream processStdin = process.getOutputStream();
BufferedOutputStream writer = new BufferedOutputStream(processStdin);
InputStream processStdout = process.getInputStream();
 
FileInputStream fileIn = new FileInputStream(filename);
 
byte[] buffer = new byte[1024];
int length = 0;
while((length = fileIn.read(buffer)) != -1 ) {
  writer.write(buffer, 0, length);
  writer.flush();
  processStdin.flush();
}
 
writer.write("exit\n".getBytes());
writer.flush();
processStdin.flush();
 
// Fetch the process' STDOUT to prevent it from overflowing
while((length = processStdout.read(buffer)) != -1) {}
 
processStdin.close();
processStdout.close();
fileIn.close();
 
try {
  process.waitFor();
} catch(InterruptedException e) {}
KategorienProgrammierung Tags:

Lösung für Tastaturproblem beim Korg Kronos

15. Januar 2012 Keine Kommentare

Endlich gibt es eine offizielle Nachricht von Korg bezueglich des Tastaturproblems, das bei einigen Kronos Geraeten aufgetreten ist. Wie Richard Formidoni im offiziellen Korg Forum verlauten laesst, sollen die defekten Kronos zur Reparatur eingesandt werden. Offensichtlich werden bei der Reparatur Modifikationen an der Tastatur vorgenommen, diese wird jedoch nicht ausgetauscht. Was genau nun die Ursache fuer das Problem ist, war der Meldung nicht zu entnehmen.

Quelle: http://www.korgforums.com/forum/phpBB2/viewtopic.php?t=65292&postdays=0&postorder=asc&start=1350

KategorienAllgemeines Tags:

Defekte Tastaturmechanik bei Korg Kronos 88 und 73?

19. Dezember 2011 4 Kommentare

Bereits beim ersten Anspielen des Kronos hatte ich das Gefuehl, als wuerde irgend etwas mit der Tastatur nicht ganz stimmen. Zunaechst war das Problem nicht genau definierbar, aber nach einer Weile stellte ich fest, dass der Piano Sound insbesondere in leisen Passagen merkwuerdige Aussetzer hat. Bei genauerer Untersuchung stellte sich heraus, dass die Tastatur des Kronos beim Zurueckfedern unter bestimmten Umstaenden einen zweiten Ton mit erheblich geringerer Velocity triggert, der den urspruenglich angeschlagenen Ton “abreissen” laesst. Mit anderen Worten: Drueckt man eine Taste herunter und haelt sie, um den Ton auszuhalten, so klappt das in vielen Faellen nicht – der Ton ist nur kurz zu hoeren und verstummt dann sofort.

Dieses Phaenomen ist besonders beim Spielen der (akustischen und elektrischen) Pianos wahrnehmbar. Ohne Einsatz des Pedals faellt es sogar extrem stoerend auf. Ein Blick ins Korg Forum bestaetigte meine Vermutung, dass es sich dabei um einen Fabrikationsfehler handelt. Offenbar haben mehrere Besitzer eines Kronos aehnliche Probleme – andere koennen den Fehler hingegen nicht reproduzieren. Nach Reklamation bei meinem Haendler und Schilderung der Sachlage erhielt ich die Antwort, dass Korg innerhalb von zwei Wochen eine Loesung fuer das Problem gefunden haben will. Das war vor fuenf Wochen.

Meiner Meinung nach wird Korg zunaechst versuchen, eine Software-Loesung zu finden. Dass dies jedoch ohne negative Begleiteffekte fuer das Spielverhalten moeglich ist, wage ich zu bezweifeln. Es wird wohl darauf hinauslaufen, dass die betroffenen Instrumente repariert oder ausgetauscht werden muessen. Lassen wir uns ueberraschen.

KategorienAllgemeines Tags:

First look: Korg Kronos

5. September 2011 Keine Kommentare

Eine Weile dachte ich wirklich, den Kronos gäbe es gar nicht und er wäre nur ein geschickter PR Trick von Korg. Aber letzten Freitag tauchte die lang ersehnte Email in meinem Postfach auf: Wir freuen uns, Ihnen mitteilen zu können, dass der Korg Kronos jetzt eingetroffen und abholbereit ist. Also rein ins Auto und hin zum Händler, das Ding in den Kofferraum und schnell zurück nach Hause. Beim Auspacken denke ich mir, ich hätte wohl doch besser einen Geigerzähler kaufen sollen, aber die Neugier siegt über die Strahlenangst. Der Kronos kommt sogar mit ins Wohnzimmer.

Stecker rein, Schalter an, und… na…? … na?? …… Fast zwei Minuten hochfahren. Das fängt ja schon mal gut an. Hoffentlich fällt beim Gig nie der Strom aus. Dann endlich: Piep. Der Kronos ist bereit. Das 8 Zoll Display macht schon was her. Erster Test: KRONOS German Grand. Über meine guten AKG Kopfhörer. Wahnsinn. Fast so gut wie ein VSTi mit zig Gigabytes an Samples. Auf den Youtube Videos, die ich bisher vom SGX-1 Piano gehört hatte, klang das nicht ganz so überzeugend wie direkt am Kopfhörerausgang.

Weiter gehts mit den E-Pianos. Auch hier: Wow. Das macht was her. Im Gegensatz zu den langweiligen Rhodes Presets auf dem Motif haben die Kronos Rhodes Textur und Charakter. Aber was ist mit den FM Pianos? Ein kurzer Test zeigt: Die FM Synthese im Kronos ist eine Sensation. Klanglich absolut kein Unterschied zum DX7 erkennbar.

Weiter zu den Tonewheel Orgeln. Die Presets überzeugen mich leider nicht alle, aber nach kurzem Rumschrauben an den Parametern kommt wirkliches CX3 Feeling auf. Praktisch ist die Funktion, einen Split einzurichten, wobei man beide virtuellen Manuale mit eigenen Zugriegeln regeln kann. Es gibt sogar einen erweiterten Modus, in dem man unter anderem noch mehrere Zugriegel für die Percussion erhält. Für tiefergehende Einblicke war leider noch nicht genug Zeit, aber dieses Modul hat sicherlich großes Potential.

Jetzt bin ich gespannt: Klingen die virtuell analogen Synths auch tatsächlich analog, oder doch eher zu virtuell? Mal eben ein paar Pads durchprobiert und ich bin angenehm überrascht. Für eine digitale Simulation ist das gar nicht mal so schlecht. Die Sounds sind recht warm und voll und durchaus brauchbar. Gefällt mir.

Zum Physical Modeling Synth STR-1 kann ich noch nicht viel sagen, außer dass man von ihm keine realistischen Gitarrensounds erwarten sollte. Man sollte ihn vielmehr als eigenständigen Synthesizer auffassen, wahrscheinlich der komplexeste im Kronos, mit dem sich ganz neue Sounds erzeugen lassen, die vielleicht nicht viel mit Gitarren zu tun haben, aber mit keinem anderen Synth möglich wären.

Die übrigen Sounds (HD-1) sind guter Durchschnitt, abgesehen von den gesampelten Gitarren. Aber das kann ich verschmerzen, denn der Motif wird im Setup nicht weit vom Kronos entfernt stehen…

Nun ein paar Worte zur Komplexität: Die letzten drei Tage habe ich größtenteils damit verbracht, mich in die FM Synthese mit MOD-7 einzuarbeiten. In dieser Zeit habe ich noch nicht einmal die Hälfte aller Parameter abgearbeitet geschweige denn ihre Funktion verstanden. Es ist wirklich beängstigend, wie komplex allein eines der 9 Syntheseverfahren ist. Die Möglichkeiten sind schier unbegrenzt. Ähnlich mächtig erscheinen mir AL-1 und STR-1. Dagegen ist der MS-20EX schon ein echtes Leichtgewicht. Angenehm direkt und intuitiv hingegen der PolysixEX. Damit können auch Anfänger umgehen.

Sehr empfehlenswert ist das 9seitige Tutorial zum MOD-7 im Kronos Parameter Guide (auf der beigelegten CD oder von der Korg Website). Es wäre schön gewesen, wenn Korg für AL-1 und STR-1 ein ähnliches Tutorial erstellt hätte, aber mit genügend Zeit und Geduld wird man sicherlich selbst dahinter kommen.

Das war mein erster Eindruck vom Kronos. Einen ausführlichen Testbericht werde ich in ein paar Monaten nachreichen, wenn sich das Instrument im Liveeinsatz beweisen konnte.

KategorienAllgemeines Tags:

Testbericht: Yamaha Motif XS 7

10. August 2011 Keine Kommentare

Die Motif XS ist Yamahas Flagschiff Workstation und wurde mittlerweile durch die Motif XF abgelöst, die aber bis auf den eingebauten Flash Speicher keine wesentlichen Neuerungen mitbringt. Seit etwa anderthalb Jahren bin ich im Besitz der XS 7, also der Version mit 76 leichtgewichteten Tasten. Mittlerweile konnte ich mir ein detailliertes Bild von den Stärken und Schwächen dieses Instruments machen und möchte daher in diesem Testbericht ausführlich darüber berichten.

Zunächst ein paar Worte zur Austattung und Verarbeitung der Motif. Die 76er Tastatur mit leichter Gewichtung ist eine der besten, die ich jemals spielen durfte. Sie eignet sich besonders gut für Synth Solos oder zum Orgel spielen. Aber auch Pianos und andere Keyboardsounds spielen sich sehr angenehm, obwohl ich eigentlich sonst ein fast schon militanter Verfechter von Hammermechanik für das Klavierspiel bin.

Das Display der Motif ist ein bisschen klein geraten für eine moderne Workstation. Auflösung, Kontrast und Farbwiedergabe sind leider nicht mehr zeitgemäß, aber es gibt ja selbst heute noch Workstations mit Monochromdisplay, was das ganze wieder etwas relativiert.

Die Controller Sektion besteht aus 8 Kanalzügen mit jeweils einem Fader und Poti in recht professioneller Ausführung. Hier hätte ich mir noch ein paar Knöpfe gewünscht, aber dazu später mehr. Auf der rechten Seite sind sehr viele Taster für die Auswahl von Modus und Sounds, das Bewegen des Cursors, usw. Viele haben eine eingebaute LED und lassen sich gut bedienen. Für die Transport Controls (Start, Stop, Record, etc.) hat Yamaha besondere Taster verbaut, die deutlich größer sind als die anderen Taster und einen geringeren Druckpunkt aufweisen, was für diesen Einsatzzweck sehr sinnvoll ist.

Auf der Rückseite findet man die üblichen Anschlüsse aber leider keine konfigurierbaren Ausgänge zusätzlich zu den Main Outs. Erwähnenswert sind die vielen Pedalanschlüsse, darunter zwei Foot Switches und zwei Expression Pedals. Schade: Keine XLR Outs und der Mikrofoneingang ist als Klinkenbuchse ausgeführt. Hier hätte ich mir eine Kombibuchse XLR/Klinke gewünscht.

Kommen wir zu den Sounds. Die Pianos sind durchweg eher mäßig, das Grand Piano mit seinen 8 Layern klingt dumpf und lässt sich wenig dynamisch spielen. Schade. Deutlich besser schlagen sich Rhodes und Wurlitzer. Eine Katasrophe jedoch die FM Pianos: Hier hat Yamaha leider geschlafen. Die Klassiker fehlen, alle Presets sind wenig präsent und kaum durchsetzungsfähig. Für mich völlig unbrauchbar. Einen sehr guten Eindruck machen hingegen die Orgeln, allerdings sind nicht alle Presets gleich gut und längst nicht alle Orgeln unterstützen die Zugriegelsimulation per Fader. Das hatte ich mir ehrlich gesagt anders vorgestellt. Die akustischen Bässe klingen sehr gut, weniger gut gefallen mir die elektrischen Bässe. Etwas mehr Druck und Klarheit hätte ich mir hier gewünscht. Unbearbeitet klingen sie im Bandmix recht matschig und undefiniert. Beim Durchhören der Streicher fällt auf, dass es allen etwas an Substanz und Textur fehlt. Auch die Breitenwirkung ist verbesserungsfähig. Bei den Blechbläsern reißen mich die Solosounds zwar nicht vom Hocker, sind aber durchaus in der oberen Mittelklasse einzuordnen. Die Sections fügen sich schlecht in den Bandkontext ein. Besser sieht es bei den Holzbläsern aus, hier ist eigentlich fast alles brauchbar. Alle Synth Sounds sind durchaus brauchbar, allerdings sind mir die „analogen“ Sounds zu digital und die Leads zu brav. Hier empfiehlt sich die Anschaffung der beiden Analog Synth Expansions, die wirklich eine exzellente Auswahl an Presets mitbringen. Die Drums sind durchweg gut, aber die Auswahl an Standard Drumsets ist etwas klein bzw. undifferenziert.

Eine Soundkategorie haben wir ausgelassen, und das aus gutem Grund. Denn bei den Gitarren gibt es keine Konkurrenz für die Motif. Akustische Gitarren, Clean Electric, Distorted – hier kann Yamaha punkten. Erstklassige Samples in Kombination mit den hervorragenden Effekten sorgen für den besten Gitarrensound den man in einem Keyboard kaufen kann. Punkt.

Was nützen aber die besten Sounds, wenn die Bedienung nicht stimmt? Und genau hier kommen wir zum größten Problem der Motif Serie. Aber alles der Reihe nach.

Schalten wir die Motif XS das erste Mal an, fällt zunächst die etwas lange Bootdauer auf, die gerade noch so akzeptabel ist. Das Betriebssystem reagiert oft recht träge, was aber in den meisten Fällen nicht stört.

Wie bei vielen Workstations gibt es auch hier mehrere Modi für zum Beispiel einzelne Voices, Performances, Pattern, Songs usw. Dazu kommen aber noch mehrere Submodi, deren Organisation oftmals etwas unlogisch erscheint und eher verwirrt.

Der sogenannte Master Modus ist der Versuch einer Setlist Implementierung und soll besonders für den Live Betrieb nützlich sein. Leider ist er jedoch völlig unbrauchbar, da Sounds beim Umschalten von Presets einfach abgeschnitten werden – das passiert übrigens auch in jedem anderen Modus. Äußerst ärgerlich.

Auf den ersten Blick erscheinen die acht Kanalzüge recht komfortabel, allerdings geht mir die Konfigurierbarkeit der Controller nicht weit genug. Warum kann ich die Fader nur für die Lautstärke der Voice Elemente verwenden und nicht auch für frei definierbare Parameter? Warum gibt es keine konfigurierbaren Taster im Kanalzug? Und warum muss man sich mit den Cursortasten durch die Eingabefelder im Display hangeln um dann mit dem Datawheel den Wert zu ändern, statt einfach jedem Feld einen Poti zuzuordnen?

Damit haben wir auch schon ein weiteres Problem angesprochen. Viele Einstellungsseiten enthalten so viele Parameter, dass man schon alleine zum Ansteuern des gewünschten Felds 12 Mal auf die Cursortasten drücken muss. Es ist fast schon grotesk, wenn man auf seinem 150 Euro Handy mal eben ein Bild mit zwei Fingern vergrößert, um dann auf der 3000 Euro Workstation einen Cursor durch 20 Bildschirmseiten und 15 Eingabefelder durchzumanövrieren. Sorry Yamaha, aber das geht heute gar nicht mehr.

Factory Presets lassen sich nicht einfach ändern oder anpassen, sondern müssen dazu in einem der User Programs abgespeichert werden, von denen es nur 378 gibt (drei Bänke zu je 128). Wer mehr User Sounds braucht, hat Pech gehabt. Überhaupt ist die Verwaltung von Sounds und Samples eine reine Katastrophe. Einfaches Umsortieren? Fehlanzeige. Nur mit dem Software Editor, und selbst dann ist das alles andere als einfach.

Häufiges Szenario: In der Bandprobe muss mal eben ein Split Sound erstellt werden, mit Bass in der linken Hand und irgend was anderem auf der rechten Seite. Sollte ja kein Problem sein. Aber da habe ich nicht mit Yamaha gerechnet: Es ist unglaublich umständlich und kaum unter 2 Minuten hinzubekommen. Zuerst muss man eine uninitialisierte Performance aufrufen oder – falls es keine uninitialisierten mehr gibt – eine zurücksetzen. Dann muss der Sound und die Keyboard Range für Kanal 1 gewählt werden. Als nächstes muss Kanal 2 aktiviert, ein Sound gewählt und auch hier die Keyboard Range festgelegt werden. Und jetzt das Speichern nicht vergessen!

Hat man dann erst mal eine Sammlung von User Sounds zusammengestellt sollte man diese auch auf dem USB Stick speichern. Aber wie? Einzeln ist zu umständlich, und die nächstgrößere Granularität ist eine ganze User Bank. Nächstes Problem: Was mache ich, wenn ich zweieinhalb User Bänke mit Sounds belegt habe, und nun noch 20 Sounds aus einem Expansion Set benötige? Ich muss alle 20 einzeln importieren, denn die ganze User Bank kann ich schlecht laden ohne meine eigene zu überschreiben. Leider passiert das dann aus Versehen doch öfter als einem lieb ist und alle mühsam erstellten Sounds sind unwiederbringlich verloren. Das ist wirklich die Kulmination der Benutzerunfreundlichkeit. Noch dazu dauern Speichern und Laden von Samples fast unendlich lange. Wenn dann ein nervöser Bandkollege schon den Strom ausschaltet…

Na gut, aber einen einfachen Transpose werde ich doch wohl hinbekommen? Von meinem Stage Piano bin ich das gewohnt: Transpose gedrückt halten und Value Up oder Down so oft drücken, wie ich Halbtöne verschieben möchte. Das sollte doch für eine teure Workstation kein Problem sein, oder? Weit gefehlt, hier muss man zunächst mal ins Global Menü wechseln, mit dem Cursor auf Transpose fahren und dann einen Wert eintragen. Kann ich dann wenigstens den Wert direkt eingeben? Nein, natürlich nicht, denn es fehlt eine Zehnertastatur.

Das Eingeben von Texten zur Benennung der Presets ist die reinste Qual. Mit dem Cursor muss man sich durch die Bildschirmtastatur hangeln um jeden Buchstaben einzeln einzufügen. Wenigstens kann man eine externe Tastatur per USB anschließen.

Nun aber mal wieder etwas positives: Das Erstellen von Pattern oder Songs ist recht einfach. Es gibt praktische nichtdestruktive Realtime Modifier (Quantize, Gate, etc.) für jeden Kanal. Sobald man jedoch Audio Spuren aufnehmen will wars das mit der Freude. Jede Audio Spur belegt eine der maximal 16 Spuren, die sich MIDI und Audio teilen müssen. Die Aufnahme erfolgt über den Sampling Modus – ziemlich umständlich und unintuitiv. Der Event Editor von Song und Pattern Modus zur Bearbeitung der einzelnen Note Events ist leider nicht besonders komfortabel.

Die Anbindung an eine DAW (Cubase LE) ist ausgezeichnet. Hier funktioniert alles out-of-the-box und dafür kann man die Motif tatsächlich gebrauchen.

Fazit: Als einziges oder Master-Keyboard für den Livebetrieb ist die Motif völlig unbrauchbar, hingegen gut geeignet als Supplemental für einzelne Sounds. Leider werden die Sounds abgeschnitten, was die Nutzbarkeit deutlich einschränkt. Die DAW Integration ist exzellent, als eigenständige Workstation ist die Motif aber etwas zu umständlich zu bedienen. Gut geeignet ist sie zum Erstellen von Songs, solange man keine Audio Spuren braucht. Die Sounds sind insgesamt von guter bis sehr guter Qualität, insbesondere die Gitarren Sounds und Orgeln. Dazu passt auch die exzellente Tastatur.

Leider ist die Motif Reihe nicht mehr zeitgemäß. Man merkt, dass ein jahrelang bewährtes Konzept mit Kleinigkeiten weiter ausgebaut wurde. Bereits nach dem Motif ES wäre aber ein Neudesign notwendig gewesen. Um weiter konkurrenzfähig zu bleiben, muss Yamaha in Zukunft neue und frische Ideen in den Workstation Markt bringen, wie Korg das unlängst mit dem Kronos vorgemacht hat.

KategorienAllgemeines Tags:

Implementation eines Konfigurationsparsers in Python mit PLY (Teil 2)

19. Mai 2011 Keine Kommentare

Im letzten Teil haben wir gesehen, wie sich mit wenig Aufwand ein funktionierender Lexer in Python implementieren laesst, der unsere Konfigurationsdateien in eine Folge von Tokens zerlegen kann. Natuerlich sind wir damit noch lange nicht fertig. Es fehlen eine Grammatik, die die Syntax des Tokenstreams definiert und semantische Aktionen, die uns eine Baumstruktur aus den Konfigurationssettings erstellen.

Beides laesst sich in PLY recht elegant implementieren. Erstellen wir uns aber zunaechst eine Klassen fuer unsere Baumstruktur, aus der wir spaeter unsere in-memory Repraesentation aufbauen werden:

class Group(object):
  def __init__(self):
    self._data = dict()
 
  def insert(self, key, value):
    self._data[key] = value
 
  def safe_lookup(self, key, default):
    try:
      return self.lookup(key)
    except CfgFileException, e:
      return default
 
  def lookup(self, key):
    p = uriparser.URIParser()
    tokens = p.parse(key)
    return Group._recursive_lookup(self, tokens)
 
  def __getitem__(self, key):
    return self.lookup(key)
 
  [... weitere Standardfunktionen wie __iter__, __len__, etc]

Diese ist nichts anderes als ein Wrapper um ein Dictionary, der die Element Lookup Funktionen ueberschreibt, denn wir moechten als Key auch zusammengesetzte URI’s angeben koennen wie zum Beispiel “Gruppe1.Gruppe2.Liste[4]“. Das Parsen dieser URI uebernimmt der URIParser auf den wir hier nicht naeher eingehen wollen (siehe Quellcode). Fehlt noch die Implementierung der Funktion _recursive_lookup:

  @staticmethod
  def _recursive_lookup(node, tokens):
    t = tokens[0]
    if isinstance(t, int) and not isinstance(node, List):
      raise CfgFileException("URI qualifier incompatible with config structure: [%d]" % t)
    if isinstance(t, str) and not isinstance(node, Group):
      raise CfgFileException("URI qualifier incompatible with config structure: %s" % t)
 
    try:
      n = node._data[tokens[0]]
    except KeyError:
      raise CfgFileException("Key not found: %s" % t)
    except IndexError:
      raise CfgFileException("Key not found: %s" % t)
 
    if len(tokens) == 1:
      return n
    else:
      return Group._recursive_lookup(n, tokens[1:])

Der groesste Teil dieser Funktion dient dem Error Handling. Lediglich die letzten vier Zeilen machen die eigentliche Arbeit: Wenn unsere Tokenliste nur noch ein Element enthaelt, so sind wir an einem Leaf Node angelangt und koennen dessen Wert direkt zurueckgeben, ansonsten muss es sich um eine Gruppe handeln, und wir machen einen erneuten (rekursiven) Lookup auf dem Node, wobei wir das soeben bearbeitete Token aus der Tokenliste entfernen.

Fuer die Abbildung der Arrays und Listen verwenden wir die eingebaute Listenklasse in Python, da wir keine zusaetzliche Funktionalitaet benoetigen.

Jetzt koennen wir damit beginnen, unseren Parser zu implementieren. Erstellen wir zunaechst eine neue Klasse fuer den Parser:

class CfgParser(object):
  def __init__(self):
    self.lex = CfgLexer()
    self.lex.build()
    self.parser = ply.yacc.yacc(module=self, start='file')

Wir erzeugen eine Instanz unseres Lexers, rufen seine build() Funktion auf und erstellen eine Instanz des yacc Parsers, dem wir ausserdem mitteilen, in welchem Objekt die Grammatik definiert ist (self) und wie die Startregel heissen wird (‘file’). Diese Startregel definieren wir folgendermassen:

  def p_file(self, p):
    """ file : variable_assignments
    """
    p[0] = p[1]

Wie schon beim Lexer muessen wir fuer die Funktionen, die zur Grammatikdefinition dienen sollen, gewisse Namenskonventionen einhalten. Das bedeutet im Fall des Parsers, dass sie mit einem “p_” beginnen muessen. Um die Produktionsregel zu definieren, wird der Python Docstring missbraucht. Das sieht zwar nicht huebsch aus, hat aber den Vorteil, dass man sich an die Syntax der BNF halten kann, ohne dafuer viel Aufwand betreiben zu muessen. Die Lesbarkeit gegenueber einer Grammatik in boost::spirit ist ohne Frage wesentlich besser.

Unsere Regel sagt in diesem Fall, dass ein File aus einer einzelnen Regel ‘variable_assignments’ besteht. Die semantische Aktion ist aehnlich einfach. Mit dem ‘p[0] = p[1]‘ sorgen wir dafuer, dass der Rueckgabewert (das assoziierte Attribut) der Regel ‘file’ gleich dem Rueckgabewert der Regel ‘variable_assignments’ ist. Implementieren wir nun diese neue Regel:

  def p_variable_assignments(self, p):
    """ variable_assignments : LITERAL EQUALS value SEMICOLON
                 | LITERAL EQUALS value SEMICOLON variable_assignments
    """
    if len(p) == 5:
      p[0] = Group()
    else:
      p[0] = p[5]
    p[0].insert(p[1], p[3])

Die Grammatikdefinition sagt uns also, dass ‘variable_assignments’ entweder aus einem LITERAL gefolgt von einem EQUALS, einer Produktion ‘value’ und einem SEMICOLON besteht, oder einem LITERAL gefolgt von EQUALS, einem ‘value’, einem SEMICOLON und einem weiteren ‘variable_assignments’. Das ist einfach eine rekursive Definition fuer “ein oder mehrere Statements LITERAL EQUALS value SEMICOLON”. Beachten Sie die Verwendung der Tokens LITERAL, EQUALS und SEMICOLON, die der Lexer fuer uns erzeugt (siehe ersten Teil).

Als semantische Aktion haben wir ein etwas komplizierteres und daher erklaerungsbeduerftiges Codefragment: Da wir zwei Varianten definiert haben, die die Regel matchen kann, muessen wir zunaechst unterscheiden, welcher Fall eingetreten ist. Das koennen wir ueber die Laenge des Parameters p tun. Dieser ist eine Liste mit Attributen fuer jedes Element der Produktion, sowohl fuer Tokens als auch Regeln, PLUS dem Rueckgabewert. Ist p also 5 Elemente lang, so muss es sich um die erste Variante der Regel handeln (die zweite Variante fuehrt zu 6 Elementen).

Haben wir also die erste Variante vorliegen, so hat unser Configfile entweder nur einen Eintrag oder wir sind am Ende der Liste aller Eintraege angelangt. In diesem Fall erzeugen wir ein neues Group Objekt; im anderen Fall verwenden wir einfach das Group Objekt der ‘variable_assignments’ Regel.

In jedem Fall muessen wir den neuen Eintrag der Gruppe hinzufuegen, was in der letzten Zeile geschieht. Dabei verwenden wir als Key (erster Parameter) den Wert des LITERAL Tokens, als Value (zweiter Parameter) den Rueckgabewert von ‘value’.

Nachdem wir nun das Prinzip verstanden haben, sind hier die uebrigen strukturgebenden Produktionen, die wir anschliessend kurz besprechen werden:

  def p_group_definition(self, p):
    """ group_definition : LBRACE variable_assignments RBRACE
    """
    p[0] = p[2]
 
  def p_list_definition(self, p):
    """ list_definition : LPAREN RPAREN
                        | LPAREN list_body RPAREN
    """
    p[0] = []
    if len(p) == 4:
      p[0].extend(p[2])
 
  def p_list_body(self, p):
    """ list_body : value
                  | value COMMA list_body
    """
    p[0] = []
    p[0].append(p[1])
    if len(p) == 4:
      p[0].extend(p[3])
 
  def p_value(self, p):
    """ value : group_definition
              | list_definition
              | array_definition
              | atom
    """
    p[0] = p[1]
 
  def p_array_definition(self, p):
    """ array_definition : LBRACKET RBRACKET
                         | LBRACKET array_body RBRACKET
    """
    p[0] = []
    if len(p) == 4:
      p[0].extend(p[2])
 
  def p_array_body(self, p):
    """ array_body : double_array
                   | long_array
                   | bool_array
                   | string_array
    """
    p[0] = p[1]

Diese Regeln sind im wesentlichen selbsterklaerend, lediglich die semantischen Aktionen fuer die Listen und Arrays scheinen auf den ersten Blick etwas kompliziert. In ‘list_body’ erzeugen wir grundsaetzlich eine neue Liste und haengen den Wert von ‘value’ an. In der zweiten Variante der Regel haben wir aber schon ein ‘list_body’, weshalb wir einfach dessen Inhalt ebenfalls zur Liste hinzufuegen.

Kuemmern wir uns nun um die Definition der Regeln fuer die Arrays:

  def p_double_array(self, p):
    """ double_array : DOUBLE
                     | DOUBLE COMMA double_array
    """
    p[0] = self._generic_array(p)
 
  def p_long_array(self, p):
    """ long_array : LONG
                   | LONG COMMA long_array
    """
    p[0] = self._generic_array(p)
 
  def p_bool_array(self, p):
    """ bool_array : bool
                   | bool COMMA bool_array
    """
    p[0] = self._generic_array(p)
 
  def p_string_array(self, p):
    """ string_array : STRING_LITERAL
                     | STRING_LITERAL COMMA string_array
    """
    p[0] = self._generic_array(p)

Die Definitionen aehneln sich, lediglich der Typ der Tokens ist unterschiedlich. Aus diesem Grund habe ich die semantischen Aktionen in eine Funktion ausgelagert, die folgendermassen definiert ist:

  def _generic_array(self, p):
    if len(p) == 4:
      r = [p[1]]
      r.extend(p[3])
    else:
      r = [p[1]]
    return r

Diese Funktion gehoert natuerlich nicht zur Grammatik (denn ihr Name beginnt nicht mit ‘p_’). Die Verarbeitung der Parameter geschieht analog zur Regel ‘list_body’.

Nun fehlen noch die Regeln fuer atom und bool:

  def p_atom(self, p):
    """ atom : DOUBLE
             | LONG
             | bool
             | STRING_LITERAL
    """
    p[0] = p[1]
 
  def p_bool(self, p):
    """ bool : TRUE | FALSE
    """
    p[0] = (p[1] == 'true' and True or False)

Damit ist der Parser fertig und wir koennen ihn testen:


>>> p = CfgParser()
>>> cfg = p.parse("a = { b = 5; c = [1,2,3]; };")
>>> print cfg["a.c[1]"]
2
>>>

Der komplette Sourcecode fuer den Python Parser folgt in Kuerze.

KategorienAllgemeines Tags:

Update fuer cfg_parser (v1.2)

17. Februar 2011 Keine Kommentare

Das cfg_parser Paket wurde aktualisiert. Bei dem Update handelt es sich hauptsaechlich um Bugfixes (unter anderem wurde das new_-Memory-Leak beseitigt), allerdings gibt es auch ein paar neue Features:

  • Parser benutzt multi-pass Iteratoren, wenn verfuegbar
  • Default Konstruktor und eine separate load() Funktion hinzugefuegt
  • Strings koennen nun sowohl mit doppelten als auch einfachen Anfuehrungszeichen geschrieben werden oder auch ganz ohne Delimiter. In diesem Fall interpretiert der Parser alles vom ersten non-Whitespace-Zeichen bis zum naechsten Semikolon als String.

Die Library kann unter http://www.johannes-asal.de/code/cfg_file_1_2.tar.gz heruntergeladen werden.

KategorienProgrammierung Tags:

Notensatz mit PriMus 1.1

12. Oktober 2010 1 Kommentar

Im umkämpften Markt der Notensatzsoftware überbieten sich die beiden Platzhirsche Sibelius und Finale jedes Jahr (oder alle zwei Jahre) mit neuen Features, die zwar zweifelsohne nützlich und meist auch zeitsparend sind, aber in immer stärkerem Maße zeigen, dass die Zeit der großen Innovationen auf diesem Sektor längst vorbei ist. Besonders beim Erscheinen von Finale 2011 im Juli diesen Jahres fragte man sich, ob die bis zu 170 Euro für das Upgrade tatsächlich gut investiert sind, oder MakeMusic hier nicht einfach versucht hat, den jährlichen Release trotz Ideenmangel konsequent durchzuziehen.

Vor einiger Zeit habe ich einen Artikel zu Lilypond verfasst, ein sehr interessantes Satzsystem für Noten. Dabei zeigte sich, dass es hervorragend für einen Zweck geeignet ist, den alle anderen Notensatzprogramme nicht abdecken können: Die Kombination von DTP mit Notensatz, wie sie beispielsweise für das Verfassen von Unterrichtsliteratur, Büchern zu Musiktheorie oder andere Anwendungszwecke unabdingbar ist. Leider ist Lilypond kein grafischer Editor sondern ein System ähnlich wie TeX, das Befehle in Form von Text in den gewünschten Notensatz umwandelt. Damit ist es zum einen nur für technisch versierte Anwender geeignet, zum anderen fehlt dadurch, wie damals schon bemängelt, die direkte Verbindung der Kreativität mit dem Entwurfsprozess.

Bei meinen Recherchen bezüglich neuer Notationssoftware bin ich in diesem Zusammenhang auf PriMus gestoßen, das sich nicht nur als Konkurrenz zu Sibelius und Finale versteht, sondern gleichzeitig, ganz nebenbei, DTP Funktionen integriert. Das war Grund genug für mich, einmal die Demoversion dieser Software herunterzuladen und mich eingehend damit zu beschäftigen. Kann PriMus im Funktionsumfang tatsächlich mit den alteingesessenen Softwaregrößen mithalten? Und wie praxisorientiert sind die DTP Fähigkeiten?

Beim ersten Start präsentiert sich PriMus als eher unscheinbares Programm. Keine hübschen, durchdesignten Knöpfe, keine Papiertextur auf der Arbeitsfläche. Aber wer braucht das schon, wenn die Bedienung stimmt? Versuchen wir also, unsere erste Notenzeile einzugeben. Die Methode der Mauseingabe sollte weder für Finale- noch Sibelius-Nutzer eine große Umstellung sein, ein kurzer Blick in das Tutorial, und man hat es verstanden. Überhaupt wurde offensichtlich versucht, die Mauswege möglichst kurz zu halten, kein unnötiges Herumfahren auf dem Bildschirm, kein wildes Hangeln durch Menüs. Das gefällt mir.

Auch die wichtigsten komplexen Notationen wie Feathered Beams, Balkenverbindungen zwischen zwei Systemen, Tabulatur und Ossiasysteme sind erstaunlicherweise kein Problem. Diese Featureliste war in Sibelius erst ab Version 6 komplett. Hier und da findet man natürlich Kleinigkeiten, die PriMus noch nicht kann, aber: PriMus ist noch ein sehr junges Programm, und bietet in diesem frühen Stadium schon beinahe die komplette Funktionalität eines Sibelius. Das ist erstaunlich. Auch die Qualität des Satzes kann sich sehen lassen. Leider ist der Algorithmus für die automatische Kollisionserkennung weit weniger schlau als in Sibelius. Aber immerhin: Finale hat da gar nichts zu bieten.

Kommen wir zu dem für mich interessantesten Teil des Tests: Die DTP Funktionen. War man bisher für den Entwurf von Unterrichtsliteratur darauf angewiesen, den Text in einem Programm wie Indesign zu setzen und sämtliche Notenbeispiele über den Grafikexport aus Sibelius oder Finale herauszuziehen, so kann man jetzt all das in einem einzigen Programm erledigen. Dass das ein entscheidender Vorteil ist, merkt man bei den bisherigen Lösungen spätestens dann, wenn die importierte Grafik für das vierte Notenbeispiel mal wieder größer skaliert wurde als der Rest, oder man bei einer der Noten das Vorzeichen vergessen hat.

Der Funktionsumfang der PriMus-internen Textverarbeitung ist zwar nicht vergleichbar mit Word und schon gar nicht mit Indesign, ist aber dem Zweck durchaus angemessen. Textblöcke lassen sich frei erzeugen und verschieben, gleiches gilt für Notensysteme (in PriMus “Stücke” genannt). Das ist tatsächlich eine Offenbarung, sind in allen anderen Notensatzprogrammen Texte doch stets an die Notensysteme gebunden, was für textgetriebene Publikationen natürlich denkbar schlecht ist.

Leider gibt es auch einige kleinere Kritikpunkte, insbesondere in Bezug auf das Verschieben von Text- und Notenblöcken sowie das Verformen von Grafikobjekten. Das artet zum Teil in größere Fummelei aus. Hier sollte man eventuell noch ein bisschen mehr Arbeit in das GUI Design und dessen Logik stecken. Meiner Meinung nach (die sich laut Handbuch nicht mit der des Entwicklers deckt) spricht nichts dagegen, einen gesonderten Modus für die Bearbeitung der Blöcke aktivieren zu müssen, und ansonsten durch das Klicken der Maus einfach eine Selektion auszulösen. Mir passiert es beinahe jedes Mal, dass ich Noten in das System setze, obwohl ich es nur verschieben wollte. Ein weiterer Punkt ist die Stabilität, die, zumindest in der mir vorliegenden Demoversion 1.1, nicht immer optimal war. Bei extensivem Bearbeiten von Text stürzte das Programm gelegentlich ab. Hoffentlich hat Columbussoft daran gedacht, in der finalen Version eine Autosave Funktion einzubauen.

Zusammenfassend kann man sagen, dass PriMus zwar (noch) keine vollwertige Alternative zu Sibelius oder Finale ist, jedoch eine Marktnische abdeckt, für die sich bisher scheinbar niemand interessiert hat: Den DTP-Notensatz. Und das macht es ziemlich gut, so dass man den recht hohen Preis von 349 Euro durchaus hinblättern kann, sofern man die DTP Funktionen auch wirklich braucht. Ansonsten ist Sibelius die bessere Wahl.

KategorienMusik Tags:

MR.bee live beim Kieswerk Open Air

3. August 2010 Keine Kommentare

Am Mittwoch den 04.08. live beim Kieswerk Open Air ab ca. 19 Uhr mit Coverversionen von Katie Melua, Billy Joel, Jack Johnson, The Eagles, Robbie Williams und andern. Im Anschluss Filmvorfuehrung auf Grossleinwand “Mit dir an meiner Seite”. Weitere Infos unter http://www.kieswerk-open-air.de/ und http://mrbee.johannes-asal.de/.

KategorienMusik Tags:

Implementation eines Konfigurationsparsers in Python mit PLY (Teil 1)

17. Juni 2010 Keine Kommentare

Nach der Entwicklung eines Parsers fuer Konfigurationsdateien in C++ kam mir der Gedanke, auch fuer andere Programmiersprachen entsprechende Parser zu schreiben, um die gemeinsame Nutzung ein und derselben Konfigurationen in mehreren Sprachen zu ermoeglichen. Dies ist insbesondere in der Entwicklung von Applikationen von Interesse, die ein prozessbasiertes mehrschichtiges Design aufweisen und jeweils die am besten geeignete Programmiersprache fuer jede Schicht verwenden. Hat man in solchen Faellen kein gemeinsames Konfigurationsmodell zur Verfuegung, so muessen saemtliche relevanten Daten von der Controller Applikation an die anderen Prozesse weitergegeben werden, was aber wiederum die Verwendung eines speziellen Protokolls voraussetzt, um die Daten zwischen den Prozessen auszutauschen. Fuer solche Anwendungen ist es sicherlich einfacher, die Konfiguration zentral zu verwalten und in jedem Prozess separat einzulesen.

Die erste Programmiersprache, die ich in diesem Zusammenhang angegangen habe, ist Python. Fuer Python existiert mit PLY ein einfacher aber dennoch leistungsfaehiger Parser Generator, der gegenueber dem C++-Pendant boost::spirit auf die Vorteile einer dynamischen Sprache zurueckgreifen kann. Bereits nach 15 Minuten hatte ich einen funktionsfaehigen Lexer implementiert, der mir einen Tokenstream fuer das Konfigurationsformat lieferte. Nach weiteren 20 Minuten war auch der Parser fertiggestellt. Das zeigt, dass PLY viel besser als boost::spirit fuer Rapid Prototyping geeignet ist, zumal auch das Testen von Lexer und Parser dank schneller Instanziierung im Python Interpreter zuegig von der Hand geht.

In diesem Artikel moechte ich kurz anreissen, wie sich ein Parser fuer unser Konfigurationsformat (siehe vorangegangene Artikel) mit PLY implementieren laesst. Details sollen diesmal aussen vor bleiben, dazu verweise ich auf den Sourcecode, der am Ende des zweiten Teils verlinkt ist. Fuer ein erstes Experimentieren mit PLY sollten die Ausfuehrungen jedoch ausreichend sein, zumal Python mit seinem Interpreter eine ideale Testumgebung bietet.

Um den zu parsenden String in einzelne Tokens zu zerlegen, benoetigen wir einen Lexer. Dieser muss sowohl Keywords (in unserem Fall sind das ‘true’ und ‘false’), einzelne Zeichen (Klammern, Semikolon, Komma und ‘=’) sowie die atomaren Datentypen (double, long und string) erkennen koennen. Im Gegensatz zu boost::spirit koennen wir in PLY direkt regulaere Ausdruecke verwenden, um die einzelnen Token zu spezifizieren. Diese fassen wir geeigneterweise in einer Klasse zusammen (auch alle folgenden Codesegmente sollen als Member dieser Klasse angelegt werden). Zunaechst definieren wir eine Liste ‘tokens’ (der Name ist vorgegeben), die die (frei waehlbaren) Namen der Tokens enthaelt.

class CfgLexer(object):
  keywords = ('TRUE', 'FALSE')
  tokens = keywords + (
    'LPAREN', 'RPAREN',
    'LBRACKET', 'RBRACKET',
    'LBRACE', 'RBRACE',
    'COMMA',
    'EQUALS',
    'SEMICOLON',
    'LITERAL',
    'DOUBLE',
    'LONG',
    'STRING_LITERAL'
  )

Fuer die beiden Keywords ‘TRUE’ und ‘FALSE’ habe ich zudem eine separate Liste angelegt; der Grund dafuer ist, dass fuer die Keywords beim Parsen nicht zwischen Gross- und Kleinschreibung unterschieden werden soll und diese deshalb spaeter gesondert behandelt werden muessen.

Um die Tokens festzulegen, fuegen wir der Klasse nun regulaere Ausdruecke fuer jedes der Tokens hinzu und weisen diese jeweils einer Variablen ‘t_TOKENNAME’ hinzu, also folgendermassen:

t_LPAREN = "\("
t_RPAREN = "\)"
t_LBRACKET = "\["
t_RBRACKET = "\]"
t_LBRACE = "\{"
t_RBRACE = "\}"
t_COMMA = ","
t_EQUALS = "="
t_SEMICOLON = ";"

Beachten Sie, dass die Klammern escaped werden muessen, da es sich bei den Strings um regulaere Ausdruecke handelt, und die Klammern sonst als Steuerzeichen interpretiert werden.

Fuer die uebrigen Tokens reicht ein einfaches Matching nicht aus, wir muessen ausserdem Aktionen verknuepfen, die bei einem erfolgreichen Match ausgefuehrt werden. Dazu koennen wir wie zuvor eine Variable mit einem regulaeren Ausdruck definieren und diese anschliessend an eine Funktion binden:

literal = "[a-zA-Z_][0-9a-zA-Z]*"
 
@TOKEN(literal)
def t_LITERAL(self, t):
  t.type = self.keyword_map.get(t.value.lower(), "LITERAL")
  return t

Nach einem erfolgreichen Match des regulaeren Ausdrucks suchen wir also zuerst in einer Keyword Map, ob wir ein passendes Keyword finden (beachten Sie die lowercase Transformation!) und verwenden dieses fuer den Typ des Tokens, ansonsten weisen wir den Typ ‘LITERAL’ zu. Die Keyword Map definieren wir wie folgt:

keyword_map = {}
for k in keywords:
  keyword_map[k.lower()] = k

Auch fuer die uebrigen Tokens muessen wir Funktionen definieren:

string_literal = r'"[^"\n]*"' + '|' + r"'[^'\n]*'"
double_constant = r"\-?([1-9]\d* | 0)\.\d*"
long_constant = r"\-?[1-9]\d* | 0"
 
@TOKEN(string_literal)
def t_STRING_LITERAL(self, t):
  t.type = "STRING_LITERAL"
  t.value = t.value.strip("\"'")
  return t
 
@TOKEN(double_constant)
def t_DOUBLE(self, t):
  t.type = "DOUBLE"
  t.value = float(t.value)
  return t
 
@TOKEN(long_constant)
def t_LONG(self, t):
  t.type = "LONG"
  t.value = int(t.value)

Bei den String Literals entfernen wir die Anfuehrungszeichen am Anfang und Ende, waehrend wir die value Felder der ‘DOUBLE’ und ‘LONG’ Tokens in den jeweiligen Datentyp konvertieren. Es fehlt nun nur noch ein Skip Parser, der den insignifikanten Whitespace ueberspringt (d.h. kein Token dafuer anlegt). Dazu weisen wir einen String mit zu ignorierenden Zeichen der (reservierten) Variablen t_ignore zu:

t_ignore = " \t"

Wir ueberspringen damit sowohl Leerzeichen als auch Tabs. Was geschieht aber mit den Linefeeds? Diese beduerfen wieder einer Sonderbehandlung:

def t_newline(self, t):
  r'\n+'
  t.lexer.lineno += len(t.value)

Hier missbrauchen wir den Docstring, um den regulaeren Ausdruck unterzubringen (der Lexer interpretiert diesen automatisch als die zu verwendende Regular Expression) und addieren anschliessend die Anzahl der gematchten Linefeeds zur Variablen t.lexer.lineno, um in den Fehlermeldungen auch die richtige Zeilennummer des fehlerhaften Tokens ausgeben zu koennen.

Wie wird nun ein solches Lexer Objekt erzeugt und auf die gelexten Tokens zugegriffen? Dazu definieren wir noch drei weitere Funktionen:

def build(self):
  self.lexer = ply.lex.lex(object=self)
 
def input(self, text):
  self.lexer.input(text)
 
def token(self):
  t = self.lexer.token()
  return t

Nachdem wir noch die Import Statements “import ply.lex” und “from ply.lex import TOKEN” hinzugefuegt haben, koennen wir den Lexer bereits testen. Wir starten eine Python Konsole und importieren das Modul (das wir beispielsweise als cfg_lexer.py) gespeichert haben:

>>> import cfg_lexer
>>> lexer = cfg_lexer.CfgLexer()
>>> lexer.build()
>>> lexer.input("a=5; b=(1,2,"Hallo"); c = { test = "test"; hallo = "welt"; };")
>>> lexer.token()
LexToken(LITERAL,'a',1,0)
>>> lexer.token()
LexToken(EQUALS,'=',1,1)
>>> lexer.token()
LexToken(LONG,5,1,2)
[...]

Wie funktioniert das ganze nun ueberhaupt? Eine solch lose Zusammenstellung von Funktionen in einer Klasse kann doch unmoeglich einen funktionsfaehigen Lexer darstellen! Der Trick ist, dass PLY die Liste der Tokens benutzt, um dynamisch die passenden Variablen bzw. Funktionen mit den regulaeren Ausdruecken in der Klasse zu suchen. Daraus wird on-the-fly Python Code fuer das Lexing generiert. Damit wird auch klar, warum die Benennung der Member unserer CfgLexer Klasse wichtig ist. Werden sie falsch bezeichnet, so findet PLY waehrend dem build() Prozess die regulaeren Ausdruecke bzw. semantischen Aktionen nicht und kann den Lexer nicht erstellen. Die Fehlermeldungen sind aber in diesem Fall deutlich besser verstaendlich als die C++ Templateerrors, die einen bei der Parserentwicklung mit boost::spirit erwarten.

Im naechsten Teil werden wir unseren Lexer als Tokenfeed benutzen und darauf aufbauend einen Parser implementieren.

KategorienProgrammierung Tags: