Как лексер может извлечь токен на неоднозначных языках?

Я хочу понять, как работает парсер. Я узнал о частях LL, LR (0), LR (1), о том, как создавать, NFA, DFA, таблицах синтаксического анализа и т. Д.

Теперь проблема в том, что я знаю, что лексер должен извлекать токены только по запросу парсера в некоторых ситуациях, когда невозможно извлечь все токены за один проход. Я не совсем понимаю такую ​​ситуацию, поэтому я открыт для любого объяснения по этому поводу.

Теперь вопрос в том, как лексер должен выполнять свою работу? должен ли он основывать свое распознавание на текущих «контекстах», текущих нетерминалах, которые предполагается анализировать? это что-то совсем другое? А как насчет синтаксического анализа GLR: это еще один случай, когда лексер может попробовать разные терминалы, или это всего лишь синтаксический бизнес? Я также хотел бы понять, с чем это связано, например, связано ли это с техникой синтаксического анализа (LL, LR и т. Д.) Или только с грамматикой?

Большое спасибо


person unomadh    schedule 21.07.2012    source источник


Ответы (2)


Ответ прост: извлечение лексемы должно производиться в контексте. То, что можно считать лексемами в языке, может значительно различаться в разных частях языка. Например, в COBOL в разделе объявления данных есть строки «PIC» и зависящие от местоположения номера уровней 01-99, которые не отображаются в разделе процедуры.

Таким образом, лексер должен каким-то образом знать, какая часть языка обрабатывается, чтобы знать, какие лексемы собирать. Это часто достигается за счет наличия состояний лексирования, каждое из которых обрабатывает некоторое подмножество всего языкового набора лексем (часто со значительным перекрытием в подмножестве; например, по моему опыту, идентификаторы, как правило, очень похожи). Эти состояния образуют конечный автомат высокого уровня с переходами между ними, когда встречаются лексемы с изменением фазы, например, ключевые слова, указывающие на вход в объявление данных или раздел процедуры программы COBOL. Современные языки, такие как Java и C #, сводят к минимуму потребность в этом, но большинство других языков, с которыми я встречался, действительно нуждаются в такой помощи в лексере.

Так называемые парсеры "без сканирования" (вы думаете "GLR") работают, полностью избавляясь от лексера; теперь лексеру не нужно создавать лексемы, и нет необходимости отслеживать лексические состояния: -} Такие парсеры работают, просто записывая грамматику на уровень отдельных символов; обычно вы найдете грамматические правила, которые являются точным эквивалентом того, что вы бы написали для описания лексемы. Тогда возникает вопрос, почему такой синтаксический анализатор не понимает, какую «лексему» производить? Вот где пригодится часть GLR. Парсеры GLR с удовольствием обрабатывают множество возможных интерпретаций ввода («локально неоднозначный синтаксический анализ») до тех пор, пока выбор в конечном итоге будет разрешен. Итак, что на самом деле происходит в случае «неоднозначных токенов», так это то, что правила грамматики для обоих «токенов» создают нетерминалы для их соответствующих «лексем», и синтаксический анализатор GLR продолжает синтаксический анализ до тех пор, пока один из путей синтаксического анализа не прекратит работу или не завершится синтаксический анализатор. с неоднозначным синтаксическим анализом.

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

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

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

person Ira Baxter    schedule 21.07.2012
comment
Когда вы говорите о синтаксическом анализаторе без сканирования, вы говорите, что они, по сути, работают, выполняя поиск по нескольким продуктам, чтобы увидеть, какой из них действителен. Разве безсканирующий синтаксический анализатор также не имел бы преимущества «знать», в каком правиле он находится, чтобы разрешить большую часть этой двусмысленности? Т.е. если синтаксический анализатор анализирует присвоение, то он знает, что ищет идентификатор, а не ключевое слово, верно? - person doliver; 28.08.2014
comment
Проблема с синтаксическим анализатором без сканирования заключается в том, что, даже если он знает, что обрабатывает присвоение, понятие «ключевое слово против идентификатора» - это просто больше правил. Итак, какие из них можно проверить, чтобы решить? Если in может решить, какие правила грамматики (ключевое слово или идентификатор) являются релевантными, то на самом деле вы можете сделать обычный сканер, который ищет только ключевые слова или только идентификаторы, и поэтому вам не нужен безсканирующий парсер есть. Безсканирующий синтаксический анализатор действительно помогает там, где он не может знать, пока не прочитает намного больше входных данных, является ли что-то идентификатором или нет. ... - person Ira Baxter; 28.08.2014
comment
.... Вот пример из Фортрана. Мы все знаем, что I1 - это идентификатор, верно (здесь вы попадете в ловушку). Теперь давайте рассмотрим код Фортрана 17 FORMAT (X2, I1) ‹непроверенный текст›. Ясно, что X2 и I1 - это идентификаторы, верно, а FORMAT - ключевое слово, верно? Хорошо, теперь давайте расширим код: 17 FORMAT (X2, I1) = expression, хорошо, да, они такие, как ожидалось. А как насчет 17 FORMAT (X2, I1) ‹newline›? К сожалению, это не идентификаторы, а FORMAT - ключевое слово. Сканирующему парсеру очень трудно с этим справиться (и большинство из них хитрят, чтобы справиться с этим). ... - person Ira Baxter; 28.08.2014
comment
... Анализатор без сканирования просто анализирует FORMAT как одновременно ключевое слово и идентификатор (в силу правил грамматики на уровне символов) и обрабатывает I1 и X2 одинаково. В конце концов он встречает различающий текст в конце оператора и отклоняет анализ, который не работает. Согласитесь, это довольно красиво. Так почему бы нам этого не сделать (у меня есть парсеры FORTRAN). Ответ заключается в том, что наши сканеры могут, когда они видят конкретный специфический идентификатор подобного объекта (особенно неоднозначные, такие как FORMAT и I1), вводят как ключевое слово, так и идентификатор ... - person Ira Baxter; 28.08.2014
comment
... и наш GLR-parser-working-on-tokens может разрешить проблемы так же, как и безсканирующий. Разница в том, что наши сканеры эффективны так же, как обычные лексические сканеры (потому что они фактически являются обычными), и эффективно распознают токены, не перетаскивая синтаксический анализатор в процесс. Безсканирующий тоже работает успешно, но теперь у вас есть синтаксический анализатор GLR (обрабатывающий несколько префиксов синтаксического анализа параллельных значений), выполняющий много работы на символ, все время - ›медленно. Доктор, доктор, мне больно, когда я делаю X. Хорошо, не делайте X. - person Ira Baxter; 28.08.2014
comment
Если вы можете решить, какие грамматические правила (ключевое слово или идентификатор) являются релевантными, то на самом деле вы можете создать обычный сканер, который ищет только ключевые слова или только идентификаторы, и поэтому вам не нужен синтаксический анализатор без сканирования. Вы имеете в виду сделать обычный сканер и искать только ключевые слова или идентификаторы на основе состояния лексирования? Я говорю о том, что если у вас есть что-то вроде <function definition>->function <identifier>(<params>), то после прочтения "function " оно начинает читать <identifier>. Идентификатор может быть ключевым словом, но он знает, что функция чтения, поэтому он знает, что нужно вызывать - person doliver; 28.08.2014
comment
.. consume_identifier(), поэтому ему не нужно беспокоиться о попытках выяснить, является ли следующий токен ключевым словом. Насколько я понимаю, если бы у вас был сканер, альтернативой было бы захватить состояние из парсера, чтобы получить ту же информацию. Это правильно? - person doliver; 28.08.2014
comment
Таким образом, лексер (в том числе и наш) может отслеживать явное состояние анализатора путем а) ​​имитации того, где находится анализатор абстрактно, или б) запроса синтаксического анализатора, нужно ли ему знать. Учитывая эту информацию, он может решить, в каком состоянии лексирования находиться. У нас есть дополнительная гибкость, заключающаяся в вставке нескольких токенов в качестве следующего токена, что позволяет нам во многих случаях избегать построения лексических состояний. В случае FORTRAN у нас есть некоторые лексические состояния, когда, когда FORMAT рассматривается как строка символов, генерируется как токен ключевого слова FORMAT , так и токен идентификатора (с именем идентификатора FORMAT). - person Ira Baxter; 28.08.2014
comment
... мы используем все эти уловки. (Отслеживать состояние парсера можно разными способами, одним из которых является то, что вы делаете в своем примере, если лексер видит ключевое слово функции и переходит в состояние, в котором он только желает искать идентификатор) - person Ira Baxter; 28.08.2014
comment
Думаю, я понимаю, о чем вы говорите, за исключением того, что безсканирующий тоже преуспевает, но теперь у вас есть парсер GLR (обрабатывающий несколько префиксов синтаксического анализа параллельных значений), выполняющий много работы для каждого символа. Не похоже, что большую часть времени придется копаться в параллельных префиксах, потому что синтаксический анализатор может отслеживать состояние так же, как лексер, чтобы знать, какие токены подходят. В частности, если состояние лексирования запускается текущим правилом, оно должно происходить автоматически, верно? - person doliver; 28.08.2014
comment
Дело в том, что парсер GLR проходит через процесс управления своими псевдопараллельными парсерами для каждого входного элемента. Вопрос в том, хотите ли вы платить эту цену за каждый входной символ или каждый входной токен? Если вы считаете, что средняя длина токенов составляет N символов, то версия обработки символов платит в N раз больше стоимости выполнения своей работы. Даже N = 1,3 имеет существенное значение. Еще хуже: синтаксические анализаторы без сканирования должны обрабатывать пробелы и комментарии, которые на практике составляют значительную часть входного потока, и они, как правило, длинные - ›большой N. - person Ira Baxter; 28.08.2014
comment
Из википедии. Время, необходимое для запуска алгоритма, пропорционально степени недетерминизма в грамматике: на детерминированных грамматиках алгоритм GLR выполняется за O (n) время - вот что меня смущает. Другими словами, параллельная обработка на самом деле происходит только тогда, когда ввод недетерминирован, а отдельные символы никогда не будут, верно? - person doliver; 28.08.2014
comment
Что ж, GLR выполняет свою псевдопараллельную обработку только с одним псевдо-потоком, когда грамматика (часть, которую он обрабатывает) детерминирована. Разница, когда у него более одного выбора, заключается в том, что он просто отслеживает N псевдопараллельных ветвей (которые могут идти вверх или вниз в зависимости от того, сколько возможностей предлагает грамматика при смещении токенов). Таким образом, это тот же код GLR, который обрабатывает N как процессы 1 (вы можете создать более быстрые парсеры GLR, которые будут различать эти случаи, и это обычно того стоит). ... - person Ira Baxter; 29.08.2014
comment
... В любом случае, вы используете тяжелый механизм GLR для каждого символа, даже при детерминированной обработке, вместо того, чтобы бросать механизм лексирования на каждый символ. Последнее может быть невероятно простым: в лексическом состоянии X используйте индекс следующего символа, чтобы перейти по таблице к действию (обычно сохранить символ в буфере), которое переходит в следующее состояние. Мы говорим о нескольких машинных инструкциях для лексической части, сотнях для цикла GLR. Все это O (N) детерминированно, но обозначение O означает игнорирование постоянных факторов. Вся эта дискуссия посвящена константе. - person Ira Baxter; 29.08.2014
comment
Итак, в основном, в парсере GLR (в детерминированном случае) возникает большой объем накладных расходов из-за обхода всех ненужных возможностей GLR для того, чтобы делать то, что сканер собирался делать в любом случае, верно? Мне было бы интересно посмотреть, как выглядят эти дополнительные накладные расходы (дополнительное выделение памяти, вызовы функций, условные выражения и вилки?), Но я думаю, что этот вопрос лучше всего разрешить, если я посмотрю на внутренности парсера GLR. Спасибо за вашу помощь / терпение по этому вопросу двухлетней давности. - person doliver; 29.08.2014
comment
Правильно. Одно место, где безсканирующий синтаксический анализ имеет смысл, - это то, где последовательности символов, в зависимости от выбора синтаксического анализа, не разбиваются на токены одинакового размера. Тогда у вас не может быть единственного лексера, который генерирует единый поток лексем. В этом случае, я думаю, вам нужно вернуться к посимвольному синтаксическому анализу. ИМХО идеальный ответ - это тот, в котором вы выполняете лексерное сканирование там, где оно работает, и посимвольное сканирование только там, где это необходимо. За 20 лет создания внешнего интерфейса с использованием парсера / традиционного лексера GLR нам каждый раз удавалось от этого избавляться. - person Ira Baxter; 29.08.2014

В идеале сами токены должны быть однозначными; вы всегда должны иметь возможность токенизировать входной поток без выполнения синтаксическим анализатором какой-либо дополнительной работы.

Это не всегда так просто, поэтому у вас есть несколько инструментов, которые могут вам помочь:

  1. Условия запуска

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

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

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

  2. Лексические привязки

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

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

  3. Логика, рассуждение

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

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

person Asherah    schedule 21.07.2012
comment
Я обнаружил, что условия запуска иногда реализуются через режимы лексирования: github.com/SAP/Chevrotain/blob/master/examples/lexer/ И некоторые токены при лексировании переводят лексический анализатор в другое состояние / режим, а затем определенные токены существуют в этом состоянии / режиме. - person CMCDragonkai; 13.11.2017