Skip to content

Commit 0ee11c7

Browse files
committed
split (C-c C-y) can take a list of terms to do a multiple match
1 parent 891cccd commit 0ee11c7

File tree

3 files changed

+118
-53
lines changed

3 files changed

+118
-53
lines changed

docs/source/interactive.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ Splitting in holes
5959

6060
Narya has a limited ability to infer the shape of a term to solve a hole with from the type of that hole or from the type of an argument to match against. In ProofGeneral mode, if you position the cursor over a hole and type ``C-c C-y``, you will be prompted for a term on which to split (using the contents of the current hole as the default), or to leave it blank to split on the type of the goal. (As a mnemonic for this command, the letter ``Y`` looks like a "split".) Narya will then try to guess the shape of a term to fill the hole with, leaving additional holes in appropriate places. You will be given the opportunity to edit the suggested term before it is used to solve the hole (for instance, to change the names of new variables being bound). This includes:
6161

62-
- If you enter a term, that term must synthesize a datatype (see :ref:`Inductive datatypes and matching`). The term inserted will then be a match against that term, with appropriate branches for all of its constructors. The default variable names for the arguments of each constructor are taken from the datatype declaration, although you can change them when prompted with the term. This includes higher-dimensional versions of data types (see :ref:`Id of datatypes`). However, it is not currently possible to use this method to insert :ref:`Multiple matches and deep matches`.
62+
- If you enter a term, that term must synthesize a datatype (see :ref:`Inductive datatypes and matching`). The term inserted will then be a match against that term, with appropriate branches for all of its constructors. The default variable names for the arguments of each constructor are taken from the datatype declaration, although you can change them when prompted with the term. This includes higher-dimensional versions of data types (see :ref:`Id of datatypes`).
63+
64+
- Similarly, if you enter a comma-separated list of terms, each of which synthesizes a datatype, the term inserted will be a :ref:`multiple match <Multiple matches and deep matches>` against those terms. Note that if more than one of the terms you enter belong to the same datatype, the variable names for the arguments of their constructors in the proposed match will be the same, which will cause an error unless you edit the match term to change some of them. It is not currently possible to insert a deep match by splitting.
6365

6466
- If you don't enter a term, and the hole has a function type, the term inserted will be an abstraction with a new hole in the body. The variable names in the abstraction are taken from the function type, e.g. for ``(x : A) → B`` the term inserted will be ``x ↦ ?``. For a function type with unnamed variable like ``A → B``, the variable inserted will be a placeholder ``_`` (which you will probably want to change when prompted to edit the term). Iterated function-types like ``(x : A) (y : B) → C`` lead to iterated abstractions ``x y ↦ ?``, and higher-dimensional function-types like ``Id ((x : A) → B) f g`` lead to :ref:`Cubes of variables` ``x ⤇ ?``.
6567

lib/parser/command.ml

Lines changed: 89 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
open Bwd
2+
open Bwd.Infix
23
open Dim
34
open Util
45
open List_extra
@@ -104,7 +105,8 @@ module Command = struct
104105
number : int;
105106
wsnumber : Whitespace.t list;
106107
wscoloneq : Whitespace.t list;
107-
tm : wrapped_parse;
108+
(* The whitespace is for the commas. The first one is ignored. *)
109+
tms : (Whitespace.t list * wrapped_parse) list;
108110
mutable printed_term : PPrint.document;
109111
}
110112
(* Show and Undo don't get reformatted (see pp_command, below), so there's no need to store whitespace in them, but we do it anyway for completeness. *)
@@ -556,7 +558,13 @@ module Parse = struct
556558
let* number, wsnumber = integer in
557559
let* wscoloneq = token Coloneq in
558560
let* tm = C.term [] in
559-
return (Split { wssplit; number; wsnumber; wscoloneq; tm; printed_term = PPrint.empty })
561+
let* tms =
562+
zero_or_more
563+
(let* wscomma = token (Op ",") in
564+
let* tm = C.term [] in
565+
return (wscomma, tm)) in
566+
let tms = ([], tm) :: tms in
567+
return (Split { wssplit; number; wsnumber; wscoloneq; tms; printed_term = PPrint.empty })
560568

561569
let show =
562570
let* wsshow = token Show in
@@ -773,6 +781,59 @@ let condense : Command.t -> [ `Import | `Option | `None | `Bof ] = function
773781
| Option _ -> .
774782
| _ -> `None
775783

784+
(* Subroutine for "split" that generates the cases in a multiple match. *)
785+
let split_match_cases : type a b.
786+
(a, b) Ctx.t ->
787+
(string option, a) Bwv.t ->
788+
(Whitespace.t list * wrapped_parse) list ->
789+
observation list list =
790+
fun ctx vars tms ->
791+
let open Asai.Range in
792+
let module S = Monad.State (Bool) in
793+
let module LS = Monad.ListT (S) in
794+
let open Monad.Ops (LS) in
795+
let tok t : observation = Token (t, ([], None)) in
796+
let rec do_args : type a p ap.
797+
(a, p, ap) Term.Telescope.t ->
798+
(No.plus_omega, No.strict, No.plus_omega, No.nonstrict) parse Asai.Range.located list =
799+
fun args ->
800+
match args with
801+
| Emp -> []
802+
| Ext (None, _, args) -> locate_opt None (Placeholder []) :: do_args args
803+
| Ext (Some x, _, args) -> locate_opt None (Ident ([ x ], [])) :: do_args args in
804+
let rec go = function
805+
| [] ->
806+
let* higher = LS.lift S.get in
807+
let mapsto = if higher then Token.DblMapsto else Mapsto in
808+
let li, ri = (No.Interval.entire, No.Interval.entire) in
809+
let h = Hole { li; ri; num = ref 0; ws = []; contents = None } in
810+
return [ tok mapsto; Term (locate_opt None h) ]
811+
| (_, Wrap tm) :: tms -> (
812+
match process vars tm with
813+
| { value = Synth rtm; loc } -> (
814+
let _, sty = Check.synth (Kinetic `Nolet) ctx (Asai.Range.locate_opt loc rtm) in
815+
match View.view_type sty "split" with
816+
| Canonical (_, Data { dim; constrs; _ }, _, _) ->
817+
let* () =
818+
match D.compare_zero dim with
819+
| Zero -> return ()
820+
| Pos _ -> LS.lift (S.put true) in
821+
let* c, Dataconstr { args; _ } = S.return (Bwd.to_list constrs) in
822+
let left_ok = No.le_refl No.plus_omega in
823+
let right_ok = No.le_refl No.plus_omega in
824+
let first =
825+
Term
826+
(List.fold_left
827+
(fun fn arg -> locate_opt None (App { fn; arg; left_ok; right_ok }))
828+
(locate_opt None (Constr (Constr.to_string c, [])))
829+
(do_args args)) in
830+
let* rest = go tms in
831+
if List.length rest = 2 then return (first :: rest)
832+
else return (first :: tok (Op ",") :: rest)
833+
| _ -> fatal (Invalid_split (`Term, "non-datatype")))
834+
| _ -> fatal (Nonsynthesizing "splitting term")) in
835+
fst (go tms false)
836+
776837
(* Most execution of commands we can do here, but there are a couple things where we need to call out to the executable: noting when an effectual action like 'echo' is taken (for recording warnings in compiled files), and loading another file. So this function takes a couple of callbacks as arguments. *)
777838
let execute ~(action_taken : unit -> unit) ~(get_file : string -> Scope.trie) (cmd : Command.t) :
778839
int option * (int * int * int) list =
@@ -956,11 +1017,10 @@ let execute ~(action_taken : unit -> unit) ~(get_file : string -> Scope.trie) (c
9561017
let (Found_hole { instant; termctx; ty; vars; parametric; _ }) =
9571018
Global.find_hole data.number in
9581019
Global.rewind_command ~parametric ~holes_allowed:(Ok ()) instant @@ fun () ->
959-
let (Wrap tm) = data.tm in
9601020
let content =
9611021
let ctx = Norm.eval_ctx termctx in
962-
match tm.value with
963-
| Placeholder _ -> (
1022+
match data.tms with
1023+
| [ (_, Wrap { value = Placeholder _; _ }) ] -> (
9641024
let ety = Norm.eval_term (Ctx.env ctx) ty in
9651025
match View.view_type ety "split" with
9661026
| Canonical (_, Pi (_, doms, _), _, _) ->
@@ -1020,50 +1080,30 @@ let execute ~(action_taken : unit -> unit) ~(get_file : string -> Scope.trie) (c
10201080
fatal (Invalid_split (`Goal, "datatype with multiple constructors"))
10211081
| Canonical (_, UU _, _, _) -> fatal (Invalid_split (`Goal, "universe"))
10221082
| Neutral _ -> fatal (Invalid_split (`Goal, "neutral")))
1023-
| _ -> (
1024-
match process vars tm with
1025-
| { value = Synth rtm; loc } -> (
1026-
let _, sty = Check.synth (Kinetic `Nolet) ctx (Asai.Range.locate_opt loc rtm) in
1027-
match View.view_type sty "split" with
1028-
| Canonical (_, Data { dim; constrs; _ }, _, _) ->
1029-
let mapsto =
1030-
match D.compare_zero dim with
1031-
| Zero -> Token.to_string Mapsto
1032-
| Pos _ -> Token.to_string DblMapsto in
1033-
let rec do_args : type a p ap. (a, p, ap) Term.Telescope.t -> string -> string =
1034-
fun args acc ->
1035-
match args with
1036-
| Emp -> acc
1037-
| Ext (x, _, args) ->
1038-
Option.value ~default:(Token.to_string Underscore) x
1039-
^ " "
1040-
^ do_args args acc in
1041-
let do_constrs : type m ij.
1042-
Constr.t * (m, ij) Value.dataconstr -> string list -> string list =
1043-
fun (c, Dataconstr { args; _ }) acc ->
1044-
(Constr.to_string c
1045-
^ ". "
1046-
^ do_args args " "
1047-
^ mapsto
1048-
^ " "
1049-
^ Token.to_string (Hole None))
1050-
:: acc in
1051-
let constrs = Bwd.fold_right do_constrs constrs [] in
1052-
let buf = Buffer.create 10 in
1053-
PPrint.(
1054-
ToBuffer.pretty 1.0 (Display.columns ()) buf
1055-
(pp_complete_term (Wrap tm) `None));
1056-
Token.to_string Match
1057-
^ " "
1058-
^ Buffer.contents buf
1059-
^ " "
1060-
^ Token.to_string LBracket
1061-
^ " "
1062-
^ String.concat " | " constrs
1063-
^ " "
1064-
^ Token.to_string RBracket
1065-
| _ -> fatal (Invalid_split (`Term, "non-datatype")))
1066-
| _ -> fatal (Nonsynthesizing "splitting term")) in
1083+
| _ ->
1084+
let tok t : observation = Token (t, ([], None)) in
1085+
let comma_tms =
1086+
List.tl
1087+
@@
1088+
let open Monad.Ops (Monad.List) in
1089+
let* wscomma, Wrap tm = data.tms in
1090+
[ Token (Op ",", (wscomma, None)); Term tm ] in
1091+
let lines = split_match_cases ctx vars data.tms in
1092+
let mtch =
1093+
Asai.Range.locate_opt None
1094+
@@ outfix ~notn:Builtins.implicit_mtch
1095+
~inner:
1096+
(Multiple
1097+
( Left (Match, ([], None)),
1098+
Emp
1099+
<@ comma_tms
1100+
<: tok LBracket
1101+
<@ List.flatten (List.map (fun line -> tok (Op "|") :: line) lines),
1102+
Left (RBracket, ([], None)) )) in
1103+
let buf = Buffer.create 10 in
1104+
PPrint.(
1105+
ToBuffer.pretty 1.0 (Display.columns ()) buf (pp_complete_term (Wrap mtch) `None));
1106+
Buffer.contents buf in
10671107
let ptm = TermParse.Term.(final (parse (`String { title = None; content }))) in
10681108
let disp = Display.get () in
10691109
Display.run ~init:{ disp with holes = `Without_number } @@ fun () ->

lib/util/monad.ml

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ module Ops (M : Plain) = struct
1818
let ( let* ) = M.bind
1919
let ( >> ) x y = M.bind x (fun _ -> y)
2020

21-
let liftM (f : 'a -> 'b) (mx : 'a M.t) : 'b M.t =
22-
let* x = mx in
23-
return (f x)
21+
let rec mapM (f : 'a -> 'b M.t) (xs : 'a list) : 'b list M.t =
22+
match xs with
23+
| [] -> M.return []
24+
| x :: xs ->
25+
let* fx = f x in
26+
let* fxs = mapM f xs in
27+
M.return (fx :: fxs)
2428
end
2529

2630
(* The identity monad *)
@@ -160,6 +164,25 @@ module List = struct
160164
let force (x : 'a t) : 'a list = x
161165
end
162166

167+
module ListT (M : Plain) = struct
168+
open Ops (M)
169+
170+
type 'a t = 'a list M.t
171+
172+
let return : type a. a -> a t = fun x -> M.return [ x ]
173+
174+
let bind : type a b. a t -> (a -> b t) -> b t =
175+
fun o f ->
176+
M.bind o (fun o ->
177+
let* fo = mapM f o in
178+
M.return (GList.flatten fo))
179+
180+
let lift : type a. a M.t -> a t =
181+
fun mx ->
182+
let* x = mx in
183+
M.return [ x ]
184+
end
185+
163186
(*
164187
open Bwd
165188

0 commit comments

Comments
 (0)