2012-03-24 12 views
7

Sto lavorando su una semplice selezione SQL come il parser di query e devo essere in grado di acquisire le sottoquery che possono verificarsi in determinati posti letteralmente. Ho trovato gli stati lexer sono la soluzione migliore ed è stato in grado di fare un POC utilizzando parentesi graffe per segnare l'inizio e la fine. Tuttavia, le subquery saranno delimitate da parentesi, non da curlys, e la parentesi può verificarsi anche in altri luoghi, quindi non posso essere lo stato di ogni open-paren. Queste informazioni sono prontamente disponibili con il parser, quindi speravo di chiamare begin e end nelle posizioni appropriate nelle regole del parser. Ciò tuttavia non ha funzionato perché il lexer sembra tokenizzare il flusso tutto in una volta, e così i token vengono generati nello stato INITIAL. C'è una soluzione per questo problema? Ecco una descrizione di ciò che ho cercato di fare:Controllo degli stati lexer di PLY Python dal parser

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 

Lo start_subquery() e end_subquery() sono definiti in questo modo:

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 

I gettoni lexer sono semplicemente lì per rilevare i primi 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 

Apprezzerei qualsiasi aiuto.

risposta

2

Sulla base della risposta dell'autore PLY, ho trovato questa soluzione migliore. Devo ancora capire come restituire la subquery come un token, ma il resto sembra molto meglio e non deve più essere considerato 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

Poiché nessuno ha una risposta, mi ha infastidito nel trovare una soluzione alternativa, e qui c'è un brutto attacco utilizzando il recupero e il riavvio().

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

Questa risposta può essere solo parzialmente utile, ma vorrei anche suggerire a guardare la sezione "6.11 Azioni embedded" della documentazione PLY (http://www.dabeaz.com/ply/ply.html). In poche parole, è possibile scrivere regole grammaticali in cui le azioni si verificano a metà della regola. Sembrerebbe qualcosa di simile a questo:

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') 

Per quanto riguarda il comportamento del lexer, c'è solo un segno di lookahead utilizzato. Quindi, in ogni particolare regola grammaticale, al massimo è stato letto solo un token extra. Se si capovolgono gli stati lexer, è necessario assicurarsi che ciò accada prima che il parser venga utilizzato dal parser, ma prima che il parser chieda di leggere il prossimo token in entrata.

Inoltre, se possibile, proverei a stare fuori dallo stack di gestione degli errori yacc() fino alla soluzione. C'è troppa magia nera in corso nella gestione degli errori - più puoi evitarlo, meglio è.

Sono un po 'pressato per il momento al momento, ma questo sembra essere qualcosa che potrebbe essere studiato per la prossima versione di PLY. Lo metterà sulla mia lista delle cose da fare.

+0

Grazie per il puntatore alle azioni incorporate, sembra molto promettente. Tuttavia, nel tuo esempio, dovremmo controllare il token lookahead invece dell'ultimo token? L'ultimo token sarebbe 'B', ma il lookahead sarebbe' LBRACE' giusto? – haridsv

Problemi correlati