diff options
author | Rutger Broekhoff | 2025-07-07 21:52:08 +0200 |
---|---|---|
committer | Rutger Broekhoff | 2025-07-07 21:52:08 +0200 |
commit | ba61dfd69504ec6263a9dee9931d93adeb6f3142 (patch) | |
tree | d6c9b78e50eeab24e0c1c09ab45909a6ae3fd5db /lib | |
download | verified-dyn-lang-interp-ba61dfd69504ec6263a9dee9931d93adeb6f3142.tar.gz verified-dyn-lang-interp-ba61dfd69504ec6263a9dee9931d93adeb6f3142.zip |
Initialize repository
Diffstat (limited to 'lib')
-rw-r--r-- | lib/extraction/dune | 56 | ||||
-rw-r--r-- | lib/extraction/extraction.ml | 18 | ||||
-rw-r--r-- | lib/extraction/prelude.v | 52 | ||||
-rw-r--r-- | lib/mininix/builtins.ml | 77 | ||||
-rw-r--r-- | lib/mininix/builtins.nix | 302 | ||||
-rw-r--r-- | lib/mininix/conv.ml | 96 | ||||
-rw-r--r-- | lib/mininix/dune | 15 | ||||
-rw-r--r-- | lib/mininix/import.ml | 54 | ||||
-rw-r--r-- | lib/mininix/mininix.ml | 13 | ||||
-rw-r--r-- | lib/mininix/mininix2nix.ml | 54 | ||||
-rw-r--r-- | lib/mininix/nix2mininix.ml | 254 | ||||
-rw-r--r-- | lib/mininix/run.ml | 17 | ||||
-rw-r--r-- | lib/mininix/sexp.ml | 160 | ||||
-rw-r--r-- | lib/nix/dune | 15 | ||||
-rw-r--r-- | lib/nix/elaborator.ml | 208 | ||||
-rw-r--r-- | lib/nix/lexer.mll | 315 | ||||
-rw-r--r-- | lib/nix/nix.ml | 20 | ||||
-rw-r--r-- | lib/nix/parser.mly | 310 | ||||
-rw-r--r-- | lib/nix/printer.ml | 176 | ||||
-rw-r--r-- | lib/nix/tokens.ml | 64 | ||||
-rw-r--r-- | lib/nix/types.ml | 112 |
21 files changed, 2388 insertions, 0 deletions
diff --git a/lib/extraction/dune b/lib/extraction/dune new file mode 100644 index 0000000..b56caf9 --- /dev/null +++ b/lib/extraction/dune | |||
@@ -0,0 +1,56 @@ | |||
1 | (coq.extraction | ||
2 | (prelude prelude) | ||
3 | (extracted_modules | ||
4 | Ascii | ||
5 | BinInt | ||
6 | Bits | ||
7 | Decimal | ||
8 | prelude | ||
9 | gmap | ||
10 | Nat | ||
11 | PeanoNat | ||
12 | SpecFloat | ||
13 | ZArith_dec | ||
14 | base | ||
15 | BinNat | ||
16 | Bool | ||
17 | DecimalString | ||
18 | interp | ||
19 | numbers | ||
20 | pretty | ||
21 | Specif | ||
22 | Zbool | ||
23 | Basics | ||
24 | BinNums | ||
25 | countable | ||
26 | list0 | ||
27 | operational | ||
28 | res | ||
29 | String | ||
30 | Zpower | ||
31 | Binary | ||
32 | BinPosDef | ||
33 | Datatypes | ||
34 | fin_maps | ||
35 | List | ||
36 | option | ||
37 | Round | ||
38 | strings | ||
39 | BinarySingleNaN | ||
40 | BinPos | ||
41 | decidable | ||
42 | floats | ||
43 | mapset | ||
44 | orders | ||
45 | sorting | ||
46 | utils) | ||
47 | (flags | ||
48 | (-output-directory ".")) | ||
49 | (theories Flocq stdpp mininix)) | ||
50 | |||
51 | (library | ||
52 | (name extraction) | ||
53 | (flags | ||
54 | (:standard -w -33)) | ||
55 | (instrumentation | ||
56 | (backend bisect_ppx))) | ||
diff --git a/lib/extraction/extraction.ml b/lib/extraction/extraction.ml new file mode 100644 index 0000000..a737700 --- /dev/null +++ b/lib/extraction/extraction.ml | |||
@@ -0,0 +1,18 @@ | |||
1 | include Prelude | ||
2 | include Interp | ||
3 | include Operational | ||
4 | include Res | ||
5 | |||
6 | (* Stuff that's not part of the paper. Still exposed because we sometimes want | ||
7 | to be able to create a natural number, float, process a list etc. *) | ||
8 | module Internal = struct | ||
9 | module BinNums = BinNums | ||
10 | module Datatypes = Datatypes | ||
11 | module List = List | ||
12 | |||
13 | module Floats = struct | ||
14 | include Floats | ||
15 | include Binary | ||
16 | include SpecFloat | ||
17 | end | ||
18 | end | ||
diff --git a/lib/extraction/prelude.v b/lib/extraction/prelude.v new file mode 100644 index 0000000..ef35bcb --- /dev/null +++ b/lib/extraction/prelude.v | |||
@@ -0,0 +1,52 @@ | |||
1 | Require Import Coq.Numbers.DecimalString ExtrOcamlBasic ExtrOcamlString. | ||
2 | From stdpp Require Import strings stringmap gmap. | ||
3 | From mininix Require Import nix.interp. | ||
4 | |||
5 | Definition attr_set_insert (x : string) (α : attr) (αs : gmap string attr) : gmap string attr := | ||
6 | <[x:=α]> αs. | ||
7 | |||
8 | Definition attr_set_contains (x : string) (αs : gmap string attr) : bool := | ||
9 | bool_decide (x ∈ dom αs). | ||
10 | |||
11 | Definition attr_set_fold {A} (f : string → attr → A → A) (init : A) (αs : gmap string attr) : A := | ||
12 | map_fold f init αs. | ||
13 | |||
14 | Definition attr_set_empty : gmap string attr := ∅. | ||
15 | |||
16 | Definition env_fold {A} (f : string → (kind * thunk) → A → A) (init : A) (E : env) : A := | ||
17 | map_fold f init E. | ||
18 | |||
19 | Definition env_insert_abs (x : string) (t : thunk) (E : env) : env := | ||
20 | <[x:=(ABS,t)]> E. | ||
21 | |||
22 | Definition thunk_map_fold {A} (f : string → thunk → A → A) (init : A) (bs : gmap string thunk) : A := | ||
23 | map_fold f init bs. | ||
24 | |||
25 | Definition thunk_map_insert (x : string) (t : thunk) (bs : gmap string thunk) : gmap string thunk := | ||
26 | <[x:=t]> bs. | ||
27 | |||
28 | Definition thunk_map_empty : gmap string thunk := ∅. | ||
29 | |||
30 | Definition matcher := gmap string (option expr). | ||
31 | |||
32 | Definition matcher_empty : matcher := ∅. | ||
33 | |||
34 | Definition matcher_insert (x : string) (me : option expr) (ms : matcher) : matcher := | ||
35 | <[x:=me]> ms. | ||
36 | |||
37 | Definition matcher_fold {A} (f : string → option expr → A → A) (init : A) (ms : matcher) : A := | ||
38 | map_fold f init ms. | ||
39 | |||
40 | Definition env_empty : env := ∅. | ||
41 | |||
42 | Definition string_of_Z (x : Z) : string := | ||
43 | NilZero.string_of_int (Z.to_int x). | ||
44 | |||
45 | Definition string_to_Z (s : string) : option Z := | ||
46 | Z.of_int <$> NilZero.int_of_string s. | ||
47 | |||
48 | Separate Extraction | ||
49 | attr_set_insert env_insert_abs matcher_insert thunk_map_insert | ||
50 | attr_set_contains attr_set_fold env_fold matcher_fold thunk_map_fold | ||
51 | env_empty attr_set_empty matcher_empty thunk_map_empty string_of_Z | ||
52 | string_to_Z interp' forallb. | ||
diff --git a/lib/mininix/builtins.ml b/lib/mininix/builtins.ml new file mode 100644 index 0000000..0809668 --- /dev/null +++ b/lib/mininix/builtins.ml | |||
@@ -0,0 +1,77 @@ | |||
1 | open Core | ||
2 | open Nix2mininix | ||
3 | |||
4 | let minimal_prelude = | ||
5 | mn_attr | ||
6 | [ | ||
7 | ("true", `Nonrec, Extraction.ELit (Extraction.LitBool true)); | ||
8 | ("false", `Nonrec, Extraction.ELit (Extraction.LitBool false)); | ||
9 | ("null", `Nonrec, Extraction.ELit Extraction.LitNull); | ||
10 | ("seq", `Nonrec, mn_abs [ "e1"; "e2" ] (mn_seq (mn_id "e1") (mn_id "e2"))); | ||
11 | ( "deepSeq", | ||
12 | `Nonrec, | ||
13 | mn_abs [ "e1"; "e2" ] (mn_deep_seq (mn_id "e1") (mn_id "e2")) ); | ||
14 | ("typeOf", `Nonrec, mn_abs [ "e" ] (mn_type_of (mn_id "e"))); | ||
15 | ("functionArgs", `Nonrec, mn_abs [ "f" ] (mn_function_args (mn_id "f"))); | ||
16 | ( "bitAnd", | ||
17 | `Nonrec, | ||
18 | mn_abs [ "x"; "y" ] (mn_bit_and (mn_id "x") (mn_id "y")) ); | ||
19 | ("bitOr", `Nonrec, mn_abs [ "x"; "y" ] (mn_bit_or (mn_id "x") (mn_id "y"))); | ||
20 | ( "bitXor", | ||
21 | `Nonrec, | ||
22 | mn_abs [ "x"; "y" ] (mn_bit_xor (mn_id "x") (mn_id "y")) ); | ||
23 | ("ceil", `Nonrec, mn_abs [ "x" ] (mn_ceil (mn_id "x"))); | ||
24 | ("floor", `Nonrec, mn_abs [ "x" ] (mn_floor (mn_id "x"))); | ||
25 | ("__mn_nearestEven", `Nonrec, mn_abs [ "x" ] (mn_nearest_even (mn_id "x"))); | ||
26 | ( "__mn_singleton", | ||
27 | `Nonrec, | ||
28 | mn_abs [ "x"; "e" ] (mn_singleton_attr (mn_id "x") (mn_id "e")) ); | ||
29 | ( "__mn_attr_delete", | ||
30 | `Nonrec, | ||
31 | mn_abs [ "as"; "x" ] (mn_delete_attr (mn_id "as") (mn_id "x")) ); | ||
32 | ( "__mn_attr_has_prim", | ||
33 | `Nonrec, | ||
34 | mn_abs [ "d"; "e" ] (mn_has_attr (mn_id "d") (mn_id "e")) ); | ||
35 | ("__mn_attr_match", `Nonrec, mn_abs [ "as" ] (mn_attr_match (mn_id "as"))); | ||
36 | ("__mn_list_match", `Nonrec, mn_abs [ "xs" ] (mn_list_match (mn_id "xs"))); | ||
37 | ( "__mn_string_match", | ||
38 | `Nonrec, | ||
39 | mn_abs [ "s" ] (mn_string_match (mn_id "s")) ); | ||
40 | ] | ||
41 | |||
42 | (* Watch out to not introduce constructs here that refer to themselves using | ||
43 | the mnbi_* functions in Nix2mininix - this can cause undesired loops. *) | ||
44 | let builtins_nix = | ||
45 | Nix.elaborate (Nix.parse ~filename:"<builtins>" [%blob "builtins.nix"]) | ||
46 | |||
47 | let builtins = | ||
48 | Extraction.ELetAttr | ||
49 | (Extraction.ABS, minimal_prelude, Nix2mininix.from_nix builtins_nix) | ||
50 | |||
51 | let exported_builtins = | ||
52 | [ | ||
53 | "__mn_assert"; | ||
54 | "__mn_attr_has"; | ||
55 | "__mn_attr_insertNew"; | ||
56 | "__mn_attr_select"; | ||
57 | "__mn_attr_selectOr"; | ||
58 | "abort"; | ||
59 | "false"; | ||
60 | "head"; | ||
61 | "map"; | ||
62 | "null"; | ||
63 | "removeAttrs"; | ||
64 | "tail"; | ||
65 | "throw"; | ||
66 | "toString"; | ||
67 | "true"; | ||
68 | ] | ||
69 | |||
70 | let apply_prelude e = | ||
71 | let bindings = | ||
72 | mn_attr | ||
73 | (("builtins", `Nonrec, builtins) | ||
74 | :: List.map exported_builtins ~f:(fun x -> | ||
75 | (x, `Rec, mn_select_attr (mn_id "builtins") (mn_str x)))) | ||
76 | in | ||
77 | Extraction.ELetAttr (Extraction.ABS, bindings, e) | ||
diff --git a/lib/mininix/builtins.nix b/lib/mininix/builtins.nix new file mode 100644 index 0000000..9c7ed32 --- /dev/null +++ b/lib/mininix/builtins.nix | |||
@@ -0,0 +1,302 @@ | |||
1 | rec { | ||
2 | inherit true false null functionArgs typeOf seq deepSeq bitAnd bitOr bitXor floor ceil; # from the minimal prelude | ||
3 | |||
4 | abort = _: null null; # we ignore the provided message | ||
5 | throw = abort; # same here | ||
6 | |||
7 | head = xs: (__mn_list_match xs).head; | ||
8 | tail = xs: (__mn_list_match xs).tail; | ||
9 | |||
10 | __mn_matchAttr = f: as: f (__mn_attr_match as); | ||
11 | __mn_matchList = f: xs: f (__mn_list_match xs); | ||
12 | __mn_matchString = f: s: f (__mn_string_match s); | ||
13 | |||
14 | __mn_foldr = op: nul: | ||
15 | __mn_matchList ({ head, tail, empty }: | ||
16 | if empty then nul else op head (__mn_foldr op nul tail)); | ||
17 | |||
18 | # foldl' should really be strict. But if we do that (using seq), the | ||
19 | # complexity of this function suddenly morphs from linear to | ||
20 | # exponential, which is way worse than not actually being strict. | ||
21 | foldl' = op: nul: | ||
22 | __mn_matchList | ||
23 | ({ head, tail, empty }: | ||
24 | if empty then nul else | ||
25 | let v = op nul head; in | ||
26 | seq v (foldl' op v tail)); | ||
27 | map = f: | ||
28 | __mn_matchList ({ head, tail, empty }: | ||
29 | if empty then [ ] else [ (f head) ] ++ map f tail); | ||
30 | elem = x: any (y: x == y); | ||
31 | elemAt = xs: n: assert n >= 0; | ||
32 | let go = xs: n: if n == 0 then head xs else go (tail xs) (n - 1); | ||
33 | in go xs n; | ||
34 | length = __mn_matchList ({ head, tail, empty }: | ||
35 | if empty then 0 else 1 + length tail); | ||
36 | sort = __mn_mergesort; | ||
37 | any = f: __mn_matchList ({ head, tail, empty }: !empty && (f head || any f tail)); | ||
38 | all = f: __mn_matchList ({ head, tail, empty }: !empty -> (f head && all f tail)); | ||
39 | concatLists = | ||
40 | __mn_matchList ({ head, tail, empty }: | ||
41 | if empty then [ ] else head ++ concatLists tail); | ||
42 | concatMap = f: xss: concatLists (map f xss); | ||
43 | concatStringsSep = sep: | ||
44 | __mn_matchList ({ head, tail, empty }: | ||
45 | if empty then "" else if tail == [ ] then head | ||
46 | else head + sep + concatStringsSep sep tail); | ||
47 | filter = f: | ||
48 | __mn_matchList ({ head, tail, empty }: | ||
49 | if empty then [ ] else (if f head then [ head ] else [ ]) ++ filter f tail); | ||
50 | groupBy = f: xs: | ||
51 | let update = x: acc: acc // { ${f x} = [ x ] ++ (acc.${f x} or [ ]); }; | ||
52 | in __mn_foldr update { } xs; | ||
53 | partition = f: groupBy (x: if f x then "right" else "wrong"); | ||
54 | |||
55 | hasAttr = x: as: as ? ${x}; | ||
56 | getAttr = x: as: as.${x}; | ||
57 | attrNames = __mn_matchAttr ({ key, tail, empty, ... }: | ||
58 | if empty then [ ] else [ key ] ++ attrNames tail); | ||
59 | attrValues = __mn_matchAttr ({ head, tail, empty, ... }: | ||
60 | if empty then [ ] else [ head ] ++ attrValues tail); | ||
61 | mapAttrs = f: __mn_matchAttr ({ key, head, tail, empty }: | ||
62 | if empty then { } else | ||
63 | mapAttrs f tail // { ${key} = f key head; }); | ||
64 | removeAttrs = __mn_foldr (x: as': __mn_attr_delete as' x); | ||
65 | zipAttrsWith = f: ass: mapAttrs f (__mn_zipAttrs ass); | ||
66 | catAttrs = x: | ||
67 | __mn_matchList ({ head, tail, empty }: | ||
68 | if empty then [ ] | ||
69 | else (if head ? ${x} then [ head.${x} ] else [ ]) ++ catAttrs x tail); | ||
70 | listToAttrs = | ||
71 | __mn_foldr (attr: as': as' // { ${attr.name} = attr.value; }) { }; | ||
72 | intersectAttrs = e1: e2: | ||
73 | __mn_matchAttr | ||
74 | ({ key, head, tail, empty }: | ||
75 | if empty then { } else | ||
76 | (if e2 ? ${key} then { ${key} = e2.${key}; } else { }) // | ||
77 | intersectAttrs tail (__mn_attr_delete e2 key)) | ||
78 | e1; | ||
79 | |||
80 | lessThan = x: y: x < y; # documentation is misleading, not only for numbers | ||
81 | add = x: y: x + y; | ||
82 | mul = x: y: x * y; | ||
83 | div = x: y: x / y; | ||
84 | sub = x: y: x - y; | ||
85 | genList = gen: n: | ||
86 | let | ||
87 | aux = off: if off >= n then [ ] else | ||
88 | [ (gen off) ] ++ aux (off + 1); | ||
89 | in | ||
90 | aux 0; | ||
91 | |||
92 | __mn_genericClosure = { operator, seen, startSet }: | ||
93 | __mn_matchList | ||
94 | ({ head, tail, empty }: | ||
95 | if empty then [ ] else | ||
96 | if seen head.key | ||
97 | then __mn_genericClosure { inherit operator seen; startSet = tail; } | ||
98 | else [ head ] ++ __mn_genericClosure { | ||
99 | inherit operator; | ||
100 | seen = k: k == head.key || seen k; | ||
101 | startSet = tail ++ operator head; | ||
102 | }) | ||
103 | startSet; | ||
104 | genericClosure = { operator, startSet }: | ||
105 | __mn_genericClosure { inherit operator startSet; seen = _: false; }; | ||
106 | |||
107 | isAttrs = e: typeOf e == "set"; | ||
108 | isBool = e: typeOf e == "bool"; | ||
109 | isFloat = e: typeOf e == "float"; | ||
110 | isFunction = e: typeOf e == "lambda"; | ||
111 | isInt = e: typeOf e == "int"; | ||
112 | isList = e: typeOf e == "list"; | ||
113 | isNull = e: typeOf e == "null"; | ||
114 | isString = e: typeOf e == "string"; | ||
115 | |||
116 | toString = e: | ||
117 | if isAttrs e then | ||
118 | if e ? __toString then e.__toString e else e.outPath | ||
119 | else if isBool e then | ||
120 | if e then "1" else "" | ||
121 | else if isFloat e then | ||
122 | __mn_float_toString e | ||
123 | else if isInt e then | ||
124 | __mn_int_toString e | ||
125 | else if isList e then | ||
126 | concatStringsSep " " (map toString e) | ||
127 | else if isNull e then | ||
128 | "" | ||
129 | else if isString e then | ||
130 | e | ||
131 | else abort null; | ||
132 | |||
133 | stringLength = | ||
134 | __mn_matchString ({ head, tail, empty }: | ||
135 | if empty then 0 else 1 + stringLength tail); | ||
136 | |||
137 | substring = start: assert start >= 0; len: | ||
138 | __mn_matchString ({ head, tail, empty }: | ||
139 | if empty || len == 0 then "" else | ||
140 | if start > 0 | ||
141 | then substring (start - 1) len tail | ||
142 | else head + substring 0 (len - 1) tail); | ||
143 | |||
144 | replaceStrings = from: to: s: | ||
145 | __mn_matchList | ||
146 | ({ head, tail, empty }: | ||
147 | let from = if empty then [ ] else [ head ] ++ tail; in | ||
148 | __mn_matchList | ||
149 | ({ head, tail, empty }: | ||
150 | let to = if empty then [ ] else [ head ] ++ tail; in | ||
151 | assert length from == length to; | ||
152 | __mn_strings_replace from to s) | ||
153 | to) | ||
154 | from; | ||
155 | |||
156 | __mn_strings_replace = subsFrom: subsTo: s: | ||
157 | let go = __mn_strings_replace subsFrom subsTo; in | ||
158 | __mn_strings_replace_aux go subsFrom subsTo s; | ||
159 | |||
160 | __mn_strings_replace_aux = go: subsFrom: subsTo: s: | ||
161 | __mn_matchList | ||
162 | ({ head, tail, empty }: | ||
163 | if empty | ||
164 | then | ||
165 | __mn_matchString | ||
166 | ({ head, tail, empty }: if empty then "" else head + go tail) | ||
167 | s | ||
168 | else | ||
169 | let subFrom = head; subsFrom' = tail; in | ||
170 | __mn_matchList | ||
171 | ({ head, tail, ... }: | ||
172 | let subTo = head; subsTo' = tail; in | ||
173 | if subFrom == "" | ||
174 | then | ||
175 | # We can only ask ourselves why, but it is so -- in Nix: | ||
176 | # replaceStrings ["" "a"] ["X" "_"] "asdf" ~> "XaXsXdXfX" | ||
177 | # and so we emulate this 'behavior' | ||
178 | subTo + __mn_matchString | ||
179 | ({ head, tail, empty }: | ||
180 | if empty then "" else head + go tail) | ||
181 | s | ||
182 | else | ||
183 | ({ ok, rest }: | ||
184 | if ok | ||
185 | then subTo + go rest | ||
186 | else __mn_strings_replace_aux go subsFrom' subsTo' s) | ||
187 | (__mn_string_chopPrefix subFrom s)) | ||
188 | subsTo) | ||
189 | subsFrom; | ||
190 | |||
191 | __mn_string_chopPrefix = prefix: s: | ||
192 | __mn_matchString | ||
193 | ({ head, tail, empty }: | ||
194 | if empty then { ok = true; rest = s; } else | ||
195 | let prefix = head; prefix' = tail; in __mn_matchString | ||
196 | ({ head, tail, empty }: | ||
197 | if empty || prefix != head then { ok = false; rest = null; } else | ||
198 | __mn_string_chopPrefix prefix' tail) | ||
199 | s) | ||
200 | prefix; | ||
201 | |||
202 | __mn_string_drop = n: s: | ||
203 | if n <= 0 then s else | ||
204 | __mn_matchString | ||
205 | ({ tail, empty, ... }: | ||
206 | if empty then "" else | ||
207 | __mn_string_drop (n - 1) tail) | ||
208 | s; | ||
209 | |||
210 | __mn_float_toString = x: | ||
211 | let | ||
212 | sign = x < 0; | ||
213 | abs = __mn_abs x; | ||
214 | int = floor abs; | ||
215 | dec = __mn_nearestEven ((abs - int) * 1000000); | ||
216 | in | ||
217 | (if sign then "-" else "") + | ||
218 | __mn_int_toString int + "." + __mn_int_toString dec; | ||
219 | __mn_int_toString = x: (if x < 0 then "-" else "") + | ||
220 | ( | ||
221 | let d10 = __mn_quotRem (__mn_abs x) 10; in | ||
222 | (if d10.quot != 0 then toString d10.quot else "") + | ||
223 | (if d10.rem == 0 then "0" else | ||
224 | if d10.rem == 1 then "1" else | ||
225 | if d10.rem == 2 then "2" else | ||
226 | if d10.rem == 3 then "3" else | ||
227 | if d10.rem == 4 then "4" else | ||
228 | if d10.rem == 5 then "5" else | ||
229 | if d10.rem == 6 then "6" else | ||
230 | if d10.rem == 7 then "7" else | ||
231 | if d10.rem == 8 then "8" else | ||
232 | if d10.rem == 9 then "9" else | ||
233 | abort null) | ||
234 | ); | ||
235 | |||
236 | __mn_quotRem = x: y: | ||
237 | let quot = x / y; in | ||
238 | { inherit quot; rem = x - quot * y; }; | ||
239 | __mn_abs = x: if x < 0 then -x else x; | ||
240 | |||
241 | __mn_attr_insertNew = as: x: e: | ||
242 | if x == null then { } else | ||
243 | assert !(as ? ${x}); as // __mn_singleton x e; | ||
244 | __mn_attr_has_aux = d: e: | ||
245 | if typeOf d != "set" then false else __mn_attr_has_prim d e; | ||
246 | __mn_attr_has = e: | ||
247 | __mn_matchList ({ head, tail, empty }: | ||
248 | if empty then true else | ||
249 | if __mn_attr_has_aux e head then __mn_attr_has e.${head} tail | ||
250 | else false); | ||
251 | __mn_attr_select = e: | ||
252 | __mn_matchList ({ head, tail, empty }: | ||
253 | if empty then e | ||
254 | else __mn_attr_select e.${head} tail); | ||
255 | __mn_attr_selectOr = e: as: d: | ||
256 | if __mn_attr_has e as | ||
257 | then __mn_attr_select e as | ||
258 | else d; | ||
259 | __mn_assert = e1: e2: | ||
260 | if e1 then e2 else abort null; | ||
261 | |||
262 | __mn_consAttrs = as: acc: | ||
263 | __mn_foldr | ||
264 | (x: acc: acc // { | ||
265 | ${x} = [ as.${x} ] ++ (acc.${x} or [ ]); | ||
266 | }) | ||
267 | acc | ||
268 | (attrNames as); | ||
269 | __mn_zipAttrs = __mn_foldr __mn_consAttrs { }; | ||
270 | |||
271 | # Old merge sort algorithm, taken from GHC.Internal.Data.OldList. | ||
272 | __mn_mergesort = cmp: xs: __mn_mergesort' cmp (__mn_singletons xs); | ||
273 | __mn_singletons = map (x: [ x ]); | ||
274 | __mn_mergesort' = cmp: xs: | ||
275 | __mn_matchList | ||
276 | ({ head, tail, empty }: | ||
277 | if empty then [ ] else if tail == [ ] then head else | ||
278 | __mn_mergesort' cmp (__mn_mergePairs cmp xs)) | ||
279 | xs; | ||
280 | __mn_mergePairs = cmp: | ||
281 | __mn_matchList ({ head, tail, empty }: if empty then [ ] else | ||
282 | let xs' = head; in __mn_matchList | ||
283 | ({ head, tail, empty }: | ||
284 | if empty then [ xs' ] else | ||
285 | let ys' = head; xss' = tail; in | ||
286 | [ (__mn_merge cmp xs' ys') ] ++ __mn_mergePairs cmp xss') | ||
287 | tail); | ||
288 | __mn_merge = cmp: xs: ys: | ||
289 | __mn_matchList | ||
290 | ({ head, tail, empty }: | ||
291 | if empty then ys else | ||
292 | let x = head; xs' = tail; in | ||
293 | __mn_matchList | ||
294 | ({ head, tail, empty }: | ||
295 | if empty then xs else | ||
296 | let y = head; ys' = tail; in | ||
297 | if cmp y x | ||
298 | then [ y ] ++ __mn_merge cmp xs ys' # y < x, i.e., x > y | ||
299 | else [ x ] ++ __mn_merge cmp xs' ys) | ||
300 | ys) | ||
301 | xs; | ||
302 | } | ||
diff --git a/lib/mininix/conv.ml b/lib/mininix/conv.ml new file mode 100644 index 0000000..8062099 --- /dev/null +++ b/lib/mininix/conv.ml | |||
@@ -0,0 +1,96 @@ | |||
1 | open Core | ||
2 | |||
3 | let _ = assert (Sys.word_size_in_bits = 64) | ||
4 | let chlist s = String.to_list s | ||
5 | let ( <> ) l1 l2 = not (List.equal Char.( = ) l1 l2) | ||
6 | let str = String.of_char_list | ||
7 | let prec = 53 | ||
8 | let emax = 1024 | ||
9 | let exp_bits = 11 | ||
10 | let saturated_exp = Int.shift_left 1 exp_bits - 1 | ||
11 | |||
12 | let rec int_bits (x : int) : bool list = | ||
13 | if Int.(x < 0) then raise (Invalid_argument "Number must be nonnegative") | ||
14 | else if Int.(x = 0) then [] | ||
15 | else | ||
16 | let q = x /% 2 and r = x % 2 in | ||
17 | int_bits q @ [ r = 1 ] | ||
18 | |||
19 | let int_to_positive (x : int) : Extraction.Internal.BinNums.positive = | ||
20 | if x <= 0 then raise (Invalid_argument "Number must be positive") | ||
21 | else | ||
22 | let bits = List.tl_exn (int_bits x) in | ||
23 | List.fold_left | ||
24 | ~f:(fun acc digit -> | ||
25 | if digit then Extraction.Internal.BinNums.Coq_xI acc | ||
26 | else Extraction.Internal.BinNums.Coq_xO acc) | ||
27 | ~init:Extraction.Internal.BinNums.Coq_xH bits | ||
28 | |||
29 | let int_to_z (x : int) : Extraction.Internal.BinNums.coq_Z = | ||
30 | if x = 0 then Z0 | ||
31 | else if x < 0 then Zneg (int_to_positive (-x)) | ||
32 | else Zpos (int_to_positive x) | ||
33 | |||
34 | let rec int63_of_positive (x : Extraction.Internal.BinNums.positive) : Int63.t = | ||
35 | let two = Int63.(succ one) in | ||
36 | match x with | ||
37 | | Coq_xH -> Int63.of_int_exn 1 | ||
38 | | Coq_xO x -> Int63.(two * int63_of_positive x) | ||
39 | | Coq_xI x -> Int63.((two * int63_of_positive x) + one) | ||
40 | |||
41 | let int63_of_z (x : Extraction.Internal.BinNums.coq_Z) : Int63.t = | ||
42 | match x with | ||
43 | | Z0 -> Int63.zero | ||
44 | | Zpos x -> int63_of_positive x | ||
45 | | Zneg x -> Int63.neg (int63_of_positive x) | ||
46 | |||
47 | let int63_to_positive x = Int63.to_int_exn x |> int_to_positive | ||
48 | |||
49 | (* Conversions are the same as those in Coq's FloatOps. | ||
50 | See https://github.com/coq/coq/blob/master/theories/Floats/FloatOps.v *) | ||
51 | |||
52 | let normfr_mantissa f = | ||
53 | let f = Float.abs f in | ||
54 | if Float.(f >= 0.5) && Float.(f < 1.) then Float.to_int (Float.ldexp f prec) | ||
55 | else 0 | ||
56 | |||
57 | let float_to_flocq (x : float) : Extraction.Internal.Floats.float = | ||
58 | match Float.classify x with | ||
59 | | Zero -> Extraction.Internal.Floats.B754_zero (Float.ieee_negative x) | ||
60 | | Nan -> | ||
61 | Extraction.Internal.Floats.B754_nan | ||
62 | (Float.ieee_negative x, Float.ieee_mantissa x |> int63_to_positive) | ||
63 | | Infinite -> Extraction.Internal.Floats.B754_infinity (Float.ieee_negative x) | ||
64 | | Normal | Subnormal -> ( | ||
65 | let prec_z = int_to_z prec and emax_z = int_to_z emax in | ||
66 | let r, exp = Float.frexp x in | ||
67 | let e = int_to_z (exp - prec) and r' = int_to_z (normfr_mantissa r) in | ||
68 | let shr, e' = | ||
69 | Extraction.Internal.Floats.(shr_fexp prec_z emax_z r' e Coq_loc_Exact) | ||
70 | in | ||
71 | match shr.shr_m with | ||
72 | | Zpos p -> B754_finite (Float.is_negative x, p, e') | ||
73 | | Zneg _ | Z0 -> assert false) | ||
74 | |||
75 | let float_from_flocq x : float = | ||
76 | match x with | ||
77 | | Extraction.Internal.Floats.B754_zero s -> | ||
78 | Float.create_ieee_exn ~negative:s ~mantissa:Int63.zero ~exponent:0 | ||
79 | | Extraction.Internal.Floats.B754_infinity s -> | ||
80 | if s then Float.neg_infinity else Float.infinity | ||
81 | | Extraction.Internal.Floats.B754_nan (s, m) -> | ||
82 | let m_int = int63_of_positive m in | ||
83 | Float.create_ieee_exn ~negative:s ~mantissa:m_int ~exponent:saturated_exp | ||
84 | | Extraction.Internal.Floats.B754_finite (s, m, e) -> | ||
85 | let pm = Float.of_int63 (int63_of_positive m) in | ||
86 | let f = Float.ldexp pm (Int63.to_int_exn (int63_of_z e)) in | ||
87 | if s then Float.neg f else f | ||
88 | |||
89 | open struct | ||
90 | open Base_quickcheck | ||
91 | |||
92 | let%expect_test "float conversion" = | ||
93 | Test.run_exn | ||
94 | (module Float) | ||
95 | ~f:(fun x -> [%test_eq: float] (float_from_flocq (float_to_flocq x)) x) | ||
96 | end | ||
diff --git a/lib/mininix/dune b/lib/mininix/dune new file mode 100644 index 0000000..aabbf45 --- /dev/null +++ b/lib/mininix/dune | |||
@@ -0,0 +1,15 @@ | |||
1 | (library | ||
2 | (name mininix) | ||
3 | (inline_tests) | ||
4 | (preprocessor_deps | ||
5 | (file builtins.nix)) | ||
6 | (preprocess | ||
7 | (pps | ||
8 | ppx_blob | ||
9 | ppx_sexp_conv | ||
10 | ppx_expect | ||
11 | ppx_assert | ||
12 | base_quickcheck.ppx_quickcheck)) | ||
13 | (instrumentation | ||
14 | (backend bisect_ppx)) | ||
15 | (libraries core extraction nix ppx_blob ppx_sexp_conv)) | ||
diff --git a/lib/mininix/import.ml b/lib/mininix/import.ml new file mode 100644 index 0000000..ca1bfb5 --- /dev/null +++ b/lib/mininix/import.ml | |||
@@ -0,0 +1,54 @@ | |||
1 | open Core | ||
2 | |||
3 | exception ImportError of string | ||
4 | |||
5 | type tree = { filename : string; deps : forest } | ||
6 | and forest = tree list | ||
7 | |||
8 | let provide (imports : (string * Extraction.coq_val) list) = | ||
9 | let imports_set = | ||
10 | Extraction.( | ||
11 | VAttr | ||
12 | (List.fold imports ~init:thunk_map_empty ~f:(fun attrs (filename, v) -> | ||
13 | thunk_map_insert (Conv.chlist filename) (Forced v) attrs))) | ||
14 | in | ||
15 | let make_env = | ||
16 | Extraction.( | ||
17 | env_insert_abs (Conv.chlist "imports") (Forced imports_set) env_empty) | ||
18 | in | ||
19 | Extraction.( | ||
20 | VClo | ||
21 | ( Conv.chlist "path", | ||
22 | make_env, | ||
23 | EBinOp | ||
24 | ( SelectAttrOp, | ||
25 | EId (Conv.chlist "imports", None), | ||
26 | EId (Conv.chlist "path", None) ) )) | ||
27 | |||
28 | let make_env (imports : (string * Extraction.coq_val) list) = | ||
29 | Extraction.( | ||
30 | env_insert_abs (Conv.chlist "import") (Forced (provide imports)) env_empty) | ||
31 | |||
32 | let rec import trees : (string * Extraction.coq_val) list = | ||
33 | List.map trees ~f:(fun { filename; deps } -> | ||
34 | let data = In_channel.read_all filename in | ||
35 | Nix.parse ~filename data |> Nix.elaborate |> Nix2mininix.from_nix | ||
36 | |> Builtins.apply_prelude | ||
37 | |> Run.interp ~fuel:`Unlimited ~mode:`Shallow | ||
38 | ~env:(make_env (import deps)) | ||
39 | |> function | ||
40 | | Res (Some v) -> (filename, v) | ||
41 | | Res None -> | ||
42 | raise | ||
43 | (ImportError | ||
44 | (sprintf "Could not import %s: Failed to evaluate" filename)) | ||
45 | | NoFuel -> assert false) | ||
46 | |||
47 | let rec tree_map ~(f : string -> string) { filename; deps } = | ||
48 | { filename = f filename; deps = forest_map ~f deps } | ||
49 | |||
50 | and forest_map ~(f : string -> string) trees = List.map ~f:(tree_map ~f) trees | ||
51 | |||
52 | (* [relative_to] must be an absolute path *) | ||
53 | let materialize forest ~relative_to : (string * Extraction.coq_val) list = | ||
54 | forest_map forest ~f:(Filename.to_absolute_exn ~relative_to) |> import | ||
diff --git a/lib/mininix/mininix.ml b/lib/mininix/mininix.ml new file mode 100644 index 0000000..b121619 --- /dev/null +++ b/lib/mininix/mininix.ml | |||
@@ -0,0 +1,13 @@ | |||
1 | module Nix2mininix = Nix2mininix | ||
2 | module Mininix2nix = Mininix2nix | ||
3 | module Sexp = Sexp | ||
4 | module Import = Import | ||
5 | |||
6 | let interp_tl ~fuel ~mode ?(imports = []) e = | ||
7 | Run.interp ~fuel ~mode ~env:(Import.make_env imports) e | ||
8 | |||
9 | let apply_prelude = Builtins.apply_prelude | ||
10 | |||
11 | let preprocess input ~filename = | ||
12 | input |> Nix.parse ~filename |> Nix.elaborate |> Nix2mininix.from_nix | ||
13 | |> Builtins.apply_prelude | ||
diff --git a/lib/mininix/mininix2nix.ml b/lib/mininix/mininix2nix.ml new file mode 100644 index 0000000..efbc42a --- /dev/null +++ b/lib/mininix/mininix2nix.ml | |||
@@ -0,0 +1,54 @@ | |||
1 | open Conv | ||
2 | open Core | ||
3 | |||
4 | (* [or] is not a 'strong' keyword. That means that 'it depends' whether it is | ||
5 | identified as such. In the context of the left-hand side of an attribute, it | ||
6 | is not recognized as such. *) | ||
7 | let strong_keywords = | ||
8 | [ "with"; "rec"; "let"; "in"; "inherit"; "if"; "then"; "else"; "assert" ] | ||
9 | |||
10 | let id_re = Str.regexp {|^[A-Za-z_]+[A-Za-z0-9'_-]*$|} | ||
11 | |||
12 | let is_simple_id s = | ||
13 | Str.string_match id_re s 0 | ||
14 | && not (List.exists strong_keywords ~f:(String.( = ) s)) | ||
15 | |||
16 | let thunk_map_to_map tm = | ||
17 | Extraction.thunk_map_fold | ||
18 | (fun k t -> Map.add_exn ~key:(String.of_char_list k) ~data:t) | ||
19 | (Map.empty (module String)) | ||
20 | tm | ||
21 | |||
22 | let from_lit l = | ||
23 | match l with | ||
24 | | Extraction.LitString s -> Nix.Ast.Val (Nix.Ast.Str (str s, [])) | ||
25 | | Extraction.LitNull -> Nix.Ast.Id "null" | ||
26 | | Extraction.LitBool b -> Nix.Ast.Id (if b then "true" else "false") | ||
27 | | Extraction.LitNum x -> | ||
28 | Nix.Ast.Val | ||
29 | (match x with | ||
30 | | Extraction.NInt x -> Nix.Ast.Int (x |> Extraction.string_of_Z |> str) | ||
31 | | Extraction.NFloat x -> | ||
32 | Nix.Ast.Float (Printf.sprintf "%g" (float_from_flocq x))) | ||
33 | |||
34 | let rec from_val = function | ||
35 | | Extraction.VClo _ | Extraction.VCloMatch _ -> Nix.Ast.Id "<CODE>" | ||
36 | | Extraction.VLit l -> from_lit l | ||
37 | | Extraction.VAttr bs -> | ||
38 | let bs = | ||
39 | thunk_map_to_map bs | ||
40 | |> Map.to_alist ~key_order:`Increasing | ||
41 | |> List.map ~f:(fun (x, t) -> | ||
42 | let lhs = | ||
43 | if is_simple_id x then Nix.Ast.Id x | ||
44 | else Nix.Ast.Val (Nix.Ast.Str (x, [])) | ||
45 | in | ||
46 | Nix.Ast.AttrPath ([ lhs ], from_thunk t)) | ||
47 | in | ||
48 | Nix.Ast.Val (Nix.Ast.AttSet (Nix.Ast.Nonrec, bs)) | ||
49 | | Extraction.VList ts -> Nix.Ast.Val (Nix.Ast.List List.(ts >>| from_thunk)) | ||
50 | |||
51 | and from_thunk = function | ||
52 | | Extraction.Thunk (_, ELit l) -> from_lit l | ||
53 | | Extraction.Thunk _ | Extraction.Indirect _ -> Nix.Ast.Id "<CODE>" | ||
54 | | Extraction.Forced v -> from_val v | ||
diff --git a/lib/mininix/nix2mininix.ml b/lib/mininix/nix2mininix.ml new file mode 100644 index 0000000..cfd4fa3 --- /dev/null +++ b/lib/mininix/nix2mininix.ml | |||
@@ -0,0 +1,254 @@ | |||
1 | open Conv | ||
2 | open Core | ||
3 | |||
4 | exception FromNixError of string | ||
5 | |||
6 | let try_insert_attr x e bs = | ||
7 | let x = chlist x in | ||
8 | if Extraction.attr_set_contains x bs then | ||
9 | raise (FromNixError "Attribute already exists") | ||
10 | else Extraction.attr_set_insert x e bs | ||
11 | |||
12 | (* Shorthands, minor conversions *) | ||
13 | |||
14 | let mn_singleton_set x e = | ||
15 | Extraction.( | ||
16 | EAttr (attr_set_insert (chlist x) (Attr (NONREC, e)) attr_set_empty)) | ||
17 | |||
18 | let mn_abs args e = | ||
19 | List.fold_right args ~init:e ~f:(fun arg e' -> | ||
20 | Extraction.EAbs (chlist arg, e')) | ||
21 | |||
22 | let mn_lit l = Extraction.ELit l | ||
23 | let mn_int x = mn_lit (Extraction.LitNum (Extraction.NInt x)) | ||
24 | let mn_float x = mn_lit (Extraction.LitNum (Extraction.NFloat x)) | ||
25 | let mn_bool b = mn_lit (Extraction.LitBool b) | ||
26 | let mn_true = mn_bool true | ||
27 | let mn_false = mn_bool false | ||
28 | let mn_str s = mn_lit (Extraction.LitString (chlist s)) | ||
29 | let mn_null = mn_lit Extraction.LitNull | ||
30 | let mn_id x = Extraction.EId (chlist x, None) | ||
31 | let mn_app e1 e2 = Extraction.EApp (e1, e2) | ||
32 | let mn_seq e1 e2 = Extraction.ESeq (Extraction.SHALLOW, e1, e2) | ||
33 | let mn_deep_seq e1 e2 = Extraction.ESeq (Extraction.DEEP, e1, e2) | ||
34 | let mn_list es = Extraction.EList es | ||
35 | |||
36 | let mn_attr (bs : (string * [ `Rec | `Nonrec ] * Extraction.expr) list) = | ||
37 | Extraction.EAttr | ||
38 | (List.fold_left bs ~init:Extraction.attr_set_empty ~f:(fun bs' (x, r, e) -> | ||
39 | let r' = | ||
40 | match r with `Rec -> Extraction.REC | `Nonrec -> Extraction.NONREC | ||
41 | in | ||
42 | Extraction.attr_set_insert (chlist x) (Extraction.Attr (r', e)) bs')) | ||
43 | |||
44 | let mn_with e1 e2 = Extraction.ELetAttr (Extraction.WITH, e1, e2) | ||
45 | let mn_binop op e1 e2 = Extraction.EBinOp (op, e1, e2) | ||
46 | let mn_add e1 e2 = mn_binop Extraction.AddOp e1 e2 | ||
47 | let mn_sub e1 e2 = mn_binop Extraction.SubOp e1 e2 | ||
48 | let mn_mul e1 e2 = mn_binop Extraction.MulOp e1 e2 | ||
49 | let mn_div e1 e2 = mn_binop Extraction.DivOp e1 e2 | ||
50 | let mn_bit_and e1 e2 = mn_binop Extraction.AndOp e1 e2 | ||
51 | let mn_bit_or e1 e2 = mn_binop Extraction.OrOp e1 e2 | ||
52 | let mn_bit_xor e1 e2 = mn_binop Extraction.XOrOp e1 e2 | ||
53 | let mn_lt e1 e2 = mn_binop Extraction.LtOp e1 e2 | ||
54 | let mn_eq e1 e2 = mn_binop Extraction.EqOp e1 e2 | ||
55 | let mn_if e1 e2 e3 = Extraction.EIf (e1, e2, e3) | ||
56 | let mn_delete_attr e1 e2 = mn_binop Extraction.DeleteAttrOp e1 e2 | ||
57 | let mn_has_attr e1 e2 = mn_binop Extraction.HasAttrOp e1 e2 | ||
58 | let mn_select_attr e1 e2 = mn_binop Extraction.SelectAttrOp e1 e2 | ||
59 | |||
60 | let mn_singleton_attr e1 e2 = | ||
61 | mn_app (mn_binop Extraction.SingletonAttrOp e1 mn_null) e2 | ||
62 | |||
63 | let mn_update_attr e1 e2 = mn_binop Extraction.UpdateAttrOp e1 e2 | ||
64 | let mn_type_of e = mn_binop Extraction.TypeOfOp e mn_null | ||
65 | let mn_function_args e = mn_binop Extraction.FunctionArgsOp e mn_null | ||
66 | let mn_list_append e1 e2 = mn_binop Extraction.AppendListOp e1 e2 | ||
67 | let mn_list_match e = mn_binop Extraction.MatchListOp e mn_null | ||
68 | let mn_string_match e = mn_binop Extraction.MatchStringOp e mn_null | ||
69 | let mn_attr_match e = mn_binop Extraction.MatchAttrOp e mn_null | ||
70 | let mn_ceil e = mn_binop (Extraction.RoundOp Ceil) e mn_null | ||
71 | let mn_nearest_even e = mn_binop (Extraction.RoundOp NearestEven) e mn_null | ||
72 | let mn_floor e = mn_binop (Extraction.RoundOp Floor) e mn_null | ||
73 | |||
74 | (* Macros *) | ||
75 | |||
76 | let mn_cast_bool e = mn_if e mn_true mn_false | ||
77 | let mn_or e1 e2 = mn_if e1 mn_true (mn_cast_bool e2) | ||
78 | let mn_and e1 e2 = mn_if e1 (mn_cast_bool e2) mn_false | ||
79 | let mn_impl e1 e2 = mn_if e1 (mn_cast_bool e2) mn_true | ||
80 | let mn_not e = mn_if e mn_false mn_true | ||
81 | let mn_negate e = mn_sub (mn_int Extraction.Internal.BinNums.Z0) e | ||
82 | let mn_neq e1 e2 = mn_not (mn_eq e2 e1) | ||
83 | let mn_gt e1 e2 = mn_lt e2 e1 | ||
84 | let mn_lte e1 e2 = mn_not (mn_gt e1 e2) | ||
85 | let mn_gte e1 e2 = mn_not (mn_lt e1 e2) | ||
86 | |||
87 | (* Macros based on exported functions from the prelude *) | ||
88 | |||
89 | let mnbi_assert e1 e2 = mn_app (mn_app (mn_id "__mn_assert") e1) e2 | ||
90 | let mnbi_has_attr e ds = mn_app (mn_app (mn_id "__mn_attr_has") e) (mn_list ds) | ||
91 | let mnbi_select e ds = mn_app (mn_app (mn_id "__mn_attr_select") e) (mn_list ds) | ||
92 | |||
93 | let mnbi_select_or e1 ds e2 = | ||
94 | mn_app (mn_app (mn_app (mn_id "__mn_attr_selectOr") e1) (mn_list ds)) e2 | ||
95 | |||
96 | let mnbi_insert_new e1 e2 e3 = | ||
97 | mn_app (mn_app (mn_app (mn_id "__mn_attr_insertNew") e1) e2) e3 | ||
98 | |||
99 | let is_dynamic_binding (b : Nix.Ast.binding) = | ||
100 | match b with | ||
101 | | Nix.Ast.AttrPath ([ Nix.Ast.Val (Nix.Ast.Str (_, [])) ], _) | ||
102 | | Nix.Ast.Inherit _ -> | ||
103 | false | ||
104 | | Nix.Ast.AttrPath ([ _ ], _) -> true | ||
105 | | _ -> assert false | ||
106 | |||
107 | let has_dynamic_bindings (bs : Nix.Ast.binding list) = | ||
108 | List.exists bs ~f:is_dynamic_binding | ||
109 | |||
110 | (* Static bindings left, dynamic bindings right *) | ||
111 | let partition_dynamic (bs : Nix.Ast.binding list) : | ||
112 | Nix.Ast.binding list * Nix.Ast.binding list = | ||
113 | List.fold_left bs ~init:([], []) ~f:(fun (static, dynamic) b -> | ||
114 | if is_dynamic_binding b then (static, b :: dynamic) | ||
115 | else (b :: static, dynamic)) | ||
116 | |||
117 | (* Precondition: e must be have been processed by the elaborator. *) | ||
118 | let rec from_nix e = | ||
119 | match e with | ||
120 | | Nix.Ast.BinaryOp (op, e1, e2) -> ( | ||
121 | let e1', e2' = (from_nix e1, from_nix e2) in | ||
122 | match op with | ||
123 | | Nix.Ast.Plus -> mn_add e1' e2' | ||
124 | | Nix.Ast.Minus -> mn_sub e1' e2' | ||
125 | | Nix.Ast.Mult -> mn_mul e1' e2' | ||
126 | | Nix.Ast.Div -> mn_div e1' e2' | ||
127 | | Nix.Ast.Gt -> mn_gt e1' e2' | ||
128 | | Nix.Ast.Lt -> mn_lt e1' e2' | ||
129 | | Nix.Ast.Lte -> mn_lte e1' e2' | ||
130 | | Nix.Ast.Gte -> mn_gte e1' e2' | ||
131 | | Nix.Ast.Eq -> mn_eq e1' e2' | ||
132 | | Nix.Ast.Neq -> mn_neq e1' e2' | ||
133 | | Nix.Ast.Or -> mn_or e1' e2' | ||
134 | | Nix.Ast.And -> mn_and e1' e2' | ||
135 | | Nix.Ast.Impl -> mn_impl e1' e2' | ||
136 | | Nix.Ast.Merge -> mn_update_attr e1' e2' | ||
137 | | Nix.Ast.Concat -> mn_list_append e1' e2') | ||
138 | | Nix.Ast.UnaryOp (op, e) -> ( | ||
139 | let e = from_nix e in | ||
140 | match op with Nix.Ast.Negate -> mn_negate e | Nix.Ast.Not -> mn_not e) | ||
141 | | Nix.Ast.Cond (e1, e2, e3) -> mn_if (from_nix e1) (from_nix e2) (from_nix e3) | ||
142 | | Nix.Ast.With (e1, e2) -> mn_with (from_nix e1) (from_nix e2) | ||
143 | | Nix.Ast.Assert (e1, e2) -> mnbi_assert (from_nix e1) (from_nix e2) | ||
144 | | Nix.Ast.Test (e, ds) -> mnbi_has_attr (from_nix e) List.(ds >>| from_nix) | ||
145 | | Nix.Ast.SetLet bs -> | ||
146 | from_nix | ||
147 | (Nix.Ast.Select | ||
148 | ( Nix.Ast.Val (Nix.Ast.AttSet (Nix.Ast.Rec, bs)), | ||
149 | [ Nix.Ast.Val (Nix.Ast.Str ("body", [])) ], | ||
150 | None )) | ||
151 | | Nix.Ast.Let (bs, e2) -> | ||
152 | if has_dynamic_bindings bs then | ||
153 | raise (FromNixError "Let bindings may not be dynamic"); | ||
154 | let e1 = from_nix (Nix.Ast.Val (Nix.Ast.AttSet (Nix.Ast.Rec, bs))) in | ||
155 | Extraction.ELetAttr (Extraction.ABS, e1, from_nix e2) | ||
156 | | Nix.Ast.Val v -> from_nix_val v | ||
157 | | Nix.Ast.Id x -> mn_id x | ||
158 | | Nix.Ast.Select (e, parts, md) -> ( | ||
159 | match md with | ||
160 | | Some d -> | ||
161 | mnbi_select_or (from_nix e) List.(parts >>| from_nix) (from_nix d) | ||
162 | | None -> ( | ||
163 | match parts with | ||
164 | | [ part ] -> mn_select_attr (from_nix e) (from_nix part) | ||
165 | | _ -> mnbi_select (from_nix e) List.(parts >>| from_nix))) | ||
166 | | Nix.Ast.Apply (e1, e2) -> mn_app (from_nix e1) (from_nix e2) | ||
167 | | Nix.Ast.Aquote _ -> | ||
168 | assert false (* should be gone after processing by elaborator *) | ||
169 | |||
170 | and from_nix_val v = | ||
171 | match v with | ||
172 | | Str (s, parts) -> | ||
173 | let parts = List.(parts >>= fun (e, s) -> [ from_nix e; mn_str s ]) in | ||
174 | List.fold_left parts ~init:(mn_str s) ~f:mn_add | ||
175 | | IStr _ -> raise (FromNixError "Indented strings are not supported") | ||
176 | | Int n -> ( | ||
177 | match Extraction.string_to_Z (chlist n) with | ||
178 | | Some n -> mn_int n | ||
179 | | None -> raise (FromNixError "Bad integer literal")) | ||
180 | | Float n -> | ||
181 | let n = | ||
182 | try Float.of_string n | ||
183 | with Invalid_argument _ -> raise (FromNixError "Bad float literal") | ||
184 | in | ||
185 | if Float.(is_nan n || is_inf n) then | ||
186 | raise (FromNixError "Bad float literal") | ||
187 | else mn_float (float_to_flocq n) | ||
188 | | Path _ | SPath _ | HPath _ -> raise (FromNixError "Paths are not supported") | ||
189 | | Uri s -> mn_str s | ||
190 | | Lambda (Alias x, e) -> mn_abs [ x ] (from_nix e) | ||
191 | | Lambda (ParamSet (Some x, fs), e) -> | ||
192 | from_nix_val | ||
193 | (Lambda (Alias x, Apply (Val (Lambda (ParamSet (None, fs), e)), Id x))) | ||
194 | | Lambda (ParamSet (None, (fs, strictness)), e) -> | ||
195 | let ms = | ||
196 | List.fold_left fs ~init:Extraction.matcher_empty ~f:(fun ms (x, me) -> | ||
197 | Extraction.matcher_insert (chlist x) (Option.map ~f:from_nix me) ms) | ||
198 | in | ||
199 | Extraction.EAbsMatch | ||
200 | ( ms, | ||
201 | (match strictness with Loose -> false | Exact -> true), | ||
202 | from_nix e ) | ||
203 | | List es -> mn_list (List.map es ~f:from_nix) | ||
204 | | AttSet (recursivity, bs) -> | ||
205 | let static, dynamic = partition_dynamic bs | ||
206 | and recursivity' = | ||
207 | match recursivity with | ||
208 | | Nix.Ast.Rec -> Extraction.REC | ||
209 | | Nix.Ast.Nonrec -> Extraction.NONREC | ||
210 | in | ||
211 | |||
212 | let set_no_dyn = | ||
213 | Extraction.EAttr | ||
214 | (List.fold_left static ~init:Extraction.attr_set_empty | ||
215 | ~f:(fun static' bnd -> | ||
216 | match bnd with | ||
217 | | Nix.Ast.AttrPath ([ Nix.Ast.Val (Nix.Ast.Str (x, [])) ], e) -> | ||
218 | try_insert_attr x | ||
219 | (Extraction.Attr (recursivity', from_nix e)) | ||
220 | static' | ||
221 | | Nix.Ast.Inherit (None, xs) -> | ||
222 | List.fold_left xs ~init:static' ~f:(fun static' x -> | ||
223 | match x with | ||
224 | | Id x -> | ||
225 | try_insert_attr x | ||
226 | (Extraction.Attr (Extraction.NONREC, mn_id x)) | ||
227 | static' | ||
228 | | _ -> assert false) | ||
229 | | Nix.Ast.Inherit (Some e, xs) -> | ||
230 | let e = from_nix e in | ||
231 | List.fold_left xs ~init:static' ~f:(fun static' x -> | ||
232 | match x with | ||
233 | | Id x -> | ||
234 | try_insert_attr x | ||
235 | (Extraction.Attr | ||
236 | (recursivity', mn_select_attr e (mn_str x))) | ||
237 | static' | ||
238 | | _ -> assert false) | ||
239 | | _ -> assert false)) | ||
240 | in | ||
241 | |||
242 | List.fold_right dynamic ~init:set_no_dyn ~f:(fun bnd set -> | ||
243 | match bnd with | ||
244 | | Nix.Ast.AttrPath ([ d ], e) -> | ||
245 | mnbi_insert_new set | ||
246 | (match recursivity with | ||
247 | | Nix.Ast.Rec -> | ||
248 | Extraction.ELetAttr (Extraction.ABS, set_no_dyn, from_nix d) | ||
249 | | Nix.Ast.Nonrec -> from_nix d) | ||
250 | (match recursivity with | ||
251 | | Nix.Ast.Rec -> | ||
252 | Extraction.ELetAttr (Extraction.ABS, set_no_dyn, from_nix e) | ||
253 | | Nix.Ast.Nonrec -> from_nix e) | ||
254 | | _ -> assert false) | ||
diff --git a/lib/mininix/run.ml b/lib/mininix/run.ml new file mode 100644 index 0000000..f33bace --- /dev/null +++ b/lib/mininix/run.ml | |||
@@ -0,0 +1,17 @@ | |||
1 | open Core | ||
2 | |||
3 | (* The [n]th Church numeral *) | ||
4 | let rec church n f x = if n <= 0 then x else church (n - 1) f (f x) | ||
5 | |||
6 | let limited = | ||
7 | church 2048 | ||
8 | (fun x -> Extraction.Internal.Datatypes.S x) | ||
9 | Extraction.Internal.Datatypes.O | ||
10 | |||
11 | let rec infinity = Extraction.Internal.Datatypes.S infinity | ||
12 | |||
13 | let interp ~fuel ~mode ~env e = | ||
14 | let mode : Extraction.mode = | ||
15 | match mode with `Shallow -> SHALLOW | `Deep -> DEEP | ||
16 | and fuel = match fuel with `Unlimited -> infinity | `Limited -> limited in | ||
17 | Extraction.interp' fuel mode env e | ||
diff --git a/lib/mininix/sexp.ml b/lib/mininix/sexp.ml new file mode 100644 index 0000000..95da655 --- /dev/null +++ b/lib/mininix/sexp.ml | |||
@@ -0,0 +1,160 @@ | |||
1 | open Conv | ||
2 | open Core | ||
3 | open Extraction | ||
4 | |||
5 | exception ToSexpError of string | ||
6 | |||
7 | let tag t l = Sexp.List (Sexp.Atom t :: l) | ||
8 | |||
9 | let lit_to_sexp = function | ||
10 | | LitString s -> tag "LitString" [ Sexp.Atom (str s) ] | ||
11 | | LitNum (NInt n) -> | ||
12 | tag "LitNum" [ Sexp.Atom "INT"; Sexp.Atom (str (string_of_Z n)) ] | ||
13 | | LitNum (NFloat n) -> | ||
14 | tag "LitNum" | ||
15 | [ | ||
16 | Sexp.Atom "FLOAT"; | ||
17 | Sexp.Atom (Printf.sprintf "%g" (float_from_flocq n)); | ||
18 | ] | ||
19 | | LitBool b -> tag "LitBool" [ Sexp.Atom (Bool.to_string b) ] | ||
20 | | LitNull -> tag "LitNull" [] | ||
21 | |||
22 | let option_to_sexp mv ~f = | ||
23 | match mv with Some v -> tag "Some" [ f v ] | None -> Sexp.Atom "None" | ||
24 | |||
25 | let mode_to_sexp mode = | ||
26 | Sexp.Atom (match mode with SHALLOW -> "SHALLOW" | DEEP -> "DEEP") | ||
27 | |||
28 | let rec_to_sexp r = Sexp.Atom (match r with REC -> "REC" | NONREC -> "NONREC") | ||
29 | |||
30 | let binop_to_sexp op = | ||
31 | Sexp.Atom | ||
32 | (match op with | ||
33 | | UpdateAttrOp -> "UpdateAttrOp" | ||
34 | | AddOp -> "AddOp" | ||
35 | | SubOp -> "SubOp" | ||
36 | | MulOp -> "MulOp" | ||
37 | | DivOp -> "DivOp" | ||
38 | | AndOp -> "AndOp" | ||
39 | | OrOp -> "OrOp" | ||
40 | | XOrOp -> "XOrOp" | ||
41 | | RoundOp Ceil -> "Ceil" | ||
42 | | RoundOp NearestEven -> "NearestEven" | ||
43 | | RoundOp Floor -> "Floor" | ||
44 | | LtOp -> "LtOp" | ||
45 | | EqOp -> "EqOp" | ||
46 | | HasAttrOp -> "HasAttrOp" | ||
47 | | SelectAttrOp -> "SelectAttrOp" | ||
48 | | DeleteAttrOp -> "DeleteAttrOp" | ||
49 | | SingletonAttrOp -> "SingletonAttrOp" | ||
50 | | TypeOfOp -> "TypeOfOp" | ||
51 | | AppendListOp -> "AppendListOp" | ||
52 | | MatchAttrOp -> "MatchAttrOp" | ||
53 | | MatchListOp -> "MatchListOp" | ||
54 | | MatchStringOp -> "MatchStringOp" | ||
55 | | FunctionArgsOp -> "FunctionArgsOp") | ||
56 | |||
57 | let kind_to_sexp k = Sexp.Atom (match k with ABS -> "ABS" | WITH -> "WITH") | ||
58 | |||
59 | let rec expr_to_sexp = function | ||
60 | | ELit l -> tag "ELit" [ lit_to_sexp l ] | ||
61 | | EId (x, None) -> tag "EId" [ Sexp.Atom (str x) ] | ||
62 | | EId (x, Some (k, e)) -> | ||
63 | tag "EId" | ||
64 | [ Sexp.Atom (str x); tag "alt" [ kind_to_sexp k; expr_to_sexp e ] ] | ||
65 | | EAbs (x, e) -> tag "EAbs" [ Sexp.Atom (str x); expr_to_sexp e ] | ||
66 | | EAbsMatch (ms, strict, e) -> | ||
67 | tag "EAbsMatch" | ||
68 | [ | ||
69 | Sexp.Atom (if strict then "EXACT" else "LOOSE"); | ||
70 | tag "formals" | ||
71 | (matcher_fold | ||
72 | (fun x me se -> | ||
73 | Sexp.List | ||
74 | [ Sexp.Atom (str x); option_to_sexp me ~f:expr_to_sexp ] | ||
75 | :: se) | ||
76 | [] ms); | ||
77 | expr_to_sexp e; | ||
78 | ] | ||
79 | | EApp (e1, e2) -> tag "EApp" [ expr_to_sexp e1; expr_to_sexp e2 ] | ||
80 | | ELetAttr (k, e1, e2) -> | ||
81 | tag "ELetAttr" [ kind_to_sexp k; expr_to_sexp e1; expr_to_sexp e2 ] | ||
82 | | ESeq (mode, e1, e2) -> | ||
83 | tag "ESeq" [ mode_to_sexp mode; expr_to_sexp e1; expr_to_sexp e2 ] | ||
84 | | EAttr bs -> | ||
85 | tag "EAttr" | ||
86 | (attr_set_fold | ||
87 | (fun x (Attr (r, e)) se -> | ||
88 | Sexp.List [ Sexp.Atom (str x); rec_to_sexp r; expr_to_sexp e ] | ||
89 | :: se) | ||
90 | [] bs) | ||
91 | | EList es -> | ||
92 | tag "EList" | ||
93 | (Internal.List.fold_right (fun e se -> expr_to_sexp e :: se) [] es) | ||
94 | | EBinOp (op, e1, e2) -> | ||
95 | tag "EBinOp" [ binop_to_sexp op; expr_to_sexp e1; expr_to_sexp e2 ] | ||
96 | | EIf (e1, e2, e3) -> | ||
97 | tag "EIf" [ expr_to_sexp e1; expr_to_sexp e2; expr_to_sexp e3 ] | ||
98 | |||
99 | let rec val_to_sexp = function | ||
100 | | VLit l -> tag "VLit" [ lit_to_sexp l ] | ||
101 | | VClo _ -> tag "VClo" [] | ||
102 | | VCloMatch _ -> tag "VCloMatch" [] | ||
103 | | VAttr bs -> | ||
104 | tag "VAttr" | ||
105 | (Extraction.thunk_map_fold | ||
106 | (fun x t bs' -> | ||
107 | Sexp.List [ Sexp.Atom (str x); thunk_to_sexp t ] :: bs') | ||
108 | [] bs) | ||
109 | | VList ts -> | ||
110 | tag "VList" | ||
111 | (Internal.List.fold_right (fun t st -> thunk_to_sexp t :: st) [] ts) | ||
112 | |||
113 | and env_to_sexp env = | ||
114 | tag "Env" | ||
115 | (Extraction.env_fold | ||
116 | (fun x (k, t) envs -> | ||
117 | Sexp.List | ||
118 | [ | ||
119 | Sexp.Atom (str x); | ||
120 | Sexp.Atom | ||
121 | (match k with | ||
122 | | Extraction.ABS -> "ABS" | ||
123 | | Extraction.WITH -> "WITH"); | ||
124 | thunk_to_sexp t; | ||
125 | ] | ||
126 | :: envs) | ||
127 | [] env) | ||
128 | |||
129 | and thunk_to_sexp = function | ||
130 | | Thunk _ -> tag "Thunk" [ Sexp.Atom "DELAYED" ] | ||
131 | | Indirect _ -> tag "Thunk" [ Sexp.Atom "INDIRECT" ] | ||
132 | | Forced v -> tag "Thunk" [ Sexp.Atom "FORCED"; val_to_sexp v ] | ||
133 | |||
134 | let expr_res_to_sexp = function | ||
135 | | NoFuel -> Sexp.Atom "NoFuel" | ||
136 | | Res e -> tag "Res" [ option_to_sexp e ~f:expr_to_sexp ] | ||
137 | |||
138 | let val_res_to_sexp = function | ||
139 | | NoFuel -> Sexp.Atom "NoFuel" | ||
140 | | Res e -> tag "Res" [ option_to_sexp e ~f:val_to_sexp ] | ||
141 | |||
142 | let rec (sexp_of_import_tree : Import.tree -> Sexp.t) = function | ||
143 | | { filename; deps = [] } -> Sexp.Atom filename | ||
144 | | { filename; deps } -> | ||
145 | Sexp.List [ Sexp.Atom filename; sexp_of_import_forest deps ] | ||
146 | |||
147 | and sexp_of_import_forest forest = | ||
148 | Sexp.List (Sexp.Atom "deps" :: List.map forest ~f:sexp_of_import_tree) | ||
149 | |||
150 | exception OfSexpError of string | ||
151 | |||
152 | let rec import_tree_of_sexp : Sexp.t -> Import.tree = function | ||
153 | | Sexp.Atom filename -> { filename; deps = [] } | ||
154 | | Sexp.List [ Sexp.Atom filename; deps ] -> | ||
155 | { filename; deps = import_forest_of_sexp deps } | ||
156 | | _ -> raise (OfSexpError "Could not parse import tree") | ||
157 | |||
158 | and import_forest_of_sexp = function | ||
159 | | Sexp.List (Sexp.Atom "deps" :: deps) -> List.map ~f:import_tree_of_sexp deps | ||
160 | | _ -> raise (OfSexpError "Could not parse import forest") | ||
diff --git a/lib/nix/dune b/lib/nix/dune new file mode 100644 index 0000000..3954c8a --- /dev/null +++ b/lib/nix/dune | |||
@@ -0,0 +1,15 @@ | |||
1 | (menhir | ||
2 | (modules parser) | ||
3 | (flags "--dump" "--strict" "--external-tokens" "Tokens") | ||
4 | (infer true)) | ||
5 | |||
6 | (ocamllex | ||
7 | (modules lexer)) | ||
8 | |||
9 | (library | ||
10 | (name nix) | ||
11 | (preprocess | ||
12 | (pps ppx_sexp_conv)) | ||
13 | (instrumentation | ||
14 | (backend bisect_ppx)) | ||
15 | (libraries core core_unix core_unix.filename_unix pprint ppx_sexp_conv str)) | ||
diff --git a/lib/nix/elaborator.ml b/lib/nix/elaborator.ml new file mode 100644 index 0000000..36ee0d4 --- /dev/null +++ b/lib/nix/elaborator.ml | |||
@@ -0,0 +1,208 @@ | |||
1 | open Core | ||
2 | open Types | ||
3 | |||
4 | (* The Nix elaborator does a few things: | ||
5 | - Attribute paths are transformed into a simple list of expressions: | ||
6 | + Simple identifiers are rewritten to string values | ||
7 | + Antiquotations are rewritten to their component expressions | ||
8 | + Anything else, that is not a string value, is rejected | ||
9 | and raises an exception | ||
10 | - In 'inherit (...) x1 ... xn', x1 ... xn are checked for 'reasonably' being | ||
11 | identifiers, i.e., being one of x, "x" and ${"x"}. | ||
12 | - Nested attribute paths are unfolded and attribute sets are merged where | ||
13 | possible. (Where we mean 'what Nix does' with 'where possible'; see the | ||
14 | comment at the respective function.) | ||
15 | - Paths are turned into strings and made absolute w.r.t. the current | ||
16 | working directory. | ||
17 | - Indented strings are converted to their 'normal' counterpart. *) | ||
18 | |||
19 | exception ElaborateError of string | ||
20 | |||
21 | type attr_set = recursivity * binding list | ||
22 | |||
23 | let set_expr (r, bs) = Val (AttSet (r, bs)) | ||
24 | let get_id = function Id x -> x | _ -> assert false | ||
25 | |||
26 | let rec update_bnd (bs : binding list) (x : string) ~(f : expr option -> expr) = | ||
27 | match bs with | ||
28 | | [] -> [ AttrPath ([ Val (Str (x, [])) ], f None) ] | ||
29 | | AttrPath ([ Val (Str (y, [])) ], e) :: s' when String.(x = y) -> | ||
30 | AttrPath ([ Val (Str (y, [])) ], f (Some e)) :: s' | ||
31 | | Inherit (_, ids) :: _ | ||
32 | when List.exists ids ~f:(fun e -> String.(get_id e = x)) -> | ||
33 | raise (ElaborateError "Cannot update inherit") | ||
34 | | bnd :: s' -> bnd :: update_bnd s' x ~f | ||
35 | |||
36 | let set_update_bnd (r, bs) x ~f = (r, update_bnd bs x ~f) | ||
37 | |||
38 | let rec has_bnd (bs : binding list) (x : string) : bool = | ||
39 | match bs with | ||
40 | | [] -> false | ||
41 | | AttrPath ([ Val (Str (y, [])) ], _) :: _ when String.(x = y) -> true | ||
42 | | Inherit (_, ids) :: _ | ||
43 | when List.exists ids ~f:(fun e -> String.(get_id e = x)) -> | ||
44 | true | ||
45 | | _ :: bs' -> has_bnd bs' x | ||
46 | |||
47 | let merge_bnds bs1 bs2 : binding list = | ||
48 | List.fold_left bs2 ~init:bs1 ~f:(fun bs1' b2 -> | ||
49 | match b2 with | ||
50 | | AttrPath ([ Val (Str (x, [])) ], e) -> | ||
51 | update_bnd bs1' x ~f:(function | ||
52 | | Some _ -> raise (ElaborateError "Duplicated attribute") | ||
53 | | None -> e) | ||
54 | | AttrPath ([ d ], e) -> AttrPath ([ d ], e) :: bs1' | ||
55 | | Inherit (md, xs) -> | ||
56 | if List.for_all xs ~f:(fun e -> not (has_bnd bs1' (get_id e))) then | ||
57 | Inherit (md, xs) :: bs1' | ||
58 | else raise (ElaborateError "Duplicated attribute") | ||
59 | | _ -> assert false) | ||
60 | |||
61 | (* This function intentionally clobbers recursivity, because that is the way | ||
62 | that Nix likes to handle attribute insertion. See | ||
63 | (1) https://github.com/NixOS/nix/issues/9020 | ||
64 | (2) https://github.com/NixOS/nix/issues/11268 | ||
65 | (3) https://github.com/NixOS/nix/pull/11294 *) | ||
66 | let rec insert (bs : binding list) (path : expr list) (e : expr) = | ||
67 | match path with | ||
68 | | [] -> raise (ElaborateError "Cannot insert attribute with empty path") | ||
69 | | [ Val (Str (x, [])) ] -> | ||
70 | update_bnd bs x ~f:(function | ||
71 | | None -> e | ||
72 | | Some (Val (AttSet (r1, bs1))) -> ( | ||
73 | match e with | ||
74 | | Val (AttSet (_, bs2)) -> set_expr (r1, merge_bnds bs1 bs2) | ||
75 | | _ -> raise (ElaborateError "Duplicated attribute")) | ||
76 | | _ -> raise (ElaborateError "Duplicated attribute")) | ||
77 | | Val (Str (x, [])) :: rest -> | ||
78 | update_bnd bs x ~f:(function | ||
79 | | Some (Val (AttSet (r, bs))) -> Val (AttSet (r, insert bs rest e)) | ||
80 | | Some _ -> raise (ElaborateError "Duplicated attribute") | ||
81 | | None -> Val (AttSet (Nonrec, insert [] rest e))) | ||
82 | | [ part ] -> AttrPath ([ part ], e) :: bs | ||
83 | | part :: rest -> | ||
84 | AttrPath ([ part ], Val (AttSet (Nonrec, insert [] rest e))) :: bs | ||
85 | |||
86 | let insert_inherit (bs : binding list) (from : expr option) (es : expr list) = | ||
87 | if List.for_all es ~f:(fun e -> not (has_bnd bs (get_id e))) then | ||
88 | Inherit (from, es) :: bs | ||
89 | else raise (ElaborateError "Duplicated attribute") | ||
90 | |||
91 | let simplify_path_component = function | ||
92 | | Id x -> Val (Str (x, [])) | ||
93 | | Val (Str (s, ess)) -> Val (Str (s, ess)) | ||
94 | | Aquote e -> e | ||
95 | | _ -> raise (ElaborateError "Unexpected path component") | ||
96 | |||
97 | let simplify_path = List.map ~f:simplify_path_component | ||
98 | |||
99 | let simplify_bnd_paths = | ||
100 | List.map ~f:(fun bnd -> | ||
101 | match bnd with | ||
102 | | AttrPath (path, e) -> AttrPath (simplify_path path, e) | ||
103 | | Inherit (me, xs) -> Inherit (me, xs)) | ||
104 | |||
105 | (* Law: concat_lines ∘ split_lines = id *) | ||
106 | |||
107 | let rec split_lines s = | ||
108 | match String.lsplit2 s ~on:'\n' with | ||
109 | | Some (s1, s2) -> s1 :: split_lines s2 | ||
110 | | None -> [ s ] | ||
111 | |||
112 | let rec concat_lines = function | ||
113 | | [] -> "" | ||
114 | | [ x ] -> x | ||
115 | | x :: xs -> x ^ "\n" ^ concat_lines xs | ||
116 | |||
117 | let map_tail ~f = function [] -> [] | x :: xs -> x :: List.map ~f xs | ||
118 | |||
119 | let unindent n s ~skip_first_line = | ||
120 | let map_op ~f = if skip_first_line then map_tail ~f else List.map ~f in | ||
121 | split_lines s | ||
122 | |> map_op ~f:(fun line -> | ||
123 | let expected_prefix = String.make n ' ' in | ||
124 | String.chop_prefix_if_exists ~prefix:expected_prefix line) | ||
125 | |> concat_lines | ||
126 | |||
127 | let is_spaces l = String.(strip l ~drop:(Char.( = ) ' ') |> is_empty) | ||
128 | |||
129 | let drop_first_empty_line s = | ||
130 | match String.lsplit2 s ~on:'\n' with | ||
131 | | Some (l, s') when is_spaces l -> s' | ||
132 | | _ -> s | ||
133 | |||
134 | let rec process ?(dir = None) = function | ||
135 | | BinaryOp (op, e1, e2) -> BinaryOp (op, process ~dir e1, process ~dir e2) | ||
136 | | UnaryOp (op, e) -> UnaryOp (op, process ~dir e) | ||
137 | | Cond (e1, e2, e3) -> Cond (process ~dir e1, process ~dir e2, process ~dir e3) | ||
138 | | With (e1, e2) -> With (process ~dir e1, process ~dir e2) | ||
139 | | Assert (e1, e2) -> Assert (process ~dir e1, process ~dir e2) | ||
140 | | Test (e1, es) -> | ||
141 | Test (process ~dir e1, List.(simplify_path es >>| process ~dir)) | ||
142 | | SetLet bs -> SetLet (process_bnds ~dir bs) | ||
143 | | Let (bs, e) -> Let (process_bnds ~dir bs, process ~dir e) | ||
144 | | Val v -> Val (process_val ~dir v) | ||
145 | | Id x -> Id x | ||
146 | | Select (e, es, me) -> | ||
147 | Select | ||
148 | ( process ~dir e, | ||
149 | List.(simplify_path es >>| process ~dir), | ||
150 | Option.(me >>| process ~dir) ) | ||
151 | | Apply (e1, e2) -> Apply (process ~dir e1, process ~dir e2) | ||
152 | | Aquote e -> Aquote (process ~dir e) | ||
153 | |||
154 | and process_val ~dir = function | ||
155 | | Str (s, ess) -> Str (s, List.(ess >>| fun (e, s) -> (process ~dir e, s))) | ||
156 | | IStr (n, s, ess) -> | ||
157 | let s' = drop_first_empty_line (unindent n s ~skip_first_line:false) | ||
158 | and ess' = | ||
159 | List.map ess ~f:(fun (e, s) -> | ||
160 | (process ~dir e, unindent n s ~skip_first_line:true)) | ||
161 | in | ||
162 | Str (s', ess') | ||
163 | | Lambda (p, e) -> Lambda (process_pattern ~dir p, process ~dir e) | ||
164 | | List es -> List List.(es >>| process ~dir) | ||
165 | | AttSet (r, bs) -> AttSet (r, process_bnds ~dir bs) | ||
166 | | Path p -> ( | ||
167 | if Filename.is_absolute p then Str (p, []) | ||
168 | else | ||
169 | match dir with | ||
170 | | Some dir when Filename.is_absolute dir -> | ||
171 | Str (Filename.concat dir p, []) | ||
172 | | Some _ -> | ||
173 | raise | ||
174 | (ElaborateError "Provided directory should be an absolute path") | ||
175 | | None -> raise (ElaborateError "Do not know how to resolve path")) | ||
176 | | v -> v | ||
177 | |||
178 | and process_bnds ~dir bs = | ||
179 | bs | ||
180 | |> List.map ~f:(function | ||
181 | | AttrPath (es, e) -> | ||
182 | AttrPath (List.(es >>| process ~dir), process ~dir e) | ||
183 | | Inherit (me, xs) -> | ||
184 | Inherit (Option.(me >>| process ~dir), process_inherit_ids xs)) | ||
185 | |> simplify_bnd_paths | ||
186 | |> List.fold ~init:[] ~f:(fun bs' bnd -> | ||
187 | match bnd with | ||
188 | | AttrPath (path, e) -> insert bs' path e | ||
189 | | Inherit (from, es) -> insert_inherit bs' from es) | ||
190 | |||
191 | and process_inherit_ids = | ||
192 | List.map ~f:(function | ||
193 | | Id x | Val (Str (x, [])) | Aquote (Val (Str (x, []))) -> Id x | ||
194 | | _ -> raise (ElaborateError "Unexpected expression in inherit")) | ||
195 | |||
196 | and process_pattern ~dir = function | ||
197 | | Alias x -> Alias x | ||
198 | | ParamSet (mx, (ps, k)) -> ParamSet (mx, (process_param_set ~dir mx ps, k)) | ||
199 | |||
200 | and process_param_set ~dir ?(seen = String.Set.empty) mx ps = | ||
201 | match ps with | ||
202 | | [] -> [] | ||
203 | | (y, me) :: ps' -> | ||
204 | if Set.mem seen y || Option.mem mx y ~equal:String.( = ) then | ||
205 | raise (ElaborateError "Duplicated function argument") | ||
206 | else | ||
207 | (y, Option.(me >>| process ~dir)) | ||
208 | :: process_param_set ~dir mx ps' ~seen:(Set.add seen y) | ||
diff --git a/lib/nix/lexer.mll b/lib/nix/lexer.mll new file mode 100644 index 0000000..023d888 --- /dev/null +++ b/lib/nix/lexer.mll | |||
@@ -0,0 +1,315 @@ | |||
1 | { | ||
2 | open Core | ||
3 | open Tokens | ||
4 | |||
5 | exception Error of string | ||
6 | |||
7 | (* Types of curly braces. | ||
8 | AQUOTE corresponds to the braces for antiquotation, i.e. '${...}' | ||
9 | and SET to an attribute set '{...}'. | ||
10 | *) | ||
11 | type braces = | ||
12 | | AQUOTE | ||
13 | | SET | ||
14 | |||
15 | let print_stack s = | ||
16 | let b = Buffer.create 100 in | ||
17 | Buffer.add_string b "[ "; | ||
18 | List.iter s ~f:(function | ||
19 | | AQUOTE -> Buffer.add_string b "AQUOTE; " | ||
20 | | SET -> Buffer.add_string b "SET; " | ||
21 | ); | ||
22 | Buffer.add_string b "]"; | ||
23 | Buffer.contents b | ||
24 | |||
25 | let token_of_str state buf = | ||
26 | match state with | ||
27 | | `Start -> STR_START (Buffer.contents buf) | ||
28 | | `Mid -> STR_MID (Buffer.contents buf) | ||
29 | |||
30 | let token_of_istr state buf = | ||
31 | match state with | ||
32 | | `Start -> ISTR_START (Buffer.contents buf) | ||
33 | | `Mid -> ISTR_MID (Buffer.contents buf) | ||
34 | |||
35 | (* lookup table for one-character tokens *) | ||
36 | let char_table = Array.create ~len:94 EOF | ||
37 | let _ = | ||
38 | List.iter ~f:(fun (k, v) -> Array.set char_table ((int_of_char k) - 1) v) | ||
39 | [ | ||
40 | '.', SELECT; | ||
41 | '?', QMARK; | ||
42 | '!', NOT; | ||
43 | '=', ASSIGN; | ||
44 | '<', LT; | ||
45 | '>', GT; | ||
46 | '[', LBRACK; | ||
47 | ']', RBRACK; | ||
48 | '+', PLUS; | ||
49 | '-', MINUS; | ||
50 | '*', TIMES; | ||
51 | '/', SLASH; | ||
52 | '(', LPAREN; | ||
53 | ')', RPAREN; | ||
54 | ':', COLON; | ||
55 | ';', SEMICOLON; | ||
56 | ',', COMMA; | ||
57 | '@', AS | ||
58 | ] | ||
59 | |||
60 | (* lookup table for two- and three-character tokens *) | ||
61 | let str_table = Hashtbl.create (module String) ~size:10 | ||
62 | let _ = | ||
63 | List.iter ~f:(fun (kwd, tok) -> Hashtbl.set str_table ~key:kwd ~data:tok) | ||
64 | [ | ||
65 | "//", MERGE; | ||
66 | "++", CONCAT; | ||
67 | "<=", LTE; | ||
68 | ">=", GTE; | ||
69 | "==", EQ; | ||
70 | "!=", NEQ; | ||
71 | "&&", AND; | ||
72 | "||", OR; | ||
73 | "->", IMPL; | ||
74 | "...", ELLIPSIS | ||
75 | ] | ||
76 | |||
77 | (* lookup table for keywords *) | ||
78 | let keyword_table = Hashtbl.create (module String) ~size:10 | ||
79 | let _ = | ||
80 | List.iter ~f:(fun (kwd, tok) -> Hashtbl.set keyword_table ~key:kwd ~data:tok) | ||
81 | [ "with", WITH; | ||
82 | "rec", REC; | ||
83 | "let", LET; | ||
84 | "in", IN; | ||
85 | "inherit", INHERIT; | ||
86 | "if" , IF; | ||
87 | "then", THEN; | ||
88 | "else", ELSE; | ||
89 | "assert", ASSERT; | ||
90 | "or", ORDEF ] | ||
91 | |||
92 | (* replace an escape sequence by the corresponding character(s) *) | ||
93 | let unescape = function | ||
94 | | "\\n" -> "\n" | ||
95 | | "\\r" -> "\r" | ||
96 | | "\\t" -> "\t" | ||
97 | | "\\\\" -> "\\" | ||
98 | | "\\${" -> "${" | ||
99 | | "''$" -> "$" | ||
100 | | "$$" -> "$" | ||
101 | | "'''" -> "''" | ||
102 | | "''\\t" -> "\t" | ||
103 | | "''\\r" -> "\r" | ||
104 | | "''\\n" -> "\n" | ||
105 | | x -> | ||
106 | failwith (Printf.sprintf "unescape unexpected arg %s" x) | ||
107 | |||
108 | let collect_tokens lexer q lexbuf = | ||
109 | let stack = ref [] in | ||
110 | let queue = Stdlib.Queue.create () in | ||
111 | let rec go () = | ||
112 | match (try Some (Stdlib.Queue.take queue) with Stdlib.Queue.Empty -> None) with | ||
113 | | Some token -> | ||
114 | ( | ||
115 | match token, !stack with | ||
116 | | AQUOTE_CLOSE, [] -> | ||
117 | Stdlib.Queue.add AQUOTE_CLOSE q | ||
118 | | EOF, _ -> | ||
119 | Stdlib.Queue.add EOF q; | ||
120 | | _, _ -> | ||
121 | Stdlib.Queue.add token q; | ||
122 | go () | ||
123 | ) | ||
124 | | None -> | ||
125 | lexer queue stack lexbuf; | ||
126 | go () | ||
127 | in | ||
128 | Stdlib.Queue.add AQUOTE_OPEN q; | ||
129 | stack := [AQUOTE]; | ||
130 | lexer queue stack lexbuf; | ||
131 | go () | ||
132 | |||
133 | (* utility functions *) | ||
134 | let print_position lexbuf = | ||
135 | let pos = Lexing.lexeme_start_p lexbuf in | ||
136 | Printf.sprintf "%s:%d:%d" pos.pos_fname | ||
137 | pos.pos_lnum (pos.pos_cnum - pos.pos_bol + 1) | ||
138 | |||
139 | |||
140 | let set_filename fname (lexbuf: Lexing.lexbuf) = | ||
141 | let pos = lexbuf.lex_curr_p in | ||
142 | lexbuf.lex_curr_p <- { pos with pos_fname = fname }; lexbuf | ||
143 | |||
144 | } | ||
145 | |||
146 | let nzdigit = ['1'-'9'] | ||
147 | let digit = nzdigit | '0' | ||
148 | let float = (nzdigit digit* '.' digit* | '0'? '.' digit+) (['E' 'e'] ['+' '-']? digit+)? | ||
149 | let alpha = ['a'-'z' 'A'-'Z'] | ||
150 | let alpha_digit = alpha | digit | ||
151 | let path_chr = alpha_digit | ['.' '_' '-' '+'] | ||
152 | let path = path_chr* ('/' path_chr+)+ | ||
153 | let spath = alpha_digit path_chr* ('/' path_chr+)* | ||
154 | let uri_chr = ['%' '/' '?' ':' '@' '&' '=' '+' '$' ',' '-' '_' '.' '!' '~' '*' '\''] | ||
155 | let scheme = alpha (alpha | ['+' '-' '.'])* | ||
156 | let uri = scheme ':' (alpha_digit | uri_chr)+ | ||
157 | let char_tokens = ['.' '?' '!' '=' '<' '>' '[' ']' '+' '-' '*' '/' '^' '(' ')' ':' ';' ',' '@'] | ||
158 | |||
159 | rule get_tokens q s = parse | ||
160 | (* skip whitespeces *) | ||
161 | | [' ' '\t' '\r'] | ||
162 | { get_tokens q s lexbuf } | ||
163 | (* increase line count for new lines *) | ||
164 | | '\n' | ||
165 | { Lexing.new_line lexbuf; get_tokens q s lexbuf } | ||
166 | | char_tokens as c | ||
167 | { Stdlib.Queue.add (Array.get char_table ((int_of_char c) - 1)) q } | ||
168 | | ("//" | "++" | "<=" | ">=" | "==" | "!=" | "&&" | "||" | "->" | "...") as s | ||
169 | { Stdlib.Queue.add (Hashtbl.find_exn str_table s) q} | ||
170 | | digit+ as i | ||
171 | { Stdlib.Queue.add (INT i) q } | ||
172 | | float | ||
173 | { Stdlib.Queue.add (FLOAT (Lexing.lexeme lexbuf)) q } | ||
174 | | path | ||
175 | { Stdlib.Queue.add (PATH (Lexing.lexeme lexbuf)) q } | ||
176 | | '<' (spath as p) '>' | ||
177 | { Stdlib.Queue.add (SPATH p) q } | ||
178 | | '~' path as p | ||
179 | { Stdlib.Queue.add (HPATH p) q } | ||
180 | | uri | ||
181 | { Stdlib.Queue.add(URI (Lexing.lexeme lexbuf)) q } | ||
182 | (* keywords or identifiers *) | ||
183 | | ((alpha | '_')+ (alpha_digit | ['_' '\'' '-'])*) as id | ||
184 | { Stdlib.Queue.add (Hashtbl.find keyword_table id |> Option.value ~default:(ID id)) q} | ||
185 | (* comments *) | ||
186 | | '#' ([^ '\n']* as c) | ||
187 | { ignore c; get_tokens q s lexbuf} | ||
188 | | "/*" | ||
189 | { comment (Buffer.create 64) lexbuf; | ||
190 | get_tokens q s lexbuf | ||
191 | } | ||
192 | (* the following three tokens change the braces stack *) | ||
193 | | "${" | ||
194 | { Stdlib.Queue.add AQUOTE_OPEN q; s := AQUOTE :: !s } | ||
195 | | '{' | ||
196 | { Stdlib.Queue.add LBRACE q; s := SET :: !s } | ||
197 | | '}' | ||
198 | { | ||
199 | match !s with | ||
200 | | AQUOTE :: rest -> | ||
201 | Stdlib.Queue.add AQUOTE_CLOSE q; s := rest | ||
202 | | SET :: rest -> | ||
203 | Stdlib.Queue.add RBRACE q; s := rest | ||
204 | | _ -> | ||
205 | let pos = print_position lexbuf in | ||
206 | let err = Printf.sprintf "Unbalanced '}' at %s\n" pos in | ||
207 | raise (Error err) | ||
208 | } | ||
209 | (* a double-quoted string *) | ||
210 | | '"' | ||
211 | { string `Start (Buffer.create 64) q lexbuf } | ||
212 | (* an indented string *) | ||
213 | | "''" (' '+ as ws) | ||
214 | { istring `Start (Some (String.length ws)) (Buffer.create 64) q lexbuf } | ||
215 | | "''" | ||
216 | { istring `Start None (Buffer.create 64) q lexbuf } | ||
217 | (* End of input *) | ||
218 | | eof | ||
219 | { Stdlib.Queue.add EOF q } | ||
220 | (* any other character raises an exception *) | ||
221 | | _ | ||
222 | { | ||
223 | let pos = print_position lexbuf in | ||
224 | let tok = Lexing.lexeme lexbuf in | ||
225 | let err = Printf.sprintf "Unexpected character '%s' at %s\n" tok pos in | ||
226 | raise (Error err) | ||
227 | } | ||
228 | |||
229 | (* Nix does not allow nested comments, but it is still handy to lex it | ||
230 | separately because we can properly increase line count. *) | ||
231 | and comment buf = parse | ||
232 | | '\n' | ||
233 | {Lexing.new_line lexbuf; Buffer.add_char buf '\n'; comment buf lexbuf} | ||
234 | | "*/" | ||
235 | { () } | ||
236 | | _ as c | ||
237 | { Buffer.add_char buf c; comment buf lexbuf } | ||
238 | |||
239 | and string state buf q = parse | ||
240 | | '"' (* terminate when we hit '"' *) | ||
241 | { Stdlib.Queue.add (token_of_str state buf) q; Stdlib.Queue.add STR_END q } | ||
242 | | '\n' | ||
243 | { Lexing.new_line lexbuf; Buffer.add_char buf '\n'; string state buf q lexbuf } | ||
244 | | ("\\n" | "\\r" | "\\t" | "\\\\" | "\\${") as s | ||
245 | { Buffer.add_string buf (unescape s); string state buf q lexbuf } | ||
246 | | "\\" (_ as c) (* add the character verbatim *) | ||
247 | { Buffer.add_char buf c; string state buf q lexbuf } | ||
248 | | "${" (* collect all the tokens till we hit the matching '}' *) | ||
249 | { | ||
250 | Stdlib.Queue.add (token_of_str state buf) q; | ||
251 | collect_tokens get_tokens q lexbuf; | ||
252 | string `Mid (Buffer.create 64) q lexbuf | ||
253 | } | ||
254 | | _ as c (* otherwise just add the character to the buffer *) | ||
255 | { Buffer.add_char buf c; string state buf q lexbuf } | ||
256 | |||
257 | and istring state imin buf q = parse | ||
258 | | ('\n' ' '* "''") | ||
259 | { | ||
260 | Lexing.new_line lexbuf; | ||
261 | Buffer.add_string buf "\n"; | ||
262 | let indent = match imin with | None -> 0 | Some i -> i in | ||
263 | Stdlib.Queue.add (token_of_istr state buf) q; | ||
264 | Stdlib.Queue.add (ISTR_END indent) q | ||
265 | } | ||
266 | | "''" | ||
267 | { | ||
268 | let indent = match imin with | None -> 0 | Some i -> i in | ||
269 | Stdlib.Queue.add (token_of_istr state buf) q; | ||
270 | Stdlib.Queue.add (ISTR_END indent) q | ||
271 | } | ||
272 | | ('\n' ' '* '\n') as s | ||
273 | { | ||
274 | Lexing.new_line lexbuf; | ||
275 | Lexing.new_line lexbuf; | ||
276 | Buffer.add_string buf s; | ||
277 | istring state imin buf q lexbuf | ||
278 | } | ||
279 | | ('\n' (' '* as ws)) as s | ||
280 | { | ||
281 | Lexing.new_line lexbuf; | ||
282 | Buffer.add_string buf s; | ||
283 | let ws_count = String.length ws in | ||
284 | match imin with | ||
285 | | None -> | ||
286 | istring state (Some ws_count) buf q lexbuf | ||
287 | | Some i -> | ||
288 | istring state (Some (min i ws_count)) buf q lexbuf | ||
289 | } | ||
290 | | ("''$" | "'''" | "''\\t" | "''\\r" | "''\\n") as s | ||
291 | { Buffer.add_string buf (unescape s); istring state imin buf q lexbuf } | ||
292 | | "''\\" (_ as c) | ||
293 | { Buffer.add_char buf c; istring state imin buf q lexbuf } | ||
294 | | "${" | ||
295 | { | ||
296 | Stdlib.Queue.add (token_of_istr state buf) q; | ||
297 | collect_tokens get_tokens q lexbuf; | ||
298 | istring `Mid imin (Buffer.create 64) q lexbuf | ||
299 | } | ||
300 | | _ as c | ||
301 | { Buffer.add_char buf c; istring state imin buf q lexbuf } | ||
302 | { | ||
303 | |||
304 | let rec next_token | ||
305 | (q: token Stdlib.Queue.t) | ||
306 | (s: braces list ref) | ||
307 | (lexbuf: Lexing.lexbuf) | ||
308 | : token = | ||
309 | match (try Some (Stdlib.Queue.take q) with | Stdlib.Queue.Empty -> None) with | ||
310 | | Some token -> | ||
311 | token | ||
312 | | None -> | ||
313 | get_tokens q s lexbuf; | ||
314 | next_token q s lexbuf | ||
315 | } | ||
diff --git a/lib/nix/nix.ml b/lib/nix/nix.ml new file mode 100644 index 0000000..39dc94c --- /dev/null +++ b/lib/nix/nix.ml | |||
@@ -0,0 +1,20 @@ | |||
1 | open Core | ||
2 | module Ast = Types | ||
3 | module Printer = Printer | ||
4 | |||
5 | exception ParseError of string | ||
6 | |||
7 | let parse ~filename (data : string) = | ||
8 | let lexbuf = Lexer.set_filename filename (Lexing.from_string data) | ||
9 | and q, s = (Stdlib.Queue.create (), ref []) in | ||
10 | try Parser.main (Lexer.next_token q s) lexbuf with | ||
11 | | Lexer.Error msg -> | ||
12 | let msg' = String.rstrip msg in | ||
13 | raise (ParseError (sprintf "Lexing error: %s" msg')) | ||
14 | | Parser.Error -> | ||
15 | let msg = sprintf "Parse error at %s" (Lexer.print_position lexbuf) in | ||
16 | raise (ParseError msg) | ||
17 | |||
18 | let elaborate = Elaborator.process | ||
19 | |||
20 | exception ElaborateError = Elaborator.ElaborateError | ||
diff --git a/lib/nix/parser.mly b/lib/nix/parser.mly new file mode 100644 index 0000000..dc1638d --- /dev/null +++ b/lib/nix/parser.mly | |||
@@ -0,0 +1,310 @@ | |||
1 | /* Tokens with data */ | ||
2 | %token <string> INT | ||
3 | %token <string> FLOAT | ||
4 | /* A path */ | ||
5 | %token <string> PATH | ||
6 | /* Search path, enclosed in <> */ | ||
7 | %token <string> SPATH | ||
8 | /* Home path, starts with ~ */ | ||
9 | %token <string> HPATH | ||
10 | %token <string> URI | ||
11 | %token <string> STR_START | ||
12 | %token <string> STR_MID | ||
13 | %token STR_END | ||
14 | %token <string> ISTR_START | ||
15 | %token <string> ISTR_MID | ||
16 | %token <int> ISTR_END | ||
17 | %token <string> ID | ||
18 | /* Tokens that stand for themselves */ | ||
19 | %token SELECT "." | ||
20 | %token QMARK "?" | ||
21 | %token CONCAT "++" | ||
22 | %token NOT "!" | ||
23 | %token MERGE "//" | ||
24 | %token ASSIGN "=" | ||
25 | %token LT "<" | ||
26 | %token LTE "<=" | ||
27 | %token GT ">" | ||
28 | %token GTE ">=" | ||
29 | %token EQ "==" | ||
30 | %token NEQ "!=" | ||
31 | %token AND "&&" | ||
32 | %token OR "||" | ||
33 | %token IMPL "->" | ||
34 | %token AQUOTE_OPEN "${" | ||
35 | %token AQUOTE_CLOSE "}$" | ||
36 | %token LBRACE "{" | ||
37 | %token RBRACE "}" | ||
38 | %token LBRACK "[" | ||
39 | %token RBRACK "]" | ||
40 | %token PLUS "+" | ||
41 | %token MINUS "-" | ||
42 | %token TIMES "*" | ||
43 | %token SLASH "/" | ||
44 | %token LPAREN "(" | ||
45 | %token RPAREN ")" | ||
46 | %token COLON ":" | ||
47 | %token SEMICOLON ";" | ||
48 | %token COMMA "," | ||
49 | %token ELLIPSIS "..." | ||
50 | %token AS "@" | ||
51 | /* Keywords */ | ||
52 | %token WITH "with" | ||
53 | %token REC "rec" | ||
54 | %token LET "let" | ||
55 | %token IN "in" | ||
56 | %token INHERIT "inherit" | ||
57 | %token IF "if" | ||
58 | %token THEN "then" | ||
59 | %token ELSE "else" | ||
60 | %token ASSERT "assert" | ||
61 | %token ORDEF "or" | ||
62 | |||
63 | /* End of input */ | ||
64 | %token EOF | ||
65 | |||
66 | %{ | ||
67 | open Types | ||
68 | %} | ||
69 | |||
70 | %start <Types.expr> main | ||
71 | |||
72 | %% | ||
73 | |||
74 | main: | ||
75 | | e = expr0 EOF | ||
76 | { e } | ||
77 | |||
78 | expr0: | ||
79 | | "if"; e1 = expr0; "then"; e2 = expr0; "else"; e3 = expr0 | ||
80 | { Cond (e1, e2, e3) } | ||
81 | | "with"; e1 = expr0; ";"; e2 = expr0 | ||
82 | { With (e1, e2) } | ||
83 | | "assert"; e1 = expr0; ";"; e2 = expr0 | ||
84 | { Assert (e1, e2) } | ||
85 | | "let"; xs = delimited("{", list(binding), "}") | ||
86 | { SetLet xs } | ||
87 | | "let"; xs = list(binding); "in"; e = expr0 | ||
88 | { Let (xs, e) } | ||
89 | | l = lambda | ||
90 | { Val l } | ||
91 | | e = expr1 | ||
92 | { e } | ||
93 | |||
94 | /* Rules expr1-expr14 are almost direct translation of the operator | ||
95 | precedence table: | ||
96 | https://nixos.org/nix/manual/#sec-language-operators */ | ||
97 | |||
98 | %inline binary_expr(Lhs, Op, Rhs): | ||
99 | | lhs = Lhs; op = Op; rhs = Rhs | ||
100 | { BinaryOp (op, lhs, rhs) } | ||
101 | |||
102 | expr1: | ||
103 | | e = binary_expr(expr2, "->" {Impl}, expr1) | ||
104 | | e = expr2 | ||
105 | { e } | ||
106 | |||
107 | expr2: | ||
108 | | e = binary_expr(expr2, "||" {Or}, expr3) | ||
109 | | e = expr3 | ||
110 | { e } | ||
111 | |||
112 | expr3: | ||
113 | | e = binary_expr(expr3, "&&" {And}, expr4) | ||
114 | | e = expr4 | ||
115 | { e } | ||
116 | |||
117 | %inline expr4_ops: | ||
118 | | "==" { Eq } | ||
119 | | "!=" { Neq } | ||
120 | |||
121 | expr4: | ||
122 | | e = binary_expr(expr5, expr4_ops, expr5) | ||
123 | | e = expr5 | ||
124 | { e } | ||
125 | |||
126 | %inline expr5_ops: | ||
127 | | "<" { Lt } | ||
128 | | ">" { Gt } | ||
129 | | "<=" { Lte } | ||
130 | | ">=" { Gte } | ||
131 | |||
132 | expr5: | ||
133 | | e = binary_expr(expr6, expr5_ops, expr6) | ||
134 | | e = expr6 | ||
135 | { e } | ||
136 | |||
137 | expr6: | ||
138 | | e = binary_expr(expr7, "//" {Merge}, expr6) | ||
139 | | e = expr7 | ||
140 | { e } | ||
141 | |||
142 | expr7: | ||
143 | | e = preceded("!", expr7) | ||
144 | { UnaryOp (Not, e) } | ||
145 | | e = expr8 | ||
146 | { e } | ||
147 | |||
148 | %inline expr8_ops: | ||
149 | | "+" { Plus } | ||
150 | | "-" { Minus } | ||
151 | |||
152 | expr8: | ||
153 | | e = binary_expr(expr8, expr8_ops, expr9) | ||
154 | | e = expr9 | ||
155 | { e } | ||
156 | |||
157 | %inline expr9_ops: | ||
158 | | "*" { Mult } | ||
159 | | "/" { Div } | ||
160 | |||
161 | expr9: | ||
162 | | e = binary_expr(expr9, expr9_ops, expr10) | ||
163 | | e = expr10 | ||
164 | { e } | ||
165 | |||
166 | expr10: | ||
167 | | e = binary_expr(expr11, "++" {Concat}, expr10) | ||
168 | | e = expr11 | ||
169 | { e } | ||
170 | |||
171 | expr11: | ||
172 | | e = expr12 "?" p = attr_path | ||
173 | { Test (e, p) } | ||
174 | | e = expr12 | ||
175 | { e } | ||
176 | |||
177 | expr12: | ||
178 | | e = preceded("-", expr13) | ||
179 | { UnaryOp (Negate, e) } | ||
180 | | e = expr13 | ||
181 | { e } | ||
182 | |||
183 | expr13: | ||
184 | | f = expr13; arg = expr14 | ||
185 | { Apply (f, arg) } | ||
186 | | e = expr14 | ||
187 | { e } | ||
188 | |||
189 | %inline selectable: | ||
190 | | s = set | ||
191 | { Val s } | ||
192 | | id = ID | ||
193 | { Id id } | ||
194 | | e = delimited("(", expr0, ")") | ||
195 | { e } | ||
196 | |||
197 | expr14: | ||
198 | | e = selectable; "."; p = attr_path; o = option(preceded("or", expr14)) | ||
199 | { Select (e, p, o) } | ||
200 | | e = atomic_expr; "or" | ||
201 | { Apply (e, Id "or") } | ||
202 | | e = atomic_expr | ||
203 | { e } | ||
204 | |||
205 | atomic_expr: | ||
206 | | id = ID | ||
207 | { Id id } | ||
208 | | v = value | ||
209 | { Val v } | ||
210 | | e = delimited("(", expr0, ")") | ||
211 | { e } | ||
212 | |||
213 | attr_path: | ||
214 | | p = separated_nonempty_list(".", attr_path_component) | ||
215 | { p } | ||
216 | |||
217 | attr_path_component: | ||
218 | | "or" | ||
219 | { Id "or" } | ||
220 | | id = ID | ||
221 | { Id id } | ||
222 | | e = delimited("${", expr0, "}$") | ||
223 | { Aquote e } | ||
224 | | s = str | ||
225 | { Val s } | ||
226 | |||
227 | value: | ||
228 | | s = str | ||
229 | { s } | ||
230 | | s = istr | ||
231 | { s } | ||
232 | | i = INT | ||
233 | {Int i} | ||
234 | | f = FLOAT | ||
235 | { Float f } | ||
236 | | p = PATH | ||
237 | { Path p } | ||
238 | | sp = SPATH | ||
239 | { SPath sp } | ||
240 | | hp = HPATH | ||
241 | { HPath hp } | ||
242 | | uri = URI | ||
243 | { Uri uri } | ||
244 | | l = nixlist | ||
245 | { l } | ||
246 | | s = set | ||
247 | { s } | ||
248 | |||
249 | %inline str_mid(X): | ||
250 | | xs = list(pair(delimited("${", expr0, "}$"), X)) { xs } | ||
251 | |||
252 | /* Double-quoted string */ | ||
253 | str: | ||
254 | | start = STR_START; mids = str_mid(STR_MID); STR_END | ||
255 | { Str (start, mids) } | ||
256 | |||
257 | /* Indented string */ | ||
258 | istr: | ||
259 | | start = ISTR_START; mids = str_mid(ISTR_MID); i = ISTR_END | ||
260 | { IStr (i, start, mids) } | ||
261 | |||
262 | /* Lists and sets */ | ||
263 | nixlist: | ||
264 | | xs = delimited("[", list(expr14), "]") | ||
265 | { List xs } | ||
266 | |||
267 | empty_set: | ||
268 | | "{"; "}" {} | ||
269 | |||
270 | set: | ||
271 | | empty_set | ||
272 | { AttSet (Nonrec, []) } | ||
273 | | xs = delimited("{", nonempty_list(binding), "}") | ||
274 | { AttSet (Nonrec, xs) } | ||
275 | | xs = preceded("rec", delimited("{", list(binding), "}")) | ||
276 | { AttSet (Rec, xs) } | ||
277 | |||
278 | binding: | ||
279 | | kv = terminated(separated_pair(attr_path, "=", expr0), ";") | ||
280 | { let (k, v) = kv in AttrPath (k, v) } | ||
281 | | xs = delimited("inherit", pair(option(delimited("(", expr0, ")")), list(attr_path_component)), ";") | ||
282 | { let (prefix, ids) = xs in Inherit (prefix, ids) } | ||
283 | |||
284 | lambda: | ||
285 | | id = ID; "@"; p = param_set; ":"; e = expr0 | ||
286 | { Lambda (ParamSet (Some id, p), e) } | ||
287 | | p = param_set; "@"; id = ID; ":"; e = expr0 | ||
288 | { Lambda (ParamSet (Some id, p), e) } | ||
289 | | p = param_set; ":"; e = expr0 | ||
290 | { Lambda (ParamSet (None, p), e) } | ||
291 | | id = ID; ":"; e = expr0 | ||
292 | { Lambda (Alias id, e) } | ||
293 | |||
294 | %inline param_set: | ||
295 | | empty_set | ||
296 | { ([], Exact) } | ||
297 | | "{"; "..."; "}" | ||
298 | { ([], Loose) } | ||
299 | | ps = delimited("{", pair(pair(params, ","?), boption("...")), "}") | ||
300 | { let ((ps, _), ellipsis) = ps in (ps, if ellipsis then Loose else Exact) } | ||
301 | |||
302 | params: | ||
303 | | p = param | ||
304 | { [p] } | ||
305 | | ps = params; ","; p = param | ||
306 | { ps @ [p] } | ||
307 | |||
308 | %inline param: | ||
309 | p = pair(ID, option(preceded("?", expr0))) | ||
310 | { p } | ||
diff --git a/lib/nix/printer.ml b/lib/nix/printer.ml new file mode 100644 index 0000000..57e81f4 --- /dev/null +++ b/lib/nix/printer.ml | |||
@@ -0,0 +1,176 @@ | |||
1 | open Core | ||
2 | open Types | ||
3 | open PPrint | ||
4 | |||
5 | let rec escape_chlist = function | ||
6 | | [] -> [] | ||
7 | | '$' :: '{' :: l' -> '\\' :: '$' :: '{' :: escape_chlist l' | ||
8 | | '\n' :: l' -> '\\' :: 'n' :: escape_chlist l' | ||
9 | | '\r' :: l' -> '\\' :: 'r' :: escape_chlist l' | ||
10 | | '\t' :: l' -> '\\' :: 't' :: escape_chlist l' | ||
11 | | '\\' :: l' -> '\\' :: '\\' :: escape_chlist l' | ||
12 | | '"' :: l' -> '\\' :: '"' :: escape_chlist l' | ||
13 | | c :: l' -> c :: escape_chlist l' | ||
14 | |||
15 | let escape_string s = s |> String.to_list |> escape_chlist |> String.of_list | ||
16 | let out_width = ref 80 | ||
17 | let set_width i = out_width := i | ||
18 | let indent = ref 2 | ||
19 | let set_indent i = indent := i | ||
20 | |||
21 | let rec doc_of_expr = function | ||
22 | | BinaryOp (op, lhs, rhs) -> | ||
23 | let lhs_doc = maybe_parens_bop op `Left lhs | ||
24 | and rhs_doc = maybe_parens_bop op `Right rhs in | ||
25 | infix !indent 1 (doc_of_bop op) lhs_doc rhs_doc | ||
26 | | UnaryOp (op, e) -> precede (doc_of_uop op) (maybe_parens (prec_of_uop op) e) | ||
27 | | Cond (e1, e2, e3) -> | ||
28 | surround !indent 1 | ||
29 | (soft_surround !indent 1 (string "if") (doc_of_expr e1) (string "then")) | ||
30 | (doc_of_expr e2) | ||
31 | (string "else" ^^ nest !indent (break 1 ^^ doc_of_expr e3)) | ||
32 | | With (e1, e2) -> | ||
33 | flow (break 1) [ string "with"; doc_of_expr e1 ^^ semi; doc_of_expr e2 ] | ||
34 | | Assert (e1, e2) -> | ||
35 | flow (break 1) [ string "assert"; doc_of_expr e1 ^^ semi; doc_of_expr e2 ] | ||
36 | | Test (e, path) -> | ||
37 | maybe_parens 4 e ^^ space ^^ string "?" | ||
38 | ^^ group (break 1 ^^ separate_map dot doc_of_expr path) | ||
39 | | SetLet bs -> | ||
40 | surround !indent 1 | ||
41 | (string "let " ^^ lbrace) | ||
42 | (group (separate_map (break 1) doc_of_binding bs)) | ||
43 | rbrace | ||
44 | | Let (bs, e) -> | ||
45 | surround !indent 1 (string "let") | ||
46 | (separate_map (break 1) doc_of_binding bs) | ||
47 | (prefix !indent 1 (string "in") (doc_of_expr e)) | ||
48 | | Val v -> doc_of_val v | ||
49 | | Id id -> string id | ||
50 | | Select (e, path, oe) -> | ||
51 | maybe_parens 1 e ^^ dot ^^ doc_of_attpath path | ||
52 | ^^ optional | ||
53 | (fun e -> | ||
54 | space ^^ string "or" ^^ nest !indent (break 1 ^^ maybe_parens 1 e)) | ||
55 | oe | ||
56 | | Apply (e1, e2) -> prefix !indent 1 (maybe_parens 2 e1) (maybe_parens 2 e2) | ||
57 | | Aquote e -> surround !indent 0 (string "${") (doc_of_expr e) (string "}") | ||
58 | |||
59 | and maybe_parens lvl e = | ||
60 | if prec_of_expr e >= lvl then surround !indent 0 lparen (doc_of_expr e) rparen | ||
61 | else doc_of_expr e | ||
62 | |||
63 | and maybe_parens_bop op (loc : [ `Left | `Right ]) e = | ||
64 | match (loc, assoc_of_bop op) with | ||
65 | | (`Left, Some Left | `Right, Some Right) | ||
66 | when prec_of_expr e >= prec_of_bop op -> | ||
67 | doc_of_expr e | ||
68 | | _, _ -> maybe_parens (prec_of_bop op) e | ||
69 | |||
70 | and doc_of_attpath path = separate_map dot doc_of_expr path | ||
71 | |||
72 | and doc_of_paramset (params, kind) = | ||
73 | let ps = | ||
74 | List.map ~f:doc_of_param params | ||
75 | @ if Poly.(kind = Loose) then [ string "..." ] else [] | ||
76 | in | ||
77 | surround !indent 0 lbrace (separate (comma ^^ break 1) ps) rbrace | ||
78 | |||
79 | and doc_of_param (id, oe) = | ||
80 | string id ^^ optional (fun e -> qmark ^^ space ^^ doc_of_expr e) oe | ||
81 | |||
82 | and doc_of_binding = function | ||
83 | | AttrPath (path, e) -> | ||
84 | doc_of_attpath path ^^ space ^^ equals ^^ space ^^ doc_of_expr e ^^ semi | ||
85 | | Inherit (oe, ids) -> | ||
86 | let id_docs = | ||
87 | List.map | ||
88 | ~f:(function | ||
89 | | Id x | Val (Str (x, [])) -> string x | _ -> assert false) | ||
90 | ids | ||
91 | in | ||
92 | let xs = | ||
93 | flow (break 1) | ||
94 | (match oe with | ||
95 | | Some e -> parens (doc_of_expr e) :: id_docs | ||
96 | | None -> id_docs) | ||
97 | in | ||
98 | soft_surround !indent 0 (string "inherit" ^^ space) xs semi | ||
99 | |||
100 | and doc_of_bop = function | ||
101 | | Plus -> plus | ||
102 | | Minus -> minus | ||
103 | | Mult -> star | ||
104 | | Div -> slash | ||
105 | | Gt -> rangle | ||
106 | | Lt -> langle | ||
107 | | Lte -> string "<=" | ||
108 | | Gte -> string ">=" | ||
109 | | Eq -> string "==" | ||
110 | | Neq -> string "!=" | ||
111 | | Or -> string "||" | ||
112 | | And -> string "&&" | ||
113 | | Impl -> string "->" | ||
114 | | Merge -> string "//" | ||
115 | | Concat -> string "++" | ||
116 | |||
117 | and doc_of_uop = function Negate -> minus | Not -> bang | ||
118 | |||
119 | and doc_of_val = function | ||
120 | | Str (start, xs) -> | ||
121 | dquotes | ||
122 | (string (escape_string start) | ||
123 | ^^ concat | ||
124 | (List.map | ||
125 | ~f:(fun (e, s) -> | ||
126 | surround !indent 0 (string "${") (doc_of_expr e) | ||
127 | (string "}" ^^ string (escape_string s))) | ||
128 | xs)) | ||
129 | | IStr (i, start, xs) -> | ||
130 | let qq = string "''" in | ||
131 | let str s = | ||
132 | String.split ~on:'\n' s | ||
133 | |> List.map ~f:(fun s -> | ||
134 | let len = String.length s in | ||
135 | let s' = | ||
136 | if len >= i then String.sub s ~pos:i ~len:(len - i) else s | ||
137 | in | ||
138 | string s') | ||
139 | |> separate hardline | ||
140 | in | ||
141 | enclose qq qq | ||
142 | (str start | ||
143 | ^^ concat | ||
144 | (List.map | ||
145 | ~f:(fun (e, s) -> | ||
146 | enclose (string "${") rbrace (doc_of_expr e) ^^ str s) | ||
147 | xs)) | ||
148 | | Int x | Float x | Path x | SPath x | HPath x | Uri x -> string x | ||
149 | | Lambda (pattern, body) -> | ||
150 | let pat = | ||
151 | match pattern with | ||
152 | | Alias id -> string id | ||
153 | | ParamSet (None, ps) -> doc_of_paramset ps | ||
154 | | ParamSet (Some id, ps) -> | ||
155 | doc_of_paramset ps ^^ group (break 1 ^^ at ^^ break 1 ^^ string id) | ||
156 | in | ||
157 | flow (break 1) [ pat ^^ colon; doc_of_expr body ] | ||
158 | | List [] -> lbracket ^^ space ^^ rbracket | ||
159 | | List es -> | ||
160 | surround !indent 1 lbracket | ||
161 | (separate_map (break 1) (maybe_parens 2) es) | ||
162 | rbracket | ||
163 | | AttSet (Nonrec, []) -> lbrace ^^ space ^^ rbrace | ||
164 | | AttSet (Nonrec, bs) -> | ||
165 | surround !indent 1 lbrace | ||
166 | (group (separate_map (break 1) doc_of_binding bs)) | ||
167 | rbrace | ||
168 | | AttSet (Rec, bs) -> | ||
169 | string "rec" ^^ space ^^ doc_of_val (AttSet (Nonrec, bs)) | ||
170 | |||
171 | let print chan expr = ToChannel.pretty 0.7 !out_width chan (doc_of_expr expr) | ||
172 | |||
173 | let to_string expr = | ||
174 | let buf = Stdlib.Buffer.create 0 in | ||
175 | ToBuffer.pretty 0.7 !out_width buf (doc_of_expr expr); | ||
176 | Stdlib.Buffer.contents buf | ||
diff --git a/lib/nix/tokens.ml b/lib/nix/tokens.ml new file mode 100644 index 0000000..4891d48 --- /dev/null +++ b/lib/nix/tokens.ml | |||
@@ -0,0 +1,64 @@ | |||
1 | type token = | ||
2 | (* Tokens with data *) | ||
3 | | INT of string | ||
4 | | FLOAT of string | ||
5 | (* A path (starting with / or ./) *) | ||
6 | | PATH of string | ||
7 | (* Search path, enclosed in < and > *) | ||
8 | | SPATH of string | ||
9 | (* Home path, starting with ~/ *) | ||
10 | | HPATH of string | ||
11 | | URI of string | ||
12 | | STR_START of string | ||
13 | | STR_MID of string | ||
14 | | STR_END | ||
15 | | ISTR_START of string | ||
16 | | ISTR_MID of string | ||
17 | | ISTR_END of int | ||
18 | | ID of string | ||
19 | (* Tokens that stand for themselves *) | ||
20 | | SELECT | ||
21 | | QMARK | ||
22 | | CONCAT | ||
23 | | NOT | ||
24 | | MERGE | ||
25 | | ASSIGN | ||
26 | | LT | ||
27 | | LTE | ||
28 | | GT | ||
29 | | GTE | ||
30 | | EQ | ||
31 | | NEQ | ||
32 | | AND | ||
33 | | OR | ||
34 | | IMPL | ||
35 | | AQUOTE_OPEN | ||
36 | | AQUOTE_CLOSE | ||
37 | | LBRACE | ||
38 | | RBRACE | ||
39 | | LBRACK | ||
40 | | RBRACK | ||
41 | | PLUS | ||
42 | | MINUS | ||
43 | | TIMES | ||
44 | | SLASH | ||
45 | | LPAREN | ||
46 | | RPAREN | ||
47 | | COLON | ||
48 | | SEMICOLON | ||
49 | | COMMA | ||
50 | | ELLIPSIS | ||
51 | | AS | ||
52 | (* Keywords *) | ||
53 | | WITH | ||
54 | | REC | ||
55 | | LET | ||
56 | | IN | ||
57 | | INHERIT | ||
58 | | IF | ||
59 | | THEN | ||
60 | | ELSE | ||
61 | | ASSERT | ||
62 | | ORDEF | ||
63 | (* End of input *) | ||
64 | | EOF | ||
diff --git a/lib/nix/types.ml b/lib/nix/types.ml new file mode 100644 index 0000000..8245406 --- /dev/null +++ b/lib/nix/types.ml | |||
@@ -0,0 +1,112 @@ | |||
1 | open Core | ||
2 | |||
3 | (* Binary operators *) | ||
4 | type binary_op = | ||
5 | | Plus | ||
6 | | Minus | ||
7 | | Mult | ||
8 | | Div | ||
9 | | Gt | ||
10 | | Lt | ||
11 | | Lte | ||
12 | | Gte | ||
13 | | Eq | ||
14 | | Neq | ||
15 | | Or | ||
16 | | And | ||
17 | | Impl | ||
18 | | Merge | ||
19 | | Concat | ||
20 | [@@deriving sexp_of] | ||
21 | |||
22 | (* Unary operators *) | ||
23 | type unary_op = Negate | Not [@@deriving sexp_of] | ||
24 | |||
25 | (* The top-level expression type *) | ||
26 | type expr = | ||
27 | | BinaryOp of binary_op * expr * expr | ||
28 | | UnaryOp of unary_op * expr | ||
29 | | Cond of expr * expr * expr | ||
30 | | With of expr * expr | ||
31 | | Assert of expr * expr | ||
32 | | Test of expr * expr list | ||
33 | | SetLet of binding list | ||
34 | | Let of binding list * expr | ||
35 | | Val of value | ||
36 | | Id of id | ||
37 | | Select of expr * expr list * expr option | ||
38 | | Apply of expr * expr | ||
39 | | Aquote of expr | ||
40 | [@@deriving sexp_of] | ||
41 | |||
42 | (* Possible values *) | ||
43 | and value = | ||
44 | (* Str is a string start, followed by arbitrary number of antiquotations and | ||
45 | strings that separate them *) | ||
46 | | Str of string * (expr * string) list | ||
47 | (* IStr is an indented string, so it has the extra integer component which | ||
48 | indicates the indentation *) | ||
49 | | IStr of int * string * (expr * string) list | ||
50 | | Int of string | ||
51 | | Float of string | ||
52 | | Path of string | ||
53 | | SPath of string | ||
54 | | HPath of string | ||
55 | | Uri of string | ||
56 | | Lambda of pattern * expr | ||
57 | | List of expr list | ||
58 | | AttSet of recursivity * binding list | ||
59 | [@@deriving sexp_of] | ||
60 | |||
61 | (* Patterns in lambda definitions *) | ||
62 | and pattern = Alias of id | ParamSet of id option * param_set | ||
63 | [@@deriving sexp_of] | ||
64 | |||
65 | and param_set = param list * match_kind [@@deriving sexp_of] | ||
66 | and param = id * expr option [@@deriving sexp_of] | ||
67 | and recursivity = Rec | Nonrec | ||
68 | and match_kind = Exact | Loose | ||
69 | |||
70 | (* Bindings in attribute sets and let expressions *) | ||
71 | and binding = | ||
72 | (* The first expr should be attrpath, which is the same as in Select *) | ||
73 | | AttrPath of expr list * expr | ||
74 | | Inherit of expr option * expr list | ||
75 | [@@deriving sexp_of] | ||
76 | |||
77 | (* Identifiers *) | ||
78 | and id = string | ||
79 | |||
80 | (* Precedence levels of binary operators *) | ||
81 | let prec_of_bop = function | ||
82 | | Concat -> 5 | ||
83 | | Mult | Div -> 6 | ||
84 | | Plus | Minus -> 7 | ||
85 | | Merge -> 9 | ||
86 | | Gt | Lt | Lte | Gte -> 10 | ||
87 | | Eq | Neq -> 11 | ||
88 | | And -> 12 | ||
89 | | Or -> 13 | ||
90 | | Impl -> 14 | ||
91 | |||
92 | type assoc = Left | Right | ||
93 | |||
94 | let assoc_of_bop = function | ||
95 | | Mult | Div | Plus | Minus -> Some Left | ||
96 | | Concat | Merge | And | Or -> Some Right | ||
97 | | Gt | Lt | Lte | Gte | Eq | Neq | Impl -> None | ||
98 | |||
99 | (* Precedence levels of unary operators *) | ||
100 | let prec_of_uop = function Negate -> 3 | Not -> 8 | ||
101 | |||
102 | (* Precedence level of expressions | ||
103 | (assuming that the constituents have higher levels) *) | ||
104 | let prec_of_expr = function | ||
105 | | Val (Lambda _) -> 15 | ||
106 | | Val _ | Id _ | Aquote _ -> 0 | ||
107 | | BinaryOp (op, _, _) -> prec_of_bop op | ||
108 | | UnaryOp (op, _) -> prec_of_uop op | ||
109 | | Cond _ | With _ | Assert _ | Let _ | SetLet _ -> 15 | ||
110 | | Test _ -> 4 | ||
111 | | Select _ -> 1 | ||
112 | | Apply _ -> 2 | ||