Loc.t grows an exclusive end, defaulting to the start, so a location nobody widened is a zero-width span at a point and every existing call site keeps its old meaning. Only the reader knows where a form ends, so only the reader fills them in — one helper in the one place that holds both ends, which is why nothing above Reader had to learn a span exists. The width assertion is the point of the tests: the field could exist, nothing could fill it, and every underline would be one character long while the feature looked finished.
56 lines
2.3 KiB
OCaml
56 lines
2.3 KiB
OCaml
(** Source locations. Every form carries one: error messages, the step debugger
|
|
and nREPL's find-definition all need them, and retrofitting locations onto a
|
|
reader is far worse than carrying them from the start.
|
|
|
|
A location is a *span*, not a point. The start is what [file:line:col]
|
|
prints and what every consumer that wants one place uses; the end is what
|
|
lets an error underline the thing it is about. A column number cannot draw
|
|
a squiggle and a span can, which is the whole reason the two extra fields
|
|
are here.
|
|
|
|
The end is *exclusive* and defaults to the start, so a location nobody
|
|
widened is a zero-width span at a point and every old call site keeps its
|
|
old meaning. Only the reader knows where a form ends, so only the reader
|
|
fills these in; a location the checker invents for a node with no syntax
|
|
stays a point. *)
|
|
|
|
type t = {
|
|
file : string;
|
|
line : int; (* 1-based *)
|
|
col : int; (* 1-based *)
|
|
eline : int; (* 1-based, exclusive end *)
|
|
ecol : int;
|
|
}
|
|
|
|
let make file line col = { file; line; col; eline = line; ecol = col }
|
|
let unknown = make "<unknown>" 0 0
|
|
|
|
(** [upto start stop] is [start] widened to end where [stop] begins. A [stop]
|
|
that is not after [start], or is in another file, leaves it alone: a span
|
|
that runs backwards would draw nonsense. *)
|
|
let upto (start : t) (stop : t) =
|
|
if
|
|
stop.file = start.file
|
|
&& (stop.line > start.line
|
|
|| (stop.line = start.line && stop.col > start.col))
|
|
then { start with eline = stop.line; ecol = stop.col }
|
|
else start
|
|
|
|
(** True when the span covers more than its first line. The underline is drawn
|
|
on the start line either way — a form that spans twenty lines is pointed
|
|
at, not boxed — so this is what tells the renderer to run the underline to
|
|
the end of that line rather than to [ecol]. *)
|
|
let multiline (t : t) = t.eline > t.line
|
|
|
|
(** How many columns to underline on the start line, or [None] when the span
|
|
was never widened and there is nothing but a point to draw. *)
|
|
let width (t : t) =
|
|
if t.eline = t.line && t.ecol > t.col then Some (t.ecol - t.col) else None
|
|
|
|
let to_string t = Printf.sprintf "%s:%d:%d" t.file t.line t.col
|
|
|
|
(** Raised by every stage of the frontend. *)
|
|
exception Error of t * string
|
|
|
|
let fail loc fmt = Printf.ksprintf (fun msg -> raise (Error (loc, msg))) fmt
|