Многострочный анализатор PLY

Я использую PLY для разбора арифметических выражений, занимающих несколько строк (или разделенных знаком «;»). Я не уверен, следует ли игнорировать токены новой строки, поскольку они мне действительно не нужны. Если они игнорируются (возвращают None в t_NEWLINE), нужно ли их учитывать в продукциях?

def t_NEWLINE(self, t):
    r"""\n+|;+"""
    t.lexer.lineno += t.value.count("\n") + t.value.count(";")
    return t # or not

def p_expression(self, t):
    """ expression : ..."""

def p_expressions(self, t):
    """ expressions : expression
                    | expressions NEWLINE expression
    """
    # Do I need NEWLINE at all? How does the production figure out where expression ends without NEWLINE?

person orange    schedule 29.11.2015    source источник


Ответы (1)


Кажется, это больше вопрос дизайна языка, чем реализации, хотя, конечно, реализация может быть неочевидной, даже когда дизайн полностью определен.

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

  1. Стиль питона. Выражения заканчиваются символом ; или символом новой строки, но символы новой строки внутри выражений в квадратных скобках ((...), [...], {...}) игнорируются.

  2. Стиль ECMAscript. Выражения должны заканчиваться ;, но ; будет автоматически вставляться в конец строки, если следующий токен не может расширить выражение.

  3. Максимальный жевать. Выражение — это максимально длинный поток токенов, который может быть проанализирован как выражение, но при необходимости можно использовать ; для разделения выражений.

    На самом деле я не знаю общего языка, который реализует эту опцию. Но это (или некоторая вариация) позволило бы написать, например,

    a = 3    b = 7    c = a + b
    

    вроде бы должно быть однозначно.

Стратегии реализации.

1. Скобки скрывают новые строки.

Это довольно просто реализовать, пока язык не усложнится. Для простых выражений вам просто нужно сохранить глобальный счетчик глубины круглых скобок, который увеличивается с (любым видом) открывающей круглой скобкой и уменьшается с закрытием; тогда правило \n возвращает ;, если глубина круглых скобок равна 0, и в противном случае проглатывает новую строку.

Лично меня такой стиль продолжения строки раздражает, хотя я довольно быстро привыкаю к ​​нему каждый раз, когда возвращаюсь к программированию на Python. Тем не менее, всегда немного придирается, что вы не можете написать:

x = some * long * sub-expression       +
    another * long * sub-expression    +
    magical-constant

который отлично работал бы в ECMAscript.

2. Автоматические точки с запятой

Как оказалось, вы можете справиться с этим в ECMAscript в лексере, отыскав первый токен в строке и предыдущий токен в словаре допустимых пар токенов. Если пары токенов нет в словаре, то вместо первого токена в строке возвращается токен с точкой с запятой; этот токен запоминается, чтобы следующий вызов лексера вернул его. Построение словаря допустимых пар токенов нетривиально, а проверка его правильности еще сложнее, но это нужно делать только каждый раз, когда вы меняете грамматику :)

ECMAscript на самом деле имеет ряд исключений из правил, поэтому некоторые выражения, которые в противном случае были бы допустимыми, становятся недопустимыми, если они разделены новой строкой. Эти исключения могут быть реализованы (как это происходит) путем удаления некоторых токен-пар из словаря допустимых пар. Например, оператор постинкремента ++ не может быть отделен от своего операнда символом новой строки, и поэтому в следующем фрагменте вставляется точка с запятой:

a
++b

что в противном случае было бы синтаксической ошибкой. (Первые два токена будут проанализированы как a++, а затем b не могут быть проанализированы.)

Еще один интересный случай:

a + b
(c + d).format("x")

Это может быть проанализировано как вызов функции:

a + b(c + d).format("x")

поэтому было бы желательно сделать автоматическую вставку точки с запятой между b и (.

3. Максимальный жевать

На самом деле это просто вариант автоматической вставки точки с запятой, когда точка с запятой может быть вставлена, даже если между двумя токенами нет новой строки. Из-за отмеченных выше исключений вы, вероятно, захотите иметь два разных набора допустимых пар токенов или пометить определенные пары токенов как действительные, только если они не разделены новой строкой.

person rici    schedule 30.11.2015
comment
Я заинтересован в реализации грамматики в стиле Python, но каждая строка представляет собой завершенное выражение (без переноса строк, как в вашем примере). Я думаю, что мне нужно будет явно учитывать токены \n, иначе a = 3 b = 7 c = a + b будет недопустимым выражением (если бы я проигнорировал токены \n в конце каждой строки). - person orange; 01.12.2015
comment
@orange: чтобы выполнять многострочные выражения в стиле Python, вам нужно условно возвращать токены новой строки, как я описал в первом абзаце. - person rici; 01.12.2015