@@ -296,6 +296,18 @@ and proof_auc = {
296296 puc_jdg : proof_state ;
297297 puc_flags : pucflags ;
298298 puc_crt : EcDecl .axiom ;
299+ puc_bullets : bullet_frame list ;
300+ (* Active bullet stack (head = innermost). Each frame records the
301+ focused goal handle, the still-to-process sibling handles at
302+ that depth, the bullet token (e.g. "-", "--"), and the location
303+ where the bullet was opened — used for diagnostics. *)
304+ }
305+
306+ and bullet_frame = {
307+ bf_token : string ;
308+ bf_loc : EcLocation .t ;
309+ bf_at_open : int ;
310+ bf_used : int ;
299311}
300312
301313and proof_ctxt =
@@ -811,7 +823,108 @@ module Tactics = struct
811823 | Some src -> DocState. push_srcbl scope.sc_locdoc src
812824 | None -> scope.sc_locdoc; }
813825
814- let process_r ?(src : string option ) ?reloc mark (mode : proofmode ) (scope : scope ) (tac : ptactic list ) =
826+ (* ----------------------------------------------------------------- *)
827+ (* Bullet-stack management. The "strict" mode (gated by the
828+ [+strict-bullets] pragma) requires each phrase to discharge its
829+ focused subgoal before moving on. The non-strict mode keeps the
830+ historical behavior where bullets are pure decoration.
831+
832+ Frames record the number of "background" open goals at the
833+ point the bullet level was opened. A frame's currently-focused
834+ subgoal is fully discharged iff [open_count <= bf_background].
835+ The bullet-level itself is fully closed when, in addition, the
836+ last sibling has been processed; we detect that by counting open
837+ goals and the depth stack. *)
838+
839+ let bullet_error ~loc fmt =
840+ let buf = Buffer. create 64 in
841+ let fbuf = Format. formatter_of_buffer buf in
842+ Format. kfprintf
843+ (fun fbuf ->
844+ Format. pp_print_flush fbuf () ;
845+ hierror ~loc " %s" (Buffer. contents buf))
846+ fbuf fmt
847+
848+ let n_open juc = List. length (EcCoreGoal. all_hd_opened juc)
849+
850+ (* Validate the bullet against the current stack and return the new
851+ stack to install for this phrase. *)
852+ let open_bullet ~(bullet : string located option )
853+ (juc : EcCoreGoal.proof ) (stack : bullet_frame list )
854+ : bullet_frame list =
855+ match bullet with
856+ | None -> stack
857+ | Some b ->
858+ let tok = unloc b in
859+ let loc = loc b in
860+ let opened = n_open juc in
861+ (* Search the stack from innermost outward for a frame matching
862+ [tok]. Any frames strictly inside the matched one are popped
863+ (must already be drained, i.e. opened <= bf_background). *)
864+ let rec scan acc = function
865+ | [] -> `Open
866+ | f :: rest when f.bf_token = tok -> `Match (List. rev acc, f, rest)
867+ | f :: rest -> scan (f :: acc) rest
868+ in
869+ match scan [] stack with
870+ | `Open ->
871+ (* Push a new deeper frame; this bullet consumes slot #1
872+ (out of [opened]) immediately. *)
873+ if opened = 0 then
874+ bullet_error ~loc
875+ " bullet `%s' opens a new subproof level but there are \
876+ no remaining subgoals" tok;
877+ let frame = {
878+ bf_token = tok;
879+ bf_loc = loc;
880+ bf_at_open = opened;
881+ bf_used = 1 ;
882+ } in
883+ frame :: stack
884+ | `Match (inner , frame , outer ) ->
885+ (* The expected open count after fully discharging the
886+ previous slot, given the frame's history. *)
887+ let expected = frame.bf_at_open - frame.bf_used in
888+ (* Each inner frame must already be drained — i.e., its
889+ outer view of the open count must be reachable. *)
890+ List. iter (fun (f : bullet_frame ) ->
891+ if opened > f.bf_at_open - f.bf_used then
892+ bullet_error ~loc
893+ " bullet `%s' (matches an outer level opened at %s) \
894+ skips past inner bullet `%s' opened at %s whose \
895+ subproof is not closed"
896+ tok (EcLocation. tostring frame.bf_loc)
897+ f.bf_token (EcLocation. tostring f.bf_loc))
898+ inner;
899+ (* The matching frame's current slot must be closed. *)
900+ if opened > expected then
901+ bullet_error ~loc
902+ " bullet `%s' reused but the previous subgoal (opened at \
903+ %s) is not closed"
904+ tok (EcLocation. tostring frame.bf_loc);
905+ (* Slots remaining? *)
906+ if frame.bf_used > = frame.bf_at_open then
907+ bullet_error ~loc
908+ " bullet `%s' reused but no sibling subgoals remain at \
909+ this level (opened at %s)"
910+ tok (EcLocation. tostring frame.bf_loc);
911+ let frame = { frame with
912+ bf_loc = loc;
913+ bf_used = frame.bf_used + 1 ;
914+ } in
915+ frame :: outer
916+
917+ (* Close the phrase: validate that the (possibly bulleted) phrase
918+ made progress on the focused subgoal. We do NOT pop drained
919+ frames here; that's done at the next [open_bullet] or on save. *)
920+ let close_bullet ~(bullet : _ ) (juc : EcCoreGoal.proof )
921+ (stack : bullet_frame list ) : bullet_frame list =
922+ let _ = bullet in let _ = juc in
923+ stack
924+
925+ let process_r ?(src : string option ) ?(bullet : string located option )
926+ ?(strict_bullets : bool = false )
927+ ?reloc mark (mode : proofmode ) (scope : scope ) (tac : ptactic list ) =
815928 check_state `InProof " proof script" scope;
816929
817930 let scope =
@@ -855,6 +968,11 @@ module Tactics = struct
855968 EcHiGoal. tt_redlogic = Options. get_redlogic scope;
856969 EcHiGoal. tt_und_delta = Options. get_und_delta scope; } in
857970
971+ let bullets =
972+ if strict_bullets then open_bullet ~bullet juc pac.puc_bullets
973+ else pac.puc_bullets
974+ in
975+
858976 let (hds, juc) =
859977 try TTC. process ttenv tac juc
860978 with EcCoreGoal. TcError tcerror ->
@@ -866,9 +984,14 @@ module Tactics = struct
866984 in raise (EcCoreGoal. TcError tcerror)
867985 in
868986
987+ let bullets =
988+ if strict_bullets then close_bullet ~bullet juc bullets
989+ else bullets
990+ in
991+
869992 let penv = EcCoreGoal. proofenv_of_proof juc in
870993
871- let pac = { pac with puc_jdg = PSCheck juc } in
994+ let pac = { pac with puc_jdg = PSCheck juc; puc_bullets = bullets } in
872995 let puc = { puc with puc_active = Some (pac, pct); } in
873996 let scope = { scope with sc_pr_uc = Some puc; } in
874997 Some (penv, hds), scope
@@ -880,8 +1003,9 @@ module Tactics = struct
8801003 let ts = List. map (fun t -> { pt_core = t; pt_intros = [] ; }) ts in
8811004 snd (process_r mark mode scope ts)
8821005
883- let process ?(src : string option ) scope mode tac =
884- process_r ?src true mode scope tac
1006+ let process ?(src : string option ) ?(bullet : string located option )
1007+ ?(strict_bullets : bool = false ) scope mode tac =
1008+ process_r ?src ?bullet ~strict_bullets true mode scope tac
8851009end
8861010
8871011(* -------------------------------------------------------------------- *)
@@ -955,7 +1079,8 @@ module Ax = struct
9551079 ; puc_started = false
9561080 ; puc_jdg = puc
9571081 ; puc_flags = axflags
958- ; puc_crt = axd }
1082+ ; puc_crt = axd
1083+ ; puc_bullets = [] }
9591084 in
9601085 { puc_active = Some (active, ctxt);
9611086 puc_cont = cont;
0 commit comments