flan/lib/source.ml

98 lines
4.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 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)