2012-03-24 43 views
7

Estoy trabajando en un SQL simple como el analizador de consultas y necesito poder capturar subconsultas que pueden ocurrir literalmente en ciertos lugares. Encontré que los estados de Lexer son la mejor solución y pude hacer un POC usando llaves para marcar el inicio y el final. Sin embargo, las subconsultas estarán delimitadas por paréntesis, no por curlys, y el paréntesis también puede ocurrir en otros lugares, por lo que no puedo ser el estado con cada tabla abierta. Esta información está disponible con el analizador, por lo que esperaba llamar a inicio y final en las ubicaciones apropiadas en las reglas del analizador. Sin embargo, esto no funcionó porque Lexer parece tokenizar la secuencia de una sola vez, por lo que los tokens se generan en el estado INICIAL. ¿Hay alguna solución alternativa a este problema? He aquí un resumen de lo que he intentado hacer:Controlando Python PLY estados lexer del analizador

def p_value_subquery(p): 
    """ 
    value : start_sub end_sub 
    """ 
    p[0] = "(" + p[1] + ")" 

def p_start_sub(p): 
    """ 
    start_sub : OPAR 
    """ 
    start_subquery(p.lexer) 
    p[0] = p[1] 

def p_end_sub(p): 
    """ 
    end_sub : CPAR 
    """ 
    subquery = end_subquery(p.lexer) 
    p[0] = subquery 

El start_subquery() y end_subquery() se definen así:

def start_subquery(lexer): 
    lexer.code_start = lexer.lexpos  # Record the starting position 
    lexer.level = 1 
    lexer.begin('subquery') 

def end_subquery(lexer): 
    value = lexer.lexdata[lexer.code_start:lexer.lexpos-1] 
    lexer.lineno += value.count('\n') 
    lexer.begin('INITIAL') 
    return value 

Las fichas lexer simplemente están ahí para detectar los primeros paren :

@lex.TOKEN(r"\(") 
def t_subquery_SUBQST(t): 
    lexer.level += 1 

@lex.TOKEN(r"\)") 
def t_subquery_SUBQEN(t): 
    lexer.level -= 1 

@lex.TOKEN(r".") 
def t_subquery_anychar(t): 
    pass 

Agradeceria cualquier ayuda.

Respuesta

2

Basado en la respuesta del autor PLY, se me ocurrió esta mejor solución. Todavía tengo que descubrir cómo devolver la subconsulta como un token, pero el resto se ve mucho mejor y ya no es necesario que se considere un hack.

def start_subquery(lexer): 
    lexer.code_start = lexer.lexpos  # Record the starting position 
    lexer.level = 1 
    lexer.begin("subquery") 

def end_subquery(lexer): 
    lexer.begin("INITIAL") 

def get_subquery(lexer): 
    value = lexer.lexdata[lexer.code_start:lexer.code_end-1] 
    lexer.lineno += value.count('\n') 
    return value 

@lex.TOKEN(r"\(") 
def t_subquery_OPAR(t): 
    lexer.level += 1 

@lex.TOKEN(r"\)") 
def t_subquery_CPAR(t): 
    lexer.level -= 1 
    if lexer.level == 0: 
     lexer.code_end = lexer.lexpos  # Record the ending position 
     return t 

@lex.TOKEN(r".") 
def t_subquery_anychar(t): 
    pass 

def p_value_subquery(p): 
    """ 
    value : check_subquery_start OPAR check_subquery_end CPAR 
    """ 
    p[0] = "(" + get_subquery(p.lexer) + ")" 

def p_check_subquery_start(p): 
    """ 
    check_subquery_start : 
    """ 
    # Here last_token would be yacc's lookahead. 
    if last_token.type == "OPAR": 
     start_subquery(p.lexer) 

def p_check_subquery_end(p): 
    """ 
    check_subquery_end : 
    """ 
    # Here last_token would be yacc's lookahead. 
    if last_token.type == "CPAR": 
     end_subquery(p.lexer) 

last_token = None 

def p_error(p): 
    global subquery_retry_pos 
    if p is None: 
     print >> sys.stderr, "ERROR: unexpected end of query" 
    else: 
     print >> sys.stderr, "ERROR: Skipping unrecognized token", p.type, "("+ \ 
       p.value+") at line:", p.lineno, "and column:", find_column(p.lexer.lexdata, p) 
     # Just discard the token and tell the parser it's okay. 
     yacc.errok() 

def get_token(): 
    global last_token 
    last_token = lexer.token() 
    return last_token 

def parse_query(input, debug=0): 
    lexer.input(input) 
    return parser.parse(input, tokenfunc=get_token, debug=0) 
1

Dado que nadie tiene una respuesta, me molestó encontrar una solución, y aquí está un truco feo utilizando la recuperación de errores y reiniciar().

def start_subquery(lexer, pos): 
    lexer.code_start = lexer.lexpos  # Record the starting position 
    lexer.level = 1 
    lexer.begin("subquery") 
    lexer.lexpos = pos 

def end_subquery(lexer): 
    value = lexer.lexdata[lexer.code_start:lexer.lexpos-1] 
    lexer.lineno += value.count('\n') 
    lexer.begin('INITIAL') 
    return value 

@lex.TOKEN(r"\(") 
def t_subquery_SUBQST(t): 
    lexer.level += 1 

@lex.TOKEN(r"\)") 
def t_subquery_SUBQEN(t): 
    lexer.level -= 1 
    if lexer.level == 0: 
     t.type = "SUBQUERY" 
     t.value = end_subquery(lexer) 
     return t 

@lex.TOKEN(r".") 
def t_subquery_anychar(t): 
    pass 

# NOTE: Due to the nature of the ugly workaround, the CPAR gets dropped, which 
# makes it look like there is an imbalance. 
def p_value_subquery(p): 
    """ 
    value : OPAR SUBQUERY 
    """ 
    p[0] = "(" + p[2] + ")" 

subquery_retry_pos = None 

def p_error(p): 
    global subquery_retry_pos 
    if p is None: 
     print >> sys.stderr, "ERROR: unexpected end of query" 
    elif p.type == 'SELECT' and parser.symstack[-1].type == 'OPAR': 
     lexer.input(lexer.lexdata) 
     subquery_retry_pos = parser.symstack[-1].lexpos 
     yacc.restart() 
    else: 
     print >> sys.stderr, "ERROR: Skipping unrecognized token", p.type, "("+ \ 
       p.value+") at line:", p.lineno, "and column:", find_column(p.lexer.lexdata, p) 
     # Just discard the token and tell the parser it's okay. 
     yacc.errok() 

def get_token(): 
    global subquery_retry_pos 
    token = lexer.token() 
    if token and token.lexpos == subquery_retry_pos: 
     start_subquery(lexer, lexer.lexpos) 
     subquery_retry_pos = None 
    return token 

def parse_query(input, debug=0): 
    lexer.input(inp) 
    result = parser.parse(inp, tokenfunc=get_token, debug=0) 
5

Esta respuesta sólo puede ser parcialmente útiles, pero también sugeriría mirando la sección "6.11 acciones incrustadas" de la documentación PLY (http://www.dabeaz.com/ply/ply.html). En pocas palabras, es posible escribir reglas gramaticales en las que las acciones ocurren a mitad de la regla. Se vería algo similar a esto:

def p_somerule(p): 
    '''somerule : A B possible_sub_query LBRACE sub_query RBRACE''' 

def p_possible_sub_query(p): 
    '''possible_sub_query :''' 
    ... 
    # Check if the last token read was LBRACE. If so, flip lexer state 
    # Sadly, it doesn't seem that the token is easily accessible. Would have to hack it 
    if last_token == 'LBRACE': 
     p.lexer.begin('SUBQUERY') 

En cuanto al comportamiento del analizador léxico, sólo hay una señal de búsqueda hacia delante que se utiliza. Por lo tanto, en cualquier regla de gramática en particular, a lo sumo solo se ha leído una ficha adicional. Si va a voltear estados lexer, debe asegurarse de que suceda antes de que el token sea consumido por el analizador, pero antes de que el analizador pida leer el siguiente token entrante.

Además, si es posible, trataría de mantenerme fuera de la pila de manejo de errores de yacc() en cuanto a una solución. Hay demasiada magia negra en el manejo de errores: cuanto más puedas evitarla, mejor.

Estoy un poco presionado por el tiempo en este momento, pero esto parece ser algo que podría investigarse para la próxima versión de PLY. Lo pondré en mi lista de cosas por hacer.

+0

Gracias por el puntero a acciones dmbedded, parece muy prometedor. Sin embargo, en tu ejemplo, se supone que debemos verificar el token de búsqueda anticipada en lugar del último token. El último token sería 'B', pero el lookahead sería' LBRACE' ¿verdad? – haridsv

Cuestiones relacionadas