@@ -416,6 +416,7 @@ let main () =
416416 (* ---*) docgen : bool ;
417417 (* ---*) outdirp : string option ;
418418 (* ---*) upto : (int * int option ) option ;
419+ (* ---*) trace_at : (int * int option ) option ;
419420 mutable trace : trace1 list option ;
420421 }
421422
@@ -495,6 +496,7 @@ let main () =
495496 ; docgen = false
496497 ; outdirp = None
497498 ; upto = None
499+ ; trace_at = None
498500 ; trace = None }
499501
500502 end
@@ -531,6 +533,7 @@ let main () =
531533 ; docgen = false
532534 ; outdirp = None
533535 ; upto = None
536+ ; trace_at = None
534537 ; trace = trace0 }
535538
536539 end
@@ -560,6 +563,7 @@ let main () =
560563 ; docgen = false
561564 ; outdirp = None
562565 ; upto = llmopts.llmo_upto
566+ ; trace_at = llmopts.llmo_trace
563567 ; trace = None }
564568
565569 end
@@ -605,6 +609,7 @@ let main () =
605609 ; docgen = true
606610 ; outdirp = docopts.doco_outdirp
607611 ; upto = None
612+ ; trace_at = None
608613 ; trace = None }
609614 end
610615
@@ -618,7 +623,7 @@ let main () =
618623 | Some pwd -> EcCommands. addidir pwd);
619624
620625 (* Check if the .eco is up-to-date and exit if so *)
621- (if not state.docgen && state.upto = None then
626+ (if not state.docgen && state.upto = None && state.trace_at = None then
622627 oiter
623628 (fun input -> if EcCommands. check_eco input then exit 0 )
624629 state.input);
@@ -712,6 +717,38 @@ let main () =
712717 | None -> true
713718 | Some c -> sc > = c) in
714719
720+ (* Check if a sentence's start location is at-or-past the -trace target.
721+ Returns the actual (line, col) of the matched sentence start on hit. *)
722+ let at_trace (loc : EcLocation.t ) =
723+ match state.trace_at with
724+ | None -> None
725+ | Some (line , col ) ->
726+ let (sl, sc) = loc.loc_start in
727+ let hit =
728+ sl > line || (sl = line && match col with
729+ | None -> true
730+ | Some c -> sc > = c)
731+ in if hit then Some (sl, sc) else None in
732+
733+ (* Lazy read of the whole input file as bytes, used by --trace to slice
734+ the exact source text of a sentence by byte offsets. *)
735+ let input_bytes = lazy (
736+ match state.input with
737+ | None -> " "
738+ | Some path ->
739+ let ic = open_in_bin path in
740+ let n = in_channel_length ic in
741+ let b = Bytes. create n in
742+ really_input ic b 0 n;
743+ close_in ic;
744+ Bytes. unsafe_to_string b) in
745+
746+ let sentence_source (loc : EcLocation.t ) =
747+ let s = Lazy. force input_bytes in
748+ let lo = max 0 loc.EcLocation. loc_bchar in
749+ let hi = min (String. length s) loc.EcLocation. loc_echar in
750+ if hi < = lo then " " else String. sub s lo (hi - lo) in
751+
715752 try
716753 if T. interactive terminal then Sys. catch_break true ;
717754
@@ -781,6 +818,70 @@ let main () =
781818 (fun p ->
782819 let loc = p.EP. gl_action.EcLocation. pl_loc in
783820
821+ (* -trace: at-or-past the target, print BEFORE (focused
822+ goal only), run this one sentence, print AFTER
823+ (new-or-modified goals only), then SUMMARY, exit. *)
824+ (match at_trace loc with
825+ | None -> ()
826+ | Some (sl , sc ) ->
827+ let out = Format. std_formatter in
828+ let before_goals = EcCommands. pp_all_goals () in
829+ let n1 = List. length before_goals in
830+ Format. fprintf out
831+ " === BEFORE: line %d (col %d) ===@\n " sl sc;
832+ EcCommands. pp_current_goal_or_noproof ~all: false out;
833+ let (el, ec) = loc.EcLocation. loc_end in
834+ Format. fprintf out
835+ " @\n === TACTIC (lines %d:%d - %d:%d) ===@\n %s@\n @\n "
836+ sl sc el ec (sentence_source loc);
837+ (try
838+ let _ : float option =
839+ EcCommands. process ~src ~timed: false ~break: false
840+ p.EP. gl_action
841+ in
842+ let after_goals = EcCommands. pp_all_goals () in
843+ let n2 = List. length after_goals in
844+ Format. fprintf out
845+ " === AFTER: line %d (col %d) ===@\n " sl sc;
846+ let before_set =
847+ List. fold_left
848+ (fun s g -> EcMaps.Sstr. add g s)
849+ EcMaps.Sstr. empty before_goals
850+ in
851+ (* The new focused goal always counts as
852+ "modified" (its focus status changed even if
853+ its text matches an old sibling). Subsequent
854+ goals are printed only if they didn't appear
855+ in BEFORE. *)
856+ let to_print =
857+ match after_goals with
858+ | [] -> []
859+ | head :: tail ->
860+ head ::
861+ List. filter
862+ (fun g -> not (EcMaps.Sstr. mem g before_set))
863+ tail
864+ in
865+ (match to_print with
866+ | [] ->
867+ Format. fprintf out " (no open goals)@\n "
868+ | _ ->
869+ List. iteri (fun i g ->
870+ if i > 0 then Format. fprintf out " @\n " ;
871+ Format. fprintf out " %s@\n " g)
872+ to_print);
873+ Format. fprintf out
874+ " @\n === SUMMARY ===@\n open goals: %d -> %d@\n " n1 n2
875+ with e ->
876+ Format. fprintf out
877+ " === AFTER: line %d (col %d) ===@\n <sentence failed>@\n " sl sc;
878+ EcPException. exn_printer Format. err_formatter e;
879+ Format. pp_print_newline Format. err_formatter () ;
880+ T. finalize terminal;
881+ exit 1 );
882+ T. finalize terminal;
883+ exit 0 );
884+
784885 (* -upto: if this command starts past the target, print goals and exit *)
785886 if past_upto loc then begin
786887 T. finalize terminal;
@@ -857,6 +958,15 @@ let main () =
857958
858959 if ! terminate then begin
859960 T. finalize terminal;
961+ (match state.trace_at with
962+ | Some (line , col ) ->
963+ let col_s = match col with
964+ | None -> " " | Some c -> Printf. sprintf " :%d" c in
965+ Format. eprintf
966+ " trace: no sentence at or after line %d%s@."
967+ line col_s;
968+ exit 2
969+ | None -> () );
860970 if not state.eco then
861971 finalize_input state.input (EcCommands. current () );
862972 if state.docgen then
0 commit comments