2010-03-01 17 views
10

Estoy buscando acelerar un poco mi proceso de descubrimiento aquí, ya que esta es mi primera incursión en el mundo del análisis léxico. Tal vez este es incluso el camino equivocado. Primero, describiré mi problema:Python - análisis léxico y tokenización

Tengo archivos de propiedades muy grandes (del orden de 1,000 propiedades) que, cuando se destilan, son solo unas 15 propiedades importantes y el resto se puede generar o rara vez cambio.

Así, por ejemplo:

general { 
    name = myname 
    ip = 127.0.0.1 
} 

component1 { 
    key = value 
    foo = bar 
} 

Este es el tipo de formato que quiero crear a tokenize algo como:

property.${general.name}blah.home.directory = /blah 
property.${general.name}.ip = ${general.ip} 
property.${component1}.ip = ${general.ip} 
property.${component1}.foo = ${component1.foo} 

en

property.mynameblah.home.directory = /blah 
property.myname.ip = 127.0.0.1 
property.component1.ip = 127.0.0.1 
property.component1.foo = bar 

análisis léxico y tokenización suena como mi mejor ruta, pero esta es una forma muy simple de hacerlo. Es una gramática simple, una sustitución simple y me gustaría asegurarme de no llevar un mazo para golpear un clavo.

Pude crear mi propio lexer y tokenizer, o ANTlr es una posibilidad, pero no me gusta reinventar la rueda y ANTlr suena como overkill.

No estoy familiarizado con las técnicas de compilación, por lo que los punteros en la dirección correcta & serían más apreciados.

Nota: Puedo cambiar el formato de entrada.

+2

Por qué no usar JSON en lugar de crear su propio programa de análisis ?? – AndiDog

+0

Su traducción de ejemplo parece tener algunos errores. Si no, no puedo ver por qué "$ {component1} .ip" se tradujo en "component1" en la línea 3 del ejemplo. Si la sintaxis es así de regular, probablemente estallaría los $ {identificadores} con expresiones regulares y los reemplazaría con búsquedas de diccionario de paso cuando no haya entradas de diccionario. – msw

+0

Hubo un par de errores allí, creo que los he corregido. –

Respuesta

10

Hay un artículo excelente en Using Regular Expressions for Lexical Analysis en effbot.org.

La adaptación de la tokenizer a su problema:

import re 

token_pattern = r""" 
(?P<identifier>[a-zA-Z_][a-zA-Z0-9_]*) 
|(?P<integer>[0-9]+) 
|(?P<dot>\.) 
|(?P<open_variable>[$][{]) 
|(?P<open_curly>[{]) 
|(?P<close_curly>[}]) 
|(?P<newline>\n) 
|(?P<whitespace>\s+) 
|(?P<equals>[=]) 
|(?P<slash>[/]) 
""" 

token_re = re.compile(token_pattern, re.VERBOSE) 

class TokenizerException(Exception): pass 

def tokenize(text): 
    pos = 0 
    while True: 
     m = token_re.match(text, pos) 
     if not m: break 
     pos = m.end() 
     tokname = m.lastgroup 
     tokvalue = m.group(tokname) 
     yield tokname, tokvalue 
    if pos != len(text): 
     raise TokenizerException('tokenizer stopped at pos %r of %r' % (
      pos, len(text))) 

Para probarlo, que hacemos:

stuff = r'property.${general.name}.ip = ${general.ip}' 
stuff2 = r''' 
general { 
    name = myname 
    ip = 127.0.0.1 
} 
''' 

print ' stuff '.center(60, '=') 
for tok in tokenize(stuff): 
    print tok 

print ' stuff2 '.center(60, '=') 
for tok in tokenize(stuff2): 
    print tok 

para:

========================== stuff =========================== 
('identifier', 'property') 
('dot', '.') 
('open_variable', '${') 
('identifier', 'general') 
('dot', '.') 
('identifier', 'name') 
('close_curly', '}') 
('dot', '.') 
('identifier', 'ip') 
('whitespace', ' ') 
('equals', '=') 
('whitespace', ' ') 
('open_variable', '${') 
('identifier', 'general') 
('dot', '.') 
('identifier', 'ip') 
('close_curly', '}') 
========================== stuff2 ========================== 
('newline', '\n') 
('identifier', 'general') 
('whitespace', ' ') 
('open_curly', '{') 
('newline', '\n') 
('whitespace', ' ') 
('identifier', 'name') 
('whitespace', ' ') 
('equals', '=') 
('whitespace', ' ') 
('identifier', 'myname') 
('newline', '\n') 
('whitespace', ' ') 
('identifier', 'ip') 
('whitespace', ' ') 
('equals', '=') 
('whitespace', ' ') 
('integer', '127') 
('dot', '.') 
('integer', '0') 
('dot', '.') 
('integer', '0') 
('dot', '.') 
('integer', '1') 
('newline', '\n') 
('close_curly', '}') 
('newline', '\n') 
+0

Fyi, [ese tipo de tokenizador] (http://docs.python.org/3.2/library/re.html#writing-a-tokenizer) lo convirtió en la documentación stdlib del módulo 're' – cfi

1

Si puede cambiar el formato de los archivos de entrada, puede usar un analizador para un formato existente, como JSON.

Sin embargo, en su declaración de problema parece que ese no es el caso. Por lo tanto, si desea crear un lexer y un analizador personalizados, use PLY (Python Lex/Yacc). Es fácil de usar y funciona igual que lex/yacc.

Aquí hay un enlace a un example de una calculadora construida utilizando PLY. Tenga en cuenta que todo lo que comience con t_ es una regla lexer, que define un token válido, y todo lo que comienza con p_ es una regla de analizador que define la producción de la gramática.

2

Por tan simple que parezca ser su formato, creo que un analizador/lexer completo sería demasiado exagerado. Parece que una combinación de expresiones regulares y manipulación de cadenas haría el truco.

Otra idea es cambiar el archivo a algo así como json o xml y usar un paquete existente.

2

Un DFA simple funciona bien para esto. Sólo necesita unos pocos estados:

  1. Buscando ${
  2. visto ${ en busca de al menos un carácter válido formando el nombre
  3. visto al menos un carácter nombre válido, en busca de más caracteres del nombre o }.

Si el archivo de propiedades es independiente del orden, es posible que desee un procesador de dos pasadas para verificar que cada nombre se resuelva correctamente.

Por supuesto, debe escribir el código de sustitución, pero una vez que tenga una lista de todos los nombres utilizados, la implementación más simple posible es encontrar/reemplazar en ${name} con su valor correspondiente.

1

La sintaxis que proporcione parece similar a Mako templates engine. Creo que podrías intentarlo, es una API bastante simple.

Cuestiones relacionadas