Camlp4 — это программная система для написания расширяемых парсеров для языков программирования. Она предоставляет набор библиотек OCaml , которые используются для определения грамматик, а также загружаемых расширений синтаксиса таких грамматик. Camlp4 означает Caml Preprocessor and Pretty-Printer , и одним из его важнейших приложений было определение расширений синтаксиса OCaml , специфичных для определенной области .
Camlp4 был частью официального дистрибутива OCaml, разработанного в INRIA . Его первоначальным автором является Daniel de Rauglaudre. Версия OCaml 3.10.0, выпущенная в мае 2007 года, представила значительно измененную и обратно несовместимую версию Camlp4. De Rauglaudre поддерживает отдельную обратно совместимую версию, которая была переименована в Camlp5. Все примеры ниже относятся к Camlp5 или предыдущей версии Camlp4 (версии 3.09 и более ранние).
Версия 4.08, выпущенная летом 2019 года, [1] была последней официальной версией этой библиотеки. В настоящее время она устарела; [2] вместо нее рекомендуется использовать библиотеки PPX (PreProcessor eXtensions) [3] [4] . [5]
Препроцессор Camlp4 работает, загружая набор скомпилированных модулей, которые определяют парсер , а также pretty-printer : парсер преобразует входную программу во внутреннее представление. Это внутреннее представление составляет абстрактное синтаксическое дерево (AST). Оно может быть выведено в двоичной форме, например, его можно передать напрямую одному из компиляторов OCaml , или его можно преобразовать обратно в программу с открытым текстом. Понятие конкретного синтаксиса относится к формату, в котором представлен абстрактный синтаксис .
Например, выражение OCaml (1 + 2) также может быть записано как ((+) 1 2) или (((+) 1) 2). Разница только на уровне конкретного синтаксиса, поскольку эти три версии являются эквивалентными представлениями одного и того же абстрактного синтаксического дерева. Как показывает определение пересмотренного синтаксиса для OCaml, один и тот же язык программирования может использовать разные конкретные синтаксисы. Все они будут сходиться к абстрактному синтаксическому дереву в уникальном формате, который может обрабатывать компилятор.
Абстрактное синтаксическое дерево находится в центре расширений синтаксиса, которые на самом деле являются программами OCaml. Хотя определение грамматик должно быть сделано в OCaml, определяемый или расширяемый синтаксический анализатор не обязательно связан с OCaml, в этом случае синтаксическое дерево, которым манипулируют, не является деревом OCaml. Предоставляется несколько библиотек, которые облегчают конкретную манипуляцию синтаксическими деревьями OCaml.
Предметно-ориентированные языки являются основным применением Camlp4. Поскольку OCaml является многопарадигменным языком с интерактивным верхним уровнем и компилятором собственного кода, его можно использовать в качестве бэкэнда для любого исходного языка. Единственное, что должен сделать разработчик, это написать грамматику Camlp4, которая преобразует предметно-ориентированный язык в обычную программу OCaml. Также можно использовать другие целевые языки, например C.
Если целевой язык — OCaml, можно определить простые синтаксические надстройки или синтаксический сахар , чтобы обеспечить выразительность, которую нелегко достичь с помощью стандартных функций языка OCaml. Расширение синтаксиса определяется скомпилированным модулем OCaml, который передается исполняемому файлу camlp4o вместе с программой для обработки.
Camlp4 включает в себя доменно-специфичный язык , поскольку он предоставляет расширения синтаксиса, которые облегчают разработку расширений синтаксиса. Эти расширения позволяют компактно определять грамматики ( EXTEND
выражения) и цитаты, такие как <:expr< 1 + 1 >>, т.е. деконструировать и конструировать абстрактные синтаксические деревья в конкретном синтаксисе.
Следующий пример определяет расширение синтаксиса OCaml. Он предоставляет новое ключевое слово , memo
, которое может использоваться в качестве замены function
и обеспечивает автоматическую мемоизацию функций с сопоставлением с образцом . Мемоизация заключается в сохранении результатов предыдущих вычислений в таблице, так что фактическое вычисление функции для каждого возможного аргумента происходит не более одного раза.
Это pa_memo.ml, файл, который определяет расширение синтаксиса:
пусть уникальный = пусть n = ссылка 0 в fun () -> incr n ; "__pa_memo" ^ string_of_int ! nРАСШИРИТЬ ГЛОБАЛЬНО : Pcaml.expr ; Pcaml.expr : LEVEL "expr1" [ [ " memo " ; OPT " |" ; pel = LIST1 match_case SEP " |" -> let tbl = unique () in let x = unique () in let result = unique () in <: expr < let $ lid : tbl $ = Hashtbl.create 100 in fun $ lid : x $ - > try Hashtbl.find $ lid : tbl $ $ lid : x $ with [ Not_found - > let $ lid : result $ = match $ lid : x $ with [ $ list : pel $ ] in do { Hashtbl.replace $ lid : tbl $ $ lid : x $ $ lid : result $ ; $ lid : result $ } ] >> ] ] ; match_case : [ [ p = Pcaml . patt ; w = OPT [ "когда" ; e = Pcaml . expr -> e ]; "->" ; e = Pcaml . expr -> ( p , w , e ) ] ]; КОНЕЦ
Пример программы, использующей это расширение синтаксиса:
пусть counter = ref 0 (* глобальный счетчик умножений *)(* факториал с мемоизацией *) let rec fac = memo 0 -> 1 | n когда n > 0 -> ( incr counter ; n * fac ( n - 1 )) | _ -> invalid_arg "fac"пусть выполняется n = пусть результат = fac n в пусть количество = ! счетчик в Printf . printf "%i! = %i количество умножений на данный момент = %i \n " n результат количество пусть _ = Список . iter run [ 5 ; 4 ; 6 ]
Вывод программы выглядит следующим образом, показывая, что функция fac (факториал) вычисляет только те произведения, которые ранее не вычислялись:
5! = 120 количество умножений на данный момент = 54! = 24 количество умножений на данный момент = 56! = 720 количество умножений на данный момент = 6