Skip to content

Commit 64478fc

Browse files
authored
[spectec] Implement IterPr in IL evaluator (#2031)
This doesn't affect any existing code, so I'm merging for now to make the downstream sync easier.
1 parent 2929f44 commit 64478fc

File tree

2 files changed

+124
-14
lines changed

2 files changed

+124
-14
lines changed
0 Bytes
Binary file not shown.

spectec/src/il/eval.ml

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -474,34 +474,144 @@ and reduce_exp_call env id args at = function
474474
reduce_exp_call env id args at clauses'
475475
| None -> reduce_exp_call env id args at clauses'
476476
| Some s ->
477-
match reduce_prems env Subst.(subst_list subst_prem s prems) with
477+
match reduce_prems env s prems with
478478
| None -> None
479479
| Some false -> reduce_exp_call env id args at clauses'
480480
| Some true -> Some (reduce_exp env (Subst.subst_exp s e))
481481

482-
and reduce_prems env = function
482+
and reduce_prems env s = function
483483
| [] -> Some true
484484
| prem::prems ->
485-
match reduce_prem env prem with
486-
| Some true -> reduce_prems env prems
487-
| other -> other
485+
match reduce_prem env (Subst.subst_prem s prem) with
486+
| `True s' -> reduce_prems env (Subst.union s s') prems
487+
| `False -> Some false
488+
| `None -> None
488489

489-
and reduce_prem env prem : bool option =
490+
and reduce_prem env prem : [`True of Subst.t | `False | `None] =
490491
match prem.it with
491-
| RulePr _ -> None
492+
| RulePr _ -> `None
492493
| IfPr e ->
493494
(match (reduce_exp env e).it with
494-
| BoolE b -> Some b
495-
| _ -> None
495+
| BoolE b -> if b then `True Subst.empty else `False
496+
| _ -> `None
496497
)
497-
| ElsePr -> Some true
498+
| ElsePr -> `True Subst.empty
498499
| LetPr (e1, e2, _ids) ->
499500
(match match_exp env Subst.empty e2 e1 with
500-
| Some _ -> Some true (* TODO(2, rossberg): need to keep substitution? *)
501-
| None -> None
502-
| exception Irred -> None
501+
| Some s -> `True s
502+
| None -> `None
503+
| exception Irred -> `None
503504
)
504-
| IterPr (_prem, _iter) -> None (* TODO(3, rossberg): reduce? *)
505+
| IterPr (prem1, iterexp) ->
506+
let iter', xes' = reduce_iterexp env iterexp in
507+
(* Distinguish between let-defined variables, which flow outwards,
508+
* and others, which are assumed to flow inwards. *)
509+
let rec is_let_bound prem (x, _) =
510+
match prem.it with
511+
| LetPr (_, _, xs) -> List.mem x.it xs
512+
| IterPr (premI, iterexpI) ->
513+
let _iter1', xes1' = reduce_iterexp env iterexpI in
514+
let xes1_out, _ = List.partition (is_let_bound premI) xes1' in
515+
List.exists (fun (_, e1) -> Free.(Set.mem x.it (free_exp e1).varid)) xes1_out
516+
| _ -> false
517+
in
518+
let xes_out, xes_in = List.partition (is_let_bound prem) xes' in
519+
let xs_out, es_out = List.split xes_out in
520+
let xs_in, es_in = List.split xes_in in
521+
if not (List.for_all is_head_normal_exp es_in) || iter' <= List1 && es_in = [] then
522+
(* We don't know the number of iterations (yet): can't do anything. *)
523+
`None
524+
else
525+
(match iter' with
526+
| Opt ->
527+
(* Iterationen values es_in are in hnf, so got to be options. *)
528+
let eos_in = List.map as_opt_exp es_in in
529+
if List.for_all Option.is_none eos_in then
530+
(* Iterating over empty options: nothing to do. *)
531+
`True Subst.empty
532+
else if List.for_all Option.is_some eos_in then
533+
(* All iteration variables are non-empty: reduce body. *)
534+
let es1_in = List.map Option.get eos_in in
535+
(* s substitutes in-bound iteration variables with corresponding
536+
* values. *)
537+
let s = List.fold_left2 Subst.add_varid Subst.empty xs_in es1_in in
538+
match reduce_prem env (Subst.subst_prem s prem1) with
539+
| (`None | `False) as r -> r
540+
| `True s' ->
541+
(* Body is true: now reverse-match out-bound iteration values
542+
* against iteration sources. *)
543+
match
544+
List.fold_left (fun s_opt (xI, eI) ->
545+
let* s = s_opt in
546+
let tI = match eI.note.it with IterT (tI, _) -> tI | _ -> assert false in
547+
match_exp' env s (OptE (Some (Subst.subst_exp s' (VarE xI $$ xI.at % tI))) $> eI) eI
548+
) (Some Subst.empty) xes_out
549+
with
550+
| Some s'' -> `True s''
551+
| None -> `None
552+
else
553+
(* Inconsistent arity of iteration values: can't perform mapping.
554+
* (This is a stuck computation, i.e., undefined.) *)
555+
`None
556+
| List | List1 ->
557+
(* Unspecified iteration count: get length from (first) iteration value
558+
* and start over; es_in are in hnf, so got to be lists. *)
559+
let n = List.length (as_list_exp (List.hd es_in)) in
560+
if iter' = List || n >= 1 then
561+
let en = NumE (`Nat (Z.of_int n)) $$ prem.at % (NumT `NatT $ prem.at) in
562+
reduce_prem env (IterPr (prem1, (ListN (en, None), xes')) $> prem)
563+
else
564+
(* List is empty although it is List1: inconsistency.
565+
* (This is a stuck computation, i.e., undefined.) *)
566+
`None
567+
| ListN ({it = NumE (`Nat n'); _}, xo) ->
568+
(* Iterationen values es_in are in hnf, so got to be lists. *)
569+
let ess_in = List.map as_list_exp es_in in
570+
let ns = List.map List.length ess_in in
571+
let n = Z.to_int n' in
572+
if List.for_all ((=) n) ns then
573+
(* All in-bound lists have the expected length: reduce body,
574+
* once for each tuple of values from the iterated lists. *)
575+
let rs = List.init n (fun i ->
576+
let esI_in = List.map (fun es -> List.nth es i) ess_in in
577+
(* s substitutes in-bound iteration variables with corresponding
578+
* values for this respective iteration. *)
579+
let s = List.fold_left2 Subst.add_varid Subst.empty xs_in esI_in in
580+
(* Add iteration counter variable if used. *)
581+
let s' =
582+
Option.fold xo ~none:s ~some:(fun x ->
583+
let en = NumE (`Nat (Z.of_int i)) $$ x.at % (NumT `NatT $ x.at) in
584+
Subst.add_varid s x en
585+
)
586+
in
587+
reduce_prem env (Subst.subst_prem s' prem1)
588+
)
589+
in
590+
if List.mem `None rs then `None else
591+
if List.mem `False rs then `False else
592+
(* Body was true in every iteration: now reverse-match out-bound
593+
* iteration variables against iteration sources. *)
594+
let ss = List.map (function `True s -> s | _ -> assert false) rs in
595+
(* Aggregate the out-lists for each out-bound variable. *)
596+
let es_out' =
597+
List.map2 (fun xI eI ->
598+
let tI = match eI.note.it with IterT (tI, _) -> tI | _ -> assert false in
599+
let esI = List.map (fun sJ ->
600+
Subst.subst_exp sJ (VarE xI $$ xI.at % tI)
601+
) ss
602+
in ListE esI $> eI
603+
) xs_out es_out
604+
in
605+
(* Reverse-match out-bound list values against iteration sources. *)
606+
match match_list match_exp env Subst.empty es_out' es_out with
607+
| Some s' -> `True s'
608+
| None -> `None
609+
else
610+
(* Inconsistent list lengths: can't perform mapping.
611+
* (This is a stuck computation, i.e., undefined.) *)
612+
`None
613+
| ListN _ -> `None
614+
)
505615

506616

507617
(* Matching *)

0 commit comments

Comments
 (0)