Spec-Zone.ru › OCaml
☰Инструменты OCaml
  • Пакетная компиляция (ocamlc)
  • Система верхнего уровня или REPL (ocaml)
  • Система времени выполнения (ocamlrun)
  • Компиляция в машинный код (ocamlopt)
  • Генераторы лексических анализаторов и парсеров (ocamllex, ocamlyacc)
  • Генератор зависимостей (ocamldep)
  • Генератор документации (ocamldoc)
  • Отладчик (ocamldebug)
  • Отладка машинного кода (gdb, lldb)
  • Профилирование (ocamlprof, perf)
  • Взаимодействие C с OCaml
  • Оптимизация с помощью Flambda
  • Фаззинг с помощью afl-fuzz
  • Трассировка среды выполнения с помощью событий среды выполнения
  • Преобразование программы «Tail Modulo Constructor»
  • Обнаружение гонок данных во время выполнения с помощью ThreadSanitizer

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

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

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

1 Обзор ocamllex

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

        ocamllex lexer.mll

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

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

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

1.1 Параметры

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

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

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; она описана ниже в подразделе 17.2.7.

2.1 Заголовок и завершающий фрагмент

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

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

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

2.3 Точки входа

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

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

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

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

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

regexp ::= …
' 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.
regexp1 regexp2
(конкатенация) Соответствует конкатенации двух строк: первая совпадает с regexp1, вторая — с regexp2.
( regexp )
Соответствует тем же строкам, что и regexp.
ident
Ссылается на регулярное выражение, связанное с ident более ранним определением let ident = regexp.
regexp as ident
Связывает подстроку, совпавшую с regexp, с идентификатором ident.

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

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 является последним аргументом. Полезно, например, для разбора вложенных комментариев.

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

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

Сначала рассмотрим случай линейных шаблонов, то есть случай, когда все переменные, связываемые с помощью as, различны. В конструкции regexp as ident тип ident обычно равен string (или string option), за исключением случаев, когда regexp — это символьная константа, подчеркивание, строковая константа длиной в один символ, описание набора символов или альтернатива этих выражений. Тогда тип ident равен char (или char option). Типы 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 для таких неоднозначных регулярных выражений, выбирают один из возможных результирующих наборов связей. Выбранный набор связей намеренно не специфицируется.

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
}

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

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

3 Обзор ocamlyacc

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

        ocamlyacc options grammar.mly

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

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

4 Синтаксис описаний грамматики

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

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

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

4.1 Заголовок и завершающий фрагмент

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

4.2 Объявления

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

%token constr … constr
Объявляет указанные символы constr … constr токенами (терминальными символами). Эти символы добавляются как конструкторы-константы конкретного типа token.
%token < typexpr > constr … constr
Объявляет указанные символы constr … constr токенами с прикреплённым атрибутом указанного типа. Эти символы добавляются как конструкторы с аргументами указанного типа для конкретного типа token. Часть typexpr представляет собой произвольное выражение типа OCaml, за исключением того, что имена всех конструкторов типов должны быть указаны полностью (например, Modname.typename) для всех типов, кроме стандартных встроенных типов, даже если в разделе заголовка были заданы соответствующие директивы open (например, open Modname). Это связано с тем, что заголовок копируется только в выходной файл .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 внутри правил.

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

  • Токены и правила имеют приоритеты. По умолчанию приоритет правила определяется приоритетом его самого правого терминала. Это значение по умолчанию можно переопределить, используя в правиле директиву %prec.
  • Конфликт «свертка/свертка» разрешается в пользу первого правила (согласно порядку в исходном файле), а ocamlyacc выдаёт предупреждение.
  • Конфликт «сдвиг/свертка» разрешается сравнением приоритета сворачиваемого правила с приоритетом сдвигаемого токена. Если приоритет правила выше, правило сворачивается; если выше приоритет токена, выполняется сдвиг токена.
  • Конфликт «сдвиг/свертка» между правилом и токеном с одинаковым приоритетом разрешается с учётом ассоциативности: если токен левоассоциативный, анализатор выполняет свертку; если правоассоциативный — сдвиг. Если токен неассоциативный, анализатор выдаёт синтаксическую ошибку.
  • Если конфликт «сдвиг/свертка» нельзя разрешить описанным выше способом, ocamlyacc выдаёт предупреждение, а анализатор всегда выполняет сдвиг.

4.3 Правила

Синтаксис правил стандартный:

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

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

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

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

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

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

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

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

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

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

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

5 Параметры

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

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

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

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

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 ячейками. В обычных условиях эта ошибка возникать не должна.
ocamlyacc: безопасность при параллельном выполнении

Анализаторы, сгенерированные ocamlyacc, не являются потокобезопасными. Эти анализаторы используют внутреннее рабочее состояние, общее для всех анализаторов, сгенерированных ocamlyacc. Если вам нужны потокобезопасные анализаторы, лучше воспользоваться генератором анализаторов menhir.

« Компиляция в машинный код (ocamlopt)Генератор зависимостей (ocamldep) »
Copyright © 2026 Institut National de Recherche en Informatique et en Automatique

© 1995-2026 INRIA.
https://ocaml.org/manual/5.5/lexyacc.html

Spec-Zone.ru

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