2012-03-24 6 views
7

私は単純なSQLのようなクエリパーサーのような選択をしており、文字通り特定の場所で発生するサブクエリをキャプチャできるようにする必要があります。私はレクサー州が最善の解決策であり、中括弧を使用して開始点と終了点をマークするPOCを実行できることを発見しました。ただし、サブクエリはカッコではなくカッコで区切られ、カッコは他の場所でも発生する可能性があるため、オープンparenの状態はすべてできません。この情報はパーサですぐに利用できるので、パーサルールの適切な場所でbeginとendを呼び出すことを望んでいました。これは、しかし、レクサーが一度にストリームをトークン化するように見えるので、動作しませんでした。そのため、トークンはINITIAL状態で生成されます。この問題の回避策はありますか?パーサーからのPython PLYレクサー状態の制御

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 

start_subquery()とend_subqueryを()のように定義されています:ここで私はやってみました何の概要は

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 

レクサー・トークンは、クローズ括弧を検出するだけです:

@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 

私は何か助けていただきありがとうございます。

答えて

2

PLYの著者の回答に基づいて、私はこの優れた解決策を思いつきました。サブクエリをトークンとして返す方法をまだ分かりませんが、残りははるかによく見え、もうハックと見なす必要はありません。

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

答えが見つからないので、回避策を見つけるために私に迷惑をかけました。ここでは、エラー回復と再起動()を使用する醜いハックです。

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

この回答は部分的にしか有用かもしれませんが、私はまた、セクションを見てPLYのドキュメント(http://www.dabeaz.com/ply/ply.html)の「6.11組み込みアクション」をお勧めします。簡単に言えば、ルールが途中で発生する文法ルールを記述することは可能です。これは、次のようなものになります。

レクサーの動作に関しては、使用されている先読みのトークンは1つだけです。したがって、特定の文法規則では、たかだか1つの余分なトークンが既に読み込まれています。レクサー状態を反転させる場合、トークンがパーサーによって消費される前に、パーサーが次に入ってくるトークンを読むように要求する前に、それが起こることを確認する必要があります。

また可能であれば、私は解決策としてスタックを処理するyacc()エラーから離れようとします。エラー処理には黒マジックがあまりにも多くあります。あなたがそれを避けるほど良くなるでしょう。

私は少し時間がかかりましたが、これはPLYの次のバージョンで調査できるようです。それを私のto-doリストに載せます。

+0

dmbeddedアクションへのポインタありがとうございます。非常に有望です。しかし、あなたの例では、最後のトークンの代わりに先読みトークンをチェックすることになっていますか?最後のトークンは 'B'ですが、先読みは' LBRACE'でしょうか? – haridsv

関連する問題