Как использовать конечный автомат для реализации сканера

Я делаю простой сканер. Предположим, у меня есть следующие токены, определенные для моего языка:

!, !=, !==, <, <<, {

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

!=?=? | { | <<?

Затем я использовал http://hackingoff.com для создания NFA и DFA. Каждая машина теперь может определить, является ли ввод на языке регулярных выражений или нет. Но моя программа представляет собой последовательность токенов, а не один токен:

!!=!<!==<<!{

У меня вопрос: как мне использовать машины для разбора строки на токены? Меня интересует подход, а не реализация.

введите здесь описание изображения


person Max Koretskyi    schedule 18.10.2017    source источник
comment
Что именно вы спрашиваете?   -  person melpomene    schedule 18.10.2017
comment
@melpomene, мой вопрос, что делать с этими машинами? как разобрать фактическую строку многих токенов?   -  person Max Koretskyi    schedule 18.10.2017
comment
Вы просите нас написать код для вас?   -  person melpomene    schedule 18.10.2017
comment
@melpomene, подход, я нигде не упоминаю код   -  person Max Koretskyi    schedule 18.10.2017
comment
Если вы не говорите о коде, то я не знаю, что вы подразумеваете под подходом.   -  person melpomene    schedule 18.10.2017


Ответы (1)


Наиболее распространенным правилом является «максимальный жевание», при котором всегда выбирается максимально длинный токен.

В общем, алгоритм сканирования одного токена с использованием DFA выглядит следующим образом: (Для токенизации ввода этот алгоритм повторяется до тех пор, пока не будет достигнут конец ввода, при этом каждое сканирование начинается с курсора ввода, оставленного предыдущим сканированием. )

Установите состояние DFA в начальное состояние. Затем для каждого входного символа последовательно:

  • если в DFA есть переход на символ, перейти в указанное состояние. Если это состояние является принимающим, запишите текущий курсор ввода и номер состояния.

  • в противном случае перемотать ввод на последнюю записанную позицию принятия и вернуть записанный номер состояния.

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

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

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

person rici    schedule 18.10.2017
comment
На самом деле я однажды видел статью об алгоритме, который может выполнять токенизацию с максимальным потреблением на основе регулярных выражений за O (n) (т.е. без возврата). Просто на практике не используется. - person sepp2k; 18.10.2017
comment
@ sepp2k: если у вас есть ссылка, я бы хотел ее увидеть. Я не верю, что лексирование O(N) вообще возможно в постоянном пространстве, но я был бы рад оказаться неправым. (Если вы ослабите ограничение по пространству, то это должно быть выполнимо, но это гораздо менее интересно.) В практических языках с добавлением шаблонов для таких вещей, как незавершенные строки и комментарии, обычно можно ограничить возврат до O( 1) символы, и в этом случае токенизация составляет O (N). Так что мотивация использовать более сложный алгоритм ограничена. - person rici; 18.10.2017
comment
@rici, большое спасибо. Я обнаружил следующее: DFA должен работать до тех пор, пока не достигнет точки, в которой текущее состояние S не имеет исходящего перехода к следующему символу. В этот момент реализация должна решить, какое регулярное выражение совпало. Если S является принимающим состоянием, то dfa нашел слово в языке и должен сообщить о слове и его синтаксической категории. - person Max Koretskyi; 18.10.2017
comment
В противном случае, если dfa прошел через одно или несколько состояний приема на пути к S, распознаватель должен выполнить резервное копирование до самого последнего такого состояния. Эта стратегия соответствует самому длинному допустимому префиксу во входной строке. Если он так и не достиг состояния принятия, то ни один префикс входной строки не является допустимым словом, и распознаватель должен сообщить об ошибке. - person Max Koretskyi; 18.10.2017
comment
кажется, это именно то, что вы предлагаете в своем ответе, верно? - person Max Koretskyi; 18.10.2017
comment
@angularindepth: да, точно. - person rici; 18.10.2017
comment
@sepp2k: Спасибо. Этот URL защищен платным доступом, но я нашел более раннюю версию в принимающем университете автора. Как я и подозревал, это не алгоритм постоянного пространства; это правда, что он избегает квадратичного поведения в худшем случае, но очень немногие практические токенизаторы языков программирования страдают от этой проблемы. - person rici; 18.10.2017
comment
@rici, круто, спасибо. Однако многие языки требуют обратного отслеживания — может ли это быть реализацией NFA? Или он все равно будет преобразован в DFA без возврата? - person Max Koretskyi; 18.10.2017
comment
@AngularInDepth.com: Вовсе нет. Возврат — это именно то, что я имею в виду в последнем шаге алгоритма: когда вы достигаете состояния без перехода, вы возвращаетесь к последней входной позиции, соответствующей принимающему состоянию. (Текст, который вы цитируете, явно использует слова, идущие к :)) Также: обычный алгоритм NFA не отступает. Он исследует все возможные трассы параллельно. - person rici; 18.10.2017
comment
@rici, понял, спасибо. Это не возврат, чтобы попробовать другой путь, который потенциально может привести к конечному состоянию, это возврат, чтобы определить, встречалось ли принятое состояние во время текущего пути, правильно? - person Max Koretskyi; 18.10.2017
comment
@AngularInDepth.com: он знает, что было обнаружено принятое состояние, поэтому он знает, что принятое состояние находится в конце токена. Он выполняет возврат, так что следующее сканирование начинается с нужного места. - person rici; 18.10.2017
comment
@rici, ага, так в какое состояние он возвращается? стартовое состояние? Я был бы признателен, если бы вы добавили к своему ответу базовый пример, показывающий такой возврат. - person Max Koretskyi; 18.10.2017
comment
@AngularInDepth.com: алгоритм сканирования начинается с установки состояния DFA в начальное состояние. Это не отступление от государства. Он отслеживает курсор ввода, так что следующее сканирование начинается с правильного места ввода. - person rici; 18.10.2017
comment
@rici, спасибо, кажется, теперь я понял. Каждый раз, когда сканер начинает синтаксический анализ следующего токена, он запускает машину с самого начала, верно? - person Max Koretskyi; 18.10.2017
comment
@AngularInDepth.com: Верно. (В некоторых языках программирования действие для принятого токена может изменить начальное состояние. Токенизация регулярных выражений с помощью Ecmascript — немного ужасный пример.) - person rici; 18.10.2017