Skip to content

Commit 022943c

Browse files
committed
feat(notations): add user-extensible notations
Adds a `notation` construct that lets users declare `#`-prefixed syntactic sugar for formulas, with template-based parsing and typing-time expansion. Notations are stored as operators (sharing abbreviation infrastructure), so they work uniformly with import/export, clone, locality, sections, qualified lookup, and `rewrite /#foo` unfolds. Declarations: notation #big ['a] #< i : 'a > (r : 'a list) (P : i ==> bool = predT) (F : i ==> t) "[" i " : " r ("| " P)? "] " F = big P F r. Uses: lemma L (d : 'a distr) (f : 'a -> real) J : uniq J => BRA.#big [ x : J ] (mu1 d x * f x).`1 <= 1%r. lemma M &m (bs : out_t list) : Dst.#big [ b : bs ] (Pr[A.guess() @ &m : res = b]) <= 1%r. (* slot placeholders work as pose / rewrite patterns *) pose c := #big [ _ : _ | _ ] _. Expansions round-trip back to template syntax in the pretty-printer. Includes a stdlib port: `#big` / `#bigi` are declared in Bigop.eca and used across algebra, analysis, distributions, modules, and crypto files. Tests under tests/notations/ (wired into `make unit`) and reference documentation under doc/notation.rst.
1 parent 736f20d commit 022943c

69 files changed

Lines changed: 2295 additions & 550 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

config/tests.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ exclude = examples/MEE-CBC examples/old examples/old/list-ddh !examples/incomple
1414
okdirs = examples/MEE-CBC
1515

1616
[test-unit]
17-
okdirs = tests tests/exception
17+
okdirs = tests tests/exception tests/notations

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ EasyCrypt reference manual
55
:maxdepth: 2
66

77
tactics
8+
notation

doc/notation.rst

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
========================================================================
2+
Notations
3+
========================================================================
4+
5+
Notations are user-declared surface-syntax forms for formulas. A
6+
declaration binds a name prefixed by ``#`` to a template of literal
7+
punctuation and slots; uses of the notation at the call site are parsed
8+
against the template and expanded to an underlying body at type-checking
9+
time. Notations live in the same namespace as operators and
10+
abbreviations: they follow ``import``/``export``/``clone`` rules, they
11+
can be declared ``local`` inside a ``section``, and the pretty-printer
12+
automatically renders expanded bodies back to template syntax whenever
13+
possible.
14+
15+
------------------------------------------------------------------------
16+
Anatomy of a declaration
17+
------------------------------------------------------------------------
18+
19+
.. admonition:: Syntax
20+
21+
| ``local``? ``notation`` ``#``\ *name*
22+
| *tyvars*?
23+
| *binder-slots*?
24+
| *form-slots*\ *
25+
| *template*
26+
| (``:`` *codom*)?
27+
| ``=`` *body* ``.``
28+
29+
Every piece except the name, template, and body is optional. The
30+
declaration mirrors an operator declaration with an extra *template*
31+
section that describes the surface syntax.
32+
33+
Example::
34+
35+
notation #pair (a : int) (b : int) " [" a ", " b "] " = (a, b).
36+
37+
lemma L (x y : int) : #pair [x, y] = (x, y).
38+
proof. by trivial. qed.
39+
40+
------------------------------------------------------------------------
41+
Template literals and punctuations
42+
------------------------------------------------------------------------
43+
44+
A template is a sequence that alternates **STRING literals** and **slot
45+
references**. The first element of the template must be a STRING.
46+
47+
Each STRING must lex to exactly one of six punctuation tokens:
48+
49+
``[`` ``]`` ``:`` ``|`` ``,`` ``;``
50+
51+
Additional whitespace *inside* the string is ignored by the input
52+
matcher but preserved verbatim by the pretty-printer. For example,
53+
``" [ "`` and ``"["`` both match a single ``[`` at the call site, but
54+
the former pretty-prints with spaces on both sides.
55+
56+
A STRING that lexes to zero tokens, two or more tokens, or an
57+
unsupported symbol is rejected at declaration time::
58+
59+
notation #bad " @" a " @ " = a.
60+
(* rejected: notation punctuation `" @"' must lex to one of: [ ] : | , ; *)
61+
62+
------------------------------------------------------------------------
63+
Slots
64+
------------------------------------------------------------------------
65+
66+
A slot is a bare lowercase identifier inside the template. Each slot
67+
must be declared earlier in the notation as either a **form slot** or a
68+
**binder slot**.
69+
70+
Form slots
71+
Declared with ``( name : ty )`` in the usual function-argument style.
72+
At the call site the slot consumes a formula; at typing time the
73+
formula is checked against ``ty``.
74+
75+
Binder slots
76+
Declared inside ``#< ... >`` before the form-slot block, for example
77+
``#< i : int >`` or ``#< i : int, j : int >``. A binder slot reads
78+
an *identifier* at the call site. The chosen identifier becomes a
79+
locally-bound name visible to any form slot that depends on it.
80+
81+
Per-slot binder dependencies
82+
A form slot can declare that it depends on binder slots via
83+
``==>``::
84+
85+
notation #mapI #< i : int > (n : int) (f : i ==> int)
86+
" [" i " : " n " : " f "] " =
87+
map f (iota_ 0 n).
88+
89+
At typing, ``f`` has type ``int -> int`` (curried over the binders
90+
it declares). At the call site the user writes the body of the
91+
lambda in terms of the chosen binder name, and the expansion wraps
92+
it automatically::
93+
94+
lemma M : #mapI [k : 4 : k + 1] = map (fun k => k + 1) (iota_ 0 4).
95+
proof. by trivial. qed.
96+
97+
**Template ordering constraint.** A form slot that depends on a binder
98+
``i`` must appear *after* ``i`` in the template. Forward references
99+
are rejected at declaration time.
100+
101+
**Slot kind is inferred from the declaration, not the template.** A
102+
template reference like ``i`` means "ident slot" iff ``i`` is in the
103+
``#< ... >`` group; otherwise it means "form slot".
104+
105+
------------------------------------------------------------------------
106+
Type parameters
107+
------------------------------------------------------------------------
108+
109+
Notations may be polymorphic. Type parameters are declared in the same
110+
``['a 'b]`` syntax used by operators, appearing before the slot lists::
111+
112+
notation #ppair ['a 'b] (a : 'a) (b : 'b) " [" a ", " b "] " = (a, b).
113+
114+
lemma L (x : int) (y : bool) : #ppair [x, y] = (x, y).
115+
proof. by trivial. qed.
116+
117+
Type parameters are freshened at every call site, so independent uses
118+
do not leak type equalities.
119+
120+
------------------------------------------------------------------------
121+
Semantics
122+
------------------------------------------------------------------------
123+
124+
A notation is compiled to a regular operator of kind ``OB_nott`` that
125+
records the template alongside the typed body. At a call site
126+
``#name [ ... ]``:
127+
128+
1. The parser looks up the template for ``#name`` and consumes tokens
129+
according to its items, collecting the slot arguments.
130+
2. The resulting ``PFntt`` node is type-checked. For each form slot
131+
with binder dependencies, the argument is type-checked in an
132+
environment extended with the user-chosen binder idents, then
133+
wrapped in ``fun`` over those idents. For each ident slot, a fresh
134+
local is created with the user-chosen name. The body is then
135+
obtained by substituting all slot idents with their resolved
136+
values and alpha-renaming each bound binder to the user-chosen
137+
name.
138+
139+
Because expansion happens at typing, slot-level errors attribute to the
140+
user's source and not to the declaration's body.
141+
142+
------------------------------------------------------------------------
143+
Locality
144+
------------------------------------------------------------------------
145+
146+
A declaration may be prefixed with ``local`` inside a ``section``; the
147+
notation is then discharged when the section closes::
148+
149+
section.
150+
local notation #tmp (a : int) " [" a "] " = a + 1.
151+
lemma inside : #tmp [3] = 4.
152+
proof. by trivial. qed.
153+
end section.
154+
(* #tmp is no longer visible here. *)
155+
156+
Outside a section, ``local notation`` is rejected, as for other
157+
operators.
158+
159+
------------------------------------------------------------------------
160+
Pretty-printing
161+
------------------------------------------------------------------------
162+
163+
The pretty-printer reverse-matches the body of each registered notation
164+
against each formula it prints. When a formula matches the body of a
165+
notation, the printer renders it using the template, honouring
166+
whitespace from the declaration's STRING literals::
167+
168+
notation #pair (a : int) (b : int) " [" a ", " b "] " = (a, b).
169+
170+
lemma L (x y : int) : #pair [x, y] = (x, y).
171+
172+
print L.
173+
(* lemma L: forall (x y : int), #pair [x, y] = #pair [x, y] . *)
174+
175+
For notations with binder slots, the printer unwraps the matched
176+
lambdas to recover the user's chosen binder names.
177+
178+
------------------------------------------------------------------------
179+
Examples
180+
------------------------------------------------------------------------
181+
182+
A polymorphic pair::
183+
184+
notation #pair ['a 'b] (a : 'a) (b : 'b) " [" a ", " b "] " = (a, b).
185+
186+
lemma L (x : int) (y : bool) : #pair [x, y] = (x, y).
187+
proof. by trivial. qed.
188+
189+
A polymorphic map notation with an explicit lambda::
190+
191+
notation #pmap ['a 'b] (f : 'a -> 'b) (xs : 'a list)
192+
" [" f " : " xs "] " =
193+
map f xs.
194+
195+
lemma L : #pmap [(fun x => x + 1) : [1; 2; 3]] = [2; 3; 4].
196+
proof. by trivial. qed.
197+
198+
A binder-slot map notation where the user-chosen name scopes into the
199+
body form::
200+
201+
notation #mapI #< i : int > (n : int) (f : i ==> int)
202+
" [" i " : " n " : " f "] " =
203+
map f (iota_ 0 n).
204+
205+
lemma L : #mapI [k : 4 : k + 1] = map (fun k => k + 1) (iota_ 0 4).
206+
proof. by trivial. qed.
207+
208+
A notation inside a section::
209+
210+
section.
211+
212+
notation #gpair (a : int) (b : int) " [" a ", " b "] " = (a, b).
213+
local notation #lpair (a : int) (b : int) " [" a ", " b "] " = (a, b).
214+
215+
lemma t_g : #gpair [1, 2] = (1, 2).
216+
proof. by trivial. qed.
217+
218+
lemma t_l : #lpair [1, 2] = (1, 2).
219+
proof. by trivial. qed.
220+
221+
end section.
222+
223+
lemma after : #gpair [3, 4] = (3, 4).
224+
proof. by trivial. qed.
225+
226+
------------------------------------------------------------------------
227+
Error catalogue
228+
------------------------------------------------------------------------
229+
230+
The following errors may appear at notation-declaration or call time.
231+
232+
``notation punctuation `"x"' must lex to one of: [ ] : | , ;``
233+
A template STRING could not be lexed to exactly one of the six
234+
accepted punctuation tokens.
235+
236+
``template references unknown slot: `x'``
237+
The template refers to a slot name that is not declared as either a
238+
binder slot (``#< ... >``) or a form slot (``( ... )``).
239+
240+
``form slot depends on binder `i', which must be declared earlier in the template``
241+
A form slot's ``==>`` dependency was listed, but the binder slot
242+
appears *after* the form slot in the template. Re-order the
243+
template.
244+
245+
``parse error: unknown notation `#name'``
246+
The call site used a notation name that is not in scope. The
247+
notation may not be ``import``-ed, may be ``local`` and out of
248+
scope, or simply not declared.
249+
250+
``parse error: in notation `#name': expected `:'``
251+
The call site did not match the template at the indicated punct.
252+
Check that the call reproduces the declaration template verbatim.
253+
254+
``parse error: in notation `#name': expected identifier for binder slot `i'``
255+
A binder slot consumes an identifier at the call site; another
256+
token was supplied.
257+
258+
------------------------------------------------------------------------
259+
Limitations
260+
------------------------------------------------------------------------
261+
262+
- The punctuation palette is fixed to the six tokens listed above.
263+
Arbitrary keywords or operator symbols are not accepted because the
264+
lexer tokenizes ahead of the notation-aware parser.
265+
266+
- Templates cannot describe iteration (e.g. ``item*`` or
267+
``item, ...``) or optional parts. Each declaration has a single,
268+
fixed shape.
269+
270+
- Within a single theory file loaded via ``require``, a notation
271+
declared on line *N* is visible from line *N + 1* onward. In
272+
interactive mode the visibility is immediate.
273+
274+
- A ``local notation`` must appear inside a ``section``, as for other
275+
``local`` operators.

src/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
(menhir
3232
(modules ecParser)
3333
(explain true)
34-
(flags --table --unused-token COMMENT))
34+
(flags --table --unused-token COMMENT --inspection))

src/ecCommands.ml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -556,12 +556,24 @@ and process_th_require1 ld scope (nm, (sysname, thname), io) =
556556
let i_pragma = Pragma.get () in
557557

558558
try_finally (fun () ->
559-
let commands = EcIo.parseall (EcIo.from_file filename) in
560-
let commands =
561-
List.fold_left
562-
(fun scope g -> process_internal subld scope g.gl_action)
563-
iscope commands in
564-
commands)
559+
let reader = EcIo.from_file filename in
560+
let rec loop scope =
561+
let notations = EcEnv.Op.lookup_template (EcScope.env scope) in
562+
match EcLocation.unloc (EcIo.parse ~notations reader) with
563+
| EcParsetree.P_Prog (commands, terminate) ->
564+
let scope =
565+
List.fold_left
566+
(fun scope g -> process_internal subld scope g.gl_action)
567+
scope commands in
568+
if terminate then scope else loop scope
569+
| EcParsetree.P_DocComment _ ->
570+
loop scope
571+
| EcParsetree.P_Undo _ | EcParsetree.P_Exit ->
572+
assert false
573+
in
574+
try_finally
575+
(fun () -> loop iscope)
576+
(fun () -> EcIo.finalize reader))
565577
(fun () -> Pragma.set i_pragma)
566578
in
567579

src/ecDecl.ml

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,35 @@ and opbranch = {
110110
opb_sub : opbranches;
111111
}
112112

113+
and nt_punct_kind =
114+
[ `LBRACKET | `RBRACKET | `COLON | `PIPE | `COMMA | `SEMICOLON ]
115+
116+
and nt_punct = {
117+
np_kind : nt_punct_kind; (* lexed token to match at use sites *)
118+
np_display : string; (* verbatim source, for pretty-printing *)
119+
}
120+
121+
and nt_slot_kind =
122+
| NTS_Form of EcSymbols.symbol list (* binder slots this form depends on, in order *)
123+
| NTS_Ident
124+
125+
and nt_template_item =
126+
| NTI_Punct of nt_punct
127+
| NTI_Slot of EcSymbols.symbol * nt_slot_kind
128+
| NTI_Optional of nt_template_item list (* first item must be NTI_Punct *)
129+
130+
and nt_template = nt_template_item list
131+
113132
and notation = {
114-
ont_args : (EcIdent.t * EcTypes.ty) list;
115-
ont_resty : EcTypes.ty;
116-
ont_body : expr;
117-
ont_ponly : bool;
133+
ont_args : (EcIdent.t * EcTypes.ty) list;
134+
ont_resty : EcTypes.ty;
135+
ont_body : expr;
136+
ont_ponly : bool;
137+
ont_template : nt_template option;
138+
ont_defaults : expr EcIdent.Mid.t;
139+
(* Default value for each slot that only appears inside optional
140+
template groups. Keys are ont_args idents; absent slots → no
141+
default (must appear outside all optional groups). *)
118142
}
119143

120144
and prind = {
@@ -234,10 +258,25 @@ let mk_exception (exn_loca : locality) (exn_dom : ty list) : exception_ =
234258

235259
let mk_abbrev ?(ponly = false) tparams xs (codom, body) lc =
236260
let kind = {
237-
ont_args = xs;
238-
ont_resty = codom;
239-
ont_body = body;
240-
ont_ponly = ponly;
261+
ont_args = xs;
262+
ont_resty = codom;
263+
ont_body = body;
264+
ont_ponly = ponly;
265+
ont_template = None;
266+
ont_defaults = EcIdent.Mid.empty;
267+
} in
268+
269+
gen_op ~opaque:optransparent tparams
270+
(EcTypes.toarrow (List.map snd xs) codom) (OB_nott kind) lc
271+
272+
let mk_notation ?(defaults = EcIdent.Mid.empty) tparams xs (codom, body) template lc =
273+
let kind = {
274+
ont_args = xs;
275+
ont_resty = codom;
276+
ont_body = body;
277+
ont_ponly = false;
278+
ont_template = Some template;
279+
ont_defaults = defaults;
241280
} in
242281

243282
gen_op ~opaque:optransparent tparams

0 commit comments

Comments
 (0)