package ppx_interact
Opens a REPL in context
Install
Dune Dependency
Authors
Maintainers
Sources
ppx_interact-0.1.0.tbz
sha256=add42ac9716daa999a27b7a427e08d08672517971289a26988a51f0eadfdbc39
sha512=0963f59f3fa7a10cce25b4dae5a30ae41caf53816886bb76b1eaf54d9b63ba9503dbd6f162ce88c18fc4b6a7d4468d797bb7ce860c2c9da8d93a0da8fd7837eb
doc/src/ppx_interact.runtime/ppx_interact_runtime.ml.html
Source file ppx_interact_runtime.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
(* box-drawing characters *) let box_h = "─" let box_v = "│" let box_t = "┬" let box_bot = "┴" let view_file ?(context = (4, 2)) line file = let before, after = context in let show () = let ic = open_in file in let rec loop skip left = if left <= 0 then [] else try let line = input_line ic in if skip > 0 then loop (skip - 1) left else let line = if skip = 0 then line else line in line :: loop 0 (left - 1) with End_of_file -> [] in let lines = loop (max 0 (line - before - 1)) (before + after + 1) in let line_number_width = 2 + (log10 (line + after |> float_of_int) |> int_of_float) in let title_width = line_number_width + 3 in let divider joint = List.init 60 (fun i -> if i = line_number_width + 1 then joint else box_h) |> String.concat "" in Format.printf "%s@." (divider box_h); Format.printf "%s@." (String.init title_width (fun _ -> ' ') ^ file); Format.printf "%s@." (divider box_t); List.iteri (fun i l -> Format.printf "%*d %s %s\n" line_number_width (i + max 1 (line - before)) box_v l) lines; Format.printf "%s@." (divider box_bot); close_in ic in match Sys.getenv_opt "NO_BAT" with | Some _ -> show () | None -> let open Unix in (match create_process "bat" [| "--paging=never"; "--line-range"; Format.asprintf "%d:%d" (line - before) (line + after); "--highlight-line"; string_of_int line; file; "--style"; "header,numbers,grid"; |] stdin stdout stderr |> waitpid [] |> snd with | WEXITED 0 -> () | WEXITED _ | WSIGNALED _ | WSTOPPED _ | (exception Unix_error (ENOENT, "create_process", "bat")) -> show ()) let eval ~show text = let lexbuf = Lexing.from_string text in let phrase = !Toploop.parse_toplevel_phrase lexbuf in ignore (Toploop.execute_phrase show Format.std_formatter phrase) exception Found of Env.t exception Term of int type value = V : string * _ -> value let walk dir ~init ~f = let rec loop dir acc = let acc = f dir acc in ArrayLabels.fold_left (Sys.readdir dir) ~init:acc ~f:(fun acc fn -> let fn = Filename.concat dir fn in match Unix.lstat fn with | { st_kind = S_DIR; _ } -> loop fn acc | _ -> acc) in match Unix.lstat dir with | exception Unix.Unix_error (ENOENT, _, _) -> init | _ -> loop dir init (** https://github.com/ocaml/ocaml/blob/trunk/toplevel/toploop.ml *) module Toploop2 = struct exception PPerror let phrase_buffer = Buffer.create 1024 let loop () = let ppf = Format.std_formatter in Clflags.debug := true; Location.formatter_for_warnings := ppf; (* don't initialize the toplevel environment, as we don't want to clear bindings passed in *) let lb = Lexing.from_function Topcommon.refill_lexbuf in Location.init lb "//toplevel//"; Location.input_name := "//toplevel//"; Location.input_lexbuf := Some lb; Location.input_phrase_buffer := Some phrase_buffer; Sys.catch_break true; (* loading ocamlinit is done elsewhere *) try while true do let snap = Btype.snapshot () in try Lexing.flush_input lb; Buffer.reset phrase_buffer; Location.reset (); Warnings.reset_fatal (); Topcommon.first_line := true; let phr = try !Toploop.parse_toplevel_phrase lb with Exit -> raise PPerror in let phr = Toploop.preprocess_phrase ppf phr in Env.reset_cache_toplevel (); ignore (Toploop.execute_phrase true ppf phr) with | Sys.Break -> Btype.backtrack snap; raise End_of_file | PPerror -> () | x -> Location.report_exception ppf x; Btype.backtrack snap done with End_of_file -> () (* modified to return all .ocamlinit files, in order *) let find_ocamlinit () = let exists_in_dir dir file = match dir with | None -> None | Some dir -> let file = Filename.concat dir file in if Sys.file_exists file then Some file else None in let home_dir () = Sys.getenv_opt "HOME" in let config_dir () = if Sys.win32 then None else match Sys.getenv_opt "XDG_CONFIG_HOME" with | Some _ as v -> v | None -> (match home_dir () with | None -> None | Some dir -> Some (Filename.concat dir ".config")) in let init_ml = Filename.concat "ocaml" "init.ml" in let ocamlinit = ".ocamlinit" in let local = if Sys.file_exists ocamlinit then [ocamlinit] else [] in let global = match exists_in_dir (config_dir ()) init_ml with | Some v -> [v] | None -> (match exists_in_dir (home_dir ()) ocamlinit with | Some v -> [v] | None -> []) in (* load global first, then local *) global @ local end let linenoise_prompt completion_words = let rec user_input prompt f = match LNoise.linenoise prompt with | None -> () | Some v -> f v; user_input prompt f in (* this goes from front-to-back, which is the right order, so more recent bindings are suggested first *) LNoise.set_hints_callback (fun inp -> match inp with | "" -> None | _ -> Option.bind (List.find_opt (String.starts_with ~prefix:inp) completion_words) (fun sugg -> let sl = String.length sugg in let il = String.length inp in if il < sl then let s = String.sub sugg il (sl - il) in Some (s, LNoise.White, false) else None)); LNoise.set_completion_callback (fun so_far ln_completions -> List.filter (String.starts_with ~prefix:so_far) completion_words |> List.iter (LNoise.add_completion ln_completions)); user_input "> " (fun s -> let s = String.trim s in let doesn't_end_with_semicolons s = let l = String.length s in l < 2 || String.sub s (l - 2) 2 <> ";;" in let s = if doesn't_end_with_semicolons s then s ^ ";;" else s in LNoise.history_add s |> ignore; (* LNoise.history_save ~filename:"history.txt" |> ignore; *) try eval ~show:true s with exn -> Location.report_exception Format.err_formatter exn) (** see https://github.com/ocaml-community/utop/blob/master/src/lib/uTop_main.ml *) let interact ?(search_path = []) ?(build_dir = "_build") ~unit ~loc:(fname, lnum, cnum, _) ?(init = []) ~values () = let verbose = Sys.getenv_opt "VERBOSE" |> Option.is_some in Toploop.initialize_toplevel_env (); let search_path = walk build_dir ~init:search_path ~f:(fun dir acc -> dir :: acc) in let cmt_fname = try Misc.find_in_path_uncap search_path (unit ^ ".cmt") with Not_found -> Printf.ksprintf failwith "%s.cmt not found in search path!" unit in let cmt_infos = Cmt_format.read_cmt cmt_fname in let get_required_label name args = match List.find (fun (lab, _) -> lab = Asttypes.Labelled name) args with | _, x -> x | exception Not_found -> None in let expr next (e : Typedtree.expression) = match e.exp_desc with | Texp_apply (_, args) -> begin try match (get_required_label "loc" args, get_required_label "values" args) with | Some l, Some v -> let pos = l.exp_loc.loc_start in if pos.pos_fname = fname && pos.pos_lnum = lnum && pos.pos_cnum - pos.pos_bol = cnum then raise (Found v.exp_env) | _ -> next e with Not_found -> next e end | _ -> next e in let next iterator e = Tast_iterator.default_iterator.expr iterator e in let expr iterator = expr (next iterator) in let iter = { Tast_iterator.default_iterator with expr } in let search = iter.structure iter in try begin match cmt_infos.cmt_annots with | Implementation st -> search st | _ -> () end; failwith "Couldn't find location in cmt file" with Found env -> (try List.iter Topdirs.dir_directory (search_path @ cmt_infos.cmt_loadpath); let env = Envaux.env_of_only_summary env in List.iter (fun (V (name, v)) -> Toploop.setvalue name (Obj.repr v)) values; Toploop.toplevel_env := env; (* let idents = Env.diff Env.empty env in *) (* List.iter print_endline (List.map Ident.name idents); *) let names = List.map (fun (V (name, _)) -> name) values in List.iter (fun line -> try eval ~show:verbose line with exn -> Format.printf "initialization failed: %s@." line; Location.report_exception Format.err_formatter exn) init; List.iter (fun oi -> let ic = open_in oi in let s = really_input_string ic (in_channel_length ic) in begin try eval ~show:verbose s with | End_of_file -> () | exn -> Location.report_exception Format.err_formatter exn end; close_in_noerr ic; if verbose then Format.printf "Loaded %s@." oi) (Toploop2.find_ocamlinit ()); let use_linenoise = Option.is_some (Sys.getenv_opt "NO_DOWN") || try Load_path.find "down.top" |> ignore; Toploop.use_file Format.std_formatter "down.top" |> not with Not_found -> true in (* eval "b;;"; *) (* eval "let c = b + 1;;"; *) (* let v : int = Obj.obj (Toploop.getvalue "c") in *) (* Format.printf "v = %d@." v; *) match use_linenoise with | false -> Toploop2.loop () | true -> linenoise_prompt names with exn -> Location.report_exception Format.err_formatter exn; exit 2)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>