Использование PyParsing для синтаксического анализа языка с существенными символами новой строки (например, Python) - PullRequest
1 голос
/ 12 апреля 2020

Я реализую язык, в котором переводы строк значимы, иногда, как и в Python, с точно такими же правилами.

Для целей моего вопроса мы можем взять фрагмент Python, который имеет делать с заданиями, круглыми скобками и обработкой символов новой строки и точек с запятой.

Например, можно написать:

a = 1 + 2 + 3    # ok
b = c

, но не

a = 1 + 2 + 3     b = c   # incorrect

, потому что один нужна новая строка для разделения двух операторов.

Однако мы можем использовать

a = 1 + 2 + 3;     b = c   # ok

, используя точку с запятой.

Также не допускается иметь

a = 1 + 2 +   # incorrect
3
b = c

, потому что в выражении не может быть разрывов строк.

Однако, возможно иметь

a = 1 + 2 + (     # ok
3)
b = c

или

a = 1 + 2 + \     # ok
3
b = c

Я пытался для реализации вышеуказанных правил, но я застрял.

Во-первых, я использую

ParserElement.setDefaultWhitespaceChars(' \t')

, так что теперь \n значимо.

Мне удается навязать хорошо символы новой строки в качестве разделителя, использующие

lines = ZeroOrMore(line + OneOrMore(LineEnd()))

. Вариант этого позволяет иметь ; в качестве разделителя, как мы LL. (Я не могу разобраться с дополнительной скобкой \.)

Я использую infixNotation для определения +, -, /, *.

. Часть, с которой я застрял, заключается в том, что внутри парантезов должны игнорироваться символы новой строки, как в этом случае:

a = 1 + 2 + ( 
3 +
1)

Я думаю, что здесь что-то, что может сыграть роль, использует setWhitespaceChars в выражении скобок (LPAR + term + RPAR) что infixNotation генерирует, однако, что не работает, потому что пробельные символы не наследуются нижними выражениями.

У кого-нибудь есть подсказка?

Мой вопрос также может быть выражен как "как мне разобрать (фрагмент) Python с pyParsing?". Я думал, что смогу найти пример проекта, но не смог. Гугля, я видел, как люди ссылались на примеры в репозитории pyParsing, однако parsePythonValue.py - это анализ значений (что я уже могу сделать), а не работа со значительными символами новой строки, а pythongGrammarParsing.py - анализ грамматики BNF для * 1066. * не разбирается Python.

1 Ответ

0 голосов
/ 13 апреля 2020

ПРИМЕЧАНИЕ: ЭТО НЕ РАБОЧЕЕ РЕШЕНИЕ (по крайней мере, пока). Он полагается на невыпущенные изменения, связанные с печатью, которые даже не проходят все тесты блока еще. Я размещаю его просто как способ описать возможный подход к решению.

Ooof! Это было намного сложнее, чем я думал. Для реализации я использовал механизм игнорирования pyparsing с действиями разбора, присоединенными к выражениям lpar и rpar, чтобы игнорировать <NL> внутри паренсов, но не снаружи. Это также потребовало добавления возможности очистить список ignoreExprs, вызвав expr.ignore(None). Вот как может выглядеть ваш код:

import pyparsing as pp

# works with and without packrat
pp.ParserElement.enablePackrat()

pp.ParserElement.setDefaultWhitespaceChars(' \t')

operand = pp.Word(pp.nums)
var = pp.Word(pp.alphas)

arith_expr = pp.Forward()
arith_expr.ignore(pp.pythonStyleComment)
lpar = pp.Suppress("(")
rpar = pp.Suppress(")")

# code to implement selective ignore of NL's inside ()'s
NL = pp.Suppress("\n")
base_ignore = arith_expr.ignoreExprs[:]
ignore_stack = base_ignore[:]
def lpar_pa():
    ignore_stack.append(NL)
    arith_expr.ignore(NL)
    #~ print('post-push', arith_expr.ignoreExprs)
def rpar_pa():
    ignore_stack.pop(-1)
    arith_expr.ignore(None)
    for e in ignore_stack:
        arith_expr.ignore(e)
    #~ print('post-pop', arith_expr.ignoreExprs)
def reset_stack(*args):
    arith_expr.ignore(None)
    for e in base_ignore:
        arith_expr.ignore(e)
    #~ print('post-reset', arith_expr.ignoreExprs)
lpar.addParseAction(lpar_pa)
rpar.addParseAction(rpar_pa)
arith_expr.setFailAction(reset_stack)
arith_expr.addParseAction(reset_stack)

# now define the infix notation as usual
arith_expr <<= pp.infixNotation(operand | var,
    [
    ("-", 1, pp.opAssoc.RIGHT),
    (pp.oneOf("* /"), 2, pp.opAssoc.LEFT),
    (pp.oneOf("- +"), 2, pp.opAssoc.LEFT),
    ],
    lpar=lpar, rpar=rpar
    )

assignment = var + '=' + arith_expr

# Try it out!
assignment.runTests([
"""a = 1 + 3""",
"""a = (1 + 3)""",
"""a = 1 + 2 + ( 
3 +
1)""",
"""a = 1 + 2 + (( 
3 +
1))""",
"""a = 1 + 2 +   
3""",
], fullDump=False)

Отпечатки:

a = 1 + 3
['a', '=', ['1', '+', '3']]
a = (1 + 3)
['a', '=', ['1', '+', '3']]
a = 1 + 2 + ( 
3 +
1)
['a', '=', ['1', '+', '2', '+', ['3', '+', '1']]]
a = 1 + 2 + (( 
3 +
1))
['a', '=', ['1', '+', '2', '+', ['3', '+', '1']]]
a = 1 + 2 +   
3
a = 1 + 2 +   
          ^
FAIL: Expected end of text, found '+'  (at char 10), (line:1, col:11)>Exit code: 0

Таким образом, он не вне возможного, но требует героических усилий c.

...