Skip to content

Commit 5f8952e

Browse files
committed
bullets: focusing operators behind +strict_bullets pragma
When `pragma +strict_bullets` is set, the bullet tokens `-`, `+`, `*` (and their repeated forms `--`, `++`, `**`, ...) become Coq-style focusing operators: each phrase's bullet is checked against a per-proof stack so that a subgoal must be discharged before its sibling is addressed, and so that bullet characters identify nesting levels. The default behavior is unchanged: without the pragma, bullets remain pure decoration, preserving every existing proof script. Repeated bullet characters are emitted by the lexer as single PLUSn/MINUSn/STARn tokens (carrying their literal), which makes them usable both as deeper bullet levels and as user-defined binary operators (`op (--) ...`). The final sibling of a split point can be continued at the parent level without a bullet: when the previous sibling is closed and exactly one sibling remains, the inner frame pops automatically (and cascades through nested last-sibling frames). This keeps the standard phl idiom of long `seq N : <post>' chains flat instead of forcing ever-deeper indentation under a `+' for each continuation. Multi-way splits keep their enumeration discipline for all but the last subgoal.
1 parent 3665b5e commit 5f8952e

14 files changed

Lines changed: 540 additions & 38 deletions

src/ecCommands.ml

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ type pragma = {
1515
pm_g_prall : bool; (* true => display all open goals *)
1616
pm_g_prpo : EcPrinting.prpo_display;
1717
pm_check : [`Check | `WeakCheck | `Report];
18+
pm_strict_bullets : bool; (* true => bullets focus subgoals *)
1819
}
1920

2021
let dpragma = {
2122
pm_verbose = true ;
2223
pm_g_prall = false ;
2324
pm_g_prpo = EcPrinting.{ prpo_pr = false; prpo_po = false; };
2425
pm_check = `Check;
26+
pm_strict_bullets = false;
2527
}
2628

2729
module Pragma : sig
@@ -61,10 +63,15 @@ let pragma_g_po_display (b : bool) =
6163
let pragma_check mode =
6264
Pragma.upd (fun pragma -> { pragma with pm_check = mode; })
6365

66+
let pragma_strict_bullets (b : bool) =
67+
Pragma.upd (fun pragma -> { pragma with pm_strict_bullets = b; })
68+
6469
module Pragmas = struct
6570
let silent = "silent"
6671
let verbose = "verbose"
6772

73+
let strict_bullets = "strict_bullets"
74+
6875
module Proofs = struct
6976
let check = "Proofs:check"
7077
let weak = "Proofs:weak"
@@ -505,7 +512,9 @@ and process_abbrev (scope : EcScope.scope) (a : pabbrev located) =
505512
(* -------------------------------------------------------------------- *)
506513
and process_axiom ?(src : string option) (scope : EcScope.scope) (ax : paxiom located) =
507514
EcScope.check_state `InTop "axiom" scope;
508-
let (name, scope) = EcScope.Ax.add ?src scope (Pragma.get ()).pm_check ax in
515+
let pragma = Pragma.get () in
516+
let strict = pragma.pm_strict_bullets in
517+
let (name, scope) = EcScope.Ax.add ?src ~strict scope pragma.pm_check ax in
509518
name |> EcUtils.oiter
510519
(fun x ->
511520
match (unloc ax).pa_kind with
@@ -635,8 +644,9 @@ and process_sct_close (scope : EcScope.scope) name =
635644
and process_tactics ?(src : string option) (scope : EcScope.scope) t =
636645
let mode = (Pragma.get ()).pm_check in
637646
match t with
638-
| `Actual t -> snd (EcScope.Tactics.process ?src scope mode t)
639-
| `Proof -> EcScope.Tactics.proof ?src scope
647+
| `Actual (b, t) ->
648+
snd (EcScope.Tactics.process ?src ?bullet:b scope mode t)
649+
| `Proof -> EcScope.Tactics.proof ?src scope
640650

641651
(* -------------------------------------------------------------------- *)
642652
(* Add and store src for proofs *)
@@ -653,8 +663,9 @@ and process_save ?(src : string option) (scope : EcScope.scope) ed =
653663

654664
(* -------------------------------------------------------------------- *)
655665
and process_realize (scope : EcScope.scope) pr =
656-
let mode = (Pragma.get ()).pm_check in
657-
let (name, scope) = EcScope.Ax.realize scope mode pr in
666+
let pragma = Pragma.get () in
667+
let strict = pragma.pm_strict_bullets in
668+
let (name, scope) = EcScope.Ax.realize ~strict scope pragma.pm_check pr in
658669
name |> EcUtils.oiter
659670
(fun x -> EcScope.notify scope `Info "added lemma: `%s'" x);
660671
scope
@@ -689,6 +700,9 @@ and process_option (scope : EcScope.scope) (name, value) =
689700
let gs = EcEnv.gstate (EcScope.env scope) in
690701
EcGState.setflag (unloc name) value gs; scope
691702

703+
| `Bool value when EcLocation.unloc name = Pragmas.strict_bullets ->
704+
pragma_strict_bullets value; scope
705+
692706
| (`Int _) as value ->
693707
let gs = EcEnv.gstate (EcScope.env scope) in
694708
EcGState.setvalue (unloc name) value gs; scope
@@ -716,14 +730,14 @@ and process_dump_why3 scope filename =
716730
EcScope.dump_why3 scope filename; scope
717731

718732
(* -------------------------------------------------------------------- *)
719-
and process_dump scope (source, tc) =
733+
and process_dump scope (source, (bullet, tc)) =
720734
let open EcCoreGoal in
721735

722736
let input, (p1, p2) = source.tcd_source in
723737

724738
let goals, scope =
725739
let mode = (Pragma.get ()).pm_check in
726-
EcScope.Tactics.process scope mode tc
740+
EcScope.Tactics.process ?bullet scope mode tc
727741
in
728742

729743
let wrerror fname =

src/ecLexer.mll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,31 @@
301301
| '*' | '/' | '&' | '%' -> LOP3 (name |> odfl op)
302302
| _ -> LOP4 (name |> odfl op)
303303

304+
(* ------------------------------------------------------------------ *)
305+
(* Repeated bullet characters (`--`, `+++`, `***`, ...) are emitted as
306+
a single token so that the parser can distinguish them from two
307+
separate operator characters. Single-character forms keep going
308+
through the standard operator path for backward compatibility. *)
309+
let lex_bullet_chunk (op : string) =
310+
let n = String.length op in
311+
if n < 2 then None
312+
else
313+
let c = op.[0] in
314+
if not (c = '-' || c = '+' || c = '*') then None
315+
else
316+
let rec all_same i = i = n || (op.[i] = c && all_same (i+1)) in
317+
if not (all_same 1) then None
318+
else match c with
319+
| '-' -> Some (MINUSn op)
320+
| '+' -> Some (PLUSn op)
321+
| '*' -> Some (STARn op)
322+
| _ -> None
323+
304324
(* ------------------------------------------------------------------ *)
305325
let lex_operators (op : string) =
326+
match lex_bullet_chunk op with
327+
| Some tok -> [tok]
328+
| None ->
306329
let baseop (op : string) =
307330
try fst (Hashtbl.find operators op)
308331
with Not_found ->

src/ecParser.mly

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@
599599
%token WP
600600
%token ZETA
601601
%token <string> NOP LOP1 ROP1 LOP2 ROP2 LOP3 ROP3 LOP4 ROP4 NUMOP
602+
%token <string> PLUSn MINUSn STARn
602603
%token LTCOLON DASHLT GT LT GE LE LTSTARGT LTLTSTARGT LTSTARGTGT
603604
%token <Lexing.position> FINAL
604605
%token <EcParsetree.dockind * string> DOCCOMMENT
@@ -623,10 +624,10 @@
623624
%left LOP1
624625
%right ROP1
625626
%right QUESTION
626-
%left LOP2 MINUS PLUS PLUSGT
627+
%left LOP2 MINUS PLUS PLUSGT MINUSn PLUSn
627628
%right ROP2
628629
%right RARROW
629-
%left LOP3 STAR SLASH
630+
%left LOP3 STAR SLASH STARn
630631
%right ROP3
631632
%left LOP4 AT AMP HAT BACKSLASH
632633
%right ROP4
@@ -829,10 +830,12 @@ inlinepat:
829830
| LE { "<=" }
830831

831832
%inline uniop:
832-
| x=NOP { Printf.sprintf "[%s]" x }
833-
| NOT { "[!]" }
834-
| PLUS { "[+]" }
835-
| MINUS { "[-]" }
833+
| x=NOP { Printf.sprintf "[%s]" x }
834+
| NOT { "[!]" }
835+
| PLUS { "[+]" }
836+
| MINUS { "[-]" }
837+
| x=PLUSn { Printf.sprintf "[%s]" x }
838+
| x=MINUSn { Printf.sprintf "[%s]" x }
836839

837840
%inline sbinop:
838841
| EQ { "=" }
@@ -842,6 +845,9 @@ inlinepat:
842845
| STAR { "*" }
843846
| SLASH { "/" }
844847
| AT { "@" }
848+
| x=PLUSn { x }
849+
| x=MINUSn { x }
850+
| x=STARn { x }
845851
| OR { "\\/" }
846852
| ORA { "||" }
847853
| AND { "/\\" }
@@ -3577,11 +3583,17 @@ tactics0:
35773583
| ts=tactics { Pseq ts }
35783584
| x=loc(empty) { Pseq [mk_core_tactic (mk_loc x.pl_loc (Pidtac None))] }
35793585

3586+
%inline bullet:
3587+
| b=loc(MINUS) { mk_loc b.pl_loc "-" }
3588+
| b=loc(PLUS) { mk_loc b.pl_loc "+" }
3589+
| b=loc(STAR) { mk_loc b.pl_loc "*" }
3590+
| b=loc(MINUSn) { mk_loc b.pl_loc b.pl_desc }
3591+
| b=loc(PLUSn) { mk_loc b.pl_loc b.pl_desc }
3592+
| b=loc(STARn) { mk_loc b.pl_loc b.pl_desc }
3593+
35803594
toptactic:
3581-
| PLUS t=tactics { t }
3582-
| STAR t=tactics { t }
3583-
| MINUS t=tactics { t }
3584-
| t=tactics { t }
3595+
| b=bullet t=tactics { (Some b, t) }
3596+
| t=tactics { (None, t) }
35853597

35863598
tactics_or_prf:
35873599
| t=toptactic { `Actual t }

src/ecParsetree.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,8 +1364,8 @@ type global_action =
13641364
| GsctOpen of osymbol_r
13651365
| GsctClose of osymbol_r
13661366
| Grealize of prealize located
1367-
| Gtactics of [`Proof | `Actual of ptactic list]
1368-
| Gtcdump of (tcdump * ptactic list)
1367+
| Gtactics of [`Proof | `Actual of string located option * ptactic list]
1368+
| Gtcdump of (tcdump * (string located option * ptactic list))
13691369
| Gprover_info of pprover_infos
13701370
| Gsave of save located
13711371
| Gpragma of psymbol

0 commit comments

Comments
 (0)