Управление состояниями лексера Python PLY из парсера - PullRequest
7 голосов
/ 24 марта 2012

Я работаю над простым SQL-выбором, подобным синтаксическому анализатору запросов, и мне нужно иметь возможность перехватывать подзапросы, которые могут встречаться в определенных местах буквально. Я обнаружил, что лексеры - лучшее решение, и я смог создать POC с помощью фигурных скобок, чтобы отметить начало и конец. Тем не менее, подзапросы будут разделяться круглыми, а не фигурными скобками, и скобки могут встречаться и в других местах, поэтому я не могу быть государством с каждым открытым пареном. Эта информация легко доступна с анализатором, поэтому я надеялся вызвать начало и конец в соответствующих местах в правилах синтаксического анализа. Это, однако, не сработало, потому что лексер, кажется, токенизировал поток сразу, и поэтому токены генерируются в начальном состоянии. Есть ли решение этой проблемы? Вот схема того, что я пытался сделать:

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

Буду признателен за любую помощь.

Ответы [ 3 ]

5 голосов
/ 28 марта 2012

Этот ответ может быть только частично полезным, но я бы также посоветовал взглянуть на раздел «6.11 Встроенные действия» документации PLY (http://www.dabeaz.com/ply/ply.html).) В двух словах, можно написать грамматические правила, в которых действия происходят в середине правила.Это выглядело бы примерно так:

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

Что касается поведения лексера, то используется только один токен упреждающего просмотра. Таким образом, в любом конкретном грамматическом правиле максимум один дополнительный токен имеетуже прочитано. Если вы собираетесь переворачивать состояния лексера, вам нужно убедиться, что это произойдет до того, как синтаксический анализатор использует токен, но до того, как синтаксический анализатор попросит прочитать следующий входящий токен.Если возможно, я бы старался держаться подальше от стека обработки ошибок yacc () до решения. В обработке ошибок слишком много черной магии - чем больше вы можете избежать этого, тем лучше.

В данный момент я немного обеспокоен временем, но, похоже, это можно исследовать в следующей версии.F PLY.Поместит это в мой список дел.

2 голосов
/ 30 марта 2012

Основываясь на ответе автора PLY, я придумал это лучшее решение.Мне еще предстоит выяснить, как вернуть подзапрос в качестве токена, но все остальное выглядит намного лучше, и его больше не нужно считать хаком.

1 голос
/ 28 марта 2012

Так как никто не имеет ответа, мне не удалось найти обходной путь, и вот уродливый хак, использующий восстановление ошибок и рестарт ().

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