Source file library.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
(** Lambdapi library management. *)
open Lplib open Base open Extra
open Timed
open Error open Debug
let log = Logger.make 'l' "libr" "library files"
let log = log.pp
(** Representation of the mapping from module paths to files. *)
module LibMap :
sig
(** Module path mapping. *)
type t
(** [empty] is an empty module path mapping. *)
val empty : t
(** Exception raised if an attempt is made to map an already mapped module
(including the root). *)
exception Already_mapped
(** [set_root dir m] sets the library root of [m] to be [dir].
@raise Already_mapped if library root of [m] is already set. *)
val set_root : string -> t -> t
(** [add mp fp map] extends the mapping [map] by associating the module
path [mp] to the file path [fp].
@raise Already_mapped when [mp] isalready mapped in [m]. *)
val add : Path.t -> string -> t -> t
(** Exception raised if an attempt is made to use the [get] function prior
to the root being set (using [set_root]). *)
exception Root_not_set
(** [get mp map] obtains the filename corresponding to the module path
[mp] in [map] (with no particular extension).
@raise Root_not_set when the root of [map] has not been set using
[set_root]. *)
val get : Path.t -> t -> string
(** [iter f map] calls function [f] on every binding stored in [map]. *)
val iter : (Path.t -> string -> unit) -> t -> unit
(** [pp ppf t] prints [t] on formatter [ppf] (for debug). *)
val pp : t pp
end =
struct
type t = Node of string option * t StrMap.t
let rec pp ppf (Node(po, map)) =
out ppf "Node(%a,%a)"
(D.option D.string) po (D.strmap pp) map
let empty = Node(None, StrMap.empty)
exception Already_mapped
let set_root dir (Node(po, map)) =
match po with
| None -> Node(Some(dir), map)
| Some(_) -> raise Already_mapped
let rec singleton ks fp =
match ks with
| [] -> Node(Some(fp), StrMap.empty)
| k :: ks -> Node(None, StrMap.singleton k (singleton ks fp))
let rec add ks fp (Node(po, map)) =
match (po, ks) with
| (Some(_), [] ) -> raise Already_mapped
| (None , [] ) -> Node(Some(fp), map)
| (_ , k :: ks) ->
try Node(po, StrMap.add k (add ks fp (StrMap.find k map)) map)
with Not_found -> Node(po, StrMap.add k (singleton ks fp) map)
exception Root_not_set
let get ks (Node(po, map)) =
let concat root ks =
List.fold_left Filename.concat root ks in
let rec get (root, old_ks) ks map =
if Logger.log_enabled () then
log "get @[<hv>\"%s\"@ [%a]@ [%a]@ %a@]" root
(D.list D.string) old_ks (D.list D.string) ks (D.strmap pp) map;
match ks with
| [] -> concat root old_ks
| k :: ks ->
match StrMap.find k map with
| Node(Some(root), map) -> get (root, ks) ks map
| Node(None , map) -> get (root, old_ks) ks map
| exception Not_found -> concat root old_ks
in
match po with
| None -> raise Root_not_set
| Some(root) -> get (root, ks) ks map
let iter f map =
let rec iter ks (Node(po, map)) =
Option.iter (fun fp -> f ks fp) po;
StrMap.iter (fun k m -> iter (ks @ [k]) m) map
in
iter [] map
end
(** [lib_root] stores the result of the ["--lib-root"] flag when given. *)
let lib_root : string option Stdlib.ref = Stdlib.ref None
(** [lib_mappings] stores the specified mappings of library paths. *)
let lib_mappings : LibMap.t ref = ref LibMap.empty
(** [iter f] iterates [f] on current mappings. *)
let iter : (Path.t -> string -> unit) -> unit = fun f ->
LibMap.iter f !lib_mappings
(** [set_lib_root dir] sets the library root to directory [dir].
The following directories are set sequentially, such that the active one
is the last valid one
- [/usr/local/lib/lambdapi/lib_root/]
- [$OPAM_SWITCH_PREFIX/lib/lambdapi/lib_root]
- [$LAMBDAPI_LIB_ROOT/lib/lambdapi/lib_root]
- the directory given as parameter of the [--lib-root] command line
argument. *)
let set_lib_root : string option -> unit = fun dir ->
let open Stdlib in
let prefix = Stdlib.ref "/usr/local" in
let set_prefix p = prefix := p in
Option.iter set_prefix (Sys.getenv_opt "OPAM_SWITCH_PREFIX");
Option.iter set_prefix (Sys.getenv_opt "LAMBDAPI_LIB_ROOT");
lib_root := Some(Filename.concat !prefix "lib/lambdapi/lib_root");
let set_lr p =
try lib_root := Some(Lplib.Filename.realpath p) with
Invalid_argument(_) -> ()
in
Option.iter set_lr dir;
match !lib_root with
| None -> assert false
| Some(pth) ->
Timed.(lib_mappings := LibMap.set_root pth !lib_mappings)
(** [add_mapping (mn, fn)] adds a new mapping from the module name [mn] to
the file name [fn] if [mn] is not already mapped and [fn] is a valid
directory. In case of failure the program terminates and a graceful error
message is displayed. *)
let add_mapping : Path.t * string -> unit = fun (mp, fn) ->
let fn =
try Filename.realpath fn
with Invalid_argument f -> fatal_no_pos "%s: No such file or directory" f
in
let new_mapping =
try LibMap.add mp fn !lib_mappings
with LibMap.Already_mapped ->
fatal_no_pos "Module path [%a] is already mapped." Path.pp mp
in
lib_mappings := new_mapping
(** [file_of_path mp] converts module path [mp] into the corresponding "file
path" (with no attached extension). It is assumed that [lib_root] has been
set, possibly with [set_lib_root]. *)
let file_of_path : Path.t -> string = fun mp ->
let mp = List.map Escape.unescape mp in
try
let fp = LibMap.get mp !lib_mappings in
if Logger.log_enabled () then
log "file_of_path @[<hv>%a@ %a@ = \"%s\"@]"
Path.pp mp LibMap.pp !lib_mappings fp;
fp
with LibMap.Root_not_set -> fatal_no_pos "Library root not set."
(** [lp_src_extension] is the expected extension for source files. *)
let lp_src_extension : string = ".lp"
(** [dk_src_extension] is the extension for dk source files. *)
let dk_src_extension : string = ".dk"
(** [is_valid_src_extension s] returns [true] iff [s] ends with
[lp_src_extension] or [dk_src_extension]. *)
let is_valid_src_extension : string -> bool = fun s ->
Filename.check_suffix s lp_src_extension
|| Filename.check_suffix s dk_src_extension
(** [obj_extension] is the expected extension for binary (object) files. *)
let obj_extension : string = ".lpo"
(** [valids_extensions] is the list of valid file extensions. *)
let valid_extensions : string list =
[lp_src_extension; dk_src_extension; obj_extension]
(** [path_of_file escape fname] computes the module path that corresponds to
the filename [fname]. [escape] converts irregular path elements into
escaped identifiers if needed.
@raise [Fatal] if [fname] doesn't have a valid extension. *)
let path_of_file : (string -> string) -> string -> Path.t =
fun escape fname ->
let ext = Filename.extension fname in
if not (List.mem ext valid_extensions) then
begin
fatal_msg "Invalid extension for [%s].@." fname;
let exp = List.pp (fun ppf -> out ppf "[%s]") ", " in
fatal_no_pos "Expected any of: %a." exp valid_extensions
end;
let fname = Filename.normalize fname in
let base = Filename.chop_extension fname in
let mapping = ref None in
let f mp fp =
if String.is_prefix fp base then
match !mapping with
| None -> mapping := Some(mp, fp)
| Some(_, p) ->
if String.(length p < length fp) then mapping := Some(mp, p)
in
LibMap.iter f !lib_mappings;
let (mp, fp) =
match !mapping with
| Some(mp, fp) -> (mp, fp)
| None ->
fatal_msg "%s cannot be mapped under the library root.@." fname;
fatal_msg "Consider adding a package file under your source tree, ";
fatal_no_pos "or use the [--map-dir] option."
in
let rest =
let len_fp = String.length fp in
let len_base = String.length base in
String.sub base (len_fp + 1) (len_base - len_fp - 1)
in
let full_mp = mp @ List.map escape (String.split_on_char '/' rest) in
log "path_of_file @[<hv>\"%s\"@ = %a@]" fname Path.pp full_mp;
full_mp
(** [install_path fname] prefixes the filename [fname] by the path to the
library root. *)
let install_path : string -> string = fun fname ->
let mp = path_of_file (fun s -> s) fname in
let ext = Filename.extension fname in
match Stdlib.(!lib_root) with
| None -> assert false
| Some(p) -> List.fold_left Filename.concat p mp ^ ext