(** A program source file, read by the reader its extension names: [.fln] is the indented syntax ([Indent_reader]), anything else the paren syntax ([Reader]). Both give the same [Form.t], so nothing past this point knows which one a file was written in, and a program may mix them freely. Only program sources come through here. The prelude, the wire protocol and the registry's spellings are paren text the compiler writes itself, and read it with [Reader] directly. *) let indented_ext = ".fln" let paren_ext = ".flan" let is_indented path = Filename.check_suffix path indented_ext (** A file a package directory contributes, in either syntax. *) let is_source path = Filename.check_suffix path paren_ext || is_indented path let read_file path = if is_indented path then Indent_reader.read_file path else Reader.read_file path (* ── Code from the editor ───────────────────────────────────────────── The dev loop's code-carrying requests say which syntax their [:code] is in ([:syntax]), and where in the buffer it starts ([:line], [:col]), rather than having it guessed from [:file]: an expansion shown in paren syntax is sent back under the name of the .fln file it came from, and a REPL line has no file at all. [Dev] sets these for the length of one request, and every place the session reads editor code reads it through [read_code]. *) type syntax = Paren | Indented let code_syntax = ref Paren let code_at : (int * int) option ref = ref None (* The column of the statement editor code was cut out of, when the code starts after that statement's first word: see [Indent_reader.layout]. *) let code_indent : int option ref = ref None let syntax_of_field = function | Some ("indented" | "fln") -> Indented | _ -> Paren let in_request = ref false let with_code ?indent ~syntax ~at f = let s = !code_syntax and a = !code_at and i = !code_indent and r = !in_request in code_syntax := syntax; code_at := at; code_indent := indent; in_request := true; Fun.protect ~finally:(fun () -> code_syntax := s; code_at := a; code_indent := i; in_request := r) f (* Was the code at [loc] written in the indented syntax? Inside an editor request the request says; outside one a file's name does, as [read_file] decides. *) let indented_at (loc : Loc.t) = if !in_request then !code_syntax = Indented else is_indented loc.Loc.file (* The paren reader started at a line and column: [Reader.read_all] always starts at 1:1. *) let read_paren ?(line = 1) ?(col = 1) ~file src = let st = Reader.of_string ~file src in st.Reader.line <- line; st.Reader.col <- col; let rec go acc = Reader.skip_ignorable st; if Reader.at_end st then List.rev acc else go (Reader.read_form st :: acc) in go [] (** Editor code, in the request's syntax and at its position. With [expr], an indented snippet of several statements is one expression, [(do ...)]: a block of lines means its lines in order. *) let read_code ?(expr = false) ~file code = let line, col = match !code_at with Some (l, c) -> (l, c) | None -> (1, 1) in match !code_syntax with | Paren -> read_paren ~line ~col ~file code | Indented -> (match Indent_reader.read_all ~line ~col ?indent:!code_indent ~file code with | (first :: _ :: _ as forms) when expr -> let last = List.nth forms (List.length forms - 1) in let loc = { first.Form.loc with Loc.eline = last.Form.loc.Loc.eline; ecol = last.Form.loc.Loc.ecol } in [ Form.make (Form.List (Form.make (Form.Sym "do") first.Form.loc :: forms)) loc ] | forms -> forms)