2012-03-24 26 views
7

Tôi đang làm việc trên một SQL đơn giản giống như trình phân tích cú pháp truy vấn và tôi cần có khả năng nắm bắt các truy vấn phụ có thể xảy ra ở một số nơi nhất định. Tôi tìm thấy các trạng thái lexer là giải pháp tốt nhất và có thể làm một POC sử dụng dấu ngoặc nhọn để đánh dấu sự bắt đầu và kết thúc. Tuy nhiên, các truy vấn phụ sẽ được phân cách bằng dấu ngoặc đơn, không phải dấu ngoặc và dấu ngoặc đơn cũng có thể xuất hiện ở những nơi khác, vì vậy tôi không thể là trạng thái với mọi dấu ngoặc mở. Thông tin này có sẵn với trình phân tích cú pháp, vì vậy tôi đã hy vọng sẽ bắt đầu và kết thúc tại các vị trí thích hợp trong các quy tắc của trình phân tích cú pháp. Tuy nhiên, điều này không hiệu quả vì lexer dường như mã hóa luồng tất cả cùng một lúc và do đó các mã thông báo được tạo trong trạng thái INITIAL. Có cách giải quyết nào cho vấn đề này không? Dưới đây là một phác thảo của những gì tôi đã cố gắng để làm:Kiểm soát trạng thái PLY của Python PLY từ trình phân tích cú pháp

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 

Các start_subquery() và end_subquery() được định nghĩa như thế này:

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 

Các thẻ lexer chỉ đơn giản là có để phát hiện các cận 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 

Tôi sẽ đánh giá cao bất kỳ trợ giúp nào.

Trả lời

2

Dựa trên phản hồi của tác giả PLY, tôi đã đưa ra giải pháp tốt hơn này. Tôi vẫn chưa tìm ra cách trả lại truy vấn phụ dưới dạng mã thông báo, nhưng phần còn lại trông đẹp hơn nhiều và không cần phải được xem là hack nữa.

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

Vì không ai có câu trả lời, nó đã báo lỗi cho tôi để tìm cách giải quyết, và đây là một hack xấu xí bằng cách sử dụng khôi phục lỗi và khởi động lại().

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

Câu trả lời này chỉ có thể được một phần hữu ích, nhưng tôi cũng sẽ đề nghị xem xét phần "6.11 Actions nhúng" các tài liệu PLY (http://www.dabeaz.com/ply/ply.html). Tóm lại, có thể viết các quy tắc ngữ pháp trong đó các hành động xảy ra giữa quy tắc. Nó sẽ giống như thế này:

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

Về hành vi của các lexer, chỉ có một dấu hiệu của lookahead đang được sử dụng. Vì vậy, trong bất kỳ quy tắc ngữ pháp cụ thể nào, nhiều nhất chỉ một mã thông báo bổ sung đã được đọc. Nếu bạn định lật các trạng thái lexer, bạn cần đảm bảo rằng nó xảy ra trước khi mã thông báo được trình phân tích cú pháp tiêu thụ, nhưng trước khi trình phân tích cú pháp yêu cầu đọc mã thông báo đến tiếp theo.

Ngoài ra, nếu có thể, tôi sẽ cố gắng tránh ra khỏi ngăn xếp xử lý lỗi yacc() theo như một giải pháp. Có quá nhiều ma thuật đen đang diễn ra trong quá trình xử lý lỗi - bạn càng tránh được nó càng tốt.

Tôi hơi bị ép thời gian vào lúc này, nhưng điều này có vẻ là thứ có thể được điều tra cho phiên bản tiếp theo của PLY. Sẽ đặt nó vào danh sách việc cần làm của tôi.

+0

Cảm ơn con trỏ đến hành động được nhúng, có vẻ rất hứa hẹn. Tuy nhiên, trong ví dụ của bạn, chúng ta phải kiểm tra mã thông báo tra cứu thay vì mã thông báo cuối cùng? Mã thông báo cuối cùng sẽ là 'B', nhưng lookahead sẽ là' LBRACE' đúng không? – haridsv

Các vấn đề liên quan