;;
;;
;; Ersatz evaluation.
;;
;; Based on the Ocaml Jingoo library, which is in turn based on the
;; Python Jinja2 library.
;;
;; Copyright 2012-2014 Ivan Raikov and the Okinawa Institute of
;; Science and Technology.
;;
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; A full copy of the GPL license can be found at
;; .
;;
(define (eval-expr env ctx x)
(cases texpr x
(LiteralExpr (x) x)
(IdentExpr (name)
(get-value ctx name))
(NotOpExpr (x)
(test-not (eval-expr env ctx x)))
(NegateOpExpr (x)
(op-negate (eval-expr env ctx x)))
(PlusOpExpr (l r)
(op-plus (eval-expr env ctx l)
(eval-expr env ctx r)))
(MinusOpExpr (l r)
(op-minus (eval-expr env ctx l)
(eval-expr env ctx r)))
(TimesOpExpr (l r)
(op-times (eval-expr env ctx l)
(eval-expr env ctx r)))
(PowerOpExpr (l r)
(op-power (eval-expr env ctx l)
(eval-expr env ctx r)))
(DivOpExpr (l r)
(op-div (eval-expr env ctx l)
(eval-expr env ctx r)))
(ModOpExpr (l r)
(op-mod (eval-expr env ctx l)
(eval-expr env ctx r)))
(AndOpExpr (l r)
(op-and (eval-expr env ctx l)
(eval-expr env ctx r)))
(OrOpExpr (l r)
(op-or (eval-expr env ctx l)
(eval-expr env ctx r)))
(EqEqOpExpr (l r)
(eq-eq (eval-expr env ctx l)
(eval-expr env ctx r)))
(NotEqOpExpr (l r)
(not-eq (eval-expr env ctx l)
(eval-expr env ctx r)))
(LtOpExpr (l r)
(op-lt (eval-expr env ctx l)
(eval-expr env ctx r)))
(GtOpExpr (l r)
(op-gt (eval-expr env ctx l)
(eval-expr env ctx r)))
(LtEqOpExpr (l r)
(op-lteq (eval-expr env ctx l)
(eval-expr env ctx r)))
(GtEqOpExpr (l r)
(op-gteq (eval-expr env ctx l)
(eval-expr env ctx r)))
(InOpExpr (l r)
(op-in (eval-expr env ctx l)
(eval-expr env ctx r)))
(ListExpr (lst)
(Tlist (map (lambda (x) (eval-expr env ctx x)) lst)))
(SetExpr (lst)
(Tset (map (lambda (x) (eval-expr env ctx x)) lst)))
(DotExpr (objexpr propexpr)
(cases texpr objexpr
(IdentExpr (name)
(let ((prop (ident-expr->name propexpr)))
(tobj-lookup ctx name prop)))
(DotExpr (_ _)
(let ((v (eval-expr env ctx objexpr))
(prop (ident-expr->name propexpr)))
(tobjval-lookup v prop)))
(else (error 'eval-expr "invalid object dot expression" objexpr))))
(TestOpExpr (objexpr pred)
(cases texpr pred
(IdentExpr (predname)
(case predname
((defined)
(cases texpr objexpr
(IdentExpr (name)
(test-defined ctx name))
(DotExpr (objexpr propexpr)
(let ((name (ident-expr->name objexpr))
(prop (ident-expr->name propexpr)))
(test-obj-defined ctx name prop)))
(else (error 'eval-expr "invalid predicate object expression"
objexpr))
))
((undefined)
(cases texpr objexpr
(IdentExpr (name)
(test-undefined ctx name))
(DotExpr (objexpr propexpr)
(let ((name (ident-expr->name objexpr))
(prop (ident-expr->name propexpr)))
(test-obj-undefined ctx name prop)))
(else (error 'eval-expr "invalid predicate object expression"
objexpr))
))
((none)
(cases texpr objexpr
(IdentExpr (name)
(test-none ctx name))
(else (error 'eval-expr "invalid predicate object expression"
objexpr))
))
((escaped)
(cases texpr objexpr
(IdentExpr (name)
(test-escaped ctx))
(else (error 'eval-expr "invalid predicate object expression"
objexpr))
))
(else
(tfun-apply (eval-expr env ctx pred)
(list (eval-expr env ctx objexpr))))))
(else
(tfun-apply (eval-expr env ctx pred)
(list (eval-expr env ctx objexpr))))))
(ObjExpr (exprs)
(Tobj (map (lambda (id.val)
(cases texpr (car id.val)
(IdentExpr (name)
(cons name (eval-expr env ctx (cdr id.val))))
(LiteralExpr (strval expr)
(cons (string->symbol (unbox-string strval))
(eval-expr env ctx (cdr id.val))))
(else (error 'eval-expr "invalid object expression" id.val))))
exprs)))
(ApplyExpr (opexpr argexprs)
(let ((opname (ident-expr->name/safe opexpr)))
(if (and opname (eq? 'eval opname))
(let* ((ctx (template-context-with-buffer
ctx (open-output-string)))
(stmts (statements-from-string env (->string (eval-expr env ctx (car argexprs)))))
(ctx (fold (lambda (s ctx) (eval-statement env ctx s)) ctx stmts)))
(Tstr (get-output-string (tmpl-ctx-buffer ctx))))
(let ((name (apply-name-of opexpr))
(nargs (nargs-of env ctx argexprs))
(kwargs (kwargs-of env ctx argexprs))
(callable (eval-expr env ctx opexpr)))
(cases tvalue callable
(Tfun (fn)
(if (null? nargs)
(Tfun (lambda _ (fn argexprs kwargs)))
(tfun-apply callable nargs name: name kwargs: kwargs)))
(else
(let ((mac (get-macro ctx name)))
(if mac
(begin
(eval-macro1 env ctx name nargs kwargs mac)
(Tnull))
(Tnull))))
))
)))
(else (error 'eval-expr "invalid expression" x))
))
(define (apply-name-of fnexpr)
(cases texpr fnexpr
(IdentExpr (name) name)
(DotExpr (objexpr propexpr)
(let ((name (ident-expr->name objexpr))
(prop (ident-expr->name propexpr)))
(string->symbol (sprintf "~A.~A" name prop))))
(ApplyExpr (expr args)
(apply-name-of expr))
(else (string->symbol ""))
))
(define (ident-names-of lst)
(map ident-expr->name
(filter (lambda (x)
(cases texpr x
(IdentExpr (x) #t)
(else #f)))
lst)))
(define (alias-names-of lst)
(map (lambda (x)
(cases texpr x
(IdentExpr (name) (cons name name))
(AliasExpr (e1 e2)
(let ((n1 (ident-expr->name e1))
(n2 (ident-expr->name e2)))
(cons n1 n2)))
(else (error 'alias-names-of "invalid argument" x))))
lst))
(define (nargs-of env ctx args)
(map (lambda (x) (eval-expr env ctx x))
(filter (lambda (x) (cases texpr x (KeywordExpr (x y) #f) (else #t)))
args)))
(define (kwargs-of env ctx args)
(map (lambda (x)
(cases texpr x
(KeywordExpr (x y)
(let ((name (ident-expr->name x)))
(cons name (eval-expr env ctx y))))))
(filter (lambda (x) (cases texpr x (KeywordExpr (x y) #t) (else #f)))
args)))
(define (eval-macro1 env ctx name args kwargs mac)
(let ((caller (get-macro ctx 'caller))
(f (lambda (ctx stmts)
(fold (lambda (s ctx)
(eval-statement env ctx s))
ctx stmts))))
(eval-macro env ctx name args kwargs mac f caller: (and caller #t))))
(define (eval-statement env ctx stmt)
(cases tstmt stmt
(TextStatement (text)
(tvalue-output ctx (Tstr text) safe: #t)
ctx)
(ExpandStatement (expr)
(tvalue-output ctx (eval-expr env ctx expr)
autoescape: (tmpl-env-autoescape env)
safe: (safe-expr? expr))
ctx)
(SetStatement (setexpr expr)
(cases texpr setexpr
(SetExpr (ident-lst)
(let ((ctx1
(bind-names ctx (ident-names-of ident-lst)
(eval-expr env ctx expr))))
ctx1))
(else (error 'eval-statement "invalid set expression" setexpr))))
(FilterStatement (nexpr stmts)
(let ((name (ident-expr->name nexpr)))
(let* ((ctx (set-filter ctx name))
(ctx (fold (lambda (s ctx) (eval-statement env ctx s))
ctx stmts)))
(pop-filter ctx)
)))
(IfStatement (conds elses)
(letrec ((select-case
(lambda (lst)
(if (pair? lst)
(let ((h (car lst)))
(let ((expr (car h)))
(if (is-true (eval-expr env ctx expr))
(cdr h)
(select-case (cdr lst)))
))
elses))))
(fold (lambda (x ctx) (eval-statement env ctx x)) ctx (select-case conds))
))
(ForStatement (iterator list-expr stmts)
(let ((iterator
(cases texpr iterator
(IdentExpr (name) (list name))
(SetExpr (lst) (ident-names-of lst))
(ListExpr (lst) (ident-names-of lst))
(else (error 'eval-statement "invalid iterator" iterator)))))
(iter ctx iterator
(lambda (ctx)
(fold (lambda (x ctx) (eval-statement env ctx x)) ctx stmts))
(eval-expr env ctx list-expr))
))
(BlockStatement (idexpr endexpr stmts)
(let* ((name (ident-expr->name idexpr))
(endname (or (and endexpr (ident-expr->name endexpr)) name)))
(if (equal? name endname)
(fold (lambda (x ctx) (eval-statement env ctx x)) ctx stmts)
(error 'eval-statement "mismatch between block begin name and end name"
name endname))))
(CallStatement (idexpr call-args-def macro-args call-stmts)
(let* ((name (ident-expr->name idexpr))
(mac (get-macro ctx name)))
(if mac
(let ((call-arg-names (ident-names-of call-args-def))
(call-defaults (kwargs-of env ctx call-args-def)))
(let* ((ctx (set-macro ctx 'caller
(make-template-macro call-arg-names call-defaults call-stmts)))
(text (eval-expr env ctx (ApplyExpr (IdentExpr name) macro-args)))
(ctx (pop-macro ctx)))
(cases tvalue text
(Tnull () (begin))
(else (tvalue-output ctx (Tstr (->string text)))))
ctx
))
(error 'eval-statement "macro not found" name)
)
))
(IncludeStatement (path w)
(if w
(let ((stmts (statements-from-file env path)))
(fold (lambda (x ctx) (eval-statement env ctx x)) ctx stmts))
(let ((ctx1 (init-context env)))
(let ((stmts (statements-from-file env path)))
(fold (lambda (x ctx) (eval-statement env ctx x)) ctx1 stmts)
ctx))
))
(WithStatement (binds stmts)
(let* ((kwargs (kwargs-of env ctx binds))
(names (map car kwargs))
(values (map cdr kwargs))
(ctx (push-frame ctx))
(ctx (set-values ctx names values))
(ctx (fold (lambda (s ctx) (eval-statement env ctx s)) ctx stmts)))
(pop-frame ctx)))
(AutoEscapeStatement (expr stmts)
(let ((ctx (cases tvalue (eval-expr env ctx expr)
(Tbool (v)
(if v (set-filter ctx 'escape)
(set-filter ctx 'safe)))
(else
(error 'eval-statement "invalid autoescape argument" expr)))))
(fold (lambda (s ctx) (eval-statement env ctx s)) ctx stmts)
(pop-filter ctx)
))
(else ctx)
))
(define (safe-expr? x)
(cases texpr x
(ApplyExpr (idexpr _)
(or
(eq? 'safe (ident-expr->name/safe idexpr))
(safe-expr? idexpr)))
(else #f)))
(define (unfold-extends env ctx stmts)
(let recur ((ret '()) (stmts stmts))
(if (null? stmts) ret
(cases tstmt (car stmts)
(ExtendsStatement (path)
(let ((stmts1 (unfold-extends env ctx (statements-from-file env path))))
(recur (append ret stmts1) (cdr stmts))))
(else (recur (append ret (list (car stmts))) (cdr stmts)))
))
))
(define (align-block stmts)
(define (same-block? name stmt)
(cases tstmt stmt
(BlockStatement (idexpr _ _)
(let ((name1 (ident-expr->name/safe idexpr)))
(equal? name1 name)))
(else #f)))
(define (erase-block name lst)
(filter (lambda (x) (not (same-block? name x))) lst))
(let recur ((ret '()) (stmts stmts))
(if (null? stmts) (reverse ret)
(begin
(let ((block (car stmts)))
(cases tstmt block
(BlockStatement (idexpr _ _)
(let* ((name (ident-expr->name idexpr))
(block1 (find (lambda (x) (same-block? name x)) (cdr stmts))))
(if block1
(recur (cons block1 ret)
(erase-block name (cdr stmts)))
(recur (cons block ret) (cdr stmts)))))
(else (recur (cons block ret) (cdr stmts))))
)))
))
(define (import-macro env ctx codes #!key (namespace #f) (select #f))
(let ((macro-name (lambda (name) (if namespace (string->symbol (sprintf "~A.~A" namespace name)) name)))
(alias-name (lambda (name) (if select (alist-ref name select) name)))
(can-import? (lambda (name) (if select (assoc name select) #t))))
(fold (lambda (code ctx)
(cases tstmt code
(MacroStatement (idexpr def-args stmts)
(let ((name (ident-expr->name idexpr)))
(if (can-import? name)
(let ((arg-names (ident-names-of def-args))
(kwargs (kwargs-of env ctx def-args)))
(let ((full-name (macro-name (alias-name name))))
(set-macro ctx
full-name
(make-template-macro arg-names kwargs stmts))))
ctx
)))
(BlockStatement (x x1 stmts)
(import-macro env ctx stmts namespace: namespace select: select))
(IncludeStatement (path namespace)
(import-macro env ctx
(statements-from-file env path)
namespace: namespace select: select ))
(ImportStatement (path namespace)
(let ((res (import-macro env ctx (statements-from-file env path)
namespace: namespace
select: select)))
res))
(FromImportStatement (path select-macros)
(let ((alias-names (alias-names-of select-macros)))
(let ((res (import-macro env ctx
(statements-from-file env path)
namespace: namespace
select: alias-names
)))
res
)))
(else ctx))
)
ctx codes)
))
(define (eval-statements codes #!key
(env (template-std-env))
(models '())
(ctx #f))
(let* ((ctx (or ctx (init-context env: env models: models)))
(codes (align-block (unfold-extends env ctx codes)))
(ctx (import-macro env ctx codes)))
(fold (lambda (s ctx) (eval-statement env ctx s)) ctx codes)
(let ((output (get-output-string (tmpl-ctx-buffer ctx))))
output)
))