flan/lib/source.ml

77 lines
3.0 KiB
OCaml

(** 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
let syntax_of_field = function
| Some ("indented" | "fln") -> Indented
| _ -> Paren
let with_code ~syntax ~at f =
let s = !code_syntax and a = !code_at in
code_syntax := syntax;
code_at := at;
Fun.protect ~finally:(fun () -> code_syntax := s; code_at := a) f
(* 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 ~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)