Восстановление ошибок зубров для автоматической вставки точек с запятой - PullRequest
0 голосов
/ 26 сентября 2018

Я пытаюсь написать синтаксический анализатор Bison C ++ для анализа файлов JavaScript, но не могу понять, как сделать точку с запятой необязательной.

Что касается спецификации ECMAScript 2018 (https://www.ecma)-international.org/publications/files/ECMA-ST/Ecma-262.pdf, глава 11.9) точка с запятой на самом деле необязательна, вместо этого она вставляется автоматически во время синтаксического анализа.В спецификации указано, что:

Когда, когда исходный текст анализируется слева направо, встречается токен (называемый токеном-нарушителем), который не допускается никаким производствомграмматики, затем точка с запятой автоматически вставляется перед токеном-нарушителем, если выполняется одно или несколько из следующих условий:

  • Маркер-нарушитель отделен от предыдущего токена хотя бы одним LineTerminator [...]

В соответствии с этим я пытаюсь решить эту проблему наивным способом:

  • Обнаружить ошибку, используя специальный токен error;
  • Сообщите лексеру, что во время действия произошла синтаксическая ошибка;если он встретил символ новой строки перед текущим токеном, лексер вернет новый токен с запятой при следующем вызове yylex;при последующем вызове он вернет токен, который ранее был ошибочным, когда произошла синтаксическая ошибка.

Очень упрощенная структура моего синтаксического анализатора выглядит следующим образом:

program:
   stmt_list END
;

stmt_list:
    %empty
 |  stmt_list stmt
 |  stmt_list error  { /* error detected; tell the lexer about the syntax error */ }
;

stmt:
    value SEMICOLON
|   [other types of statements...]
;

value:
    NUMBER
|   STRING
;

Но в этом случае, если файл содержит допустимый оператор JavaScript без завершающей точки с запятой, но символ новой строки, при обнаружении ошибочного токена анализатор сокращает оставшуюся часть оператора до специального error токена.Когда я рассказываю лексеру об синтаксической ошибке, синтаксический анализатор уже уменьшил токен error до stmt_list, а предыдущая действительная инструкция потеряна, что делает вставку точек с запятой бесполезной.

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

Как я могу сделать это возможным?Это правильный подход или я что-то упустил?

1 Ответ

0 голосов
/ 26 сентября 2018

Я не верю, что этот подход работоспособен.

Как примечание, вам придется обнаружить ошибку, прежде чем произойдет какое-либо сокращение.Таким образом, для вставки точки с запятой в конце оператора необходимо добавить вывод ошибки к stmt, а не stmt_list.Таким образом, вы получите что-то вроде этого:

stmt_list
     :  %empty
     |  stmt_list stmt

stmt: value ';'   { handle_value_stmt(); }
    | value error { handle_value_stmt(); }
    | [other types of statements...]

Это не вставляет точку с запятой;он просто делает вид, что точка с запятой была вставлена.(Если точка с запятой не может быть вставлена, будет вызвана другая ошибка.)

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

Существует также вопрос о том, как можно общаться с лексером на этом этапе, поскольку действия Mid-Rule не очень хорошо работают салгоритм исправления ошибок.Теоретически, вы можете использовать тот факт, что yyerror будет вызываться для сообщения об ошибке, но это означает, что yyerror должен иметь возможность сделать вывод, что это не "настоящая" ошибка, а это значит, что она должна бытьтыкать в yyparse в кишки.(Я уверен, что это возможно, но я не знаю, как это сделать на макушке головы, и мне это не рекомендуется).

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

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

Мой подход к ASI заключался в том, чтобыпросто проанализируйте грамматику, выяснив, какие пары последовательных токенов возможны.(Это легко сделать; вам просто нужно создать наборы FIRST и LAST, а затем прочитать все произведения, просматривая последовательные символы.) Затем, если ввод состоит из токена A, за которым следует один или несколько символов новой строки, за которым следует токен B, и онневозможно, чтобы за A следовал B в грамматике, тогда это кандидат на вставку точки с запятой.Вставка точки с запятой может произойти сбой, но это приведет к синтаксической ошибке, поэтому вы не можете получить ложное срабатывание.(Возможно, вам придется исправить сообщение об ошибке синтаксиса, но в этот момент вы хотя бы знаете, что вставили точку с запятой.)

Доказать, что этот алгоритм работает, сложнее, поскольку теоретически это может бытьЗа A может следовать B в некотором контексте, но это невозможно в текущем контексте, в то время как A ; B будет возможно в текущем контексте.В этом случае вы можете пропустить возможную вставку точки с запятой.Я не рассматривал подробно последние версии JS, но давно, когда я написал лексер JS, мне удалось доказать себе, что таких случаев нет.


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

Без погружения вУ бизона хватит сил, на самом деле невозможно «отменить» токен, включая токен error (который является настоящим токеном, более или менее).К тому моменту, когда токен error был смещен, анализ фактически фиксируется для создания ошибки.Поэтому, если вы хотите аннулировать ошибку, вы должны принять этот факт и обойти его.

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

    stmt: value ';'       { handle_value_stmt(); }
        | value error ';' { handle_value_stmt(); }

Однако, возможно, вам не удалось вставить автоматическую точку с запятой, вв каком случае вам действительно необходимо сообщить об ошибке синтаксиса (и, возможно, попытаться выполнить повторную синхронизацию).Приведенные выше правила просто молча сбрасывают токены до следующей точки с запятой, что, безусловно, неправильно.Таким образом, первое приближение будет для вашего устройства вставки ASI всегда вставить что-то, что может быть использовано в качестве защиты при выдаче ошибок:

    stmt: value ';'       { handle_value_stmt(); }
        | value error ';' { handle_value_stmt(); }
        | value error NO_ASI { handle_real_error(); }

Этого достаточно для "сброса при ошибке"обработки, но если вы хотите выполнить восстановление после ошибок, вам нужно сделать еще несколько хакерских атак.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...