Глава 17 Генераторы лексических и синтаксических анализаторов (ocamllex, ocamlyacc)
- 17.1 Обзор ocamllex
- 17.2 Синтаксис определений лексического анализатора
- 17.3 Обзор ocamlyacc
- 17.4 Синтаксис определений грамматики
- 17.5 Параметры
- 17.6 Полный пример
- 17.7 Распространённые ошибки
В этой главе описываются два генератора программ: ocamllex, который генерирует лексический анализатор из набора регулярных выражений с ассоциированными семантическими действиями, и ocamlyacc, который генерирует синтаксический анализатор из грамматики с ассоциированными семантическими действиями.
Эти генераторы программ очень похожи на хорошо известные команды lex и yacc, которые можно найти в большинстве сред программирования на C. Эта глава предполагает знание работы с lex и yacc: она описывает синтаксис входных данных для ocamllex и ocamlyacc и основные отличия от lex и yacc, но не объясняет основы написания описания лексического или синтаксического анализатора в lex и yacc. Читатели, незнакомые с lex и yacc, могут обратиться к книге «Компиляторы: принципы, методы и инструменты» Ахо, Лама, Сети и Ульмана (Pearson, 2006) или «Lex & Yacc» Левина, Мэйсона и Брауна (O’Reilly, 1992).
17.1 Обзор ocamllex
Команда ocamllex генерирует лексический анализатор из набора регулярных выражений с присоединёнными семантическими действиями, в стиле lex. Предполагая, что входной файл — lexer.mll, выполнение
ocamllex lexer.mll
производит код на языке OCaml для лексического анализатора в файле lexer.ml. Этот файл определяет одну функцию лексического анализа на точку входа в определение лексического анализатора. Эти функции имеют те же имена, что и точки входа. Функции лексического анализа принимают в качестве аргумента буфер лексического анализатора и возвращают семантическое атрибут соответствующей точки входа.
Буферы лексического анализатора — это абстрактный тип данных, реализованный в стандартном модуле библиотеки Lexing. Функции Lexing.from_channel, Lexing.from_string и Lexing.from_function создают буферы лексического анализатора, которые считывают данные из канала ввода, строки символов или любой функции чтения соответственно. (См. описание модуля Lexing в главе 28.)
При использовании совместно с синтаксическим анализатором, сгенерированным с помощью ocamlyacc, семантические действия вычисляют значение, принадлежащее типу token, определённому модулем сгенерированного синтаксического анализатора. (См. описание ocamlyacc ниже.)
17.1.1 Параметры
Следующие параметры командной строки распознаются ocamllex.
- -ml
- Выходной код, который не использует встроенный интерпретатор автомата OCaml. Вместо этого, автомат кодируется функциями OCaml. Этот параметр улучшает производительность при использовании собственного компилятора, но снижает её при использовании байткодового компилятора.
- -o output-file
- Укажите имя выходного файла, генерируемого ocamllex. По умолчанию используется имя входного файла с расширением, заменённым на .ml.
- -q
- Режим без вывода сообщений. ocamllex обычно выводит информационные сообщения в стандартный вывод. Они подавляются, если используется параметр -q.
- -v или -version
- Вывести строку версии и завершить работу.
- -vnum
- Вывести короткий номер версии и завершить работу.
- -help или --help
- Отобразить краткое описание использования и завершить работу.
17.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.
17.2.1 Заголовок и подвал
Разделы заголовок и подвал — произвольный текст OCaml, заключённый в фигурные скобки. Один или оба из них могут быть опущены. Если присутствуют, текст заголовка копируется в начале выходного файла, а текст подвала — в конце. Обычно раздел заголовка содержит директивы open, необходимые для действий, и, возможно, некоторые вспомогательные функции, используемые в действиях.
17.2.2 Имена регулярных выражений
Между заголовком и точками входа можно давать имена часто встречающимся регулярным выражениям. Это записывается как let идентификатор = регулярное выражение. В последующих регулярных выражениях идентификатор идентификатор может использоваться как сокращение для регулярное выражение.
17.2.3 Точки входа
Имена точек входа должны быть допустимыми идентификаторами значений OCaml (начинаться с маленькой буквы). Аналогично, аргументы аргумент1… аргументn должны быть допустимыми идентификаторами OCaml. Каждая точка входа превращается в функцию OCaml, которая принимает n+1 аргументов, дополнительный неявный последний аргумент — типа Lexing.lexbuf. Символы считываются из аргумента Lexing.lexbuf и сравниваются с предоставленными в правиле регулярными выражениями, пока префикс входных данных не совпадёт с одним из правил. Тогда соответствующее действие оценивается и возвращается как результат функции.
Если несколько регулярных выражений соответствуют префиксу входных данных, применяется правило «самое длинное соответствие»: выбирается регулярное выражение, которое соответствует самому длинному префиксу входных данных. В случае ничьей выбирается регулярное выражение, которое встречается раньше в правиле.
Однако, если правила лексического анализатора вводятся с ключевым словом shortest вместо parse, тогда применяется правило «самое короткое соответствие»: выбирается самый короткий префикс входных данных. В случае ничьей всё ещё выбирается регулярное выражение, которое встречается раньше в правиле. Эта функция не предназначена для использования в обычных лексических анализаторах, она может облегчить использование ocamllex как простого инструмента обработки текста.
17.2.4 Регулярные выражения
Регулярные выражения написаны в стиле lex с более похожим на OCaml синтаксисом.
|
- ' 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.
17.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 является последним аргументом. Полезно для анализа вложенных комментариев, например.
17.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 для таких неоднозначных регулярных выражений, выберет один из возможных наборов связей. Выбранный набор связей намеренно не определен.
17.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
}
17.2.8 Зарезервированные идентификаторы
Все идентификаторы, начинающиеся с __ocaml_lex, зарезервированы для использования ocamllex; не используйте такие идентификаторы в своих программах.
17.3 Обзор ocamlyacc
Команда ocamlyacc генерирует анализатор из спецификации контекстно-свободной грамматики с присоединенными семантическими действиями в стиле yacc. Предполагая, что входной файл — grammar.mly, выполнение
ocamlyacc options grammar.mly
генерирует код OCaml для анализатора в файле grammar.ml и его интерфейс в файле grammar.mli.
Сгенерированный модуль определяет одну функцию разбора на каждый входной пункт в грамматике. Эти функции имеют те же имена, что и входные пункты. Функции разбора принимают в качестве аргументов лексический анализатор (функция от буферов лексических анализаторов к маркерам) и буфер лексического анализатора и возвращают семантический атрибут соответствующего входного пункта. Функции лексического анализатора обычно генерируются из спецификации лексического анализатора программой ocamllex. Буферы лексического анализатора — это абстрактный тип данных, реализованный в стандартном модуле библиотеки Lexing. Маркеры — это значения из конкретного типа token, определенного в файле интерфейса grammar.mli, созданном программой ocamlyacc.
17.4 Синтаксис определений грамматики
Определения грамматики имеют следующий формат:
%{
header
%}
declarations
%%
rules
%%
trailer
Комментарии заключены между /* и */ (как в C) в разделах «объявления» и «правила», и между (* и *) (как в OCaml) в разделах «заголовок» и «приложение».
17.4.1 Заголовок и приложение
Разделы заголовка и приложения — это код OCaml, который копируется без изменений в файл grammar.ml. Оба раздела необязательны. Заголовок помещается в начале выходного файла; он обычно содержит директивы open и вспомогательные функции, необходимые для семантических действий правил. Приложение помещается в конец выходного файла.
17.4.2 Объявления
Объявления даны по одному на строку. Все они начинаются со знака %.
- %token constr … constr
- Объявить указанные символы constr … constr как токены (терминальные символы). Эти символы добавляются в качестве константных конструкторов для конкретного типа token.
- %token < typexpr > constr … constr
- Объявить указанные символы constr … constr как токены с присоединённым атрибутом заданного типа. Эти символы добавляются как конструкторы с аргументами указанного типа для конкретного типа token. Часть typexpr — это произвольное выражение типа OCaml, за исключением того, что все имена конструкторов типов должны быть полностью квалифицированными (например, Modname.typename) для всех типов, кроме стандартных встроенных типов, даже если были указаны соответствующие директивы
open. Это связано с тем, что заголовок копируется только в файл .ml, но не в файл .mli, в то время как часть typexpr объявления копируется в оба файла. - %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 выведет предупреждение, и анализатор всегда переместит.
17.4.3 Правила
Синтаксис правил обычный:
nonterminal :
symbol … symbol { semantic-action }
| …
| symbol … symbol { semantic-action }
;
Правила также могут содержать директиву %prec symbol в правой части, чтобы переопределить приоритет и ассоциативность правила приоритетом и ассоциативностью указанного символа.
Семантические действия — это произвольные выражения OCaml, которые вычисляются для получения семантического атрибута, присоединённого к определённому нетерминалу. Семантические действия могут обращаться к семантическим атрибутам символов в правой части правила с помощью обозначения $. $1 — атрибут первого (самого левого) символа, $2 — атрибут второго символа и т. д.
Правила могут содержать специальный символ error для указания точек ресинхронизации, как в yacc.
Действия, происходящие посреди правил, не поддерживаются.
Нетерминальные символы подобны обычным символам OCaml, за исключением того, что они не могут заканчиваться на ' (один апостроф).
17.4.4 Обработка ошибок
Восстановление от ошибок поддерживается следующим образом: когда анализатор достигает состояния ошибки (никакие правила грамматики не могут быть применены), он вызывает функцию с именем parse_error с аргументом строкой "syntax error". По умолчанию функция parse_error ничего не делает и возвращается, тем самым инициируя восстановление от ошибок (см. ниже). Пользователь может определить пользовательскую функцию parse_error в секции заголовка файла грамматики.
Анализатор также переходит в режим восстановления от ошибок, если одно из действий грамматики вызывает исключение Parsing.Parse_error.
В режиме восстановления от ошибок анализатор отбрасывает состояния из стека, пока не достигнет места, где ошибка может быть перенесена. Затем он отбрасывает токены из входных данных, пока не найдёт три последовательных токена, которые могут быть приняты, и начинает обработку с первого из них. Если ни одно состояние не может быть обнаружено, где токен ошибки может быть перенесён, то анализатор прерывается путём повышения исключения Parsing.Parse_error.
Обратитесь к документации по yacc для получения дополнительной информации и руководства по использованию восстановления от ошибок.
17.5 Опции
Команда ocamlyacc распознаёт следующие опции:
- -bпрефикс
- Назовите выходные файлы префикс.ml, префикс.mli, префикс.output вместо стандартной схемы именования.
- -q
- Этот параметр не оказывает никакого влияния.
- -v
- Сгенерировать описание таблиц разбора и отчет о конфликтах, возникающих из-за неоднозначностей в грамматике. Описание помещается в файл грамматика.output.
- -version
- Вывести строку версии и выйти.
- -vnum
- Вывести короткий номер версии и выйти.
- -
- Считать спецификацию грамматики со стандартного ввода. Имена выходных файлов по умолчанию — stdin.ml и stdin.mli.
- -- файл
- Обработать файл как спецификацию грамматики, даже если его имя начинается с дефиса (-). Этот параметр должен быть последним в командной строке.
Во время выполнения сгенерированный ocamlyacc-парсер может быть отлажен, установив параметр p в переменной окружения OCAMLRUNPARAM (см. раздел 15.2). Это приводит к тому, что стековая автомат, выполняющий парсер, печатает трассировку своих действий (перемещенные токены, приведенные правила и т. д.). Трассировка упоминает номера правил и номера состояний, которые можно интерпретировать, взглянув на файл грамматика.output, сгенерированный ocamlyacc -v.
17.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
17.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 — лучший вариант, если вам нужны потокобезопасные парсеры.
© 1995-2022 INRIA.
https://v2.ocaml.org/releases/5.0/htmlman/lexyacc.html