Skip to content

Commit 69d81b8

Browse files
committed
Flatten trivia representation
Trivia is now represented as a single tuple of `(start, kind, contents)`, rather than a nested `(Kind of contents) spanned`. We also store trivia in an immutable array, rather than a list — mutations are not limited to just being at the start, so we don't really benefit from a list anyway.
1 parent 8dcb836 commit 69d81b8

30 files changed

+468
-474
lines changed

src/core/emit.ml

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,21 +290,18 @@ let tprintf kind out fmt =
290290
Format.pp_open_stag out (Token kind);
291291
Format.kfprintf (fun x -> Format.pp_close_stag x ()) out fmt
292292

293-
let trivial out = function
294-
| Node.LineComment x | BlockComment x -> tprintf Comment out "%s" x
295-
| Whitespace x -> Format.fprintf out "%s" x
296-
297-
let trivial_span out { Span.value; _ } = trivial out value
293+
let trivial out t =
294+
match t.Node.Trivia.kind with
295+
| LineComment | BlockComment -> tprintf Comment out "%s" t.contents
296+
| Whitespace -> Format.pp_print_string out t.contents
298297

299298
include Make (struct
300299
type t = Format.formatter
301300

302301
let tagged stag f out x = Format.pp_open_stag out stag; f out x; Format.pp_close_stag out ()
303302

304-
let node ~kind body out = function
305-
| Node.SimpleNode { contents } -> tagged (Token kind) body out contents
306-
| Node.Node { leading_trivia; contents; trailing_trivia; _ } ->
307-
List.iter (trivial_span out) leading_trivia;
308-
tagged (Token kind) body out contents;
309-
List.iter (trivial_span out) trailing_trivia
303+
let node ~kind body out { Node.leading_trivia; contents; trailing_trivia; _ } =
304+
Illuaminate.IArray.iter (trivial out) leading_trivia;
305+
tagged (Token kind) body out contents;
306+
Illuaminate.IArray.iter (trivial out) trailing_trivia
310307
end)

src/core/emit.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ end
7575
module Make (E : Emitter) : S with type t := E.t
7676

7777
(** Write a trivial term to a formatter. *)
78-
val trivial : Format.formatter -> Node.trivial -> unit
78+
val trivial : Format.formatter -> Node.Trivia.t -> unit
7979

8080
(** By default we provide emitting functions which write to a formatter.*)
8181
include S with type t := Format.formatter

src/core/node.ml

Lines changed: 64 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -3,71 +3,58 @@
33
Nodes, along with holding an object, also include additional metadata such as their position and
44
surrounding trivia. *)
55

6-
(** A "trivial" part of the program, which is not important for the execution of the program, but
7-
may be useful for understanding or recreating it. *)
8-
type trivial =
9-
| LineComment of string (** A short, comment which is terminated by a newline. *)
10-
| BlockComment of string (** A long comment, which may span multiple lines. *)
11-
| Whitespace of string (** Any whitespace, such as spaces, newlines or tabs. *)
12-
[@@deriving show]
6+
module Trivia = struct
7+
type kind =
8+
| LineComment (** A short, comment which is terminated by a newline. *)
9+
| BlockComment (** A long comment, which may span multiple lines. *)
10+
| Whitespace (** Any whitespace, such as spaces, newlines or tabs. *)
11+
12+
(** A "trivial" part of the program, which is not important for the execution of the program, but
13+
may be useful for understanding or recreating it. *)
14+
type t =
15+
{ start : Illuaminate.Position_map.pos;
16+
kind : kind;
17+
contents : string
18+
}
19+
20+
let make kind contents start = { kind; contents; start }
21+
let pp out (x : t) = Format.fprintf out "Trivia(%S)" x.contents
22+
let start t = t.start
23+
24+
let finish t : Illuaminate.Position_map.pos =
25+
let (Pos start) = t.start in
26+
Pos (start + String.length t.contents - 1)
27+
28+
let span root trivia =
29+
let open Illuaminate.Lens in
30+
let (Illuaminate.Position_map.Pos start) = start trivia in
31+
let (Illuaminate.Position_map.Pos finish) = finish trivia in
32+
root |> Span.start_offset ^= start |> Span.finish_offset ^= finish
33+
end
1334

1435
(** A node, such as a token or identifier, but with additional metadata.
1536
16-
Every node has leading and trailing trivia, represented as a list of {!Span.spanned} {!trivial}
17-
nodes. Nodes generated by the parser will generally have a {!Node} type, while generated nodes
18-
(who have not got any trivia) are built from {!SimpleNode} s. *)
37+
Every node has leading and trailing trivia, represented as an array of {!Trivia.t } nodes. *)
1938
type 'a t =
20-
| SimpleNode of { contents : 'a }
21-
(** A "simple" node, which was generated by the compiler rather than taken from source code.
22-
*)
23-
| Node of
24-
{ leading_trivia : trivial Span.spanned list;
25-
trailing_trivia : trivial Span.spanned list;
26-
contents : 'a;
27-
span : Span.t (** The position of the node, not including leading or trailing trivia. *)
28-
}
29-
(** A token with full metadata, which can {i generally} be traced to a concrete position in
30-
the source code. *)
39+
{ leading_trivia : Trivia.t Illuaminate.IArray.t;
40+
trailing_trivia : Trivia.t Illuaminate.IArray.t;
41+
contents : 'a;
42+
span : Span.t (** The position of the node, not including leading or trailing trivia. *)
43+
}
3144
[@@deriving show]
3245

3346
(** Update the contents of this node. *)
34-
let with_contents contents = function
35-
| SimpleNode _ -> SimpleNode { contents }
36-
| Node n -> Node { n with contents }
47+
let with_contents contents (node : _ t) = { node with contents }
3748

3849
(** Get the span of this node, if defined. Otherwise throw an exception. *)
39-
let span = function
40-
| Node { span; _ } -> span
41-
| SimpleNode _ -> failwith "No span."
42-
43-
(** Get the span of the first trivia node, or the current node *)
44-
let trivia_start = function
45-
| Node { span; leading_trivia; _ } -> (
46-
match leading_trivia with
47-
| [] -> span
48-
| t :: _ -> t.span)
49-
| SimpleNode _ -> failwith "No span."
50-
51-
(** Get the span of the last trivia node, or the current node *)
52-
let trivia_finish = function
53-
| Node { span; trailing_trivia; _ } -> (
54-
match CCList.last_opt trailing_trivia with
55-
| None -> span
56-
| Some t -> t.span)
57-
| SimpleNode _ -> failwith "No span."
58-
59-
(** Get the span of this node, including trivia of this node. *)
60-
let trivia_span n = Span.of_span2 (trivia_start n) (trivia_finish n)
50+
let span node = node.span
6151

6252
open Illuaminate.Lens
6353

6454
(** A lens which exposes the contents of the term. *)
6555
let contents =
66-
let get (SimpleNode { contents } | Node { contents; _ }) = contents
67-
and over f = function
68-
| SimpleNode n -> SimpleNode { contents = f n.contents }
69-
| Node n -> Node { n with contents = f n.contents }
70-
in
56+
let get (n : _ t) = n.contents in
57+
let over f (n : _ t) = { n with contents = f n.contents } in
7158
{ get; over }
7259

7360
(** Embed a lens which transforms the whole node with a view on the body. *)
@@ -85,60 +72,40 @@ let lens_embed (type s u a b) (inner : (s, u, a, b) lens) : (s t, u t, a t, b t)
8572
When converting a term from a {!SimpleNode} to a {!Node}, we will use the position of the first
8673
trivial node. *)
8774
let trailing_trivia =
88-
let get = function
89-
| SimpleNode _ -> []
90-
| Node { trailing_trivia = t; _ } -> t
91-
in
92-
let over f x =
93-
let t = f (get x) in
94-
match (x, t) with
95-
| SimpleNode _, [] -> x
96-
| SimpleNode { contents }, { Span.span; _ } :: _ ->
97-
Node { span; contents; trailing_trivia = t; leading_trivia = [] }
98-
| Node n, _ -> Node { n with trailing_trivia = t }
99-
in
75+
let get n = n.trailing_trivia in
76+
let over f n = { n with trailing_trivia = f n.trailing_trivia } in
10077
{ get; over }
10178

10279
(** A lens which exposes the leading trivia of a term.
10380
10481
When converting a term from a {!SimpleNode} to a {!Node}, we will use the position of the first
10582
trivial node. *)
10683
let leading_trivia =
107-
let get = function
108-
| SimpleNode _ -> []
109-
| Node { leading_trivia = t; _ } -> t
110-
in
111-
let over f x =
112-
let t = f (get x) in
113-
match (x, t) with
114-
| SimpleNode _, [] -> x
115-
| SimpleNode { contents }, { Span.span; _ } :: _ ->
116-
Node { span; contents; leading_trivia = t; trailing_trivia = [] }
117-
| Node n, _ -> Node { n with leading_trivia = t }
118-
in
84+
let get n = n.leading_trivia in
85+
let over f n = { n with leading_trivia = f n.leading_trivia } in
11986
{ get; over }
12087

12188
(** Join two lists of trivial nodes together. While {!(\@)} will normally suffice for this,
12289
{!join_trivia} attempts to merge whitespace between adjacent nodes too. *)
123-
let join_trivia xs ys : trivial Span.spanned list =
124-
match ys with
125-
| [] -> xs
126-
| { Span.value = Whitespace r; span = rs } :: ys' ->
127-
let is_space = function
128-
| ' ' | '\t' -> true
129-
| _ -> false
130-
in
131-
let rec go = function
132-
| [] -> ys
133-
| [ ({ Span.value = Whitespace l; span = ls } as x) ] ->
134-
if l = "" then ys
135-
else if is_space l.[String.length l - 1] then
136-
{ Span.value = Whitespace (CCString.rdrop_while is_space l ^ r);
137-
span = Span.of_span2 ls rs
138-
}
139-
:: ys'
140-
else x :: ys
141-
| x :: xs -> x :: go xs
142-
in
143-
go xs
144-
| ys -> xs @ ys
90+
let join_trivia xs ys : Trivia.t Illuaminate.IArray.t =
91+
let module IArray = Illuaminate.IArray in
92+
if IArray.is_empty xs then ys
93+
else if IArray.is_empty ys then xs
94+
else
95+
let is_space = function
96+
| ' ' | '\t' -> true
97+
| _ -> false
98+
in
99+
match (IArray.last xs, IArray.first ys) with
100+
(* If our first trivia ends in a space (" ") and the second trivia starts with any whitespace,
101+
drop that space and just use the leading whitespace. *)
102+
| { kind = Whitespace; contents = last_c; _ }, { kind = Whitespace; _ }
103+
when is_space last_c.[String.length last_c - 1] ->
104+
(* Build an array of xs[:-1] @ ys *)
105+
let xs_len = IArray.length xs - 1 in
106+
let out =
107+
Array.init (xs_len + IArray.length ys) @@ fun i ->
108+
if i < xs_len then IArray.get xs i else IArray.get ys (i - xs_len)
109+
in
110+
IArray.of_array out
111+
| _ -> IArray.append xs ys

src/core/span.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type t =
4848

4949
let unpos (Illuaminate.Position_map.Pos x) = x
5050
let filename x = x.lines.file
51+
let position_map x = x.lines.map
5152

5253
let to_error_position (span : t) : Illuaminate.Error.Position.t =
5354
{ file = span.lines.file;
@@ -74,6 +75,7 @@ let start_offset =
7475
over = (fun f ({ lines; start; _ } as l) -> { l with start = Lines.over_offset f lines start })
7576
}
7677

78+
let start_offset' { start; _ } = Illuaminate.Position_map.Pos start
7779
let finish_line { lines; finish; _ } = Lines.get_line lines finish
7880
let finish_col { lines; finish; _ } = Lines.get_col lines finish
7981
let finish_pos { lines; finish; _ } = Lines.get_pos lines finish

src/core/span.mli

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type t
2121
(** Get the filename for this span. *)
2222
val filename : t -> File_id.t
2323

24+
val position_map : t -> Position_map.t
25+
2426
(** Convert this span to an {! Illuaminate.Error.Position.t error position}. *)
2527
val to_error_position : t -> Illuaminate.Error.Position.t
2628

@@ -36,6 +38,9 @@ val start_bol : t -> int
3638
(** Get the beginning offset of this span. *)
3739
val start_offset : (t, int) Lens.lens'
3840

41+
(** Get the beginning offset of this span. *)
42+
val start_offset' : t -> Position_map.pos
43+
3944
(** A lens over the starting column of this span. *)
4045
val start_col : t -> int
4146

src/doc_emit/html_highlight.ml

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,13 @@ let do_lua ~options:({ Html_options.data; _ } as options) input =
103103
{ program =
104104
[ Return
105105
{ return_return =
106-
Node
107-
{ leading_trivia = [];
108-
trailing_trivia = [];
109-
contents = Token.Return;
110-
span =
111-
Syntax.SepList1.first.get tree.repl_exprs
112-
|> Syntax.First.expr.get |> Node.span |> Span.start
113-
};
106+
{ leading_trivia = Illuaminate.IArray.empty;
107+
trailing_trivia = Illuaminate.IArray.empty;
108+
contents = Token.Return;
109+
span =
110+
Syntax.SepList1.first.get tree.repl_exprs
111+
|> Syntax.First.expr.get |> Node.span |> Span.start
112+
};
114113
return_vals = Some tree.repl_exprs
115114
}
116115
];

src/illuaminate/iArray.ml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ include Array
22

33
let empty = [||]
44
let pp fmt = Fmt.Dump.array fmt
5+
let singleton x = [| x |]
56

67
external of_array : 'a array -> 'a t = "%identity"
78

8-
let of_list = Array.of_list
9-
109
let of_rev_list = function
1110
| [] -> empty
1211
| hd :: _ as l ->
@@ -20,7 +19,23 @@ let of_rev_list = function
2019
in
2120
fill (len - 1) l
2221

22+
let is_empty xs = length xs = 0
23+
let first xs = xs.(0)
24+
let last xs = xs.(length xs - 1)
25+
26+
let rec filter_worker acc f xs i =
27+
if i >= length xs then of_rev_list acc
28+
else
29+
let elem = xs.(i) in
30+
let acc = if f elem then elem :: acc else acc in
31+
filter_worker acc f xs (i + 1)
32+
33+
let filter f xs = filter_worker [] f xs 0
34+
2335
let set array idx value =
2436
let array = Array.copy array in
2537
array.(idx) <- value;
2638
array
39+
40+
let push_first x xs = Array.append [| x |] xs
41+
let push_last xs x = Array.append xs [| x |]

src/illuaminate/iArray.mli

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,50 @@ val of_list : 'a list -> 'a t
2424
(** Create an immutable array from a reversed list. *)
2525
val of_rev_list : 'a list -> 'a t
2626

27-
(** {1 Operations} *)
27+
(** Create an immutable array with a single element. *)
28+
val singleton : 'a -> 'a t
29+
30+
(** Append two lists together. *)
31+
val append : 'a t -> 'a t -> 'a t
32+
33+
(** {1 Reading immutable arrays} *)
34+
35+
(** Determine if the array is empty. *)
36+
val is_empty : 'a t -> bool
2837

2938
(** Return the length (number of elements) of the given array. *)
3039
external length : 'a t -> int = "%array_length"
3140

3241
(** [get a n] returns the element number [n] of array [a]. *)
3342
external get : 'a t -> int -> 'a = "%array_safe_get"
3443

35-
(** Set an element in this array, returning the new version of it. *)
36-
val set : 'a t -> int -> 'a -> 'a t
44+
(** Get the first element in this array. *)
45+
val first : 'a t -> 'a
46+
47+
(** Get the last element in this array. *)
48+
val last : 'a t -> 'a
49+
50+
(** {1 Operating on immutable arrays} *)
3751

3852
(** Perform a left fold over this array. *)
3953
val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a
4054

55+
(** Filter an immutable array. *)
56+
val filter : ('a -> bool) -> 'a t -> 'a t
57+
58+
(** Find the first item in an array matching a predicate, transforming it to an alternative type. *)
59+
val find_map : ('a -> 'b option) -> 'a t -> 'b option
60+
4161
(** Iterate over each item in the array. *)
4262
val iter : ('a -> unit) -> 'a t -> unit
63+
64+
(** {1 Updating immutable arrays} *)
65+
66+
(** Set an element in this array, returning the new version of it. *)
67+
val set : 'a t -> int -> 'a -> 'a t
68+
69+
(** Prepend an item onto this array. *)
70+
val push_first : 'a -> 'a t -> 'a t
71+
72+
(** Prepend an item onto this array. *)
73+
val push_last : 'a t -> 'a -> 'a t

0 commit comments

Comments
 (0)