Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep track of locations in the AST #21

Merged
merged 3 commits into from
Feb 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 168 additions & 44 deletions lib/mustache.ml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ include Mustache_types
module List = ListLabels
module String = StringLabels

module Infix = struct
let (^) y x = Concat [x; y]
end

module Json = struct
type value =
[ `Null
Expand All @@ -45,9 +41,6 @@ module Json = struct
let value: t -> value = fun t -> (t :> value)
end

let parse_lx = Mustache_parser.mustache Mustache_lexer.mustache
let of_string s = parse_lx (Lexing.from_string s)

let escape_html s =
let b = Buffer.create (String.length s) in
String.iter ( function
Expand All @@ -60,8 +53,48 @@ let escape_html s =
) s ;
Buffer.contents b

let rec pp fmt = function

(* Utility functions that allow converting between the ast with locations and
without locations. *)

let dummy_loc =
{ Locs.loc_start = Lexing.dummy_pos;
Locs.loc_end = Lexing.dummy_pos }

let rec erase_locs { Locs.desc; _ } =
erase_locs_desc desc
and erase_locs_desc = function
| Locs.String s -> No_locs.String s
| Locs.Escaped s -> No_locs.Escaped s
| Locs.Section s -> No_locs.Section (erase_locs_section s)
| Locs.Unescaped s -> No_locs.Unescaped s
| Locs.Partial s -> No_locs.Partial s
| Locs.Inverted_section s -> No_locs.Inverted_section (erase_locs_section s)
| Locs.Concat l -> No_locs.Concat (List.map erase_locs l)
| Locs.Comment s -> No_locs.Comment s
and erase_locs_section { Locs.name; Locs.contents } =
{ No_locs.name; No_locs.contents = erase_locs contents }

let rec add_dummy_locs t =
{ Locs.loc = dummy_loc;
Locs.desc = add_dummy_locs_desc t }
and add_dummy_locs_desc = function
| No_locs.String s -> Locs.String s
| No_locs.Escaped s -> Locs.Escaped s
| No_locs.Section s -> Locs.Section (add_dummy_locs_section s)
| No_locs.Unescaped s -> Locs.Unescaped s
| No_locs.Partial s -> Locs.Partial s
| No_locs.Inverted_section s ->
Locs.Inverted_section (add_dummy_locs_section s)
| No_locs.Concat l -> Locs.Concat (List.map add_dummy_locs l)
| No_locs.Comment s -> Locs.Comment s
and add_dummy_locs_section { No_locs.name; No_locs.contents } =
{ Locs.name; Locs.contents = add_dummy_locs contents }

(* Printing: defined on the ast without locations. *)

let rec pp fmt =
let open No_locs in
function
| String s ->
Format.pp_print_string fmt s

Expand All @@ -88,45 +121,14 @@ let rec pp fmt = function
| Concat s ->
List.iter (pp fmt) s

let to_formatter = pp

let to_string x =
let b = Buffer.create 0 in
let fmt = Format.formatter_of_buffer b in
pp fmt x ;
Format.pp_print_flush fmt () ;
Buffer.contents b

let rec fold ~string ~section ~escaped ~unescaped ~partial ~comment ~concat t =
let go = fold ~string ~section ~escaped ~unescaped ~partial ~comment ~concat in
match t with
| String s -> string s
| Escaped s -> escaped s
| Unescaped s -> unescaped s
| Comment s -> comment s
| Section { name; contents } ->
section ~inverted:false name (go contents)
| Inverted_section { name; contents } ->
section ~inverted:true name (go contents)
| Concat ms ->
concat (List.map ms ~f:go)
| Partial p -> partial p

let raw s = String s
let escaped s = Escaped s
let unescaped s = Unescaped s
let section n c = Section { name = n ; contents = c }
let inverted_section n c = Inverted_section { name = n ; contents = c }
let partial s = Partial s
let concat t = Concat t
let comment s = Comment s

let rec expand_partials =
let section ~inverted =
if inverted then inverted_section else section
in
fun partial ->
fold ~string:raw ~section ~escaped ~unescaped ~partial ~comment ~concat
(* Rendering: defined on the ast without locations. *)

module Lookup = struct
let scalar ?(strict=true) = function
Expand Down Expand Up @@ -172,8 +174,8 @@ module Lookup = struct

end

let render_fmt ?(strict=true) (fmt : Format.formatter) (m : t) (js : Json.t) =

let render_fmt ?(strict=true) (fmt : Format.formatter) (m : No_locs.t) (js : Json.t) =
let open No_locs in
let rec render' m (js : Json.value) = match m with

| String s ->
Expand Down Expand Up @@ -211,9 +213,131 @@ let render_fmt ?(strict=true) (fmt : Format.formatter) (m : t) (js : Json.t) =

in render' m (Json.value js)

let render ?(strict=true) (m : t) (js : Json.t) =
let render ?(strict=true) (m : No_locs.t) (js : Json.t) =
let b = Buffer.create 0 in
let fmt = Format.formatter_of_buffer b in
render_fmt ~strict fmt m js ;
Format.pp_print_flush fmt () ;
Buffer.contents b

(* Parsing: produces an ast with locations. *)

let parse_lx : Lexing.lexbuf -> Locs.t =
Mustache_parser.mustache Mustache_lexer.mustache

let of_string s = parse_lx (Lexing.from_string s)

(* Packing up everything in two modules of similar signature:
[With_locations] and [Without_locations].
*)

module With_locations = struct
include Locs

let dummy_loc = dummy_loc
let parse_lx = parse_lx
let of_string = of_string

let pp fmt x = pp fmt (erase_locs x)
let to_formatter = pp

let to_string x = to_string (erase_locs x)

let render_fmt ?strict fmt m js =
render_fmt ?strict fmt (erase_locs m) js

let render ?strict m js =
render ?strict (erase_locs m) js

let rec fold ~string ~section ~escaped ~unescaped ~partial ~comment ~concat t =
let go = fold ~string ~section ~escaped ~unescaped ~partial ~comment ~concat in
let { desc; loc } = t in
match desc with
| String s -> string ~loc s
| Escaped s -> escaped ~loc s
| Unescaped s -> unescaped ~loc s
| Comment s -> comment ~loc s
| Section { name; contents } ->
section ~loc ~inverted:false name (go contents)
| Inverted_section { name; contents } ->
section ~loc ~inverted:true name (go contents)
| Concat ms ->
concat ~loc (List.map ms ~f:go)
| Partial p -> partial ~loc p

module Infix = struct
let (^) t1 t2 = { desc = Concat [t1; t2]; loc = dummy_loc }
end

let raw ~loc s = { desc = String s; loc }
let escaped ~loc s = { desc = Escaped s; loc }
let unescaped ~loc s = { desc = Unescaped s; loc }
let section ~loc n c =
{ desc = Section { name = n; contents = c };
loc }
let inverted_section ~loc n c =
{ desc = Inverted_section { name = n; contents = c };
loc }
let partial ~loc s = { desc = Partial s; loc }
let concat ~loc t = { desc = Concat t; loc }
let comment ~loc s = { desc = Comment s; loc }

let rec expand_partials =
let section ~loc ~inverted =
if inverted then inverted_section ~loc else section ~loc
in
fun partial ->
fold ~string:raw ~section ~escaped ~unescaped ~partial ~comment ~concat
end

module Without_locations = struct
include No_locs

let parse_lx lexbuf = erase_locs (parse_lx lexbuf)
let of_string s = erase_locs (of_string s)

let pp = pp
let to_formatter = pp

let to_string = to_string

let rec fold ~string ~section ~escaped ~unescaped ~partial ~comment ~concat t =
let go = fold ~string ~section ~escaped ~unescaped ~partial ~comment ~concat in
match t with
| String s -> string s
| Escaped s -> escaped s
| Unescaped s -> unescaped s
| Comment s -> comment s
| Section { name; contents } ->
section ~inverted:false name (go contents)
| Inverted_section { name; contents } ->
section ~inverted:true name (go contents)
| Concat ms ->
concat (List.map ms ~f:go)
| Partial p -> partial p

module Infix = struct
let (^) y x = Concat [x; y]
end

let raw s = String s
let escaped s = Escaped s
let unescaped s = Unescaped s
let section n c = Section { name = n ; contents = c }
let inverted_section n c = Inverted_section { name = n ; contents = c }
let partial s = Partial s
let concat t = Concat t
let comment s = Comment s

let rec expand_partials =
let section ~inverted =
if inverted then inverted_section else section
in
fun partial ->
fold ~string:raw ~section ~escaped ~unescaped ~partial ~comment ~concat
end

(* Include [Without_locations] at the toplevel, to preserve backwards
compatibility of the API. *)

include Without_locations
116 changes: 114 additions & 2 deletions lib/mustache.mli
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ type t =
| Concat of t list
| Comment of string
and section =
{ name: string
; contents: t }
{ name: string;
contents: t }

(** Read *)
val parse_lx : Lexing.lexbuf -> t
Expand Down Expand Up @@ -111,3 +111,115 @@ val comment : string -> t

(** Group a [t list] as a single [t]. *)
val concat : t list -> t

(** Variant of the [t] mustache datatype which includes source-file locations,
and associated functions. *)
module With_locations : sig
type loc =
{ loc_start: Lexing.position;
loc_end: Lexing.position }

type desc =
| String of string
| Escaped of string
| Section of section
| Unescaped of string
| Partial of string
| Inverted_section of section
| Concat of t list
| Comment of string
and section =
{ name: string;
contents: t }
and t =
{ loc : loc;
desc : desc }

(** A value of type [loc], guaranteed to be different from any valid
location. *)
val dummy_loc : loc

(** Read *)
val parse_lx : Lexing.lexbuf -> t
val of_string : string -> t

(** [pp fmt template] print a template as raw mustache to
the formatter [fmt]. *)
val pp : Format.formatter -> t -> unit

val to_formatter : Format.formatter -> t -> unit
(** Alias for compatibility *)

(** [to_string template] uses [to_formatter] in order to return
a string representing the template as raw mustache. *)
val to_string : t -> string

(** [render_fmt fmt template json] render [template], filling it
with data from [json], printing it to formatter [fmt]. *)
val render_fmt : ?strict:bool -> Format.formatter -> t -> Json.t -> unit

(** [render template json] use [render_fmt] to render [template]
with data from [json] and returns the resulting string. *)
val render : ?strict:bool -> t -> Json.t -> string

(** [fold template] is the composition of [f] over parts of [template], called
in order of occurrence, where each [f] is one of the labelled arguments
applied to the corresponding part. The default for [f] is the identity
function.

@param string Applied to each literal part of the template.
@param escaped Applied to ["name"] for occurrences of [{{name}}].
@param unescaped Applied to ["name"] for occurrences of [{{{name}}}].
@param partial Applied to ["box"] for occurrences of [{{> box}}].
@param comment Applied to ["comment"] for occurrences of [{{! comment}}]. *)
val fold : string: (loc:loc -> string -> 'a) ->
section: (loc:loc -> inverted:bool -> string -> 'a -> 'a) ->
escaped: (loc:loc -> string -> 'a) ->
unescaped: (loc:loc -> string -> 'a) ->
partial: (loc:loc -> string -> 'a) ->
comment: (loc:loc -> string -> 'a) ->
concat:(loc:loc -> 'a list -> 'a) ->
t -> 'a

val expand_partials : (loc:loc -> string -> t) -> t -> t
(** [expand_partials f template] is [template] with [f p] substituted for each
partial [p]. *)

(** Shortcut for concatening two templates pieces. *)
module Infix : sig
(** The location of the created [Concat] node has location [dummy_loc].
Use [concat] to provide a location. *)
val (^) : t -> t -> t
end

(** [<p>This is raw text.</p>] *)
val raw : loc:loc -> string -> t

(** [{{name}}] *)
val escaped : loc:loc -> string -> t

(** [{{{name}}}] *)
val unescaped : loc:loc -> string -> t

(** [{{^person}} {{/person}}] *)
val inverted_section : loc:loc -> string -> t -> t

(** [{{#person}} {{/person}}] *)
val section : loc:loc -> string -> t -> t

(** [{{> box}}] *)
val partial : loc:loc -> string -> t

(** [{{! this is a comment}}] *)
val comment : loc:loc -> string -> t

(** Group a [t list] as a single [t]. *)
val concat : loc:loc -> t list -> t
end

(** Erase locations from a mustache value of type [With_locations.t]. *)
val erase_locs : With_locations.t -> t

(** Add the [dummy_loc] location to each node of a mustache value of type
[t]. *)
val add_dummy_locs : t -> With_locations.t
Loading