aboutsummaryrefslogtreecommitdiffstats
path: root/theories/nix/operational.v
blob: d3f0777b013791fc542c3d7835d11d41fc64ceb2 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
From mininix Require Export utils nix.floats.
From stdpp Require Import options.

(** Our development does not rely on a particular order on attribute set names.
It can be any decidable total order. We pick something concrete (lexicographic
order on strings) to avoid having to parametrize the whole development. *)
Definition attr_le := String.le.
Global Instance attr_le_dec : RelDecision attr_le := _.
Global Instance attr_le_po : PartialOrder attr_le := _.
Global Instance attr_le_total : Total attr_le := _.
Global Typeclasses Opaque attr_le.

Inductive mode := SHALLOW | DEEP.
Inductive kind := ABS | WITH.
Inductive rec := REC | NONREC.

Global Instance rec_eq_dec : EqDecision rec.
Proof. solve_decision. Defined.

Inductive num :=
  | NInt (n : Z)
  | NFloat (f : float).

Inductive base_lit :=
  | LitNum (n : num)
  | LitBool (b : bool)
  | LitString (s : string)
  | LitNull.

Global Instance num_inhabited : Inhabited num := populate (NInt 0).
Global Instance base_lit_inhabited : Inhabited base_lit := populate LitNull.

Global Instance num_eq_dec : EqDecision num.
Proof. solve_decision. Defined.
Global Instance base_lit_eq_dec : EqDecision base_lit.
Proof. solve_decision. Defined.

Global Instance maybe_NInt : Maybe NInt := λ n,
  if n is NInt i then Some i else None.
Global Instance maybe_NFloat : Maybe NFloat := λ n,
  if n is NFloat f then Some f else None.
Global Instance maybe_LitNum : Maybe LitNum := λ bl,
  if bl is LitNum n then Some n else None.
Global Instance maybe_LitBool : Maybe LitBool := λ bl,
  if bl is LitBool b then Some b else None.
Global Instance maybe_LitString : Maybe LitString := λ bl,
  if bl is LitString s then Some s else None.

Inductive bin_op : Set :=
  | AddOp | SubOp | MulOp | DivOp | AndOp | OrOp | XOrOp (* Arithmetic *)
  | LtOp | EqOp (* Relations *)
  | RoundOp (m : round_mode) (* Conversions *)
  | MatchStringOp (* Strings *)
  | MatchListOp | AppendListOp (* Lists *)
  | SelectAttrOp | UpdateAttrOp | HasAttrOp
  | DeleteAttrOp | SingletonAttrOp | MatchAttrOp (* Attribute sets *)
  | FunctionArgsOp | TypeOfOp.

Global Instance bin_op_eq_dec : EqDecision bin_op.
Proof. solve_decision. Defined.

Global Instance maybe_RoundOp : Maybe RoundOp := λ op,
  if op is RoundOp m then Some m else None.

Section expr.
  Local Unset Elimination Schemes.
  Inductive expr :=
    | ELit (bl : base_lit)
    | EId (x : string) (mke : option (kind * expr))
    | EAbs (x : string) (e : expr)
    | EAbsMatch (ms : gmap string (option expr)) (strict : bool) (e : expr)
    | EApp (e1 e2 : expr)
    | ESeq (μ : mode) (e1 e2 : expr)
    | EList (es : list expr)
    | EAttr (αs : gmap string attr)
    | ELetAttr (k : kind) (e1 e2 : expr)
    | EBinOp (op : bin_op) (e1 e2 : expr)
    | EIf (e1 e2 e3 : expr)
  with attr :=
    | Attr (τ : rec) (e : expr).
End expr.

Definition EId' x := EId x None.
Notation AttrR := (Attr REC).
Notation AttrN := (Attr NONREC).
Notation ESelect e x := (EBinOp SelectAttrOp e (ELit (LitString x))).
Notation ELet x e := (ELetAttr ABS (EAttr {[ x := AttrN e ]})).
Notation EWith := (ELetAttr WITH).

Definition attr_expr (α : attr) : expr := match α with Attr _ e => e end.
Definition attr_rec (α : attr) : rec := match α with Attr μ _ => μ end.
Definition attr_map (f : expr → expr) (α : attr) : attr :=
  match α with Attr μ e => Attr μ (f e) end.

Definition from_attr {A} (f g : expr → A) (α : attr) : A :=
  match α with AttrR e => f e | AttrN e => g e end.

Definition merge_kinded {A} (new old : kind * A) : option (kind * A) :=
  match new.1, old.1 with
  | WITH, ABS => Some old
  | _, _ => Some new
  end.
Arguments merge_kinded {_} !_ !_ / : simpl nomatch.
Notation union_kinded := (union_with merge_kinded).

Definition no_recs : gmap string attr → Prop :=
  map_Forall (λ _ α, attr_rec α = NONREC).

Definition indirects (αs : gmap string attr) : gmap string (kind * expr) :=
  map_imap (λ x _, Some (ABS, ESelect (EAttr αs) x)) αs.

Fixpoint subst (ds : gmap string (kind * expr)) (e : expr) : expr :=
  match e with
  | ELit b => ELit b
  | EId x mkd => EId x $ union_kinded (ds !! x) mkd
  | EAbs x e => EAbs x (subst ds e)
  | EAbsMatch ms strict e =>
      EAbsMatch (fmap (M:=option) (subst ds) <$> ms) strict (subst ds e)
  | EApp e1 e2 => EApp (subst ds e1) (subst ds e2)
  | ESeq μ e1 e2 => ESeq μ (subst ds e1) (subst ds e2)
  | EList es => EList (subst ds <$> es)
  | EAttr αs => EAttr (attr_map (subst ds) <$> αs)
  | ELetAttr k e1 e2 => ELetAttr k (subst ds e1) (subst ds e2)
  | EBinOp op e1 e2 => EBinOp op (subst ds e1) (subst ds e2)
  | EIf e1 e2 e3 => EIf (subst ds e1) (subst ds e2) (subst ds e3)
  end.

Notation attr_subst ds := (attr_map (subst ds)).

Definition int_min : Z := -(163).
Definition int_max : Z := 163 - 1.

Definition int_ok (i : Z) : bool := bool_decide (int_min ≤ i ≤ int_max)%Z.
Definition num_ok (n : num) : bool :=
  match n with NInt i => int_ok i | _ => true end.
Definition base_lit_ok (bl : base_lit) : bool :=
  match bl with LitNum n => num_ok n | _ => true end.

Inductive final : mode → expr → Prop :=
  | ELitFinal μ bl : base_lit_ok bl → final μ (ELit bl)
  | EAbsFinal μ x e : final μ (EAbs x e)
  | EAbsMatchFinal μ ms strict e : final μ (EAbsMatch ms strict e)
  | EListShallowFinal es : final SHALLOW (EList es)
  | EListDeepFinal es : Forall (final DEEP) es → final DEEP (EList es)
  | EAttrShallowFinal αs : no_recs αs → final SHALLOW (EAttr αs)
  | EAttrDeepFinal αs :
     no_recs αs →
     map_Forall (λ _, final DEEP ∘ attr_expr) αs →
     final DEEP (EAttr αs).

Fixpoint sem_eq_list (es1 es2 : list expr) : expr :=
  match es1, es2 with
  | [], [] => ELit (LitBool true)
  | e1 :: es1, e2 :: es2 =>
      EIf (EBinOp EqOp e1 e2) (sem_eq_list es1 es2) (ELit (LitBool false))
  | _, _ => ELit (LitBool false)
  end.

Fixpoint sem_lt_list (es1 es2 : list expr) : expr :=
  match es1, es2 with
  | [], _ => ELit (LitBool true)
  | e1 :: es1, e2 :: es2 =>
      EIf (EBinOp LtOp e1 e2) (ELit (LitBool true)) $
        EIf (EBinOp EqOp e1 e2) (sem_lt_list es1 es2) (ELit (LitBool false))
  | _ :: _, [] => ELit (LitBool false)
  end.

Definition sem_and_attr (es : gmap string expr) : expr :=
  map_fold_sorted attr_le
    (λ _ e1 e2, EIf e1 e2 (ELit (LitBool false)))
    (ELit (LitBool true)) es.

Definition sem_eq_attr (αs1 αs2 : gmap string attr) : expr :=
  sem_and_attr $ merge (λ mα12,
    α1 ← mα1; α2 ← mα2; Some (EBinOp EqOp (attr_expr α1) (attr_expr α2))) αs1 αs2.

Definition num_to_float (n : num) : float :=
  match n with
  | NInt i => Float.of_Z i
  | NFloat f => f
  end.

Definition sem_bin_op_lift
    (fint : Z → Z → Z) (ffloat : float → float → float)
    (n1 n2 : num) : option num :=
  match n1, n2 with
  | NInt i1, NInt i2 =>
     let i := fint i1 i2 in
     guard (int_ok i);;
     Some (NInt i)
  | _, _ => Some $ NFloat $ ffloat (num_to_float n1) (num_to_float n2)
  end.

Definition sem_bin_rel_lift
    (fint : Z → Z → bool) (ffloat : float → float → bool)
    (n1 n2 : num) : bool :=
  match n1, n2 with
  | NInt i1, NInt i2 => fint i1 i2
  | _, _ => ffloat (num_to_float n1) (num_to_float n2)
  end.

Definition sem_eq_base_lit (bl1 bl2 : base_lit) : bool :=
  match bl1, bl2 with
  | LitNum n1, LitNum n2 => sem_bin_rel_lift Z.eqb Float.eqb n1 n2
  | LitBool b1, LitBool b2 => bool_decide (b1 = b2)
  | LitString s1, LitString s2 => bool_decide (s1 = s2)
  | LitNull, LitNull => true
  | _, _ => false
  end.

(** Precondition e1 and e2 are final *)
Definition sem_eq (e1 e2 : expr) : option expr :=
  match e1, e2 with
  | ELit bl1, ELit bl2 => Some $ ELit (LitBool (sem_eq_base_lit bl1 bl2))
  | EAbs _ _, EAbs _ _ => None
  | EList es1, EList es2 => Some $
      if decide (length es1 = length es2) then sem_eq_list es1 es2
      else ELit $ LitBool false
  | EAttr αs1, EAttr αs2 => Some $
      if decide (dom αs1 = dom αs2) then sem_eq_attr αs1 αs2
      else ELit $ LitBool false
  | _, _ => Some $ ELit (LitBool false)
  end.

Definition div_allowed (n : num) : bool :=
  match n with
  | NInt n => bool_decide (n ≠ 0%Z)
  | NFloat f => negb (Float.eqb f (Float.of_Z 0)) (* TODO: Check NaNs *)
  end.

Definition sem_bin_op_num (op : bin_op) (n1 : num) : option (num → option base_lit) :=
  match op with
  | AddOp => Some $ λ n2,
      LitNum <$> sem_bin_op_lift Z.add Float.add n1 n2
  | SubOp => Some $ λ n2,
      LitNum <$> sem_bin_op_lift Z.sub Float.sub n1 n2
  | MulOp => Some $ λ n2,
      LitNum <$> sem_bin_op_lift Z.mul Float.mul n1 n2
  | DivOp => Some $ λ n2,
      (* Quot can overflow: [MIN_INT `quot` -1] equals [MAX_INT + 1] *)
      guard (div_allowed n2);;
      LitNum <$> sem_bin_op_lift Z.quot Float.div n1 n2
  | AndOp =>
      i1 ← maybe NInt n1;
      Some $ λ n2, i2 ← maybe NInt n2;
        Some $ LitNum $ NInt $ Z.land i1 i2
  | OrOp =>
      i1 ← maybe NInt n1;
      Some $ λ n2, i2 ← maybe NInt n2;
        Some $ LitNum $ NInt $ Z.lor i1 i2
  | XOrOp =>
      i1 ← maybe NInt n1;
      Some $ λ n2, i2 ← maybe NInt n2;
        Some $ LitNum $ NInt $ Z.lxor i1 i2
  | LtOp => Some $ λ n2,
      Some $ LitBool (sem_bin_rel_lift Z.ltb Float.ltb n1 n2)
  | _ => None
  end%Z.

Definition sem_bin_op_string (op : bin_op) : option (string → string → base_lit) :=
  match op with
  | AddOp => Some $ λ s1 s2, LitString (s1 +:+ s2)
  | LtOp => Some $ λ s1 s2, LitBool (bool_decide (strict attr_le s1 s2))
  | _ => None
  end.

Definition type_of_num (n : num) : string :=
  match n with
  | NInt _ => "int"
  | NFloat _ => "float"
  end.

Definition type_of_base_lit (bl : base_lit) : string :=
  match bl with
  | LitNum n => type_of_num n
  | LitBool _ => "bool"
  | LitString _ => "string"
  | LitNull => "null"
  end.

Definition type_of_expr (e : expr) :=
  match e with
  | ELit bl => Some (type_of_base_lit bl)
  | EAbs _ _ | EAbsMatch _ _ _ => Some "lambda"
  | EList _ => Some "list"
  | EAttr _ => Some "set"
  | _ => None
  end.

(* Used for [RoundOp] *)
Definition float_to_bounded_Z (f : float) : Z :=
  match Float.to_Z f with
  | Some x => if decide (int_ok x) then x else int_min
  | None => int_min
  end.

Inductive sem_bin_op : bin_op → expr → (expr → expr → Prop) → Prop :=
  | EqSem e1 :
     sem_bin_op EqOp e1 (λ e2 e, sem_eq e1 e2 = Some e)
  | LitNumSem op n1 f :
     sem_bin_op_num op n1 = Some f →
     sem_bin_op op (ELit (LitNum n1)) (λ e2 e, ∃ n2 bl,
       e2 = ELit (LitNum n2) ∧ f n2 = Some bl ∧ e = ELit bl)
  | RoundSem m n1 :
     sem_bin_op (RoundOp m) (ELit (LitNum n1)) (λ e2 e,
       e2 = ELit LitNull ∧
       e = ELit $ LitNum $ NInt $ float_to_bounded_Z $ Float.round m $ num_to_float n1)
  | LitStringSem op s1 f :
     sem_bin_op_string op = Some f →
     sem_bin_op op (ELit (LitString s1)) (λ e2 e, ∃ s2,
       e2 = ELit (LitString s2) ∧ e = ELit (f s1 s2))
  | MatchStringSem s :
     sem_bin_op MatchStringOp (ELit (LitString s)) (λ e2 e,
       e2 = ELit LitNull ∧
       match s with
       | EmptyString => e = EAttr {[
           "empty" := AttrN (ELit (LitBool true));
           "head" := AttrN (ELit LitNull);
           "tail" := AttrN (ELit LitNull) ]}
       | String a s => e = EAttr {[
           "empty" := AttrN (ELit (LitBool false));
           "head" := AttrN (ELit (LitString (String a EmptyString)));
           "tail" := AttrN (ELit (LitString s)) ]}
       end)
  | LtListSem es :
     sem_bin_op LtOp (EList es) (λ e2 e, ∃ es',
       e2 = EList es' ∧
       e = sem_lt_list es es')
  | MatchListSem es :
     sem_bin_op MatchListOp (EList es) (λ e2 e,
       e2 = ELit LitNull ∧
       match es with
       | [] => e = EAttr {[
           "empty" := AttrN (ELit (LitBool true));
           "head" := AttrN (ELit LitNull);
           "tail" := AttrN (ELit LitNull) ]}
       | e' :: es => e = EAttr {[
           "empty" := AttrN (ELit (LitBool false));
           "head" := AttrN e';
           "tail" := AttrN (EList es) ]}
       end)
  | AppendListSem es :
     sem_bin_op AppendListOp (EList es) (λ e2 e, ∃ es',
       e2 = EList es' ∧
       e = EList (es ++ es'))
  | SelectAttrSem αs :
     no_recs αs →
     sem_bin_op SelectAttrOp (EAttr αs) (λ e2 e, ∃ x,
       e2 = ELit (LitString x) ∧ αs !! x = Some (AttrN e))
  | UpdateAttrSem αs1 :
     no_recs αs1 →
     sem_bin_op UpdateAttrOp (EAttr αs1) (λ e2 e, ∃ αs2,
       e2 = EAttr αs2 ∧ no_recs αs2 ∧ e = EAttr (αs2 ∪ αs1))
  | HasAttrSem αs :
     no_recs αs →
     sem_bin_op HasAttrOp (EAttr αs) (λ e2 e, ∃ x,
       e2 = ELit (LitString x) ∧ e = ELit (LitBool (bool_decide (is_Some (αs !! x)))))
  | DeleteAttrSem αs :
     no_recs αs →
     sem_bin_op DeleteAttrOp (EAttr αs) (λ e2 e, ∃ x,
       e2 = ELit (LitString x) ∧ e = EAttr (delete x αs))
  | SingletonAttrSem x :
     sem_bin_op SingletonAttrOp (ELit (LitString x)) (λ e2 e,
       e2 = ELit LitNull ∧
       e = EAbs "t" (EAttr {[ x := AttrN (EId' "t") ]}))
  | MatchAttrSem αs :
     no_recs αs →
     sem_bin_op MatchAttrOp (EAttr αs) (λ e2 e,
       e2 = ELit LitNull ∧
       ((αs = ∅ ∧
         e = EAttr {[
           "empty" := AttrN (ELit (LitBool true));
           "key" := AttrN (ELit LitNull);
           "head" := AttrN (ELit LitNull);
           "tail" := AttrN (ELit LitNull) ]})(∃ x e',
         αs !! x = Some (AttrN e')(∀ y, is_Some (αs !! y) → attr_le x y) ∧
         e = EAttr {[
           "empty" := AttrN (ELit (LitBool false));
           "key" := AttrN (ELit (LitString x));
           "head" := AttrN e';
           "tail" := AttrN (EAttr (delete x αs)) ]})))
  | FunctionArgsAbsSem x e' :
     sem_bin_op FunctionArgsOp (EAbs x e') (λ e2 e,
       e2 = ELit LitNull ∧
       e = EAttr ∅)
  | FunctionArgsAbsMatchSem ms strict e' :
     sem_bin_op FunctionArgsOp (EAbsMatch ms strict e') (λ e2 e,
       e2 = ELit LitNull ∧
       e = EAttr (AttrN ∘ ELit ∘ LitBool ∘ from_option (λ _, true) false <$> ms))
  | TypeOfSem e1 :
     sem_bin_op TypeOfOp e1 (λ e2 e, ∃ x,
       e2 = ELit LitNull ∧
       type_of_expr e1 = Some x ∧
       e = ELit (LitString x)).

Inductive matches :
     gmap string expr → gmap string (option expr) → bool → gmap string attr → Prop :=
  | MatchEmpty strict :
     matches ∅ ∅ strict ∅
  | MatchAny es :
     matches es ∅ false ∅
  | MatchAvail x e es ms md strict βs :
     es !! x = None →
     ms !! x = None →
     matches es ms strict βs →
     matches (<[x:=e]> es) (<[x:=md]> ms) strict (<[x:=AttrN e]> βs)
  | MatchOptDefault x es ms d strict βs :
     es !! x = None →
     ms !! x = None →
     matches es ms strict βs →
     matches es (<[x:=Some d]> ms) strict (<[x:=AttrR d]> βs).

Reserved Notation "e1 -{ μ }-> e2"
  (right associativity, at level 55, μ at level 1, format "e1  -{ μ }->  e2").

Inductive ctx1 : mode → mode → (expr → expr) → Prop :=
  | CList es1 es2 :
     Forall (final DEEP) es1 →
     ctx1 DEEP DEEP (λ e, EList (es1 ++ e :: es2))
  | CAttr αs x :
     no_recs αs →
     αs !! x = None →
     (∀ y α, αs !! y = Some α → final DEEP (attr_expr α) ∨ attr_le x y) →
     ctx1 DEEP DEEP (λ e, EAttr (<[x:=AttrN e]> αs))
  | CAppL μ e2 :
     ctx1 SHALLOW μ (λ e1, EApp e1 e2)
  | CAppR μ ms strict e1 :
     ctx1 SHALLOW μ (EApp (EAbsMatch ms strict e1))
  | CSeq μ μ' e2 :
     ctx1 μ' μ (λ e1, ESeq μ' e1 e2)
  | CLetAttr μ k e2 :
     ctx1 SHALLOW μ (λ e1, ELetAttr k e1 e2)
  | CBinOpL μ op e2 :
     ctx1 SHALLOW μ (λ e1, EBinOp op e1 e2)
  | CBinOpR μ op e1 Φ :
     final SHALLOW e1 →
     sem_bin_op op e1 Φ →
     ctx1 SHALLOW μ (EBinOp op e1)
  | CIf μ e2 e3 :
     ctx1 SHALLOW μ (λ e1, EIf e1 e2 e3).

Inductive step : mode → relation expr :=
  | Sβ μ x e1 e2 :
     EApp (EAbs x e1) e2 -{μ}-> subst {[x:=(ABS, e2)]} e1
  | SβMatch μ ms strict e1 αs βs :
     no_recs αs →
     matches (attr_expr <$> αs) ms strict βs →
     EApp (EAbsMatch ms strict e1) (EAttr αs) -{μ}->
       subst (indirects βs) e1
  | SFunctor μ αs e1 e2 :
     no_recs αs →
     αs !! "__functor" = Some (AttrN e1)EApp (EAttr αs) e2 -{μ}-> EApp (EApp e1 (EAttr αs)) e2
  | SSeqFinal μ μ' e1 e2 :
     final μ' e1 → ESeq μ' e1 e2 -{μ}-> e2
  | SLetAttrAttr μ k αs e :
     no_recs αs →
     ELetAttr k (EAttr αs) e -{μ}-> subst ((k,.) ∘ attr_expr <$> αs) e
  | SAttr_rec μ αs :
     ¬no_recs αs →
     EAttr αs -{μ}->
       EAttr (AttrN ∘ from_attr (subst (indirects αs)) id <$> αs)
  | SBinOp μ op e1 Φ e2 e :
     final SHALLOW e1 →
     final SHALLOW e2 →
     sem_bin_op op e1 Φ → Φ e2 e →
     EBinOp op e1 e2 -{μ}-> e
  | SIfBool μ b e2 e3 :
     EIf (ELit (LitBool b)) e2 e3 -{μ}-> if b then e2 else e3
  | SId μ x k e :
     EId x (Some (k,e)) -{μ}-> e
  | SCtx K μ μ' e e' :
     ctx1 μ μ' K → e -{μ}-> e' → K e -{μ'}-> K e'
where "e1 -{ μ }-> e2" := (step μ e1 e2).

Notation "e1 -{ μ }->* e2" := (rtc (step μ) e1 e2)
  (right associativity, at level 55, μ at level 1, format "e1  -{ μ }->*  e2").
Notation "e1 -{ μ }->+ e2" := (tc (step μ) e1 e2)
  (right associativity, at level 55, μ at level 1, format "e1  -{ μ }->+  e2").

Definition stuck (μ : mode) (e : expr) : Prop :=
  nf (step μ) e ∧ ¬final μ e.

(** Induction *)
Fixpoint expr_size (e : expr) : nat :=
  match e with
  | ELit _ => 1
  | EId _ mkd => S (from_option (expr_size ∘ snd) 1 mkd)
  | EAbs _ d => S (expr_size d)
  | EAbsMatch ms _ e =>
     S (map_sum_with (from_option expr_size 1) ms + expr_size e)
  | EApp e1 e2 | ESeq _ e1 e2 => S (expr_size e1 + expr_size e2)
  | EList es => S (sum_list_with expr_size es)
  | EAttr eτs => S (map_sum_with (expr_size ∘ attr_expr) eτs)
  | ELetAttr _ e1 e2 => S (expr_size e1 + expr_size e2)
  | EBinOp _ e1 e2 => S (expr_size e1 + expr_size e2)
  | EIf e1 e2 e3 => S (expr_size e1 + expr_size e2 + expr_size e3)
  end.

Lemma expr_ind (P : expr → Prop) :
  (∀ b, P (ELit b))(∀ x mkd, from_option (P ∘ snd) True mkd → P (EId x mkd))(∀ x e, P e → P (EAbs x e))(∀ ms strict e,
    map_Forall (λ _, from_option P True) ms → P e → P (EAbsMatch ms strict e))(∀ e1 e2, P e1 → P e2 → P (EApp e1 e2))(∀ μ e1 e2, P e1 → P e2 → P (ESeq μ e1 e2))(∀ es, Forall P es → P (EList es))(∀ αs, map_Forall (λ _, P ∘ attr_expr) αs → P (EAttr αs))(∀ k e1 e2, P e1 → P e2 → P (ELetAttr k e1 e2))(∀ op e1 e2, P e1 → P e2 → P (EBinOp op e1 e2))(∀ e1 e2 e3, P e1 → P e2 → P e3 → P (EIf e1 e2 e3)) →
  ∀ e, P e.
Proof.
  intros Hlit Hid Habs Hmatch Happ Hseq Hlist Hattr Hlet Hop Hif e.
  induction (Nat.lt_wf_0_projected expr_size e) as [e _ IH].
  destruct e; repeat destruct select (option _); simpl in *; eauto with lia.
  - apply Hmatch; [|by eauto with lia]=> y [e'|] Hx //=. apply IH, Nat.lt_succ_r.
    etrans; [|apply Nat.le_add_r].
    eapply (map_sum_with_lookup_le (from_option expr_size 1) _ _ _ Hx).
  - apply Hlist, Forall_forall=> e ?. apply IH, Nat.lt_succ_r.
    by apply sum_list_with_in.
  - apply Hattr, map_Forall_lookup=> y e ?. apply IH, Nat.lt_succ_r.
    by eapply (map_sum_with_lookup_le (expr_size ∘ attr_expr)).
Qed.