Skip to content

Commit 66af22b

Browse files
committed
elpi:if version <component> ...
1 parent 8f747f8 commit 66af22b

File tree

13 files changed

+130
-81
lines changed

13 files changed

+130
-81
lines changed

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# v2.0.7 (January 2025)
2+
3+
Requires Menhir 20211230 and OCaml 4.13 or above.
4+
5+
- Parser:
6+
- New `elpi:if version <name> <op> <ma>.<mi>.<p>`
7+
8+
- API:
9+
- New `Setup.init` takes a `?versions` dictionary to declare versions
10+
of external components
11+
112
# v2.0.6 (December 2024)
213

314
Requires Menhir 20211230 and OCaml 4.13 or above.

ELPI.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,15 @@ This text is ignored if the version of Elpi old
308308
% elpi:endif
309309
```
310310

311-
Currently the only variable available is `version` and it must be placed
312-
on the left of the operator (either `<` or `>` or `=`) and ifdefs cannot
313-
be nested. If not available (e.g. `dune subst` did not run) the version
311+
Currently the only supported expression is `version <component>` where
312+
`<component>` defaults to `elpi`. The OCaml APIs let one declare the version
313+
of other components of the host application that may affect the code to
314+
be parsed.
315+
The expression and it must be placed on the left of the operator
316+
(either `<` or `>` or `=`) and ifdefs cannot
317+
be nested.
318+
319+
If not available (e.g. `dune subst` did not run) the version of `elpi`
314320
defaults to `99.99.99`.
315321

316322
One can also ask the lexer to always skip some text. That can be useful if one

src/API.ml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ let set_trace argv =
2525
args
2626

2727
module Setup = struct
28-
28+
module StrMap = Util.StrMap
29+
30+
2931
type state_descriptor = Data.State.descriptor
3032
type quotations_descriptor = Compiler_data.QuotationHooks.descriptor ref
3133
type hoas_descriptor = Data.HoasHooks.descriptor ref
@@ -44,14 +46,14 @@ type elpi = {
4446
}
4547
type flags = Compiler.flags
4648

47-
let init ?(flags=Compiler.default_flags) ?(state=default_state_descriptor) ?(quotations=default_quotations_descriptor) ?(hoas=default_hoas_descriptor) ?(calc=default_calc_descriptor) ~builtins ?file_resolver () : elpi =
49+
let init ?(versions=Elpi_util.Util.StrMap.empty) ?(flags=Compiler.default_flags) ?(state=default_state_descriptor) ?(quotations=default_quotations_descriptor) ?(hoas=default_hoas_descriptor) ?(calc=default_calc_descriptor) ~builtins ?file_resolver () : elpi =
4850
(* At the moment we can only init the parser once *)
4951
let file_resolver =
5052
match file_resolver with
5153
| Some x -> x
5254
| None -> fun ?cwd:_ ~unit:_ () ->
5355
raise (Failure "'accumulate' is disabled since Setup.init was not given a ~file_resolver.") in
54-
let parser = (module Parse.Make(struct let resolver = file_resolver end) : Parse.Parser) in
56+
let parser = (module Parse.Make(struct let versions = versions let resolver = file_resolver end) : Parse.Parser) in
5557
Data.Global_symbols.lock ();
5658
let header_src =
5759
builtins |> List.map (fun (fname,decls) ->
@@ -1359,6 +1361,8 @@ module Utils = struct
13591361
module IntSet = Util.IntSet
13601362
module LocSet : Util.Set.S with type elt = Ast.Loc.t = Util.Set.Make(Ast.Loc)
13611363

1364+
let version_parser = Util.version_parser
1365+
13621366
end
13631367

13641368
module RawPp = struct

src/API.mli

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ module Setup : sig
154154
(* Handle to an elpi instance *)
155155
type elpi
156156

157+
module StrMap : sig
158+
include Map.S with type key = string
159+
val show : (Format.formatter -> 'a -> unit) -> 'a t -> string
160+
val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
161+
end
162+
157163
(** Initialize ELPI.
158164
159165
[init] must be called before invoking the parser.
@@ -170,6 +176,7 @@ module Setup : sig
170176
[builtins] and where accumulate resolves files with the given
171177
[file_resolver]. *)
172178
val init :
179+
?versions:(int * int * int) StrMap.t ->
173180
?flags:flags ->
174181
?state:state_descriptor ->
175182
?quotations:quotations_descriptor ->
@@ -302,11 +309,7 @@ end
302309

303310
module Data : sig
304311

305-
module StrMap : sig
306-
include Map.S with type key = string
307-
val show : (Format.formatter -> 'a -> unit) -> 'a t -> string
308-
val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
309-
end
312+
module StrMap = Setup.StrMap
310313

311314
(* what is assigned to the query variables *)
312315
type term
@@ -1375,6 +1378,15 @@ module Utils : sig
13751378
module IntSet : Set.S with type elt = int
13761379
module LocSet : Set.S with type elt = Ast.Loc.t
13771380

1381+
(* Parses a version string as it parses the elpi one:
1382+
- drop leading 'v'
1383+
- drop trailing '-...'
1384+
- splits on '.'
1385+
- expects 3 numerical components
1386+
- or a single component (defaults to 99.99.99)
1387+
*)
1388+
val version_parser : what:string -> string -> int * int * int
1389+
13781390
end
13791391

13801392
module RawPp : sig

src/parser/ast.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ let mkQuoted loc pad s =
126126
Loc.source_stop = loc.Loc.source_stop - m;
127127
} in
128128
(* Printf.eprintf "mkQuoted '%s'\n" s; *)
129-
let rec find_data i pad =
129+
let find_data i pad =
130130
match s.[i] with
131131
(* | '{' -> assert false; find_data (i+1) (pad+1) *)
132132
| ':' ->

src/parser/lexer.mll.in

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,25 @@
2222
b.lex_start_p <- start_p;
2323
r
2424

25-
let version_ma, version_mi, version_p =
26-
let is_number x = try let _ = int_of_string x in true with _ -> false in
27-
let v = "%%VERSION_NUM%%" in
28-
let v' = Re.Str.(replace_first (regexp "^v") "" v) in (* v1.20... -> 1.20... *)
29-
let v' = Re.Str.(replace_first (regexp "-.*$") "" v') in (* ...-10-fjdnfs -> ... *)
30-
let l = String.split_on_char '.' v' in
31-
match l with
32-
| [ma;mi;p] when List.for_all is_number l -> int_of_string ma, int_of_string mi, int_of_string p
33-
| [_] -> 99, 99, 99
34-
| _ -> Elpi_util.Util.error ("lexer: version parser: cannot parse: " ^ v)
35-
36-
let version_test ops ma mi p =
25+
let version_test v l ops ma mi p =
3726
let ma = int_of_string ma in
3827
let mi = int_of_string mi in
3928
let p = int_of_string p in
29+
let version =
30+
match l with
31+
| None | Some "elpi" ->
32+
Elpi_util.Util.version_parser ~what:"elpi" "%%VERSION_NUM%%"
33+
| Some l ->
34+
try Elpi_util.Util.StrMap.find l v
35+
with Not_found -> raise (Error ("elpi: lexer: no version declared for " ^ l)) in
4036
let op =
4137
match ops with
4238
| '<' -> (<)
4339
| '=' -> (=)
4440
| '>' -> (>)
4541
| _ -> assert false in
46-
let rc = op (version_ma,version_mi,version_p) (ma,mi,p) in
47-
(*Printf.eprintf "%d.%d.%d %c %d.%d.%d = %b\n" version_ma version_mi version_p ops ma mi p rc;*)
42+
let rc = op version (ma,mi,p) in
43+
(* let v1 ,v2 ,v3 = version in Printf.eprintf "%d.%d.%d %c %d.%d.%d = %b\n" v1 v2 v3 ops ma mi p rc;*)
4844
rc
4945

5046
}
@@ -66,46 +62,46 @@ let symbchar = lcase | ucase | digit | schar | ':'
6662
let symbcharstar = symbchar *
6763
let symbcharplus = symbchar +
6864

69-
rule linecomment = parse
70-
| '\n' { new_line lexbuf; token lexbuf }
71-
| eof { token lexbuf }
72-
| "elpi:skip " (pnum as n) { linecomment_skip (int_of_string n) lexbuf }
73-
| "elpi:if" (' '+) "version" (' '+) (['<' '>' '='] as op) (' '+) (pnum as ma) "." (pnum as mi) "." (pnum as p) {
74-
if not @@ version_test op ma mi p then linecomment_if lexbuf else linecomment_drop lexbuf }
75-
| ' ' { linecomment lexbuf }
76-
| _ { linecomment_drop lexbuf }
77-
78-
and linecomment_drop = parse
79-
| '\n' { new_line lexbuf; token lexbuf }
80-
| eof { token lexbuf }
81-
| _ { linecomment_drop lexbuf }
82-
83-
and linecomment_skip skipno = parse
84-
| '\n' { new_line lexbuf; if skipno > 0 then skip_lines skipno lexbuf else token lexbuf }
85-
| eof { token lexbuf }
86-
| _ { linecomment_skip skipno lexbuf }
87-
88-
and linecomment_if = parse
89-
| '\n' { new_line lexbuf; skip_lines_endif lexbuf }
90-
| eof { token lexbuf }
91-
| _ { linecomment_if lexbuf }
92-
93-
and skip_lines_endif = parse
94-
| '\n' { new_line lexbuf; skip_lines_endif lexbuf }
95-
| '%' (' '+) "elpi:endif" { token lexbuf }
96-
| eof { token lexbuf }
97-
| _ { skip_lines_endif lexbuf }
98-
99-
and skip_lines skipno = parse
100-
| '\n' { new_line lexbuf; let skipno = skipno - 1 in if skipno > 0 then skip_lines skipno lexbuf else token lexbuf }
101-
| eof { token lexbuf }
102-
| _ { skip_lines skipno lexbuf }
103-
104-
and multilinecomment nest = parse
105-
| '\n' { new_line lexbuf; multilinecomment nest lexbuf }
106-
| "*/" { if nest = 0 then token lexbuf else multilinecomment (nest - 1) lexbuf }
107-
| "/*" { multilinecomment (nest+1) lexbuf }
108-
| _ { multilinecomment nest lexbuf }
65+
rule linecomment v = parse
66+
| '\n' { new_line lexbuf; token v lexbuf }
67+
| eof { token v lexbuf }
68+
| "elpi:skip " (pnum as n) { linecomment_skip v (int_of_string n) lexbuf }
69+
| "elpi:if" (' '+) "version" ( ' '+ ([ 'A' - 'Z' 'a' - 'z' '-' '_' '.' ]+ as l) )? (' '+) (['<' '>' '='] as op) (' '+) (pnum as ma) "." (pnum as mi) "." (pnum as p) {
70+
if not @@ version_test v l op ma mi p then linecomment_if v lexbuf else linecomment_drop v lexbuf }
71+
| ' ' { linecomment v lexbuf }
72+
| _ { linecomment_drop v lexbuf }
73+
74+
and linecomment_drop v = parse
75+
| '\n' { new_line lexbuf; token v lexbuf }
76+
| eof { token v lexbuf }
77+
| _ { linecomment_drop v lexbuf }
78+
79+
and linecomment_skip v skipno = parse
80+
| '\n' { new_line lexbuf; if skipno > 0 then skip_lines v skipno lexbuf else token v lexbuf }
81+
| eof { token v lexbuf }
82+
| _ { linecomment_skip v skipno lexbuf }
83+
84+
and linecomment_if v = parse
85+
| '\n' { new_line lexbuf; skip_lines_endif v lexbuf }
86+
| eof { token v lexbuf }
87+
| _ { linecomment_if v lexbuf }
88+
89+
and skip_lines_endif v = parse
90+
| '\n' { new_line lexbuf; skip_lines_endif v lexbuf }
91+
| '%' (' '+) "elpi:endif" { token v lexbuf }
92+
| eof { token v lexbuf }
93+
| _ { skip_lines_endif v lexbuf }
94+
95+
and skip_lines v skipno = parse
96+
| '\n' { new_line lexbuf; let skipno = skipno - 1 in if skipno > 0 then skip_lines v skipno lexbuf else token v lexbuf }
97+
| eof { token v lexbuf }
98+
| _ { skip_lines v skipno lexbuf }
99+
100+
and multilinecomment v nest = parse
101+
| '\n' { new_line lexbuf; multilinecomment v nest lexbuf }
102+
| "*/" { if nest = 0 then token v lexbuf else multilinecomment v (nest - 1) lexbuf }
103+
| "/*" { multilinecomment v (nest+1) lexbuf }
104+
| _ { multilinecomment v nest lexbuf }
109105

110106
and string b = parse
111107
| '\n' { Buffer.add_char b '\n'; new_line lexbuf; string b lexbuf }
@@ -155,7 +151,7 @@ and lookahead_open b n = parse
155151
if n = 1 then () else lookahead_open b (n-1) lexbuf
156152
}
157153

158-
and token = parse
154+
and token v = parse
159155
| ("#line" " "+ (num as n) " "+ '"' ([^'"']+ as f) '"' " "* '\n' as x) {
160156
let open Lexing in
161157
lexbuf.lex_curr_p <- {
@@ -166,11 +162,11 @@ and token = parse
166162
};
167163
lexbuf.lex_abs_pos <- - (String.length x) - lexbuf.lex_start_p.pos_cnum;
168164
lexbuf.lex_start_p <- lexbuf.lex_curr_p;
169-
token lexbuf }
170-
| ( ' ' | '\t' | '\r' ) { token lexbuf }
171-
| '\n' { new_line lexbuf; token lexbuf }
172-
| '%' { linecomment lexbuf }
173-
| "/*" { multilinecomment 0 lexbuf }
165+
token v lexbuf }
166+
| ( ' ' | '\t' | '\r' ) { token v lexbuf }
167+
| '\n' { new_line lexbuf; token v lexbuf }
168+
| '%' { linecomment v lexbuf }
169+
| "/*" { multilinecomment v 0 lexbuf }
174170
| "." { FULLSTOP }
175171
| "_" idchar + as c { CONSTANT c }
176172
| "_" { FRESHUV }

src/parser/parse.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module type Parser_w_Internals = sig
2626
end
2727

2828
module type Config = sig
29+
val versions : (int * int * int) Util.StrMap.t
2930
val resolver : ?cwd:string -> unit:string -> unit -> string
3031

3132
end
@@ -51,7 +52,7 @@ let chunk s (p1,p2) =
5152
String.sub s p1.Lexing.pos_cnum (p2.Lexing.pos_cnum - p1.Lexing.pos_cnum)
5253

5354
let parse grammar lexbuf =
54-
let buffer, lexer = MenhirLib.ErrorReports.wrap Lexer.token in
55+
let buffer, lexer = MenhirLib.ErrorReports.wrap Lexer.(token C.versions) in
5556
try
5657
grammar lexer lexbuf
5758
with

src/parser/parse.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module type Parser_w_Internals = sig
2727
end
2828

2929
module type Config = sig
30+
val versions : (int * int * int) Util.StrMap.t
3031
val resolver : ?cwd:string -> unit:string -> unit -> string
3132
end
3233

src/parser/test_lexer.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ let rec expect s b = function
112112
| [] -> ()
113113
| sp :: spec ->
114114
begin try
115-
let tok2 = Lexer.token b in
115+
let tok2 = Lexer.token Elpi_util.Util.StrMap.empty b in
116116
let open Lexing in
117117
let p = b.lex_curr_p in
118118
let lnum2, bol2, bnum2, cnum2 = p.pos_lnum, p.pos_bol, b.lex_start_p.pos_cnum, p.pos_cnum in

src/parser/test_parser.ml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let chunk s (p1,p2) =
3434

3535
let message_of_state s = try Error_messages.message s with Not_found -> "syntax error"
3636

37-
module Parser = Parse.Make(struct let resolver = Elpi_util.Util.std_resolver ~paths:[] () end)
37+
module Parser = Parse.Make(struct let versions = Elpi_util.Util.StrMap.empty let resolver = Elpi_util.Util.std_resolver ~paths:[] () end)
3838

3939
let warn = ref None
4040
let () = Elpi_util.Util.set_warn (fun ?loc str -> warn := Some str)
@@ -276,16 +276,16 @@ let sanity_check : unit =
276276
| Extensible ({ start; mk_token; non_enclosed ; at_least_one_char; _ } as e)->
277277
let start = if at_least_one_char then start ^ "x" else start in
278278
start, mk_token, (if non_enclosed then Some (fun x -> start ^ x ^ start) else None), Some e in
279-
let tok = Lexer.token (Lexing.from_string start) in
279+
let tok = Lexer.token Elpi_util.Util.StrMap.empty (Lexing.from_string start) in
280280
let token = mk_token start in
281281
assert(tok = token);
282282
begin try match fixity with
283283
| Infix | Infixl | Infixr ->
284-
ignore(Parser.Internal.infix_SYMB Lexer.token (Lexing.from_string start))
284+
ignore(Parser.Internal.infix_SYMB Lexer.(token Elpi_util.Util.StrMap.empty) (Lexing.from_string start))
285285
| Postfix ->
286-
ignore(Parser.Internal.postfix_SYMB Lexer.token (Lexing.from_string start))
286+
ignore(Parser.Internal.postfix_SYMB Lexer.(token Elpi_util.Util.StrMap.empty) (Lexing.from_string start))
287287
| Prefix ->
288-
ignore(Parser.Internal.prefix_SYMB Lexer.token (Lexing.from_string start))
288+
ignore(Parser.Internal.prefix_SYMB Lexer.(token Elpi_util.Util.StrMap.empty) (Lexing.from_string start))
289289
with _ ->
290290
Printf.eprintf "\n (* 1 2 3 *)";
291291
Printf.eprintf "\n (* 123456789012345678901234567890 *)";
@@ -296,7 +296,7 @@ let sanity_check : unit =
296296
| None -> ()
297297
| Some f ->
298298
let v = f "xx" in
299-
assert(CONSTANT v = Lexer.token (Lexing.from_string v));
299+
assert(CONSTANT v = Lexer.token Elpi_util.Util.StrMap.empty (Lexing.from_string v));
300300
end;
301301
x) tokens) |> List.concat |> List.length in
302302
assert(extensible_SYMB = 14)

src/utils/util.ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,3 +725,15 @@ module Map = Map.Make(Self)
725725
module Set = Set.Make(Self)
726726
include Self
727727
end
728+
729+
let version_parser ~what v =
730+
try
731+
let is_number x = try let _ = int_of_string x in true with _ -> false in
732+
let v' = Re.Str.(replace_first (regexp "^v") "" v) in (* v1.20... -> 1.20... *)
733+
let v' = Re.Str.(replace_first (regexp "-.*$") "" v') in (* ...-10-fjdnfs -> ... *)
734+
let l = String.split_on_char '.' v' in
735+
match l with
736+
| [ma;mi;p] when List.for_all is_number l -> int_of_string ma, int_of_string mi, int_of_string p
737+
| [_] -> 99, 99, 99
738+
| _ -> raise (Failure "invalid format")
739+
with Failure msg -> error ("elpi: version_parser: cannot parse version of "^what^" '" ^ v ^ "': " ^ msg)

src/utils/util.mli

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,6 @@ module Constants : sig
306306
val pp : Format.formatter -> t -> unit
307307
val show : t -> string
308308
val compare : t -> t -> int
309-
end
309+
end
310+
311+
val version_parser : what:string -> string -> int * int * int

tests/sources/ifdef.elpi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ pred x.
99
x.
1010
% elpi:endif
1111

12+
% elpi:if version elpi = 100.0.0
13+
pred foo. pred pred.
14+
% elpi:endif
15+
1216
main :- x.

0 commit comments

Comments
 (0)