package jingoo

  1. Overview
  2. Docs

Source file jg_interp.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
(*
  jg_interp.ml

  Copyright (c) 2011- by Masaki WATANABE

  License: see LICENSE
*)
open Jg_types
open Jg_utils
open Jg_runtime

let rec filter_map fn = function
  | [] -> []
  | hd :: tl ->
    match fn hd with
    | Some x -> x :: filter_map fn tl
    | None -> filter_map fn tl

let rec value_of_expr env ctx = function
  | LiteralExpr(x) -> x
  | IdentExpr(name) -> jg_get_value ctx name
  | NotOpExpr(expr) -> jg_not @@ value_of_expr env ctx expr
  | NegativeOpExpr(expr) -> jg_negative @@ value_of_expr env ctx expr
  | PlusOpExpr(left, right) -> jg_plus (value_of_expr env ctx left) (value_of_expr env ctx right)
  | MinusOpExpr(left, right) -> jg_minus (value_of_expr env ctx left) (value_of_expr env ctx right)
  | TimesOpExpr(left, right) -> jg_times (value_of_expr env ctx left) (value_of_expr env ctx right)
  | PowerOpExpr(left, right) -> jg_power (value_of_expr env ctx left) (value_of_expr env ctx right)
  | DivOpExpr(left, right) -> jg_div (value_of_expr env ctx left) (value_of_expr env ctx right)
  | ModOpExpr(left, right) -> jg_mod (value_of_expr env ctx left) (value_of_expr env ctx right)
  | AndOpExpr(left, right) ->
    Tbool (jg_is_true (value_of_expr env ctx left) && jg_is_true (value_of_expr env ctx right))
  | OrOpExpr(left, right) ->
    Tbool (jg_is_true (value_of_expr env ctx left) || jg_is_true (value_of_expr env ctx right))
  | EqEqOpExpr(left, right) -> jg_eq_eq (value_of_expr env ctx left) (value_of_expr env ctx right)
  | NotEqOpExpr(left, right) -> jg_not_eq (value_of_expr env ctx left) (value_of_expr env ctx right)
  | LtOpExpr(left, right) -> jg_lt (value_of_expr env ctx left) (value_of_expr env ctx right)
  | GtOpExpr(left ,right) -> jg_gt (value_of_expr env ctx left) (value_of_expr env ctx right)
  | LtEqOpExpr(left, right) -> jg_lteq (value_of_expr env ctx left) (value_of_expr env ctx right)
  | GtEqOpExpr(left, right) -> jg_gteq (value_of_expr env ctx left) (value_of_expr env ctx right)
  | InOpExpr(left, right) -> jg_inop (value_of_expr env ctx left) (value_of_expr env ctx right)
  | ListExpr(expr_list) -> Tlist (List.map (value_of_expr env ctx) expr_list)
  | SetExpr(expr_list) -> Tset (List.map (value_of_expr env ctx) expr_list)
  | DotExpr(IdentExpr(name), prop) -> jg_obj_lookup_by_name ctx name prop
  | DotExpr(left, prop) -> jg_obj_lookup (value_of_expr env ctx left) prop
  | BracketExpr(left, expr) ->
    (match value_of_expr env ctx expr with
     | Tstr prop -> jg_obj_lookup (value_of_expr env ctx left) prop
     | Tint i -> jg_nth_aux (value_of_expr env ctx left) i
     | _ -> Tnull)
  | TestOpExpr(IdentExpr(name), IdentExpr("defined")) -> jg_test_defined ctx name
  | TestOpExpr(IdentExpr(name), IdentExpr("undefined")) -> jg_test_undefined ctx name
  | TestOpExpr(DotExpr(IdentExpr(name), prop), IdentExpr("defined")) -> jg_test_obj_defined ctx name prop
  | TestOpExpr(DotExpr(IdentExpr(name), prop), IdentExpr("undefined")) -> jg_test_obj_undefined ctx name prop
  | TestOpExpr(BracketExpr(IdentExpr(name), expr), IdentExpr("defined")) ->
    (match value_of_expr env ctx expr with
     | Tstr prop -> jg_test_obj_defined ctx name prop
     | _ -> Tbool false)
  | TestOpExpr(BracketExpr(IdentExpr(name), expr), IdentExpr("undefined")) ->
    (match value_of_expr env ctx expr with
     | Tstr prop -> jg_test_obj_undefined ctx name prop
     | _ -> Tbool true)
  | TestOpExpr(IdentExpr(name), IdentExpr("none")) -> jg_test_none ctx name
  | TestOpExpr(IdentExpr(_), IdentExpr("escaped")) -> jg_test_escaped ctx
  | TestOpExpr(IdentExpr(name), IdentExpr("upper")) -> jg_test_upper (jg_get_value ctx name)
  | TestOpExpr(IdentExpr(name), IdentExpr("lower")) -> jg_test_lower (jg_get_value ctx name)
  | TestOpExpr(target, test) -> jg_apply (value_of_expr env ctx test) [value_of_expr env ctx target]

  | ObjExpr(key_values) ->
    Tobj (List.map (fun (k, v) -> (k, value_of_expr env ctx v)) key_values)

  | ApplyExpr(IdentExpr("eval"), [name, expr]) ->
    assert (name = None) ;
    let value = ref Tnull in
    let ctx = {ctx with serialize = true ; output = fun x -> value := x } in
    let str = string_of_tvalue @@ value_of_expr env ctx expr in
    let ast = ast_from_string str in
    let _ = List.fold_left (eval_statement env) ctx ast in
    !value

  | ApplyExpr(IdentExpr("safe"), [name, expr]) ->
     assert (name = None) ;
     value_of_expr env ctx expr

  | ApplyExpr(expr, args) ->
    let name = apply_name_of expr in
    let nargs = if args = [] then [ Tnull ] else nargs_of env ctx args in
    let kwargs = kwargs_of_app env ctx args in
    let callable = value_of_expr env ctx expr in
    (match callable with
    | Tfun _ -> jg_apply callable nargs ~kwargs
    | _ ->
      (match jg_get_macro ctx name with
       | Some macro -> ignore @@ eval_macro env ctx name nargs kwargs macro; Tnull
       | None -> Tnull))

  | FunctionExpression (arg_names, body) ->
    func begin fun ?kwargs:_ args ->
      let ctx = jg_push_frame ctx in
      List.iter2 (jg_set_value ctx) arg_names args ;
      value_of_expr env ctx body
    end (List.length arg_names)

  | TernaryOpExpr (c, y, n) ->
    if jg_is_true (value_of_expr env ctx c) then value_of_expr env ctx y else value_of_expr env ctx n

and apply_name_of = function
  | IdentExpr(name) -> name
  | DotExpr(IdentExpr(name), prop) -> spf "%s.%s" name prop
  | ApplyExpr(expr, _) -> apply_name_of expr
  | _ -> "<lambda>"

and ident_names_of =
  filter_map @@ function
  | IdentExpr name -> Some name
  | _ -> None

and ident_names_of_def =
  filter_map @@ function
  | name, None -> Some name
  | _ -> None

and alias_names_of =
  List.map @@ function
  | (name, None) -> (name, name)
  | (name, Some name') -> (name, name')

and nargs_of env ctx =
  filter_map @@ function
  | Some _, _ -> None
  | None, x -> Some (value_of_expr env ctx x)

and kwargs_of_app env ctx =
  filter_map @@ function
  | Some name, expr -> Some (name, value_of_expr env ctx expr)
  | _ -> None

and kwargs_of_def env ctx =
  filter_map @@ function
  | name, Some expr -> Some (name, value_of_expr env ctx expr)
  | _ -> None

and eval_macro env ctx name args kwargs macro =
  let caller = match jg_get_macro ctx "caller" with None -> false | _ -> true in
  jg_eval_macro ctx name args kwargs macro ~caller:caller (fun ctx ast ->
    List.fold_left (eval_statement env) ctx ast
  )

and is_safe_expr = function
  | ApplyExpr(IdentExpr("safe"), _) -> true
  | ApplyExpr(expr, _) -> is_safe_expr expr
  | _ -> false

and eval_statement env ctx = function
  | Statements ast ->
    List.fold_left (eval_statement env) ctx ast

  | TextStatement(text) ->
    jg_output ctx (Tstr text) ~safe:true

  | ExpandStatement(expr) ->
    jg_output ctx (value_of_expr env ctx expr) ~autoescape:env.autoescape ~safe:(is_safe_expr expr)

  | SetStatement(SetExpr(ident_list), expr) ->
    jg_bind_names ctx (ident_names_of ident_list) (value_of_expr env ctx expr) ;
    ctx

  | SetStatement(DotExpr(IdentExpr ns, v), expr) ->
    Hashtbl.add
      (Hashtbl.find ctx.namespace_table ns) v (value_of_expr env ctx expr) ;
    ctx

  | FilterStatement(name, ast) ->
    let ctx = jg_set_filter ctx name in
    let ctx = List.fold_left (eval_statement env) ctx ast in
    jg_pop_filter ctx

  | IfStatement (branches) ->
    let rec select_case = function
      | (None, ast) :: _ -> ast
      | (Some cond, ast) :: tl ->
        if jg_is_true (value_of_expr env ctx cond) then ast
        else select_case tl
      | [] -> []
    in
    List.fold_left (eval_statement env) ctx @@ select_case branches

  | SwitchStatement (e, cases) ->
    let e = value_of_expr env ctx e in
    let rec select_case = function
      | ([], ast) :: _ -> ast
      | (cond, ast) :: tl ->
        if List.exists (fun x -> jg_eq_eq_aux (value_of_expr env ctx x) e) cond
        then ast
        else select_case tl
      | [] -> []
    in
    List.fold_left (eval_statement env) ctx @@ select_case cases

  | ForStatement(iterator, iterable_expr, ast) ->
    let iterable = value_of_expr env ctx iterable_expr in
    let is_iterable = Jg_runtime.jg_test_iterable_aux iterable in
    (* [ISSUE#23] when strict_mode is enabled, raises error if loop target is not iterable. *)
    if env.strict_mode = true && is_iterable = false then
      failwith @@ spf "%s is not iterable" (string_of_tvalue iterable)
    ;
    jg_iter ctx iterator (fun ctx ->
      ignore @@ List.fold_left (eval_statement env) ctx ast
    ) iterable;
    ctx

  | BlockStatement(_, ast) ->
    List.fold_left (eval_statement env) ctx ast

  | CallStatement(name, call_args_def, macro_args, call_ast) ->
    (match jg_get_macro ctx name with
     | Some (Macro _) ->
       let call_arg_names = ident_names_of_def call_args_def in
       let call_defaults = kwargs_of_def env ctx call_args_def in
       jg_set_macro ctx "caller" @@ Macro(call_arg_names, call_defaults, call_ast) ;
       let text = string_of_tvalue @@ value_of_expr env ctx @@ ApplyExpr(IdentExpr(name), macro_args) in
       jg_remove_macro ctx "caller" ;
       jg_output ctx (Tstr text) ~safe:true

     | None -> ctx (* do nothing *)
    )

  | IncludeStatement(e, with_ctx) ->
    begin match value_of_expr env ctx e with
      | Tstr path ->
        if with_ctx then
          let ast = ast_from_file ~env path in
          List.fold_left (eval_statement env) ctx ast
        else
          let ast = ast_from_file ~env path in
          let ctx' = jg_init_context ctx.output env in
          let _ = List.fold_left (eval_statement env) ctx' ast in
          ctx
      | x -> failwith_type_error_1 "Jg_interp:include" x
    end

  | RawIncludeStatement(e) ->
    begin match value_of_expr env ctx e with
      | Tstr path ->
        let file_path = get_file_path env path in
        let source = Jg_utils.read_file_as_string file_path in
        jg_output ctx (Tstr source) ~safe:true
      | x -> failwith_type_error_1 "Jg_interp:rawinclude" x
    end

  | WithStatement(binds, ast) ->
    let names, values = List.split binds in
    let values = List.map (value_of_expr env ctx) values in
    let ctx' = jg_push_frame ctx in
    let () = jg_set_values ctx' names values in
    ignore @@ List.fold_left (eval_statement env) ctx' ast ;
    ctx

  | AutoEscapeStatement(expr, ast) ->
    let ctx =
      match value_of_expr env ctx expr with
      | Tbool true ->
	jg_set_filter ctx "escape"
      | Tbool false ->
	jg_set_filter ctx "safe"
      | _ ->
	failwith "invalid syntax:autoescape(bool value required)" in
    let ctx = List.fold_left (eval_statement env) ctx ast in
    jg_pop_filter ctx

  | NamespaceStatement (ns, assign) ->
    let size = match List.length assign with 0 -> 10 | x -> x in
    let h = Hashtbl.create size in
    List.iter (fun (k, v) -> Hashtbl.add h k (value_of_expr env ctx v)) assign;
    Hashtbl.add ctx.namespace_table ns h;
    ctx

  | FunctionStatement(name, def_args, ast) ->
    let arg_names = ident_names_of_def def_args in
    let kwargs = kwargs_of_def env ctx def_args in
    let macro = Macro (arg_names, kwargs, ast) in
    let apply ~kwargs args =
      let value = ref Tnull in
      let ctx = { ctx with serialize = true ; output = fun x -> value := x } in
      let ctx = jg_push_frame ctx in
      ignore (jg_eval_aux ctx args kwargs macro @@ fun ctx ast ->
              List.fold_left (eval_statement env) ctx ast) ;
      !value
    in
    let fn = func (fun ?(kwargs=kwargs) args -> apply ~kwargs args) (List.length arg_names) in
    jg_set_value ctx name fn ;
    ctx

  | _ -> ctx

and unfold_extends env stmts =
  let open Jg_ast_mapper in
  let statement self = function
    | ExtendsStatement path ->
      Statements (self.ast self @@ ast_from_file ~env path)
    | e -> default_mapper.statement self e in
  let mapper = { default_mapper with statement } in
  mapper.ast mapper stmts

and replace_blocks stmts =
  let open Jg_ast_mapper in
  let h = Hashtbl.create 10 in
  let stmts =
    let statement self = function
      | BlockStatement (name, ast) ->
        Hashtbl.add h name ast ;
        BlockStatement (name, self.ast self ast)
      | e -> default_mapper.statement self e in
    let mapper = { default_mapper with statement } in
    mapper.ast mapper stmts in
  if Hashtbl.length h = 0 then stmts
  else
    let h' = Hashtbl.create 10 in
    let statement self = function
      | BlockStatement (name, _) ->
        let stmts =
          if Hashtbl.mem h' name then []
          else
            let () = Hashtbl.add h' name true in
            self.ast self @@ Hashtbl.find h name in
        Statements stmts
      | e -> default_mapper.statement self e in
    let mapper = { default_mapper with statement } in
    mapper.ast mapper stmts

(* Import macros into ctx and remove it from ast *)
and import_macros env ctx stmts =
  let open Jg_ast_mapper in
  let select = ref None in
  let namespace = ref None in
  let macro_name name = match !namespace with Some namespace -> spf "%s.%s" namespace name | _ -> name in
  let alias_name name = match !select with None -> name | Some alist -> List.assoc name alist in
  let can_import name = match !select with None -> true | Some alist -> List.mem_assoc name alist in
  let statement self = function

    | MacroStatement(name, def_args, ast) when can_import name ->
      let arg_names = ident_names_of_def def_args in
      let kwargs = kwargs_of_def env ctx def_args in
      jg_set_macro ctx (macro_name @@ alias_name name) @@ Macro(arg_names, kwargs, ast);
      Statements []

    | IncludeStatement(LiteralExpr(Tstr path), _) as stmt ->
      ignore @@ self.ast self @@ ast_from_file ~env path;
      stmt

    | ImportStatement(path, namespace') ->
      let old_namespace = !namespace in
      let () = namespace := namespace' in
      ignore @@ self.ast self @@ ast_from_file ~env path;
      let () = namespace := old_namespace in
      Statements []

    | FromImportStatement(path, select_macros) ->
      let alias_names = alias_names_of select_macros in
      let old_select = !select in
      let () = select := Some alias_names in
      ignore @@ self.ast self @@ ast_from_file ~env path;
      let () = select := old_select in
      Statements []

    | s -> default_mapper.statement self s in
  let mapper = { default_mapper with statement } in
  mapper.ast mapper stmts

and get_file_path env file_name =
  Jg_utils.get_file_path file_name ~template_dirs:env.template_dirs

and init_context ?(env=std_env) ?(models=[]) ~output () =
  let extensions = env.extensions in
  jg_load_extensions extensions;
  jg_init_context ~models output env

and ast_from_lexbuf filename lexbuf =
  Parsing.clear_parser ();
  Jg_lexer.reset_context ();
  Jg_lexer.init_lexer_pos filename lexbuf;
  let ast = Jg_parser.input Jg_lexer.main lexbuf in
  ast

and error e lexbuf =
  let curr = lexbuf.Lexing.lex_curr_p in
  let l = curr.Lexing.pos_lnum in
  let c = curr.Lexing.pos_cnum - curr.Lexing.pos_bol in
  let t = Lexing.lexeme lexbuf in
  let msg = Printf.sprintf "Error line %d, col %d, token %s (%s)" l c t e in
  raise (SyntaxError msg)

and ast_from_chan filename ch =
  let lexbuf = Lexing.from_channel ch in
  try
    let ast = ast_from_lexbuf filename lexbuf in
    close_in ch;
    ast
  with
  | SyntaxError e ->
    close_in ch ;
    error e lexbuf
  | Jg_parser.Error ->
    close_in ch ;
    error "" lexbuf

and ast_from_file ~env filename =
  let filename = get_file_path env filename in
  let ch = open_in filename in
  ast_from_chan (Some filename) ch

and ast_from_string string =
  let lexbuf = Lexing.from_string string in
  try ast_from_lexbuf None lexbuf
  with SyntaxError e -> error e lexbuf

and eval_aux ~env ~ctx ast =
  let ast =
    unfold_extends env ast
    |> replace_blocks
    |> import_macros env ctx in
  ignore @@ List.fold_left (eval_statement env) ctx ast

and from_file
    ?(env=std_env) ?(models=[]) ~output
    ?(ctx = init_context ~env ~models ~output ())
    file_name =
  eval_aux ~env ~ctx @@
    ast_from_file ~env file_name

and from_string ?(env=std_env) ?(models=[]) ?file_path:_ ~output
    ?(ctx = init_context ~env ~models ~output ())
    source =
  eval_aux ~env ~ctx @@
    ast_from_string source

and from_chan ?(env=std_env) ?(models=[]) ?file_path ~output
    ?(ctx = init_context ~env ~models ~output ())
    chan =
  eval_aux ~env ~ctx @@
    ast_from_chan file_path chan
OCaml

Innovation. Community. Security.