From 2a9df71c2712fa429edef13c1113e55cdcf1f627 Mon Sep 17 00:00:00 2001 From: Armael Date: Mon, 15 Aug 2016 21:27:49 +0200 Subject: [PATCH 1/3] Keep track of locations in the AST --- lib/mustache.ml | 77 ++++++++++++++++++++++------------------- lib/mustache.mli | 25 ++++++++----- lib/mustache_lexer.mll | 39 ++++++++++++++------- lib/mustache_parser.mly | 24 +++++++------ lib/mustache_types.ml | 22 +++++++----- 5 files changed, 113 insertions(+), 74 deletions(-) diff --git a/lib/mustache.ml b/lib/mustache.ml index 634ecc1..e34576b 100644 --- a/lib/mustache.ml +++ b/lib/mustache.ml @@ -22,11 +22,16 @@ open MoreLabels include Mustache_types +let dummy_loc = { + loc_start = Lexing.dummy_pos; + loc_end = Lexing.dummy_pos; +} + module List = ListLabels module String = StringLabels module Infix = struct - let (^) y x = Concat [x; y] + let (^) y x = Concat (dummy_loc, [x; y]) end module Json = struct @@ -62,30 +67,30 @@ let escape_html s = let rec pp fmt = function - | String s -> + | String (_, s) -> Format.pp_print_string fmt s - | Escaped s -> + | Escaped (_, s) -> Format.fprintf fmt "{{ %s }}" s - | Unescaped s -> + | Unescaped (_, s) -> Format.fprintf fmt "{{& %s }}" s - | Inverted_section s -> + | Inverted_section (_, s) -> Format.fprintf fmt "{{^%s}}%a{{/%s}}" s.name pp s.contents s.name - | Section s -> + | Section (_, s) -> Format.fprintf fmt "{{#%s}}%a{{/%s}}" s.name pp s.contents s.name - | Partial s -> + | Partial (_, s) -> Format.fprintf fmt "{{> %s }}" s - | Comment s -> + | Comment (_, s) -> Format.fprintf fmt "{{! %s }}" s - | Concat s -> + | Concat (_, s) -> List.iter (pp fmt) s let to_formatter = pp @@ -100,26 +105,26 @@ let to_string x = 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 } -> + | 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 } -> + | Inverted_section (_, { name; contents }) -> section ~inverted:true name (go contents) - | Concat ms -> + | Concat (_, ms) -> concat (List.map ms ~f:go) - | Partial p -> partial p + | 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 raw s = String (dummy_loc, s) +let escaped s = Escaped (dummy_loc, s) +let unescaped s = Unescaped (dummy_loc, s) +let section n c = Section (dummy_loc, { name = n ; contents = c }) +let inverted_section n c = Inverted_section (dummy_loc, { name = n ; contents = c }) +let partial s = Partial (dummy_loc, s) +let concat t = Concat (dummy_loc, t) +let comment s = Comment (dummy_loc, s) let rec expand_partials = let section ~inverted = @@ -176,24 +181,24 @@ let render_fmt ?(strict=true) (fmt : Format.formatter) (m : t) (js : Json.t) = let rec render' m (js : Json.value) = match m with - | String s -> + | String (_, s) -> Format.pp_print_string fmt s - | Escaped "." -> + | Escaped (_, ".") -> Format.pp_print_string fmt (escape_html (Lookup.scalar js)) - | Escaped key -> + | Escaped (_, key) -> Format.pp_print_string fmt (escape_html (Lookup.str ~strict ~key js)) - | Unescaped "." -> + | Unescaped (_, ".") -> Format.pp_print_string fmt (Lookup.scalar js) - | Unescaped key -> + | Unescaped (_, key) -> Format.pp_print_string fmt (Lookup.str ~strict ~key js) - | Inverted_section s -> + | Inverted_section (loc, s) -> if Lookup.inverted js s.name - then render' (Section s) js + then render' (Section (loc, s)) js - | Section s -> + | Section (_, s) -> begin match Lookup.section ~strict js ~key:s.name with | `Bool false -> () | `Bool true -> render' s.contents js @@ -201,12 +206,12 @@ let render_fmt ?(strict=true) (fmt : Format.formatter) (m : t) (js : Json.t) = | context -> render' s.contents context end - | Partial _ -> + | Partial (_, _) -> pp fmt m - | Comment c -> () + | Comment (_, c) -> () - | Concat templates -> + | Concat (_, templates) -> List.iter (fun x -> render' x js) templates in render' m (Json.value js) diff --git a/lib/mustache.mli b/lib/mustache.mli index c6b0571..d32b81c 100644 --- a/lib/mustache.mli +++ b/lib/mustache.mli @@ -20,19 +20,28 @@ module Json : sig (** Compatible with Ezjsonm *) | `O of (string * value) list ] end +type loc = { + loc_start: Lexing.position; + loc_end: Lexing.position; +} + type t = - | 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 + | String of loc * string + | Escaped of loc * string + | Section of loc * section + | Unescaped of loc * string + | Partial of loc * string + | Inverted_section of loc * section + | Concat of loc * t list + | Comment of loc * string and section = { name: string ; contents: t } +(** 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 diff --git a/lib/mustache_lexer.mll b/lib/mustache_lexer.mll index 2bca137..6353507 100644 --- a/lib/mustache_lexer.mll +++ b/lib/mustache_lexer.mll @@ -23,27 +23,42 @@ open Lexing open Mustache_parser open Mustache_types + + let with_space space f lexbuf = + let start_p = lexbuf.Lexing.lex_start_p in + let () = space lexbuf in + let x = f lexbuf in + space lexbuf; + lexbuf.Lexing.lex_start_p <- start_p; + x } -let space = [' ' '\t' '\n']* +let blank = [' ' '\t']* +let newline = ('\n' | "\r\n") +let raw = [^ '{' '}' '\n']* let id = ['a'-'z' 'A'-'Z' '_' '/'] ['a'-'z' 'A'-'Z' '0'-'9' '_' '/']+ -rule ident = parse - | space '.' space { "." } - | space (id as x) space { x } +rule space = parse + | blank newline { new_line lexbuf; space lexbuf } + | blank { () } + +and ident = parse + | '.' { "." } + | (id as x) { x } | _ { raise (Invalid_template "Invalid section") } and mustache = parse - | "{{{" { UNESCAPE_START (ident lexbuf) } - | "{{&" { UNESCAPE_START_AMPERSAND (ident lexbuf) } - | "{{#" { SECTION_START (ident lexbuf) } - | "{{^" { SECTION_INVERT_START (ident lexbuf) } - | "{{/" { SECTION_END (ident lexbuf) } - | "{{>" { PARTIAL_START (ident lexbuf) } + | "{{{" { UNESCAPE_START (with_space space ident lexbuf) } + | "{{&" { UNESCAPE_START_AMPERSAND (with_space space ident lexbuf) } + | "{{#" { SECTION_START (with_space space ident lexbuf) } + | "{{^" { SECTION_INVERT_START (with_space space ident lexbuf) } + | "{{/" { SECTION_END (with_space space ident lexbuf) } + | "{{>" { PARTIAL_START (with_space space ident lexbuf) } | "{{!" { COMMENT_START } - | "{{" { ESCAPE_START (ident lexbuf) } + | "{{" { ESCAPE_START (with_space space ident lexbuf) } | "}}}" { UNESCAPE_END } | "}}" { END } - | [^ '{' '}']* { RAW (lexeme lexbuf) } + | raw newline { new_line lexbuf; RAW (lexeme lexbuf) } + | raw { RAW (lexeme lexbuf) } | ['{' '}'] { RAW (lexeme lexbuf) } | eof { EOF } diff --git a/lib/mustache_parser.mly b/lib/mustache_parser.mly index 312b803..be64137 100644 --- a/lib/mustache_parser.mly +++ b/lib/mustache_parser.mly @@ -29,6 +29,10 @@ let msg = Printf.sprintf "Mismatched section %s with %s" start_s end_s in raise (Invalid_template msg) + + let loc () = + { loc_start = Parsing.symbol_start_pos (); + loc_end = Parsing.symbol_end_pos () } %} %token EOF @@ -51,19 +55,19 @@ %% section: - | SECTION_INVERT_START END mustache SECTION_END END { Inverted_section (parse_section $1 $4 $3) } - | SECTION_START END mustache SECTION_END END { Section (parse_section $1 $4 $3) } + | SECTION_INVERT_START END mustache SECTION_END END { Inverted_section (loc (), parse_section $1 $4 $3) } + | SECTION_START END mustache SECTION_END END { Section (loc (), parse_section $1 $4 $3) } mustache_element: - | UNESCAPE_START UNESCAPE_END { Unescaped $1 } - | UNESCAPE_START_AMPERSAND END { Unescaped $1 } - | ESCAPE_START END { Escaped $1 } - | PARTIAL_START END { Partial $1 } - | COMMENT_START RAW END { Comment $2 } + | UNESCAPE_START UNESCAPE_END { Unescaped (loc (), $1) } + | UNESCAPE_START_AMPERSAND END { Unescaped (loc (), $1) } + | ESCAPE_START END { Escaped (loc (), $1) } + | PARTIAL_START END { Partial (loc (), $1) } + | COMMENT_START RAW END { Comment (loc (), $2) } | section { $1 } string: - | RAW { String $1 } + | RAW { String (loc (), $1) } mustache_l: | mustache_element mustache_l { ($1 :: $2) } @@ -75,8 +79,8 @@ mustache: | mustache_l { match $1 with | [x] -> x - | x -> Concat x + | x -> Concat (loc (), x) } - | EOF { String "" } + | EOF { String (loc (), "") } %% diff --git a/lib/mustache_types.ml b/lib/mustache_types.ml index 2a0fbf7..0db02d4 100644 --- a/lib/mustache_types.ml +++ b/lib/mustache_types.ml @@ -19,15 +19,21 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}}*) + +type loc = { + loc_start: Lexing.position; + loc_end: Lexing.position; +} + type t = - | 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 + | String of loc * string + | Escaped of loc * string + | Section of loc * section + | Unescaped of loc * string + | Partial of loc * string + | Inverted_section of loc * section + | Concat of loc * t list + | Comment of loc * string and section = { name: string; contents: t; From c15c9412a77e8c18e2cb434d651a01ec27083d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C3=ABl=20Gu=C3=A9neau?= Date: Fri, 24 Feb 2017 13:45:02 +0100 Subject: [PATCH 2/3] Refactor the api to provide both AST with and without locations --- lib/mustache.ml | 255 +++++++++++++++++++++++++++++----------- lib/mustache.mli | 141 +++++++++++++++++++--- lib/mustache_parser.mly | 31 +++-- lib/mustache_types.ml | 54 ++++++--- 4 files changed, 365 insertions(+), 116 deletions(-) diff --git a/lib/mustache.ml b/lib/mustache.ml index e34576b..c383a21 100644 --- a/lib/mustache.ml +++ b/lib/mustache.ml @@ -22,18 +22,9 @@ open MoreLabels include Mustache_types -let dummy_loc = { - loc_start = Lexing.dummy_pos; - loc_end = Lexing.dummy_pos; -} - module List = ListLabels module String = StringLabels -module Infix = struct - let (^) y x = Concat (dummy_loc, [x; y]) -end - module Json = struct type value = [ `Null @@ -50,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 @@ -65,36 +53,74 @@ let escape_html s = ) s ; Buffer.contents b -let rec pp fmt = function - - | String (_, s) -> +(* 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 - | Escaped (_, s) -> + | Escaped s -> Format.fprintf fmt "{{ %s }}" s - | Unescaped (_, s) -> + | Unescaped s -> Format.fprintf fmt "{{& %s }}" s - | Inverted_section (_, s) -> + | Inverted_section s -> Format.fprintf fmt "{{^%s}}%a{{/%s}}" s.name pp s.contents s.name - | Section (_, s) -> + | Section s -> Format.fprintf fmt "{{#%s}}%a{{/%s}}" s.name pp s.contents s.name - | Partial (_, s) -> + | Partial s -> Format.fprintf fmt "{{> %s }}" s - | Comment (_, s) -> + | Comment s -> Format.fprintf fmt "{{! %s }}" s - | Concat (_, s) -> + | 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 @@ -102,36 +128,7 @@ let to_string 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 (dummy_loc, s) -let escaped s = Escaped (dummy_loc, s) -let unescaped s = Unescaped (dummy_loc, s) -let section n c = Section (dummy_loc, { name = n ; contents = c }) -let inverted_section n c = Inverted_section (dummy_loc, { name = n ; contents = c }) -let partial s = Partial (dummy_loc, s) -let concat t = Concat (dummy_loc, t) -let comment s = Comment (dummy_loc, 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 @@ -177,28 +174,28 @@ 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) -> + | String s -> Format.pp_print_string fmt s - | Escaped (_, ".") -> + | Escaped "." -> Format.pp_print_string fmt (escape_html (Lookup.scalar js)) - | Escaped (_, key) -> + | Escaped key -> Format.pp_print_string fmt (escape_html (Lookup.str ~strict ~key js)) - | Unescaped (_, ".") -> + | Unescaped "." -> Format.pp_print_string fmt (Lookup.scalar js) - | Unescaped (_, key) -> + | Unescaped key -> Format.pp_print_string fmt (Lookup.str ~strict ~key js) - | Inverted_section (loc, s) -> + | Inverted_section s -> if Lookup.inverted js s.name - then render' (Section (loc, s)) js + then render' (Section s) js - | Section (_, s) -> + | Section s -> begin match Lookup.section ~strict js ~key:s.name with | `Bool false -> () | `Bool true -> render' s.contents js @@ -206,19 +203,141 @@ let render_fmt ?(strict=true) (fmt : Format.formatter) (m : t) (js : Json.t) = | context -> render' s.contents context end - | Partial (_, _) -> + | Partial _ -> pp fmt m - | Comment (_, c) -> () + | Comment c -> () - | Concat (_, templates) -> + | Concat templates -> List.iter (fun x -> render' x js) templates 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 diff --git a/lib/mustache.mli b/lib/mustache.mli index d32b81c..3d96adc 100644 --- a/lib/mustache.mli +++ b/lib/mustache.mli @@ -20,27 +20,18 @@ module Json : sig (** Compatible with Ezjsonm *) | `O of (string * value) list ] end -type loc = { - loc_start: Lexing.position; - loc_end: Lexing.position; -} - type t = - | String of loc * string - | Escaped of loc * string - | Section of loc * section - | Unescaped of loc * string - | Partial of loc * string - | Inverted_section of loc * section - | Concat of loc * t list - | Comment of loc * string + | 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 } - -(** A value of type [loc], guaranteed to be different from any valid - location. *) -val dummy_loc : loc + { name: string; + contents: t } (** Read *) val parse_lx : Lexing.lexbuf -> t @@ -120,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 + + (** [

This is raw text.

] *) + 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 diff --git a/lib/mustache_parser.mly b/lib/mustache_parser.mly index be64137..6016544 100644 --- a/lib/mustache_parser.mly +++ b/lib/mustache_parser.mly @@ -22,6 +22,7 @@ }}}*/ %{ open Mustache_types + open Mustache_types.Locs let parse_section start_s end_s contents = if start_s = end_s then { contents; name=start_s } @@ -33,6 +34,12 @@ let loc () = { loc_start = Parsing.symbol_start_pos (); loc_end = Parsing.symbol_end_pos () } + + let with_loc desc = + let loc = + { loc_start = Parsing.symbol_start_pos (); + loc_end = Parsing.symbol_end_pos () } in + { loc; desc } %} %token EOF @@ -50,24 +57,26 @@ %token RAW %start mustache -%type mustache +%type mustache %% section: - | SECTION_INVERT_START END mustache SECTION_END END { Inverted_section (loc (), parse_section $1 $4 $3) } - | SECTION_START END mustache SECTION_END END { Section (loc (), parse_section $1 $4 $3) } + | SECTION_INVERT_START END mustache SECTION_END END { + with_loc (Inverted_section (parse_section $1 $4 $3)) } + | SECTION_START END mustache SECTION_END END { + with_loc (Section (parse_section $1 $4 $3)) } mustache_element: - | UNESCAPE_START UNESCAPE_END { Unescaped (loc (), $1) } - | UNESCAPE_START_AMPERSAND END { Unescaped (loc (), $1) } - | ESCAPE_START END { Escaped (loc (), $1) } - | PARTIAL_START END { Partial (loc (), $1) } - | COMMENT_START RAW END { Comment (loc (), $2) } + | UNESCAPE_START UNESCAPE_END { with_loc (Unescaped $1) } + | UNESCAPE_START_AMPERSAND END { with_loc (Unescaped $1) } + | ESCAPE_START END { with_loc (Escaped $1) } + | PARTIAL_START END { with_loc (Partial $1) } + | COMMENT_START RAW END { with_loc (Comment $2) } | section { $1 } string: - | RAW { String (loc (), $1) } + | RAW { with_loc (String $1) } mustache_l: | mustache_element mustache_l { ($1 :: $2) } @@ -79,8 +88,8 @@ mustache: | mustache_l { match $1 with | [x] -> x - | x -> Concat (loc (), x) + | x -> with_loc (Concat x) } - | EOF { String (loc (), "") } + | EOF { with_loc (String "") } %% diff --git a/lib/mustache_types.ml b/lib/mustache_types.ml index 0db02d4..b7935fe 100644 --- a/lib/mustache_types.ml +++ b/lib/mustache_types.ml @@ -20,24 +20,42 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}}*) -type loc = { - loc_start: Lexing.position; - loc_end: Lexing.position; -} - -type t = - | String of loc * string - | Escaped of loc * string - | Section of loc * section - | Unescaped of loc * string - | Partial of loc * string - | Inverted_section of loc * section - | Concat of loc * t list - | Comment of loc * string -and section = { - name: string; - contents: t; -} +module Locs = struct + 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 } +end + +module No_locs = struct + type t = + | 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 } +end exception Invalid_param of string exception Invalid_template of string From a722d707dec79d8bcba0e141594166abee0dd816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C3=ABl=20Gu=C3=A9neau?= Date: Sat, 25 Feb 2017 13:15:36 +0100 Subject: [PATCH 3/3] Add a test doing a roundtrip add_dummy_locs -> erase_locs --- lib_test/test_mustache.ml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib_test/test_mustache.ml b/lib_test/test_mustache.ml index cd4a161..4385694 100644 --- a/lib_test/test_mustache.ml +++ b/lib_test/test_mustache.ml @@ -89,6 +89,9 @@ let tests = [ ] +let roundtrip : t -> t = + fun t -> erase_locs (add_dummy_locs t) + let () = let assert_equal ?(printer=fun _ -> "") a b = @@ -99,7 +102,9 @@ let () = (List.mapi (fun i (input, expected_parsing, rendering_tests) -> let template = Mustache.of_string input in - (Printf.sprintf "%d - parsing" i + (Printf.sprintf "%d - erase_locs/add_dummy_locs roundtrip" i + >:: assert_equal (roundtrip template) template) + :: (Printf.sprintf "%d - parsing" i >:: assert_equal expected_parsing template) :: List.mapi (fun j (data, expected) -> (Printf.sprintf "%d - rendering (%d)" i j)