Spec-Zone.ru › OCaml
☰Инструменты OCaml
  • Партия компиляции (ocamlc)
  • Система интерпретатора или REPL (ocaml)
  • Система выполнения (ocamlrun)
  • Компиляция кода нативном языке (ocamlopt)
  • Генераторы лексических и синтаксических анализаторов (ocamllex, ocamlyacc)
  • Генератор зависимостей (ocamldep)
  • Генератор документации (ocamldoc)
  • Отладчик (ocamldebug)
  • Профилирование (ocamlprof)
  • Интегрирование 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, могут обратиться к книге «Компиляторы: принципы, методы и инструменты» Ахо, Лама, Сети и Уллмана (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 в главе ‍29.)

При использовании совместно с синтаксическим анализатором, сгенерированным с помощью 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 Заголовок и заключение

Разделы заголовок и заключение — это произвольный текст 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). Типы опций вводятся, когда соответствие правила в целом не подразумевает соответствие связанной подшаблону. Это, в частности, относится к ( 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, можно указать обработчик загрузки, функцию, которая будет вызвана при загрузке. Ей передается продолжение разбора, над которым она имеет полный контроль. ОCaml-выражение, используемое в качестве действия загрузки, должно иметь тип, являющийся экземпляром

   (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. Это связано с тем, что заголовок копируется только в файл .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 между правилом и токеном с одинаковым приоритетом будет разрешен с помощью ассоциативности: если токен левоассоциативен, то анализатор выполнит reduce; если токен правоассоциативен, то анализатор переместит токен. Если токен неассоциативен, то анализатор объявит синтаксическую ошибку.
  • Если конфликт shift/reduce не может быть разрешен с помощью описанного выше метода, то ocamlyacc выведет предупреждение, и анализатор всегда переместит токен.

4.3 Правила

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

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

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

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

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

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

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

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

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

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

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

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

5 Опции

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

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

Во время выполнения сгенерированный ocamlyacc-парсер можно отлаживать, установив параметр p в переменной окружения OCAMLRUNPARAM (см. раздел ‍15.2). Это заставит автомат стека считывания, выполняющий парсер, выводить трассировку его действий (сдвинутые токены, сокращенные правила и т. д.). В трассировке упоминаются номера правил и состояния, которые можно интерпретировать, взглянув на файл грамматика.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) »
Авторские права © 2024 Institut National de Recherche en Informatique et en Automatique

© 1995-2024 INRIA.
https://ocaml.org/manual/5.2/lexyacc.html

Spec-Zone.ru

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