(** 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 is indented text embedded in the compiler and read by [Prelude.read]; 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. A [let] is a global only in code from column 1 that is not an expression: a form cut from inside a body, for a macroexpansion say, keeps its lets local. *) 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 ~global_let:(not expr && col = 1) ~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)