Глава
1
Глава
2
Глава
3
Глава
4
Глава
5
Глава
6
Глава
7  navigation 
Глава
9
Глава
10
Глава
11
Глава
12
Глава
13
Глава
14 

Глава 8 . Пакетная компиляция (ocamlc)

В этой главе описывается пакетный компилятор языка Objective Caml ocamlc, который компилирует файлы с исходными текстами на языке Caml в объектные файлы с байткодом и компонует их, создавая самостоятельные исполняемые файлы с байткодом. Затем такие файлы запускаются интерпретатором байткода ocamlrun.

8 . 1 Обзор компилятора

По интерфейсу командной строки ocamlc похож на большинство компиляторов C. Он принимает несколько типов аргументов:

На стадии компоновки генерируется файл, содержащий скомпилированный байткод, пригодный к исполнению интерпретатором байкода Objective Caml - программой ocamlrun. Для скомпилированного файла caml.out команда

ocamlrun caml.out arg1 arg2 ... argn

выполнит скомпилированный код, содержащийся в файле caml.out, передав ему как аргументы символьные строки от arg1 до argn. (Дополнительно см. главу 10.)

В большинстве систем файл, созданный на стадии компоновки можно запускать непосредственно:

./caml.out arg1 arg2 ... argn

Бит исполнения обычно уже установлен, поэтому интерпретатор байткода запустится автоматически.

8 . 2 Опции

ocamlc разпознает следующие опции командной строки:

-a

Создает библиотеку (файл .cma) из объектных файлов .cmo, заданных в командной строке. Имя библиотеки может быть задано с помощью опции -o. Имя по умолчанию - library.cma

Если в командной строке присутствуют опции -custom, -cclib или -ccopt, то они сохраняются в библиотеке и затем автоматически добавляются при компоновке с этой библиотекой к командной строке в исходном порядке, если только не было задано опции -noautolink.

-c

Только компиляция. Фаза компоновки опускается. Файлы с исходным текстом преобразуются в компилированные файлы, но исполняемый файл не создается. Эта опция полезна для раздельной компиляции модулей.

-cc ccomp

Использовать ccomp в качестве компоновщика и компилятора С для компиляции файлов с исходными текстами .c при вызове ocamlc -custom.

-cclib -llibname

Передать компоновщику С опцию -llibname при компиляции в "заказном" режиме (см. описание опции -custom). В результате данная библиотека С будет скомпонована с программой.

-ccopt option

Передать данную опцию компилятору и компоновщику С при сборке в "заказном" режиме (см. описание опции -custom). Например, при задании -ccopt -Ldir компоновщик С будет искать библиотеки С в каталоге dir.

-custom

Сборка в "заказном" режиме. В режиме по умолчанию компоновщик создает байткод, предназначенный для исполнения разделямой системой camlrun. В заказном режиме результирующий файл содержит как байткод, так и систему времени выполнения. Он становится больше, но может выполняться сам, даже если команда ocamlrun не установлена. Кроме того, заказной режим позволяет статически компоновать код Caml c пользовательскими функциями на С, как описано в гл. 18. Под Unix не используйте команду strip для файлов, созданных ocamlc -custom. Она удаляет байкод из исполняемого файла.

-dllib -llibname

Разделяемая библиотека С libname.so (libname.dll под Windows) будет загружена динамически системой времени исполнения ocamlrun при старте программы.

-dllpath dir

Добавляет каталог dir к пути поиска времени исполнения для разделяемых библиотек С. При компоновке разделяемые библиотеки ищутся в стандартном пути поиска (он соответсвует опции -I). Опция -dllpath просто сохраняет dir в исполняемом файле, где ocamlrun может найти ее и использовать, как описано в разделе 10.3.

-g

Добавляет отладочную информацию во время компиляции и компоновки. Эта опция необходима для того, чтобы отлажиать программу с помощью ocamldebug (см. гл. 16).

-i

Компилятор выводит все определенные имена (а также автоматически распознанные типы или их определения) при компиляции реализации (файл .ml. Это бывает полезно для проверки типов, распознанных компилятором. Кроме того, поскольку вывод соответствует синтаксису интерфейсов, он может помочь явно написать интерфейс (файл .mli) для файла: достаточно перенаправить вывод компилятора в файл .mli и убрать из результата декларации имен, экспорт которых не предполагается.

-I directory

Добавляет directory к списку каталогов, в которых ищутся компилированные файлы интерфейсов (.mli), компилированные объектные файлы (.cml), библиотеки (.cma) и библиотеки С, заданнын в опциях -cclib -lxxx. По умолчанию в первую очередь файлы ищутся в текущем каталоге, затем - в каталоге стандартной библиотеки. Каталоги, заданные опцией -I учитываются после текущего, в том же порядке, как они заданы в командной строке, но перед стандартной библиотекой.

Если каталог начиначается со знака +, то он считается заданным относительно каталога стандартной библиотеки. Например, -I +labltk добавляет к списку поиска подкаталог labltk стандартной библиотеки.

-impl filename

Компилировать указанный файл как реализацию, даже если его расширение отличается от .ml.

-intf filename

Компилировать указанный файл как интерфейс, даже если его расширение отличается от .mli.

-linkall

Принудительно компоновать все модули в библиотеках. При отсутствии этой опции компонуются только модули, на которые есть ссылки. При построении библиотеки (флаг -a) установка флага -linkall приводит к тому, что в последующем при компиляции программ, использующих эту библиотеку, все модули библиотеки будут компоноваться заново.

-make-runtime

Создает пользовательскую систему времени выполнения (в файле, заданном опцией -o), внедряя объектные файлы С и библитеки, указанные в командной строке. Полученная система может быть использована позднее для запуская исполняемого байткода, скомпилированного командой ocamlc -use-runtime runtime-name. Более подробная информация содержится в разделе 18.1.6.

-noassert

Отключает проверку утверждений, и утверждения не компилируются. При компоновке ранее скомпилированных файлов этот флаг не работает.

-noautolink

При компоновке библиотек .cma опции -custom, -cclib и -ccopt, вероятно хранящиеся в библиотеках (если они были указаны при построении библиотек), игнорируются. Этот флаг полезен, если библиотека содержит неправильные спецификации библиотек или опций С. В этом случае надо указать в командной строке правильные библиотеки и опции С.

-nolabels

Игнорировать неопциональные метки в типах. В этом случае метки не могут использоваться в приложениях, и порядок аргументов становится строгим.

-o exec-file

Имя файла, создаваемого компоновщиком. По умолчанию по традициям Unix создается файл a.out. Если используется опция -a, укажите имя библиотеки. Если используется опция -output-obj, укажите имя объектного файла.

-output-obj

Вместо исполняемого байткода компоновщик создает объектный файл С. Это позволяет при необходимости оформить код Caml как библиотеку С, которую можно вызывать из любой программы С. Более подробно см. раздел 18.7.5. По умолчанию создается файл camlprog.o, но имя может быть задано с помощью опции -o.

-pack

Создает объектный файл с байткодом (.cmo) и связанный с с ним скомпилированный интерфейс (.cmi), которые включают все объектные файлы, перечисленные в командной строке. Эти файлы становятся субмодулями результирующего файла .cmo. Имя последнего задается опцией -o. Например,

ocamlc -pack -o p.cmo a.cmo b.cmo c.cmo

создает компилированные файлы p.cmo и p.cmi, описывающие единицу компиляции, включающую субмодули A, B и C, соотвествующие содержанию объектных файлов a.cmo, b.cmo и c.cmo. В дальнейшем на них можно ссылаться как P.A, P.B и P.C.

-pp command

Компилятр вызывает заданную команду command как препроцессор для каждого исходного файла. Вывод команды перенаправляется в промежуточный файл, который и компилируется. Если компиляция проходит без ошибок, по ее завершении промежуточный файл удаляется. Имя файла конструируется на основе имени исходного файла и получает расширение .ppi для интерфейса (.mli) или .ppo для реализации (.ml).

-principal

Во время проверки типов компилятор проверяет также информацию о путях, следя чтобы все типы выводились приниципально. Этот флаг обязателен при использовании аргументов с метками и/или полиморфных методов - только так можно быть уверенным, что будущие версии компилятора смогут правильно определить типы даже при изменении внутреннего алгоритма. Программы, допустимые в режиме -principal, допустимы и в режиме по умолчанию с эквивалентными типами, однако бинарные сигнатуры в этом случае другие, поэтому проверка типов работает медленнее. Тем не менее, компиляция с этим флагом может быть полезна, если исходный код еще не опубликован.

-rectypes

Разрешает во время проверки типа произвольные рекурсивные типы. По умолчанию поддерживаются только рекурсивные типы с рекурсией по типу объекта.

-thread

Компилирует или компонует многопоточные программы с использованием библиотеки thread, описанной в главе 24. На самом деле, эта опция просто подключает особую версию стандартной библиотеки, безопасную по потокам.

-unsafe

Отключает проверку границ на массивах и обращении к строкам (конструкции v.(i) и s.[i]). Программы, собранные с этой опцией несколько быстрее, но не являются безопасными: при обращении к элементу за пределами массива или границы строки может произойти все, что угодно.

-use-runtime runtime-name

Создает байткод, исполняемый в пользовательской системе времени исполнения, построенной ранее командой ocamlc -make-runtime runtime-name. Дополнительная информация содержится в разделе 18.1.6.

-v

Выводит номер версии компилятора и путь к стандартной библиотеке, после чего прекращает работу.

-verbose

Выводит все внешние команды перед их выполнением. В частности это касается компилятора и компоновщика C в режиме -custom. Эта опция полезна при отладке проблем с библиотеками C.

-version

Выводит номер версии компилятора в краткой форме (например 3.06) и прекращает работу.

-w warning-list

Включает или выключает предупреждения согласно аргументу warning-list. Аргумент является строкой из одной или нескольких букв следующего значения:

A/a

включить/выключить все предупреждения.

C/c

включить/выключить предупреждения о подозрительных комментариях.

D/d

Включить/выключить предупреждения об использовании устаревших функций.

F/f

Включить/выключить предупреждения о неполном применении функций (например, f x; expr при том, что вызов f x имеет тип функции).

L/l

Включить/выключить предупреждения о пропуске меток в вызовах.

M/m

Включить/выключить предупреждения о переопределенных методах.

P/p

Включить/выключить предупреждения о неполных совпадениях (пропущенные ветки в поиске по образцу).

S/s

Включить/выключить предупреждения о предложениях с типом, отличным от unit (например, expr1; expr2, когда expr1 не имеет тип unit.

U/u

Включить/выключить предупреждения о неиспользуемых (избыточных) совпадениях с образцом.

V/v

Включить/выключить предупреждения о скрытых переменных экземпляра.

X/x

Включить/выключить все остальные предупреждения.

По умолчанию используется установка -w Al (все предупреждения, кроме меток).

-warn-error warning-list

Все предупреждения, включенные в аргумент warning-list считаются ошибками. Компилятор останавливается на ошибке при появлении одного из предупреждений, вместо того, чтобы продолжать выполнение. warning-list является строкой из одной или нескольких букв того же значения, что и в опции w: прописная буква превращает предупреждение в ошибку, строчная оставляет ее предупреждением. Значение по умолчанию - -warn-error a (все предупреждения не считаются ошибками).

-where

Выводит путь к стандартной библиотеке и прекращает работу.

8 . 3 Модули и файловая система

Этот раздел должен объяснить связь между именами модулей, соответствующих единицам компиляции, и именами файлов, содержащих соответствующие компилированные реализации и интерфейсы.

Компилятор всегда выводит имя модуля из имени исходного файла без расширения (.ml или .mli). Таким образом, он удаляет информацию о пути (если она есть) и расширение и преобразует первую букву в верхний регистр (поскольку имена модулей должны быть капитализированы). Например, файл mylib/misc.ml содержит реализацию модуля Misc. Другие единицы компиляции могут ссылаться на компоненты, определенные в mylib/misc.ml по имени типа Misc.name или использовать выражение open Misc, а затем - неполное имя name.

Создаваемые компилятором файлы .cmi и .cmo имеют те же имена, что и файлы с исходным текстом, поскольку компилятор сохраняют для них имя модуля, который они описывают (для файлов .cmi) или реализуют (для файлов .cmo).

Встречая ссылку на идентификатор модуля Mod, компилятор просматривает путь поиска, ища файл mod.cmi (обратите внимание, что первая буква строчная), и загружает компилированный интерфейс, содержащийся в этом файле. Поэтому крайне не рекомендуется переименовывать файлы .cmi: их имена всегода должны соотвествовать имени реализованной в них единицы компиляции. В противном случае компилятор сообщит об ошибке.

Что же касается файлов с байткодом (.cmo), то их можно переименовывать сколько угодно. Дело в том, что компоновщик не ищет файл .cmo, содержащий реализацию модуля: считатется, что пользователь сам должен предоставить ему список файлов .cmo.

8 . 4 Распространенные ошибки

В этом разделе описываются наиболее распространенные сообщения об ошибках.

Cannot find file filename

Указанный файл не обнаружен в текущем каталоге или каталогах, входящих в пусть поиска. filename является либо компилированным интерфейсом (.cmi), либо байткодом (.cmo). Если filename представлен в формате mod.cmi, то это означает, что компилированный файл ссылается на модуль mod, который еще не скомпилирован. Чтобы исправить эту ошибку, достаточно скомпилировать предварительно файл mod.mli или mod.ml.

Если filename имеет формат mod.cmo, то это означает, что предпринята попытка скомнововать еще не созданный файл с байткодом. В данном случае надо предварительно скомпилировать файл mod.ml.

Если исходные тексты программы размещены в нескольких каталогах, эта ошибка может означать, что каталоги не включены в путь поиска, и надо исправить опции -I командной строки.

Corrupted compiled interface filename

Эта ошибка возникает, когда компилятор пытается прочесть скомпилированный файл интерфейса (.cmi) с неверной структурой. Она означает, что произошла ошибка при записи файла - либо диск был заполнен, либо процесс компиляции прерван во время записи файла и т.д. Кроме того, ошибка может возникать, если файл .cmi был модифицирован после его создания. Для исправления следует удалить файл .cmi и построить его заново.

This expression has type t1, but is used with type t2

Это одна из наиболее распространенных ошибок. Тип t1 выводится для выражения (в сообщении об ошибке показывается эта часть программы) из самого выражения. Тип t2 ожидается, исходя из контекста выражения. Он выводится из того, как значение выражения используется в программе далее. Если t1 и t2 несовместимы, возникает ошибка.

Иногды бывает сложно понять, почему типы несовместимы. Например, компилятор может сообщить, что "выражение типа foo не может использоваться с типом foo", хотя два типа foo явно должны быть совместимы. Но это не всегда так. Два конструктора типов могут иметь общее имя, но на самом деле представлять разные типы. Такое случается при переопределении конструктора. Например:

	    
type foo = A | B
let f = function A -> 0 | B -> 1
type foo = C | D
f C
	  

При компиляции такого кода появится сообщение об ошибке "Выражение c типа foo не может использоваться с типом foo".

The type of this expression, t, contains type variables that cannot be generalized

Переменные типа ('a, 'b, ...) в типе t могут быть в двух состояниях: обобщенные (в этом случае тип t пригоден для всех возможных инстанциаций переменных) и не обобщенные (в этом случае тип t пригоден только для одной инстанциации переменных). В связывании let name = expr система проверки типов как правило обобщает все переменные типа, возможные в типе expr. Однако, в совокупности с полиморфическими изменяемыми структурами данных это приводит к уязвимости программ (исполнение правильной программ заканчивается ошибками). Поэтому обобщение производится для связываний let только в том случае, если выражение expr принадлежит к классу "синтаксических значений", включающему константы, идентификаторы, функции, кортежи синтаксических значений и т.д. Во всех остальных случаях (например, expr является приложением функции) может создаваться полиморфная изменяемая структура, и обобщение выключается.

Внутри данной структуры или единицы компиляции (содержимое файла .ml или интерактивной сессии) необобщенные переменные типа не несут никаких проблем, но в сигнатурах или компилированных интерфейсах их быть не должно, поскольку позднее они могут быть использованы неправильно. Поэтому компилятор сообщает об ошибке, когда структура или единица компиляции определяет значение name, тип которой содержит необобщенные переменные типа. Есть два способа исправить эту ошибку:

  • Можно добавить в файл .mli ограничение типа. В результате тип станет мономорфным (без переменных типа). Например, вместо

    let sort_int_list = Sort.list (<)
    (* выводится тип 'a list -> 'a list, причем 'a не обобщается *)

    пишите

    let sort_int_list = (Sort.list (<) : int list -> int list);;
  • Если необходим именно полиморфный тип, определяющее его выражение можно преобразовать в функцию, добавив параметр. Например, вместо

    let map_length = List.map Array.length
    (* выводится тип 'a list -> int list, причем 'a не обобщается *)

    пишите

    let map_length lv = List.map Array.length lv
Reference to undefined global mod

Эта ошибка появляется при ссылке на неполный или неправильно упорядоченный набор файлов. Возможно, в командной строке не указана реализация для единицы компиляции mod(как правило, файл mod.cmo или содержащая его библиотека). Для исправления достаточно указать в командной строке недостающий файл .ml или .cmo. Кроме того, реализация модуля mod может находиться в командной строке после модуля, ссылающегося на нее, а должно быть наоборот. В этом случае надо изменить порядок модулей.

Разумеется, при взаимно рекурсивных ссылках в модулях (например, функция Mod1.f вызывает функцию Mod2.g, а та вызывает Mod1.f) эта ошибка будет проявляться всегда. В этом случае исходные файлы не будут скомпилированы, как не переставляй их в командной строке. Возможные решения:

  • Поместить f и g в один модуль.

  • Параметризовать одну функцию другой. То есть вместо:

    mod1.ml:    let f x = ... Mod2.g ...
    mod2.ml:    let g y = ... Mod1.f ...

    писать:

    mod1.ml:    let f g x = ... g ...
    mod2.ml:    let rec g y = ... Mod1.f g ...

    и передавать компоновщику сначала mod1.cmo, затем mod2.cmo.

  • Одну из двух функций можно поместить в ссылку, например так

    mod1.ml:    let forward_g =
                    ref((fun x -> failwith "forward_g") : <type>)
                let f x = ... !forward_g ...
    mod2.ml:    let g y = ... Mod1.f ...
                let _ = Mod1.forward_g := g

    Впрочем, если g полиморфна, это работать не будет.

The external function f is not available

Эта ошибка появляется при попытке компиляции кола, вызывающего с внешнюю функцию на языке С. В гл. 18 объясняется, что такой код должен компоноваться с библиотекой С, содержащей функцию f. Если библиотека не разделяемая (DLL), компоновка должна проходить в "заказном режиме" Решение: указать необходимую библиотеку С и, возможно, ключ -custom.