Какое отношение имеет разбор без сканера к «Проблеме с висящими остальными»?

13

Я не понимаю это предложение из статьи в Википедии, посвященной проблеме оборванного остального :

[Проблема Dangling Else] - это проблема, которая часто возникает при построении компилятора, особенно при анализе без сканирования.

Может кто-нибудь объяснить мне, как методы анализа без сканирования могут обострить эту проблему? Мне кажется, что проблема в грамматике, поскольку она неоднозначна, а не в выборе метода синтаксического анализа. Что мне не хватает?


источник
2
Единственное, о чем я могу думать, это то, что синтаксическому анализатору без сканера требуется более сложная грамматика, что усложняет обеспечение эвристики для устранения неоднозначности.
Джорджио
3
@ Роберт Харви: Дело в том, что это предположение должно быть отражено синтаксическим деревом. Если грамматика позволяет получить два разных синтаксических дерева для строки if a then if b then s1 else s2, то грамматика неоднозначна.
Джорджио
1
@RobertHarvey Распространенный способ определения языков - это использование контекстно-свободной грамматики плюс набор правил, которые устраняют неоднозначность грамматики, если это необходимо.
2
Не все парсеры без сканера созданы равными. Например, для PEG или GLR, поведение висящего другого всегда предсказуемо.
SK-logic
1
[Проблема Dangling Else] не имеет ничего общего с анализом без сканирования. [Проблема Dangling Else] связана с операциями сдвига-уменьшения парсеров LR (снизу вверх). AFAIK
ddur

Ответы:

6

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

Грамматики для синтаксических анализаторов (т. Е. Грамматики, описывающие язык как набор последовательностей символов, а не как набор последовательностей токенов с токенами, описанными отдельно как строки символов), как правило, имеют много неясностей. E. Visser paper Фильтры устранения неоднозначности для бессканерных обобщенных анализаторов LR (*) предлагает несколько механизмов для устранения неоднозначностей, один из которых полезен для решения висящей проблемы. Но в статье не утверждается, что точная неоднозначность, называемая «висячей проблемой», связана с синтаксическими анализаторами без сканера (и даже с тем, что механизм особенно полезен для анализаторов без сканера).

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


(*) Это, вероятно, статья, послужившая основой статьи в Википедии о парсерах без сканера, даже если они ссылаются на другой, в том числе Э. Виссер, Scannerless Scanner Generalized-LR .

AProgrammer
источник
13

Просто для того, чтобы заявить о проблеме, проблема «оборванного остального» - это двусмысленность в спецификации синтаксиса кода, где она может быть неясной, в случае использования следующих if и elses, что еще принадлежит к которому if.

Самый простой и классический пример:

if(conditionA)
if(conditionB)
   doFoo();
else
   doBar();

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

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

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

if(conditionA)if(conditionB)doFoo();else doBar;

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

if(conditionA)

О, я знаю, что это значит (в C #), это означает « pushусловиеA в стек eval, а затем вызов, brfalseчтобы перейти к оператору после следующей точки с запятой, если это не так». Прямо сейчас я не вижу точку с запятой, поэтому сейчас я установлю свое смещение перехода на следующий пробел после этой инструкции и увеличу это смещение, вставляя дополнительные инструкции, пока не укажу точку с запятой. Продолжая разбирать ...

if(conditionB)

Хорошо, это анализирует аналогичную пару операций IL и выполняется сразу после только что проанализированной инструкции. Я не вижу точку с запятой, поэтому я увеличу смещение перехода моего предыдущего оператора на длину двух моих команд (одну для толчка и одну для перерыва) и продолжу поиск.

doFoo();

Хорошо это легко Это " callDoFoo". И это точка с запятой, которую я вижу? Ну, это здорово, это конец строки. Я увеличу смещение прыжков обоих блоков на длину этих двух команд и забуду, что мне было все равно. ОК, двигаясь дальше ...

else

... э-э-э Это не так просто, как казалось. Хорошо, я забыл, что я только что делал, но это elseозначает, что где-то есть условный оператор прерывания, который я уже видел, так что позвольте мне оглянуться назад ... да, вот оно brfalse, сразу после того, как я нажимаю "conditionB" на стек, что бы это ни было. Хорошо, теперь мне нужно безусловное breakследующее утверждение. Заявление, которое придет после этого, теперь определенно является целью моего условного перерыва, поэтому я позабочусь о том, чтобы все было правильно, и увеличу безусловный разрыв, который я вставил.

doBar();

Это легко. " callDoBar". И есть точка с запятой, и я никогда не видел скобок. Итак, безусловное breakдолжно перейти к следующему утверждению, что бы это ни было, и я могу забыть, что меня это когда-либо волновало.


Итак, что у нас есть ... (примечание: сейчас 22:00, и мне не хочется преобразовывать битовые смещения в шестнадцатеричное или заполнять полную оболочку IL функции этими командами, так что это просто псевдо-IL используя номера строк, где обычно бывают смещения байтов):

ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>

Что ж, это на самом деле выполняется правильно, ЕСЛИ правило (как в большинстве языков стиля C) состоит в том, что оно elseподходит ближе всего if. С отступом, чтобы следовать за вложением выполнения, он будет выполняться так, где, если условие A ложно, весь остаток фрагмента пропускается:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();

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

Кроме того, что если в спецификации языка сказано, что висячий элемент elseпринадлежит первому if, а если условие A ложно, то выполняется doBar, а если условие A истинно, но не условие B, то ничего не происходит, как это происходит?

if(conditionA)
    if(conditionB)
       doFoo();
else
   doBar();

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

Теперь синтаксический анализатор может быть достаточно умен, чтобы запомнить ifs и elses, которые он имеет в течение более длительного времени, но если спецификация языка говорит, что одинарное elseпосле двух ifs соответствует первому if, это вызывает проблему с двумя ifs с совпадающими elses:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();
else
    doBaz();

Парсер увидит первый else, соответствует первому if, затем увидит второй и впадет в панику в режиме «что, черт возьми, я делал снова». На этом этапе синтаксический анализатор получил довольно много кода в изменчивом состоянии, которое он, скорее всего, уже вытолкнул бы в выходной файловый поток.

Есть решения для всех этих проблем и что-если. Но либо код, который должен быть настолько интеллектуальным, увеличивает сложность алгоритма синтаксического анализатора, либо спецификация языка, позволяющая парсеру быть таким тупым, увеличивает многословность исходного кода языка, например, требуя завершающие операторы типа end ifили скобки, указывающие на вложенность блокирует, если в ifоператоре есть else(оба из них обычно видны в других языковых стилях).

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

Keiths
источник
1
Интересно, но я далеко не уверен, что именно это было задумано в статье в Википедии. Он ссылается (через запись без сканера) на сообщение Eelco Visser, содержание которого с первого взгляда не соответствует вашему объяснению.
AProgrammer
3
Спасибо за ответ, но на самом деле это не касается ОП. Я не согласен с предположениями в посте о том, какова цель синтаксического анализатора без сканера и как он реализован. Есть много способов реализовать парсеры без сканера, и этот пост, похоже, имеет дело только с ограниченным подмножеством.