Seite 1 von 2

Python-Syntax-Highlighter

Verfasst: Freitag 21. März 2008, 09:03
von sma
Ich habe aus Spaß an der Aufgabe einen einfachen Syntax-Highlighter für Python in Python prototypisiert. Ich brauche den eigentlich in JavaScript, daher auch keine Python-spezifischen Imports für Scanner, Parser oder so. Dennoch frage ich mich (und euch), geht es noch kürzer? Mich stört die elif-Kaskade, doch mir fällt nichts besseres ein.

Code: Alles auswählen

import re

TOKENS = re.compile('''
    (\\#[^\r\n]*) |
    (and|as|assert|break|class|continue|def|del|elif|else|except|exec|finally|
     for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|
     try|while|with|yield)\\b |
    ([rR]?[uU]?
     (?:""".*?"""|"(?:\\\\"|[^"])*?"|\'\'\'.*?\'\'\'|'(?:\\\\'|[^'])*?')) |
    (0[xX][0-9a-fA-F]+|\\d+(?:\\.\\d+)?(?:[eE][-+]?\\d+)?[lLjJ]?) |
    ((?:(?<=def)|(?<=class))(\\s+)(\\w+)) |
    (\\s+|\\w+|.)''', re.DOTALL + re.VERBOSE)

def colorize(s):
    tokens = []
    for m in TOKENS.finditer(s):
        if m.group(1): # comments
            tokens.append("<i>%s</i>" % m.group())
        elif m.group(2): # keywords
            tokens.append("<b>%s</b>" % m.group())
        elif m.group(3): # strings
            tokens.append("<span class='str'>%s</span>" % m.group())
        elif m.group(4): # numbers
            tokens.append("<span class='num'>%s</span>" % m.group())
        elif m.group(5): # defined names
            tokens.append("%s<u>%s</u>" % (m.group(6), m.group(7)))
        else:
            tokens.append(m.group())
    return "".join(tokens)
Stefan

Verfasst: Freitag 21. März 2008, 09:43
von sma
Jetzt, wo ich's aufgeschrieben habe, biete ich noch diese Variante, die kompakter und einfacher erweiterbar ist, jedoch vielleicht nicht ganz so schnell. Leider gibt es keine direkte Entsprechung von `expand` in JavaScript.

Code: Alles auswählen

import re

def _(p, r): return (re.compile(p), r)

TOKENS = [
    _('(\\#[^\r\n]*)', '<i>\\1</i>'),
    _('''(?x)(and|as|assert|break|class|continue|def|del|elif|else|except|exec|
      finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|
      return|try|while|with|yield)\\b''', '<b>\\1</b>'),
    _('''(?xs)([rR]?[uU]?(?:""".*?"""|"(?:\\\\"|[^"])*?"|\'\'\'.*?\'\'\'|
      '(?:\\\\'|[^'])*?'))''', '<span class="str">\\1</span>'),
    _('(0[xX][0-9a-fA-F]+|\\d+(?:\\.\\d+)?(?:[eE][-+]?\\d+)?[lLjJ]?)',
      '<span class="num">\\1</span>'),
    _('(?:(?<=def)|(?<=class))(\\s+)(\\w+)', '\\1<u>\\2</u>'),
    _('(\\#[^\r\n]*)', '<i>\\1</i>'),
    _('(\\s+|\\w+|.)', '\\1'),
]

def colorize(s):
    chunks = []
    while s:
        for pattern, replacement in TOKENS:
            m = pattern.match(s)
            if m:
                chunks.append(m.expand(replacement))
                s = s[m.end():]
                break
    return "".join(chunks)
Stefan

Verfasst: Freitag 21. März 2008, 11:09
von lunar
Darf man fragen, warum der Highligther in JavaScript implementiert werden soll?

Wenn du Highlighting Serverseitig implementierst, kannst du auf ausgereiftere Syntaxhighligther zurückgreifen.

Btw, bist du wirklich sicher, dass da nichts für dich dabei ist? Nicht, dass ich deinen Programmiereifer bremsen will, aber du läuft Gefahr, dass Rad schon wieder zu erfinden ;)

Verfasst: Freitag 21. März 2008, 15:14
von sma
lunar hat geschrieben:Darf man fragen, warum der Highligther in JavaScript implementiert werden soll?
Weil's clientseitig laufen sollte. Und nein, ich habe nicht gesucht, wenn ich so was mache, geht's mir um's selbstmachen. Andere lösen Kreuzworträtsel. Das ist ein Ausgleich zum Pflegen von Projektplänen und Hüten von widerspenstigen Entwicklern ;)[/quote]
lunar hat geschrieben:Nicht, dass ich deinen Programmiereifer bremsen will, aber du läuft Gefahr, dass Rad schon wieder zu erfinden ;)
Ich weiß, aber das ist mir egal. Ich hatte das neulich zum Zug geschrieben und dachte mir, teile ich's der Welt mit. Mein Bestreben ist, minimale Lösungen zu finden. Der erste Google-Hit etwa braucht über 20k Quelltext, um Python einzufärben.

Stefan

Verfasst: Freitag 21. März 2008, 15:37
von lunar
sma hat geschrieben:
lunar hat geschrieben:Darf man fragen, warum der Highligther in JavaScript implementiert werden soll?
Weil's clientseitig laufen sollte.
Ach nein ;)

Die Frage ist doch, ob clientseitig sinnvoll ist. Ich würde eher AJAX nehmen, und das Highligthing auf den Server verschieben. Immerhin ist Syntax-Highlighting recht komplex, was dementsprechend große Bibliotheken erfordern würde.

Wobei das natürlich völlig egal ist, wenn du einen JavaScript-Highlighter aus akademischem Interesse implementieren willst. Nur für Real-World-Programme würde ich ihn nicht unbedingt einsetzen ;)

Verfasst: Samstag 22. März 2008, 10:41
von sma
lunar hat geschrieben:Immerhin ist Syntax-Highlighting recht komplex, was dementsprechend große Bibliotheken erfordern würde.
Ahem, den vollständigen Code habe ich doch gepostet. Die JavaScript-Version wird sich davon nicht wesentlich unterscheiden. Wieso ist das groß? Missverstehen wir uns irgendwie?

Stefan

Verfasst: Samstag 22. März 2008, 11:20
von lunar
sma hat geschrieben:
lunar hat geschrieben:Immerhin ist Syntax-Highlighting recht komplex, was dementsprechend große Bibliotheken erfordern würde.
Ahem, den vollständigen Code habe ich doch gepostet. Die JavaScript-Version wird sich davon nicht wesentlich unterscheiden. Wieso ist das groß? Missverstehen wir uns irgendwie?
Offenbar besteht schon ein kleines Missverständnis hinsichtlich des Anspruchs an einen Formatter. Ich persönlich hätte gerne einen, der korrekte Tokens zurückgibt und infolge dessen korrekt hervorhebt. Dir scheint es offenbar lediglich um geringe Codegröße zu gehen, was sich in der Qualität entsprechend niederschlägt. Dein Code funktioniert nicht. Nichts für ungut, aber die Resultate können nicht überzeugen:

Der Test-Code
Dein Formatter mit zusätzlichem HTML-Code und entsprechenden Definitionen für die CSS-Klassen
Das Resultat von Pygments (full-Option für den HTML-Formatter angegeben, um CSS-Definitionen einzubetten)

Sorry, dass es gepastet ist, aber mir viel kein besseres Medium ein. Kennst du ein Tool, um einen komplette Website mit der Gecko-Engine in ein Bild rendern zu lassen? Das wäre für diesen Zweck besser gewesen.

Verfasst: Samstag 22. März 2008, 11:56
von sma
lunar, erst mal vielen Dank für deinen Test und Bug-Report. Das "Dein Code funktioniert nicht" bedeutet, dass ich vergessen habe, Schlüsselworte auf Worte zu beschränken, ja? Weitere Probleme außer dem nakten "<" sehe ich nicht.

Schaue ich mir das Ergebnis von Pygments an, wird da nicht nur nach einem def und class das nächste Wort anders hervorgehoben, sondern auch nach from und import. Das könnte ich auch. Dann werden """-Strings an Doc-String-Position anders dargestellt. Damit täte ich mich schwer. Schließlich werden offenbar eingebaute Funktionen und vordefinierte Typen hervorgehoben. Das wäre wieder kein Problem - zwei weitere Listen von Schlüsselworten - gefällt mir aber nicht so, weil ich nicht einsehe, warum ValueError bzw. irgendwie anders ist als ArgumentError.

Stefan

Verfasst: Samstag 22. März 2008, 12:33
von lunar
sma hat geschrieben:lunar, erst mal vielen Dank für deinen Test und Bug-Report. Das "Dein Code funktioniert nicht" bedeutet, dass ich vergessen habe, Schlüsselworte auf Worte zu beschränken, ja? Weitere Probleme außer dem nakten "<" sehe ich nicht.
Ich habe keine Ahnung, was nicht stimmt, ich sehe nur, dass das Ergebnis im Firefox nicht so ganz überzeugend war ;)

Bei genauerer Betrachtung scheint das allerdings tatsächlich nur daran zu liegen, dass du HTML-Sonderzeichen nicht gequotet hast. Allerdings ist das nicht der einzige Bug:

Code: Alles auswählen

[lunar@nargond]-[12:30:04] >> /home/lunar/test
[4]--> print colorize(r"'foo\''")
<span class="str">'foo\'</span>'
Maskierte Anführungszeichen in einem Literal werden ebenfalls nicht erkannt, was mich bei einem Parser, der auf regulären Ausdrücken aufbaut, auch wundern würde.

Die Ausgabe von Pygments war mehr als Vergleich (weil Pygments korrekt parst) gedacht denn als Anregung.

Verfasst: Samstag 22. März 2008, 13:47
von BlackJack
@sma: Vordefinierte Funktionen und Typen gesondert hervor zu heben, finde ich ganz gut, weil man dann eher sieht, wenn man etwas "eingebautes" überschreibt.

Verfasst: Samstag 22. März 2008, 13:48
von Leonidas
lunar hat geschrieben:Maskierte Anführungszeichen in einem Literal werden ebenfalls nicht erkannt, was mich bei einem Parser, der auf regulären Ausdrücken aufbaut, auch wundern würde.

Die Ausgabe von Pygments war mehr als Vergleich (weil Pygments korrekt parst) gedacht denn als Anregung.
Wobei ja zu sagen ist, dass Pygments durchaus auch Reguläre Ausdrücke verwendet.

Verfasst: Samstag 22. März 2008, 13:54
von jens
Also ich finde diese minimalistische Lösung ganz nett. Könnte ich in PyLucid einbauen, als alternative, wenn Pygments nicht verfügbar ist ;)
Allerdings sollten dann die Klassennamen am besten gleich sein, damit die Stylesheets die selben sein können...

Verfasst: Samstag 22. März 2008, 14:42
von EnTeQuAk
Du vergisst, das nicht jeder nen Python-Highlighter braucht. Pygments kann da durchaus etwas mehr :)


MfG EnTeQuAk

Verfasst: Samstag 22. März 2008, 16:07
von lunar
Leonidas hat geschrieben:
lunar hat geschrieben:Maskierte Anführungszeichen in einem Literal werden ebenfalls nicht erkannt, was mich bei einem Parser, der auf regulären Ausdrücken aufbaut, auch wundern würde.[...]
Wobei ja zu sagen ist, dass Pygments durchaus auch Reguläre Ausdrücke verwendet.
Allerdings innerhalb eines Zustandsautomaten, denn gerade Dinge wie Strings mit maskierten Anführungszeichen darin lassen sich mit regulären Ausdrücken nur schwerlich abbilden.

Verfasst: Samstag 22. März 2008, 16:49
von mitsuhiko
lunar hat geschrieben:Allerdings innerhalb eines Zustandsautomaten, denn gerade Dinge wie Strings mit maskierten Anführungszeichen darin lassen sich mit regulären Ausdrücken nur schwerlich abbilden.
Python's Reguläre Ausdrücke sind aber auch keine.

Verfasst: Samstag 22. März 2008, 17:03
von lunar
mitsuhiko hat geschrieben:
lunar hat geschrieben:Allerdings innerhalb eines Zustandsautomaten, denn gerade Dinge wie Strings mit maskierten Anführungszeichen darin lassen sich mit regulären Ausdrücken nur schwerlich abbilden.
Python's Reguläre Ausdrücke sind aber auch keine.
Inwiefern?

Verfasst: Samstag 22. März 2008, 17:18
von mitsuhiko
lunar hat geschrieben:
mitsuhiko hat geschrieben:Python's Reguläre Ausdrücke sind aber auch keine.
Inwiefern?
Die können Backrefs, Lookaheads und alles mögliche was mit einem NFA nicht möglich ist.

Unabhängig davon spricht nichts dagegen Strings mit Regulär Expressions inkl. Escapes zu matchen:

Code: Alles auswählen

string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
                       r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?s)')

Verfasst: Samstag 22. März 2008, 17:43
von lunar
mitsuhiko hat geschrieben:
lunar hat geschrieben:
mitsuhiko hat geschrieben:Python's Reguläre Ausdrücke sind aber auch keine.
Inwiefern?
Die können Backrefs, Lookaheads und alles mögliche was mit einem NFA nicht möglich ist.
Ich hatte dein vorletztes Posting so verstanden, dass du behaupten wolltest, Python's reguläre Ausdrücke wäre in Wirklichkeit keine regulären Ausdrücke. Insofern erscheint es mir gerade etwas irreführend, reguläre Ausdrücke mit Zustandsautomaten zu vergleichen...
Unabhängig davon spricht nichts dagegen Strings mit Regulär Expressions inkl. Escapes zu matchen:

Code: Alles auswählen

string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
                       r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?s)')
Dagegen spricht die Lesbarkeit.

Verfasst: Samstag 22. März 2008, 18:34
von BlackJack
Reguläre Ausdrücke und endliche Automaten sind äquivalent. Irreführend ist, die Bezeichnung reguläre Ausdrücke für dass was Programmiersprachen bzw. regex-Engines alles können. Jedenfalls für theoretische Informatiker. :-)

Verfasst: Samstag 22. März 2008, 20:33
von Y0Gi
lunar hat geschrieben:

Code: Alles auswählen

string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
                       r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?s)')
Dagegen spricht die Lesbarkeit.
Die spricht immer gegen Regex ;)