namespace debugger inductive mode | init | step | run | done instance : decidable_eq mode := by tactic.mk_dec_eq_instance structure state := (md : mode) (csz : nat) (fn_bps : list name) (active_bps : list (nat × name)) def init_state : state := { md := mode.init, csz := 0, fn_bps := [], active_bps := [] } def is_space (c : char) : bool := if c = ' ' ∨ c = char.of_nat 11 ∨ c = '\n' then tt else ff def split_core : string → string → list string | (c::cs) [] := if is_space c then split_core cs [] else split_core cs [c] | (c::cs) r := if is_space c then r^.reverse :: split_core cs [] else split_core cs (c::r) | [] [] := [] | [] r := [r^.reverse] def split (s : string) : list string := (split_core s [])^.reverse def to_qualified_name_core : string → string → name | [] r := if r = [] then name.anonymous else mk_simple_name r^.reverse | (c::cs) r := if is_space c then to_qualified_name_core cs r else if c = '.' then if r = [] then to_qualified_name_core cs [] else name.mk_string r^.reverse (to_qualified_name_core cs []) else to_qualified_name_core cs (c::r) def to_qualified_name (s : string) : name := to_qualified_name_core s [] meta def get_file (fn : name) : vm string := do { d ← vm.get_decl fn, some n ← return (vm_decl.olean d) | failure, return n } <|> return "[current file]" meta def pos_info (fn : name) : vm string := do { d ← vm.get_decl fn, some (line, col) ← return (vm_decl.pos d) | failure, file ← get_file fn, return (file ++ ":" ++ line^.to_string ++ ":" ++ col^.to_string) } <|> return "" meta def display_fn (header : string) (fn : name) (frame : nat) : vm unit := do pos ← pos_info fn, vm.put_str ("[" ++ frame^.to_string ++ "] " ++ header ++ " " ++ fn^.to_string ++ " at " ++ pos ++ "\n") meta def display_curr_fn (header : string) : vm unit := do fn ← vm.curr_fn, sz ← vm.call_stack_size, display_fn header fn (sz-1) meta def display_help : vm unit := do vm.put_str "exit - stop debugger\n", vm.put_str "help - display this message\n", vm.put_str "run - continue execution\n", vm.put_str "step - execute until another function in on the top of the stack\n", vm.put_str "stack trace\n", vm.put_str " up - move up in the stack trace\n", vm.put_str " down - move down in the stack trace\n", vm.put_str " vars - display variables in the current stack frame\n", vm.put_str "breakpoints\n", vm.put_str " break fn - add breakpoint for fn\n", vm.put_str " rbreak fn - remove breakpoint\n", vm.put_str " bs - show breakpoints\n" meta def is_valid_fn_prefix (p : name) : vm bool := do env ← vm.get_env, return $ env^.fold ff (λ d r, r || let n := d^.to_name in p^.is_prefix_of n) meta def add_breakpoint (s : state) (args : list string) : vm state := match args with | [arg] := do fn ← return $ to_qualified_name arg, ok ← is_valid_fn_prefix fn, if ok then return {s with fn_bps := fn :: list.filter (λ fn', fn ≠ fn') s^.fn_bps} else vm.put_str "invalid 'break' command, given name is not the prefix for any function\n" >> return s | _ := vm.put_str "invalid 'break ' command, incorrect number of arguments\n" >> return s end meta def remove_breakpoint (s : state) (args : list string) : vm state := match args with | [arg] := do fn ← return $ to_qualified_name arg, return {s with fn_bps := list.filter (λ fn', fn ≠ fn') s^.fn_bps} | _ := vm.put_str "invalid 'rbreak ' command, incorrect number of arguments\n" >> return s end meta def show_breakpoints : list name → vm unit | [] := return () | (fn::fns) := do vm.put_str " ", vm.put_str fn^.to_string, vm.put_str "\n", show_breakpoints fns meta def show_frame (frame : nat) : vm unit := do sz ← vm.call_stack_size, fn ← if frame >= sz then vm.curr_fn else vm.call_stack_fn frame, display_fn "frame" fn frame meta def up (frame : nat) : vm nat := if frame = 0 then return 0 else show_frame (frame - 1) >> return (frame - 1) meta def down (frame : nat) : vm nat := do sz ← vm.call_stack_size, if frame >= sz - 1 then return frame else show_frame (frame + 1) >> return (frame + 1) meta def type_to_string : option expr → nat → vm string | none i := do o ← vm.stack_obj i, match o^.kind with | vm_obj_kind.simple := return "[tagged value]" | vm_obj_kind.constructor := return "[constructor]" | vm_obj_kind.closure := return "[closure]" | vm_obj_kind.mpz := return "[big num]" | vm_obj_kind.name := return "name" | vm_obj_kind.level := return "level" | vm_obj_kind.expr := return "expr" | vm_obj_kind.declaration := return "declaration" | vm_obj_kind.environment := return "environment" | vm_obj_kind.tactic_state := return "tactic_state" | vm_obj_kind.format := return "format" | vm_obj_kind.options := return "options" | vm_obj_kind.other := return "[other]" end | (some type) i := do fmt ← vm.pp_expr type, opts ← vm.get_options, return (fmt^.to_string opts) meta def show_vars_core : nat → nat → nat → vm unit | c i e := if i = e then return () else do (n, type) ← vm.stack_obj_info i, type_str ← type_to_string type i, vm.put_str $ "#" ++ c^.to_string ++ " " ++ n^.to_string ++ " : " ++ type_str ++ "\n", show_vars_core (c+1) (i+1) e meta def show_vars (frame : nat) : vm unit := do (s, e) ← vm.call_stack_var_range frame, show_vars_core 0 s e meta def cmd_loop_core : state → nat → list string → vm state | s frame default_cmd := do is_eof ← vm.eof, if is_eof then do vm.put_str "stopping debugger... 'end of file' has been found\n", return {s with md := mode.done } else do vm.put_str "% ", l ← vm.get_line, tks ← return $ split l, tks ← return $ if tks = [] then default_cmd else tks, match tks with | [] := cmd_loop_core s frame default_cmd | (cmd::args) := if cmd = "help" then display_help >> cmd_loop_core s frame default_cmd else if cmd = "exit" then return {s with md := mode.done } else if cmd = "run" ∨ cmd = "r" then return {s with md := mode.run } else if cmd = "step" ∨ cmd = "s" then return {s with md := mode.step } else if cmd = "break" ∨ cmd = "b" then do new_s ← add_breakpoint s args, cmd_loop_core new_s frame default_cmd else if cmd = "rbreak" then do new_s ← remove_breakpoint s args, cmd_loop_core new_s frame default_cmd else if cmd = "bs" then do vm.put_str "breakpoints\n", show_breakpoints s^.fn_bps, cmd_loop_core s frame default_cmd else if cmd = "up" ∨ cmd = "u" then do frame ← up frame, cmd_loop_core s frame ["u"] else if cmd = "down" ∨ cmd = "d" then do frame ← down frame, cmd_loop_core s frame ["d"] else if cmd = "vars" ∨ cmd = "v" then do show_vars frame, cmd_loop_core s frame ["vars"] else do vm.put_str "unknown command, type 'help' for help\n", cmd_loop_core s frame default_cmd end meta def cmd_loop (s : state) (default_cmd : list string) : vm state := do csz ← vm.call_stack_size, cmd_loop_core s (csz - 1) default_cmd def prune_active_bps_core (csz : nat) : list (nat × name) → list (nat × name) | [] := [] | ((csz', n)::ls) := if csz < csz' then prune_active_bps_core ls else ((csz',n)::ls) meta def prune_active_bps (s : state) : vm state := do sz ← vm.call_stack_size, return {s with active_bps := prune_active_bps_core sz s^.active_bps} meta def updt_csz (s : state) : vm state := do sz ← vm.call_stack_size, return {s with csz := sz} meta def init_transition (s : state) : vm state := do opts ← vm.get_options, if opts^.get_bool `server ff then return {s with md := mode.done} else do bps ← vm.get_attribute `breakpoint, new_s ← return {s with fn_bps := bps}, if opts^.get_bool `debugger.autorun ff then return {new_s with md := mode.run} else do vm.put_str "Lean debugger\n", display_curr_fn "debugging", vm.put_str "type 'help' for help\n", cmd_loop new_s [] meta def step_transition (s : state) : vm state := do sz ← vm.call_stack_size, if sz = s^.csz then return s else do display_curr_fn "step", cmd_loop s ["s"] meta def bp_reached (s : state) : vm bool := do fn ← vm.curr_fn, return $ s^.fn_bps^.any (λ p, p^.is_prefix_of fn) meta def in_active_bps (s : state) : vm bool := do sz ← vm.call_stack_size, match s^.active_bps with | [] := return ff | (csz, _)::_ := return $ to_bool (sz = csz) end meta def run_transition (s : state) : vm state := do b1 ← in_active_bps s, b2 ← bp_reached s, if b1 ∨ not b2 then return s else do display_curr_fn "breakpoint", fn ← vm.curr_fn, sz ← vm.call_stack_size, new_s ← return $ {s with active_bps := (sz, fn) :: s^.active_bps}, cmd_loop new_s ["r"] meta def step_fn (s : state) : vm state := do s ← prune_active_bps s, if s^.md = mode.init then do new_s ← init_transition s, updt_csz new_s else if s^.md = mode.done then return s else if s^.md = mode.step then do new_s ← step_transition s, updt_csz new_s else if s^.md = mode.run then do new_s ← run_transition s, updt_csz new_s else return s meta def monitor : vm_monitor state := { init := init_state, step := step_fn } def attr : user_attribute := { name := `breakpoint, descr := "breakpoint for debugger" } end debugger run_command vm_monitor.register `debugger.monitor run_command attribute.register `debugger.attr set_option debugger true set_option debugger.autorun true def g (c : nat) := c + 1 def h (a : nat) (b : nat) := g b + a def s (a : nat) := h 2 a + h 3 a local attribute [breakpoint] h def f : nat → nat | 0 := s 0 | (a+1) := f a vm_eval f 3