Spec-Zone.ru › OCaml 4.14

Глава 15 Генераторы лексических и синтаксических анализаторов (ocamllex, ocamlyacc)

  • 15.1 Обзор ocamllex
  • 15.2 Синтаксис определений лексического анализатора
  • 15.3 Обзор ocamlyacc
  • 15.4 Синтаксис определений грамматики
  • 15.5 Параметры
  • 15.6 Полный пример
  • 15.7 Распространённые ошибки

В этой главе описываются два генератора программ: ocamllex, который генерирует лексический анализатор из набора регулярных выражений со связанными семантическими действиями, и ocamlyacc, который генерирует синтаксический анализатор из грамматики со связанными семантическими действиями.

Эти генераторы программ очень похожи на хорошо известные команды lex и yacc, которые можно найти в большинстве сред программирования на языке C. В этой главе предполагается знание lex и yacc: в ней описывается синтаксис входных данных для ocamllex и ocamlyacc и основные отличия от lex и yacc, но не объясняются основы написания описания лексического или синтаксического анализатора на lex и yacc. Читателям, незнакомым с lex и yacc, рекомендуется обратиться к книге «Компиляторы: принципы, методы и инструменты» Ахо, Лама, Сети и Уллмана (Pearson, 2006) или «Lex & Yacc» Левина, Мэйсона и Брауна (O’Reilly, 1992).

15.1 Обзор ocamllex

Команда ocamllex генерирует лексический анализатор из набора регулярных выражений со связанными семантическими действиями, в стиле lex. Предполагая, что входной файл — lexer.mll, выполнение

        ocamllex lexer.mll

производит код OCaml для лексического анализатора в файле lexer.ml. Этот файл определяет одну функцию лексического анализа на каждый входной пункт в определении лексического анализатора. Эти функции имеют те же имена, что и входные точки. Функции лексического анализа принимают в качестве аргумента буфер лексического анализатора и возвращают семантическое атрибут соответствующей входной точки.

Буферы лексического анализатора — абстрактный тип данных, реализованный в стандартной библиотеке модуля Lexing. Функции Lexing.from_channel, Lexing.from_string и Lexing.from_function создают буферы лексического анализатора, которые считывают данные из входного канала, строковой переменной или любой функции чтения соответственно. (См. описание модуля Lexing в главе 26.)

При использовании совместно с синтаксическим анализатором, сгенерированным ocamlyacc, семантические действия вычисляют значение, принадлежащее типу token, определённому модулем генерируемого синтаксического анализатора. (См. описание ocamlyacc ниже.)

15.1.1 Параметры

Следующие параметры командной строки распознаются ocamllex.

-ml
Код вывода, который не использует встроенный интерпретатор автомата OCaml. Вместо этого автомат кодируется функциями OCaml. Этот параметр улучшает производительность при использовании собственного компилятора, но ухудшает её при использовании компилятора байткода.
-o output-file
Укажите имя выходного файла, созданного ocamllex. По умолчанию — имя входного файла с расширением, заменённым на .ml.
-q
Режим без вывода сообщений. ocamllex обычно выводит информационные сообщения в стандартный вывод. Они подавляются, если используется параметр -q.
-v или -version
Вывести строку версии и завершить работу.
-vnum
Вывести короткий номер версии и завершить работу.
-help или --help
Отобразить краткую сводку использования и завершить работу.

15.2 Синтаксис определений лексического анализатора

Формат определений лексического анализатора следующий:

{ header }
let ident = regexp …
[refill { refill-handler }]
rule entrypoint [arg1… argn] =
  parse regexp { action }
      | …
      | regexp { action }
and entrypoint [arg1… argn] =
  parse …
and …
{ trailer }

Комментарии ограничены (* и *), как и в OCaml. Ключевое слово parse можно заменить на ключевое слово shortest со связанными семантическими последствиями, которые описаны ниже.

Обработчики переполнения — новая (необязательная) функция, введённая в версии 4.02, описанная ниже в подразделе 15.2.7.

15.2.1 Заголовок и заключение

Разделы заголовок и заключение представляют собой произвольный текст OCaml, заключённый в фигурные скобки. Можно опустить один или оба из них. Если они присутствуют, текст заголовка копируется в начало выходного файла, а текст заключения — в конец. Обычно раздел заголовка содержит директивы open, необходимые для действий, и, возможно, некоторые вспомогательные функции, используемые в действиях.

15.2.2 Имена регулярных выражений

Между заголовком и входными точками можно давать имена часто встречающимся регулярным выражениям. Это записывается как let ident = regexp. В последующих регулярных выражениях идентификатор ident может использоваться в качестве сокращения для regexp.

15.2.3 Точки входа

Имена входных точек должны быть допустимыми идентификаторами значений OCaml (начинающимися с строчной буквы). Аналогично, аргументы arg1… argn должны быть допустимыми идентификаторами для OCaml. Каждая входная точка превращается в функцию OCaml, которая принимает n+1 аргументов, дополнительным неявным последним аргументом является тип Lexing.lexbuf. Символы считываются из аргумента Lexing.lexbuf и сопоставляются с предоставленными регулярными выражениями в правилах, пока префикс входных данных не будет совпадать с одним из правил. Соответствующее действие затем оценивается и возвращается как результат функции.

Если несколько регулярных выражений соответствуют префиксу входных данных, применяется правило «самого длинного совпадения»: выбирается регулярное выражение, которое соответствует самому длинному префиксу входных данных. В случае ничьей выбирается регулярное выражение, которое встречается раньше в правиле.

Однако, если правила лексического анализатора вводятся с ключевым словом shortest вместо ключевого слова parse, то применяется правило «самого короткого совпадения»: выбирается самый короткий префикс входных данных. В случае ничьей регулярное выражение, которое встречается раньше в правиле, по-прежнему выбирается. Эта функция не предназначена для использования в обычных лексических анализаторах, она может облегчить использование ocamllex как простого инструмента обработки текста.

15.2.4 Регулярные выражения

Регулярные выражения находятся в стиле lex, с более похожим на OCaml синтаксисом.

regexp ::= …
END_OF_DOCUMENT_MARKER
' regular-char ∣ escape-sequence '
Константа символа, имеющая такой же синтаксис, как и константы символов в OCaml. Сопоставляет указанный символ.
_
(подчеркивание) Сопоставляет любой символ.
eof
Сопоставляет конец входных данных лексического анализатора.
Примечание: В некоторых системах при интерактивном вводе файл может быть завершен, а за ним могут следовать дополнительные символы. Однако ocamllex не сможет правильно обработать регулярные выражения, содержащие eof, за которым следует что-то еще.
" { string-character } "
Строковая константа, имеющая такой же синтаксис, как и строковые константы в OCaml. Сопоставляет соответствующую последовательность символов.
[ character-set ]
Сопоставляет любой одиночный символ, принадлежащий заданному набору символов. Допустимые наборы символов: константы одиночных символов ' c '; диапазоны символов ' c1 ' - ' c2 ' (все символы между c1 и c2 включительно); и объединение двух или более наборов символов, обозначаемое конкатенацией.
[ ^ character-set ]
Сопоставляет любой одиночный символ, не принадлежащий заданному набору символов.
regexp1 # regexp2
(разность множеств символов) Регулярные выражения regexp1 и regexp2 должны быть наборами символов, определенными с помощью [… ] (или одиночное выражение символа или символ подчеркивания _). Сопоставляет разность двух указанных наборов символов.
regexp *
(повторение) Сопоставляет конкатенацию нуля или более строк, которые соответствуют regexp.
regexp +
(строгое повторение) Сопоставляет конкатенацию одной или более строк, которые соответствуют regexp.
regexp ?
(опция) Сопоставляет пустую строку или строку, соответствующую regexp.
regexp1 | regexp2
(альтернатива) Сопоставляет любую строку, которая соответствует regexp1 или regexp2
regexp1 regexp2
(конкатенация) Сопоставляет конкатенацию двух строк, первая из которых соответствует regexp1, а вторая — regexp2.
( regexp )
Сопоставляет те же строки, что и regexp.
ident
Ссылается на регулярное выражение, привязанное к ident более ранним определением let ident = regexp.
regexp as ident
Привязывает подстроку, соответствующую regexp, к идентификатору ident.

Что касается приоритетов операторов, то # имеет наивысший приоритет, за которым следуют *, + и ?, затем конкатенация, затем | (альтернация), затем as.

15.2.5 Действия

Действия — произвольные выражения OCaml. Они вычисляются в контексте, где идентификаторы, определённые с помощью конструкции as, привязаны к частям сопоставленной строки. Кроме того, lexbuf привязан к текущему буферу лексического анализатора. Некоторые типичные применения lexbuf, в сочетании с операциями над буферами лексического анализатора, предоставленными модулем стандартной библиотеки Lexing, перечислены ниже.

Lexing.lexeme lexbuf
Возвращает сопоставленную строку.
Lexing.lexeme_char lexbuf n
Возвращает n-й символ в сопоставленной строке. Первый символ соответствует n = 0.
Lexing.lexeme_start lexbuf
Возвращает абсолютную позицию в тексте входных данных начала сопоставленной строки (т.е. смещение первого символа сопоставленной строки). Первый прочитанный символ из входного текста имеет смещение 0.
Lexing.lexeme_end lexbuf
Возвращает абсолютную позицию в тексте входных данных конца сопоставленной строки (т.е. смещение первого символа после сопоставленной строки). Первый символ, прочитанный из входного текста, имеет смещение 0.
entrypoint [exp1… expn] lexbuf
(Где entrypoint — имя другой точки входа в том же определении лексического анализатора.) Рекурсивно вызывает лексический анализатор в указанной точке входа. Обратите внимание, что lexbuf является последним аргументом. Полезно для лексического анализа вложенных комментариев, например.

15.2.6 Переменные в регулярных выражениях

Конструкция as похожа на «группы», предоставляемые многочисленными пакетами регулярных выражений. Тип этих переменных может быть string, char, string option или char option.

Сначала рассмотрим случай линейных шаблонов, то есть случай, когда все связанные переменные as являются различными. В regexp as ident тип ident обычно является string (или string option), за исключением случаев, когда regexp представляет собой символ, нижнее подчеркивание, строковую константу длиной один, спецификацию множества символов или альтернативу этих вариантов. В этом случае, тип ident — char (или char option). Типы опций вводятся, когда соответствие правила в целом не подразумевает соответствие связанной подмаске. Это особенно относится к ( regexp as ident ) ? и regexp1 | ( regexp2 as ident ).

Нет ограничений на линейность по связанным переменным as. Когда переменная связана более одного раза, предыдущие правила необходимо расширить следующим образом:

  • Переменная является переменной типа char, когда все её вхождения связывают вхождения char в предыдущем смысле.
  • Переменная является переменной типа option, когда выражение в целом может сопоставляться без привязки этой переменной.

Например, в ('a' as x) | ( 'a' (_ as x) ) переменная x имеет тип char, тогда как в ("ab" as x) | ( 'a' (_ as x) ? ) переменная x имеет тип string option.

В некоторых случаях успешное соответствие может не привести к уникальному набору привязок. Например, сопоставление aba с регулярным выражением (('a'|"ab") as x) (("ba"|'a') as y) может привести к привязке либо x к "ab" и y к "a", либо x к "a" и y к "ba". Автоматы, созданные ocamllex для таких неоднозначных регулярных выражений, выберут один из возможных результирующих наборов привязок. Выбранный набор привязок намеренно не оговаривается.

15.2.7 Обработчики заполнения

По умолчанию, когда ocamllex достигает конца своего буфера лексического анализа, он молча вызовет функцию refill_buff структуры lexbuf и продолжит лексический анализ. Иногда полезно иметь возможность контролировать действие заполнения; например, если вы используете библиотеку для асинхронного вычисления, вы можете обернуть действие заполнения в функцию задержки, чтобы избежать блокирования синхронных операций.

Начиная с OCaml 4.02, можно указать обработчик заполнения, функцию, которая будет вызываться при выполнении заполнения. Ей передаётся продолжение лексического анализа, над которым она имеет полный контроль. Выражение OCaml, используемое в качестве действия заполнения, должно иметь тип, являющийся экземпляром

   (Lexing.lexbuf -> 'a) -> Lexing.lexbuf -> 'a

где первый аргумент — это продолжение, которое фиксирует обработку, которую ocamllex обычно выполняет (заполняет буфер, затем снова вызывает функцию лексического анализа), а тип результата, который реализует [’a], должен быть согласован с типом результата всех правил лексического анализа.

В качестве примера рассмотрим следующий лексический анализатор, который параметризован произвольной монадой:

{
type token = EOL | INT of int | PLUS

module Make (M : sig
               type 'a t
               val return: 'a -> 'a t
               val bind: 'a t -> ('a -> 'b t) -> 'b t
               val fail : string -> 'a t

               (* Set up lexbuf *)
               val on_refill : Lexing.lexbuf -> unit t
             end)
= struct

let refill_handler k lexbuf =
    M.bind (M.on_refill lexbuf) (fun () -> k lexbuf)

}

refill {refill_handler}

rule token = parse
| [' ' '\t']
    { token lexbuf }
| '\n'
    { M.return EOL }
| ['0'-'9']+ as i
    { M.return (INT (int_of_string i)) }
| '+'
    { M.return PLUS }
| _
    { M.fail "unexpected character" }
{
end
}

15.2.8 Зарезервированные идентификаторы

Все идентификаторы, начинающиеся с __ocaml_lex, зарезервированы для использования ocamllex; не используйте такие идентификаторы в своих программах.

15.3 Обзор ocamlyacc

Команда ocamlyacc генерирует анализатор из спецификации контекстно-свободной грамматики с присоединёнными семантическими действиями в стиле yacc. Предполагая, что входной файл — grammar.mly, выполнение

        ocamlyacc options grammar.mly

генерирует код OCaml для анализатора в файле grammar.ml и его интерфейс в файле grammar.mli.

Сгенерированный модуль определяет одну функцию разбора на каждый входной пункт в грамматике. Эти функции имеют те же имена, что и входные точки. Функции разбора принимают в качестве аргументов лексический анализатор (функция от буферов лексем до лексем) и буфер лексем и возвращают семантическое атрибут соответствующего входного пункта. Функции лексических анализаторов обычно генерируются из спецификации лексического анализатора программой ocamllex. Буферы лексем — это абстрактный тип данных, реализованный в стандартном библиотечном модуле Lexing. Лексемы — это значения из конкретного типа token, определённого в файле интерфейса grammar.mli, сгенерированном ocamlyacc.

15.4 Синтаксис определений грамматик

Определения грамматик имеют следующий формат:

%{
  header
%}
  declarations
%%
  rules
%%
  trailer

Комментарии заключены между /* и */ (как в C) в разделах «объявления» и «правила», и между (* и *) (как в OCaml) в разделах «заголовок» и «приложение».

15.4.1 Заголовок и приложение

Разделы заголовка и приложения представляют собой код OCaml, который копируется как есть в файл grammar.ml. Оба раздела являются необязательными. Заголовок помещается в начало выходного файла; обычно он содержит директивы open и вспомогательные функции, необходимые для семантических действий правил. Приложение помещается в конец выходного файла.

15.4.2 Объявления

Объявления задаются по одному на строку. Все они начинаются со знака %.

%token constr … constr
Объявить указанные символы constr … constr как токены (терминальные символы). Эти символы добавляются в качестве константных конструкторов для конкретного типа token.
%token < typexpr > constr … constr
Объявить указанные символы constr … constr как токены с прикрепленным атрибутом заданного типа. Эти символы добавляются как конструкторы с аргументами заданного типа для конкретного типа token. Часть typexpr — это произвольное выражение типа OCaml, за исключением того, что все имена конструкторов типов должны быть полностью квалифицированными (например, Modname.typename) для всех типов, кроме стандартных встроенных типов, даже если были указаны соответствующие директивы open. Это связано с тем, что заголовок копируется только в файл .ml, но не в файл .mli, в то время как часть typexpr в объявлении %token копируется в оба файла.
%start symbol … symbol
Объявить указанные символы как точки входа для грамматики. Для каждой точки входа определяется функция разбора с тем же именем в выходном модуле. Для нетерминальных символов, не объявленных как точки входа, такой функции разбора нет. Точкам входа необходимо задать тип с помощью директивы %type.
%type < typexpr > symbol … symbol
Указать тип семантических атрибутов для заданных символов. Это обязательно только для точек входа. Другим нетерминальным символам не нужно задавать типы вручную: эти типы будут выведены при запуске выходных файлов через компилятор OCaml (если опция -s не активна). Часть typexpr — это произвольное выражение типа OCaml, за исключением того, что все имена конструкторов типов должны быть полностью квалифицированными, как объяснено выше для %token.
%left symbol … symbol
%right symbol … symbol
%nonassoc symbol … symbol

Указать приоритеты и ассоциативность для заданных символов. Все символы в одной строке имеют одинаковый приоритет. Они имеют более высокий приоритет, чем символы, объявленные ранее в строке %left, %right или %nonassoc. Они имеют более низкий приоритет, чем символы, объявленные после в строке %left, %right или %nonassoc. Символы объявляются как ассоциативные слева (%left), справа (%right) или неассоциативные (%nonassoc). Символы обычно являются токенами. Они также могут быть фиктивными нетерминальными символами, используемыми с директивой %prec.

Объявления приоритетов используются следующим образом для разрешения конфликтов reduce/reduce и shift/reduce:

  • У токенов и правил есть приоритеты. По умолчанию приоритет правила — приоритет его правого терминала. Вы можете переопределить это значение, используя директиву %prec в правиле.
  • Конфликт reduce/reduce разрешается в пользу первого правила (в порядке, заданном исходным файлом), и ocamlyacc выводит предупреждение.
  • Конфликт shift/reduce разрешается путем сравнения приоритета правила, которое должно быть уменьшено, с приоритетом токена, который должен быть сдвинут. Если приоритет правила выше, то правило будет уменьшено; если приоритет токена выше, то токен будет сдвинут.
  • Конфликт shift/reduce между правилом и токеном с одинаковым приоритетом будет разрешен с использованием ассоциативности: если токен ассоциативен слева, то анализатор уменьшит правило; если токен ассоциативен справа, то анализатор сдвинет токен. Если токен неассоциативен, тогда анализатор объявит синтаксическую ошибку.
  • Когда конфликт shift/reduce не может быть разрешен с помощью указанного метода, тогда ocamlyacc выведет предупреждение, и анализатор всегда сдвинет токен.

15.4.3 Правила

Синтаксис правил как обычно:

nonterminal :
    symbol … symbol { semantic-action }
  | …
  | symbol … symbol { semantic-action }
;

Правила также могут содержать директиву %prec symbol в правой части, чтобы переопределить приоритет и ассоциативность правила приоритетом и ассоциативностью заданного символа.

Семантические действия — это произвольные выражения OCaml, которые вычисляются для получения семантического атрибута, прикрепленного к определённому нетерминалу. Семантические действия могут обращаться к семантическим атрибутам символов в правой части правила с помощью обозначения $. $1 — атрибут для первого (самого левого) символа, $2 — атрибут для второго символа и т. д.

Правила могут содержать специальный символ error для обозначения точек ресинхронизации, как в yacc.

Действия, происходящие посредине правил, не поддерживаются.

Нетерминальные символы похожи на обычные символы OCaml, за исключением того, что они не могут оканчиваться на ' (одинарная кавычка).

15.4.4 Обработка ошибок

Восстановление от ошибок поддерживается следующим образом: когда анализатор достигает состояния ошибки (правила грамматики не могут быть применены), он вызывает функцию с именем parse_error со строкой "syntax error" в качестве аргумента. По умолчанию функция parse_error ничего не делает и возвращается, тем самым инициируя восстановление от ошибки (см. ниже). Пользователь может определить пользовательскую функцию parse_error в разделе заголовка файла грамматики.

Анализатор также переходит в режим восстановления от ошибок, если одно из действий грамматики вызывает исключение Parsing.Parse_error.

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

Обратитесь к документации yacc для получения более подробной информации и руководства по использованию восстановления от ошибок.

15.5 Параметры

Команда ocamlyacc распознает следующие параметры:

-bprefix
Назовите выходные файлы prefix.ml, prefix.mli, prefix.output вместо стандартной схемы именования.
-q
Этот параметр не оказывает никакого влияния.
-v
Сгенерировать описание таблиц разбора и отчет о конфликтах, возникающих из-за неоднозначностей в грамматике. Описание будет помещено в файл grammar.output.
-version
Вывести строку версии и завершить работу.
-vnum
Вывести короткий номер версии и завершить работу.
-
Считать спецификацию грамматики со стандартного ввода. Стандартные имена выходных файлов — stdin.ml и stdin.mli.
-- file
Обработать file как спецификацию грамматики, даже если его имя начинается с символа минус (-) . Этот параметр должен быть последним в командной строке.

Во время выполнения сгенерированный ocamlyacc-парсер может быть отлажен путём установки параметра p в переменной окружения OCAMLRUNPARAM (см. раздел 13.2). Это приводит к тому, что автомат стековой памяти, выполняющий парсер, выводит трассировку своих действий (сдвинутые токены, сокращенные правила и т. д.). В трассировке упоминаются номера правил и номеров состояний, которые можно интерпретировать, посмотрев на файл grammar.output, сгенерированный командой ocamlyacc -v.

15.6 Пример использования

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

        /* File parser.mly */
        %token <int> INT
        %token PLUS MINUS TIMES DIV
        %token LPAREN RPAREN
        %token EOL
        %left PLUS MINUS        /* lowest precedence */
        %left TIMES DIV         /* medium precedence */
        %nonassoc UMINUS        /* highest precedence */
        %start main             /* the entry point */
        %type <int> main
        %%
        main:
            expr EOL                { $1 }
        ;
        expr:
            INT                     { $1 }
          | LPAREN expr RPAREN      { $2 }
          | expr PLUS expr          { $1 + $3 }
          | expr MINUS expr         { $1 - $3 }
          | expr TIMES expr         { $1 * $3 }
          | expr DIV expr           { $1 / $3 }
          | MINUS expr %prec UMINUS { - $2 }
        ;

Вот определение соответствующего лексического анализатора:

        (* File lexer.mll *)
        {
        open Parser        (* The type token is defined in parser.mli *)
        exception Eof
        }
        rule token = parse
            [' ' '\t']     { token lexbuf }     (* skip blanks *)
          | ['\n' ]        { EOL }
          | ['0'-'9']+ as lxm { INT(int_of_string lxm) }
          | '+'            { PLUS }
          | '-'            { MINUS }
          | '*'            { TIMES }
          | '/'            { DIV }
          | '('            { LPAREN }
          | ')'            { RPAREN }
          | eof            { raise Eof }

Вот основная программа, которая объединяет парсер с лексическим анализатором:

        (* File calc.ml *)
        let _ =
          try
            let lexbuf = Lexing.from_channel stdin in
            while true do
              let result = Parser.main Lexer.token lexbuf in
                print_int result; print_newline(); flush stdout
            done
          with Lexer.Eof ->
            exit 0

Чтобы всё скомпилировать, выполните:

        ocamllex lexer.mll       # generates lexer.ml
        ocamlyacc parser.mly     # generates parser.ml and parser.mli
        ocamlc -c parser.mli
        ocamlc -c lexer.ml
        ocamlc -c parser.ml
        ocamlc -c calc.ml
        ocamlc -o calc lexer.cmo parser.cmo calc.cmo

15.7 Распространенные ошибки

ocamllex: переполнение таблицы переходов, автомат слишком большой

Детерминированные автоматы, генерируемые ocamllex, ограничены максимум 32767 переходами. Вышеуказанное сообщение указывает на то, что ваше определение лексического анализатора слишком сложно и превышает этот предел. Часто это происходит из-за определений лексического анализатора, имеющих отдельные правила для каждого алфавитного ключевого слова языка, как в следующем примере.

rule token = parse
  "keyword1"   { KWD1 }
| "keyword2"   { KWD2 }
| ...
| "keyword100" { KWD100 }
| ['A'-'Z' 'a'-'z'] ['A'-'Z' 'a'-'z' '0'-'9' '_'] * as id
               { IDENT id}

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

{ let keyword_table = Hashtbl.create 53
  let _ =
    List.iter (fun (kwd, tok) -> Hashtbl.add keyword_table kwd tok)
              [ "keyword1", KWD1;
                "keyword2", KWD2; ...
                "keyword100", KWD100 ]
}
rule token = parse
  ['A'-'Z' 'a'-'z'] ['A'-'Z' 'a'-'z' '0'-'9' '_'] * as id
               { try
                   Hashtbl.find keyword_table id
                 with Not_found ->
                   IDENT id }
ocamllex: переполнение памяти позиций, слишком много связей
Детерминированные автоматы, генерируемые ocamllex, хранят таблицу позиций внутри буфера сканирования лексического анализатора. Размер этой таблицы ограничен максимум 255 ячейками. Эта ошибка обычно не должна возникать в обычных ситуациях.

© 1995-2022 INRIA.
https://v2.ocaml.org/releases/4.14/htmlman/lexyacc.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API