Eigentlich ist es einfach: 1. Sprache überlegen und Grammatik definieren. 2. Überlegen, wie man zugreifen will. 3. Scanner & Parser bauen. 4. Benutzen.
Ich kann nur raten, wie die Grammatik wäre. Leider scheint man einen Scanner mit zwei Zuständen zu benötigen, da innerhalb einer Klammer nach einem Namen beliebig viel bis zum Zeilenende kommen kann. Daher kann man nicht einen simplen RE-basierten Scanner mit einem einfachen rekursiv absteigenden Parser benutzen. Ein PEG-Parser müsste es aber können...
Grammatik bis hin zu regulären Ausdrücken:
Code: Alles auswählen
config = {group}.
group = groupname ws "{" {property} ws "}".
groupname = name {name}.
property = name value.
name = ws \w+
value = ws [^\n]*\n
ws = \s*
Mein Parserrahmenwerk:
Code: Alles auswählen
import re
def word(r):
r = re.compile(r)
def word(s):
m = r.match(s)
if m: return m.group(1), s[m.end():]
return word
def token(t): return word("()\s*" + re.escape(t))
def seq(*ps):
def seq(s):
r = []
for p in ps:
rv = p(s)
if not rv: return None
if rv[0]: r.append(rv[0])
s = rv[1]
return r, s
return seq
def rep(p):
def rep(s):
r = []
while True:
rv = p(s)
if not rv: return r, s
if rv[0]: r.append(rv[0])
s = rv[1]
return rep
Parser:
Code: Alles auswählen
def p_name(): return word(r"\s*(\w+)")
def p_value(): return word(r"\s*([^\n]*)\n")
def p_prop(): return seq(p_name(), p_value())
def p_groupname(): return seq(p_name(), rep(p_name()))
def p_group(): return seq(p_groupname(), word(r"\s*\{"), rep(p_prop()), word(r"\s*\}"))
def p_config(): return rep(p_group())
Ich gehe davon aus, dass der `:` hinter `redirect_url` ein Fehler ist. Dann kann der Parser `p_config` die Eingabe parsen. Statt sich selbst Parser und Parserkombinatoren zu bauen, kann man z.B. PyParsing benutzen. Wäre aber lame.
Ich will mit `config["cat porn"]["acl"]` auf die Daten zugreifen können. Wie bringe ich meinen Parser dazu, mir ein dict von dicts zu bauen? Ich definiere Aktionen, die mir die Daten umwandeln und filtere damit meine Parserergebnisse.
Jeder Parser liefert nämlich die erkannten Token zurück. `word` liefert das, was im regulären Ausdruck in Klammern steht. `token` wird ignoriert. `seq` und `rep` liefern je eine Liste. `p_name` liefert mir also einen Namen als String; dito `p_value`. `p_prop` liefert mit eine Liste mit dem Namen und dem Wert. `p_groupname` liefert mit eine zweielementige Liste mit einem Namen und einer weiteren weiteren Liste mit den weiteren Namen. Hieraus macht `a_groupname` einen String. `p_group` liefert eine zweielementige Liste mit dem Gruppennamen und einer Liste mit den Listen der Properties. Diese wandelt `a_group` in ein dict um. Und `p_config` liefert nochmal eine Liste mit den Listen der Gruppen und `a_config` macht daraus nochmal ein dict.
Code: Alles auswählen
def action(p, a):
def action(s):
rv = p(s)
if rv: return a(rv[0]), rv[1]
return action
def a_groupname(v): return " ".join(v[:1] + v[1])
def a_group(v): return v[0], dict(prop for prop in v[1])
def a_config(v): return dict(v)
def p_groupname(): return action(seq(p_name(), rep(p_name())), a_groupname)
def p_group(): return action(seq(p_groupname(), token("{"), rep(p_prop()), token("}")), a_group)
def p_config(): return action(rep(p_group()), a_config)
Jetzt funktioniert dies:
Code: Alles auswählen
print p_config()("""
cat porn {
domainlist /etc/...
urllist /etc/...
acl porn
}
acl porn {
validtime 12:30-13:30,14:30-15:30
redirect_url http://www...
}
""")[0]
Stefan