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
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
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
)
| 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
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