Действующие лица:
Jump-table, они же таблицы переходов, они же таблицы решений, он же ON GOSUB...
Сканер, он же лексический анализатор, он же парсер (хотя тут часто путаница из-за неправильного понимания...).
Автоматы, используемые для разбора строк.
Использовано в моём недо-браузере для разбора HTML (вот к нему
пояснительная записка) и ещё кое-где из пока неопубликованного.
Объяснять дополнительно не буду, просто дам примеры (тем более что я уже объяснял в пояснительной записке; плохо ли, хорошо ли, но ещё раз уже лень).
Пример, таблица решений (переходов):
Код:
REQUIRE таблица ~profit/lib/chartable.f
9 таблица раздватри \ три состояния
1 выполняет: ." I" ;
2 выполняет: ." II" ;
3 выполняет: ." III" ;
4 9 диапазон: ." IV-IX" ;
5 раздватри
\ вывод: IV-IX
Вот ещё пример:
Код:
REQUIRE таблица ~profit/lib/chartable.f
256 таблица символы \ таблица обработчиков символов
все: ." не знаю такого" ;
разделители: ." разделитель, но не пробел" ;
пробел: ." пробел" ;
перевод-строки: ." перевод-строки" ;
CHAR 0 CHAR 9 диапазон: ." цифра" ;
CHAR a CHAR z диапазон: ." буква" ;
CHAR A CHAR Z диапазон: тоже-самое ;
CHAR а CHAR я диапазон: тоже-самое ;
CHAR А CHAR Я диапазон: тоже-самое ;
CR KEY символы CR
Автомат разбора текста (каждое состояние=таблица переходов с 256+несколько специальных обработчиков):
Код:
REQUIRE состояние ~profit/lib/chartable.f
VARIABLE счётчик
состояние не-считать
состояние прибавлять
состояние отнимать
не-считать
\ все: ; \ необязательно, по-умолчанию так и есть
символ: | не-считать ;
символ: + прибавлять ;
символ: - отнимать ;
прибавлять
\ взять-из-таблицы не-считать ( \ копируем обработчики |+-, в старой chartable.f ошибка с этим, поэтому закомментировано
символ: | не-считать ;
символ: + прибавлять ;
символ: - отнимать ; \ )
CHAR 0 CHAR 9 диапазон: счётчик 1+! ;
отнимать
\ взять-из-таблицы не-считать ( \ копируем обработчики |+-
символ: | не-считать ;
символ: + прибавлять ;
символ: - отнимать ; \ )
CHAR 0 CHAR 9 диапазон: -1 счётчик +! ;
: пустить-автомат ( a u -- )
1 TO размер-символа SWAP поставить-курсор
не-считать
-символов-обработать ;
S" 12+345-67|90" пустить-автомат
счётчик @ .
\ вывод: 1
В этом примере у автомата разбора три состояния. Первое состояние, 'не-считать' ,только переключает автомат в другие состояния на символах "|+-", на остальные же символы не реагирует. Состояние "прибавлять" на цифрах увеличивает счётчик, на символах переключения (|+-) -- переключает состояние. Тоже самое с состоянием "отнимать", только оно счётчик на цифрах уменьшает.
Как ещё один пример могу предложить посмотреть (для запуска надо будет тащить всё остальное окружение, поэтому даю только отрывок) отрывок из xml-сканера:
Код:
...
в-тексте
на-входе: отсюда начать-копить ;
все: копить-дальше ;
символ: < сбросить-накопленное в-ожидании-имени-тэга ;
строка-кончилась: сбросить-накопленное ;
: от-конца-накопленного-текста
накопленный-текст 2@ + начать-копить
отсюда протянуть вернуть-букву сбросить-накопленное ;
в-ожидании-имени-тэга
все: вернуть-букву в-имени-тэга ;
символ: ! в-комментариях ;
символ: > от-конца-накопленного-текста в-тексте ;
символ: / в-имени-закрывающегося-тэга ;
строка-кончилась: от-конца-накопленного-текста ;
в-имени-тэга
на-входе: отсюда начать-копить ;
все: копить-дальше ;
разделители: имя-тэга запомнить имя-тэга 2@ открыть-тэг в-ожидании-имени-атрибута ;
символ: > имя-тэга запомнить имя-тэга 2@ открыть-тэг закончить-атрибуты в-тексте ;
строка-кончилась: от-конца-накопленного-текста ;
в-имени-закрывающегося-тэга
на-входе: отсюда начать-копить ;
все: копить-дальше ;
разделители: имя-тэга запомнить имя-тэга 2@ закрыть-тэг пропустить-атрибуты ;
символ: > имя-тэга запомнить имя-тэга 2@ закрыть-тэг в-тексте ;
строка-кончилась: от-конца-накопленного-текста ;
TRUE VALUE реагировать-на-закрывающую
в-комментариях
на-входе: отсюда начать-копить ;
все: копить-дальше ;
символ: > реагировать-на-закрывающую IF общий-накопитель 2@ внести-комментарий в-тексте ELSE копить-дальше THEN ;
символ: - дать-букву [CHAR] - = IF копить-дальше реагировать-на-закрывающую 0= TO реагировать-на-закрывающую
ELSE вернуть-букву THEN копить-дальше ;
...
... которому примерно соответствует эта иллюстрация из моего диплома:
Общие вопросы:
1. Нужно ли делать несколько автоматов? Пока не уверен, но наверно всё-таки надо... С другой стороны во всех программах в которых я использовал эту библиотеку автомат пусть и использовался с разными замкнутыми наборами состояний, но использование их всегда как-то само собой разносилось по времени.
2. Накопители (collectors.f, как полный и работающий пример: ~profit/prog/browser.f)... Насколько оправданно их применение? Не лучше ли всё-таки переносить в кучу? Когда писал браузер это было нужно (всего лишь) пару раз (для WinAPI функций, требовавших строк оканчивающихся на ноль, тогда как у меня были только накопители-обрывки из главного текста).
Вопросы по реализации:
3. Слово 'пустить-автомат'. Пока получается, что определять его надо отдельно от самой библиотеки. Вначале нужно указать сколько занимает каждый символ во входной последовательности: 2 (уникод) или 1 (всё остальное). Потом устанавливаем курсор-бегунок на начало последовательности. И после этого только можно устанавливать начальное состояние автомата. Потому что состояние когда активируется может иметь обработчик, который почти всегда будет использовать текущее значение курсора. И после этого запускаем или '-символов-обработать' с указанием кол-ва символов в последовательности, или 'обрабатывать-до-сигнала' который работает пока переменная 'продолжать-обработку' не равна нулю.
4. Нужно ли делать возможным запуск автомата из обработчика автомата? Опять же, практической необходимости в этом пока не возникало...
5. Я так понимаю, что это должно быть много быстрее чем обычные CASE. Или нет?