javascript — исправление ошибок Bison для автоматической вставки точек с запятой

Я пытаюсь написать синтаксический анализатор 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 одна и предыдущая действительная инструкция теряется, что делает вставку точек с запятой бесполезной.

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

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

2

Решение

Я не верю, что такой подход осуществим.

Как примечание, вам нужно будет обнаружить ошибку, прежде чем произойдет какое-либо сокращение. Таким образом, для вставки точки с запятой в конце оператора необходимо добавить вывод ошибки в 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кишки (Я уверен, что это возможно, но я не знаю, как сделать это на макушке, и мне это не рекомендуется).

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

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

Мой подход к 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(); }

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

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

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]