├── t ├── var ├── foo ├── backend.lisp ├── pass.lisp ├── zenekindarl.lisp ├── att.lisp └── parse.lisp ├── .gitignore ├── src ├── lexer.lisp ├── util.lisp ├── parse.lisp ├── backend │ ├── fast-io.lisp │ ├── sequence.lisp │ └── stream.lisp ├── token.lisp ├── parser.lisp ├── zenekindarl.lisp ├── lexer │ └── default.lisp ├── pass.lisp ├── backend.lisp └── att.lisp ├── benchmark ├── simple.tmpl.zenekindarl ├── simple.tmpl.html-template ├── 1var.tmpl.zenekindarl ├── 1var.tmpl.html-template ├── repeat.tmpl.zenekindarl ├── repeat.tmpl.html-template ├── bench.lisp ├── 100var.tmpl.zenekindarl ├── 100var.tmpl.html-template └── master_all.bench ├── tools ├── test.sh └── benchmark.sh ├── .travis.yml ├── zenekindarl-test.asd ├── experiment ├── concat.lisp └── concat2.lisp ├── LICENSE ├── zenekindarl.asd └── README.markdown /t/var: -------------------------------------------------------------------------------- 1 | {{var bar}} -------------------------------------------------------------------------------- /t/foo: -------------------------------------------------------------------------------- 1 | {{repeat 2 as i}}bar{{endrepeat}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.fasl 2 | *.dx32fsl 3 | *.dx64fsl 4 | *.lx32fsl 5 | *.lx64fsl 6 | *.x86f 7 | *~ 8 | .#* -------------------------------------------------------------------------------- /src/lexer.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage zenekindarl.lexer 3 | (:use :cl) 4 | (:export lex)) 5 | (in-package zenekindarl.lexer) 6 | 7 | (defgeneric lex (template lexer)) 8 | -------------------------------------------------------------------------------- /benchmark/simple.tmpl.zenekindarl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

Hello

9 |

This is an example

10 | 11 | 12 | -------------------------------------------------------------------------------- /benchmark/simple.tmpl.html-template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

Hello

9 |

This is an example

10 | 11 | 12 | -------------------------------------------------------------------------------- /benchmark/1var.tmpl.zenekindarl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

Hello

9 |

Your name is {{ var name}}, isn't it?

10 | 11 | 12 | -------------------------------------------------------------------------------- /benchmark/1var.tmpl.html-template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

Hello

9 |

Your name is , isn't it?

10 | 11 | 12 | -------------------------------------------------------------------------------- /tools/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ZENEKINDARL_ROOT=$(cd "$(dirname $0)/.."; pwd) 3 | TEST_ROOT="${ZENEKINDARL_ROOT}/t" 4 | CURRENT_BRANCH="$(git branch --list --no-color | grep '^\*' | cut -d\ -f2)" 5 | cd "${TEST_ROOT}" 6 | 7 | test_all(){ 8 | sbcl --eval "(require 'asdf)" --eval "(asdf:test-system 'zenekindarl)" --quit 9 | } 10 | 11 | test_all 12 | -------------------------------------------------------------------------------- /benchmark/repeat.tmpl.zenekindarl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

Hello

9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tools/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ZENEKINDARL_ROOT=$(cd "$(dirname $0)/.."; pwd) 4 | BENCH_ROOT="${ZENEKINDARL_ROOT}/benchmark" 5 | CURRENT_BRANCH="$(git branch --list --no-color | grep '^\*' | cut -d\ -f2)" 6 | cd "${BENCH_ROOT}" 7 | 8 | bench_all(){ 9 | sbcl --load bench.lisp --quit > "${CURRENT_BRANCH}_all.bench" 2>&1 10 | } 11 | 12 | bench_all 13 | -------------------------------------------------------------------------------- /benchmark/repeat.tmpl.html-template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

Hello

9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/util.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage zenekindarl.util 3 | (:use :cl) 4 | (:export :octets)) 5 | (in-package :zenekindarl.util) 6 | 7 | (deftype octets () 8 | '(simple-array (unsigned-byte 8) (*))) 9 | 10 | (defun octets (&rest contents) 11 | (make-array (length contents) 12 | :element-type '(unsigned-byte 8) 13 | :initial-contents contents)) 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: lisp 2 | sudo: false 3 | 4 | env: 5 | global: 6 | - PATH=~/.roswell/bin:$PATH 7 | - ROSWELL_INSTALL_DIR=$HOME/.roswell 8 | - COVERAGE_EXCLUDE=t 9 | matrix: 10 | - LISP=sbcl-bin COVERALLS=true 11 | - LISP=ccl-bin 12 | 13 | install: 14 | - curl -L https://raw.githubusercontent.com/snmsts/roswell/release/scripts/install-for-ci.sh | sh 15 | - ros install prove 16 | 17 | script: 18 | - cd t 19 | - run-prove ../zenekindarl-test.asd 20 | -------------------------------------------------------------------------------- /src/parse.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage zenekindarl.parse 3 | (:use :cl 4 | :zenekindarl.att 5 | :zenekindarl.lexer 6 | :zenekindarl.parser) 7 | (:import-from :alexandria 8 | :read-file-into-string) 9 | (:export :parse-template-string 10 | :parse-template-file)) 11 | (in-package zenekindarl.parse) 12 | 13 | (defun parse-template-string (str &optional (syntax :default)) 14 | (maxpc:parse (zenekindarl.lexer:lex str syntax) (zenekindarl.parser:=template))) 15 | 16 | (defun parse-template-file (file &optional (syntax :default)) 17 | (with-open-file (f file) 18 | (parse-template-string f syntax))) 19 | -------------------------------------------------------------------------------- /zenekindarl-test.asd: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | 7 | #-asdf3.1 (error "zenekindarl-test requires ASDF 3.1") 8 | (defsystem zenekindarl-test 9 | :author "κeen" 10 | :license "" 11 | :depends-on (:zenekindarl 12 | :prove 13 | :flexi-streams) 14 | :defsystem-depends-on (:prove) 15 | :components ((:module "t" 16 | :components 17 | ((:test-file "zenekindarl") 18 | (:test-file "att") 19 | (:test-file "pass") 20 | (:test-file "backend") 21 | (:test-file "parse")))) 22 | 23 | :perform (test-op (op c) (uiop:symbol-call :prove :run-test-system c))) 24 | -------------------------------------------------------------------------------- /src/backend/fast-io.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage zenekindarl.backend.fast-io 3 | (:use :cl :zenekindarl.util :zenekindarl.att :zenekindarl.backend) 4 | (:import-from :zenekindarl.backend.stream 5 | :buffer-of) 6 | (:import-from :zenekindarl.backend.sequence 7 | :octet-backend) 8 | (:import-from :babel 9 | :string-to-octets) 10 | (:import-from :fast-io 11 | :with-fast-output 12 | :fast-write-sequence) 13 | (:export :fast-io-backend)) 14 | (in-package :zenekindarl.backend.fast-io) 15 | 16 | (defclass fast-io-backend (octet-backend) 17 | ()) 18 | 19 | (defmethod make-backend ((backend (eql :fast-io)) &key &allow-other-keys) 20 | (make-instance 'fast-io-backend)) 21 | 22 | (defmethod emit-lambda ((backend fast-io-backend) att) 23 | (let* ((code (emit-code backend att))) 24 | (eval 25 | `(lambda (,(buffer-of backend) ,@(emit-parameters backend)) 26 | (declare (ignorable ,(buffer-of backend))) 27 | ,code)))) 28 | 29 | -------------------------------------------------------------------------------- /experiment/concat.lisp: -------------------------------------------------------------------------------- 1 | (declaim (ftype (function (list) string) 2 | cat-with-concatenate cat-with-stream )) 3 | 4 | (defun cat-with-concatenate (list) 5 | (declare (optimize (speed 3) (space 0) (safety 0) (debug 0))) 6 | (flet ((strcat (x y) 7 | (declare (string x y) 8 | (ftype (function (string string) string)) 9 | (optimize (speed 3) (space 0) (safety 0) (debug 0)) ) 10 | (concatenate 'string x y) )) 11 | (reduce #'strcat (cdr list) :initial-value (car list)) )) 12 | 13 | (defun cat-with-stream (list) 14 | (declare (optimize (speed 3) (space 0) (safety 0) (debug 0))) 15 | (with-output-to-string (sstream) 16 | (loop for str in list do (princ str sstream)) )) 17 | 18 | (eval-when (:execute) 19 | (defvar input-list nil) 20 | (setq input-list (loop for i from 1 to 1000 collect (write-to-string i))) 21 | 22 | (let ((*trace-output* *standard-output*)) 23 | (time (dotimes (_ 100) (cat-with-concatenate input-list))) 24 | (time (dotimes (_ 100) (cat-with-stream input-list))) )) 25 | 26 | 27 | -------------------------------------------------------------------------------- /experiment/concat2.lisp: -------------------------------------------------------------------------------- 1 | (declaim (ftype (function (list) string) 2 | cat-with-stream 3 | cat-with-stream2 4 | cat-with-stream3 5 | cat-with-concatenate)) 6 | 7 | (defun cat-with-concatenate (list) 8 | (declare (optimize (speed 3) (space 0) (safety 0) (debug 0))) 9 | (apply #'concatenate 'string list)) 10 | 11 | (defun cat-with-stream (list) 12 | (declare (optimize (speed 3) (space 0) (safety 0) (debug 0))) 13 | (with-output-to-string (sstream) 14 | (loop for str in list do (princ str sstream)) )) 15 | 16 | (defun cat-with-stream2 (list) 17 | (declare (optimize (speed 3) (space 0) (safety 0) (debug 0))) 18 | (with-output-to-string (sstream) 19 | (loop for str in list do (write-sequence str sstream)) )) 20 | 21 | (defun cat-with-stream3 (list) 22 | (declare (optimize (speed 3) (space 0) (safety 0) (debug 0))) 23 | (with-output-to-string (sstream) 24 | (loop for str in list do (write-string str sstream)) )) 25 | 26 | (eval-when (:execute) 27 | (defvar input-list nil) 28 | (setq input-list (loop for i from 1 to 255 collect (write-to-string i))) 29 | 30 | (let ((*trace-output* *standard-output*)) 31 | (time (dotimes (_ 100) (cat-with-concatenate input-list))) 32 | (time (dotimes (_ 100) (cat-with-stream input-list))) 33 | (time (dotimes (_ 100) (cat-with-stream2 input-list))) 34 | (time (dotimes (_ 100) (cat-with-stream3 input-list))))) 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Sunrin SHIMURA 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of the nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL Sunrin SHIMURA BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /zenekindarl.asd: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | #| 7 | Author: κeen 8 | |# 9 | 10 | (defsystem zenekindarl 11 | :version "0.1" 12 | :author "κeen" 13 | :license "" 14 | :depends-on ( 15 | :alexandria 16 | :anaphora 17 | :babel 18 | :optima 19 | :cl-ppcre 20 | :html-encode 21 | :fast-io 22 | :maxpc 23 | :cl-annot 24 | ) 25 | :components ((:module "src" 26 | :components 27 | ((:file "zenekindarl" :depends-on ("parse" "pass" "backend" "be")) 28 | (:file "att" :depends-on ("util")) 29 | (:file "backend" :depends-on ("util" "att")) 30 | (:module "be" 31 | :pathname "backend" 32 | :depends-on ("backend") 33 | :components ((:file "stream") 34 | (:file "sequence" :depends-on ("stream")) 35 | (:file "fast-io" :depends-on ("sequence")))) 36 | (:file "token") 37 | (:file "pass" :depends-on ("util" "att")) 38 | (:file "parse" :depends-on ("att" "lexer" "le" "parser")) 39 | (:file "lexer") 40 | (:module "le" 41 | :pathname "lexer" 42 | :depends-on ("lexer" "token") 43 | :components ((:file "default"))) 44 | (:file "parser" :depends-on ("token" "att" "lexer" "le")) 45 | (:file "util")))) 46 | :description "A fast precompiling template engine" 47 | :in-order-to ((test-op (test-op zenekindarl-test)))) 48 | -------------------------------------------------------------------------------- /src/token.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage zenekindarl.token 3 | (:use :cl 4 | :annot.class) 5 | (:export 6 | :token-string-p 7 | :token-variable-p 8 | :token-if-p 9 | :token-else-p 10 | :token-end-p 11 | :token-loop-p 12 | :token-repeat-p 13 | :token-include-p 14 | :token-insert-p)) 15 | (in-package zenekindarl.token) 16 | 17 | (annot:enable-annot-syntax) 18 | 19 | @export-accessors 20 | (defstruct token 21 | (start 0 :type integer) 22 | (end 0 :type integer) 23 | (template)) 24 | 25 | @export 26 | @export-accessors 27 | @export-constructors 28 | (defstruct (token-string 29 | (:conc-name token-) 30 | (:include token)) 31 | (str "" :type string)) 32 | 33 | @export 34 | @export-accessors 35 | @export-constructors 36 | (defstruct (token-variable 37 | (:conc-name token-) 38 | (:include token)) 39 | (value nil :type (or null symbol)) 40 | (auto-escape t :type boolean)) 41 | 42 | @export 43 | @export-accessors 44 | @export-constructors 45 | (defstruct (token-if 46 | (:conc-name token-) 47 | (:include token)) 48 | (cond-clause)) 49 | 50 | @export 51 | @export-accessors 52 | @export-constructors 53 | (defstruct (token-else 54 | (:conc-name token-) 55 | (:include token))) 56 | 57 | @export 58 | @export-accessors 59 | @export-constructors 60 | (defstruct (token-end 61 | (:conc-name token-) 62 | (:include token))) 63 | 64 | 65 | @export 66 | @export-accessors 67 | @export-constructors 68 | (defstruct (token-loop 69 | (:conc-name token-) 70 | (:include token)) 71 | (seq () :type (or list symbol)) 72 | (loop-sym nil :type (or null symbol))) 73 | 74 | @export 75 | @export-accessors 76 | @export-constructors 77 | (defstruct (token-repeat 78 | (:conc-name token-) 79 | (:include token)) 80 | (times () :type (or integer symbol)) 81 | (repeat-sym nil :type (or null symbol))) 82 | 83 | @export 84 | @export-accessors 85 | @export-constructors 86 | (defstruct (token-include 87 | (:conc-name token-) 88 | (:include token)) 89 | (include-template nil :type list)) 90 | 91 | @export 92 | @export-accessors 93 | @export-constructors 94 | (defstruct (token-insert 95 | (:conc-name token-) 96 | (:include token)) 97 | (insert-string "" :type string)) 98 | 99 | -------------------------------------------------------------------------------- /t/backend.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.backend-test 8 | (:use :cl 9 | :zenekindarl.util 10 | :zenekindarl.att 11 | :zenekindarl.backend 12 | :zenekindarl.backend.stream 13 | :cl-test-more) 14 | (:import-from :html-encode 15 | :encode-for-tt)) 16 | (in-package :zenekindarl.backend-test) 17 | 18 | (plan nil) 19 | (diag "backend tests") 20 | 21 | (is-expand 22 | '#.(emit-code (make-backend :stream) (att-output (att-string "aaa"))) 23 | 24 | '(write-string "aaa" $stream) 25 | "stream backend of att-string with att-output") 26 | 27 | (is-expand 28 | '#.(emit-code (make-backend :stream) (att-eval '(+ 1 2))) 29 | 30 | '(+ 1 2) 31 | "stream backend of att-eval") 32 | 33 | (is-expand 34 | '#.(emit-code (make-backend :stream) (att-output (att-eval '(+ 1 2)))) 35 | '(write-string (encode-for-tt (princ-to-string(+ 1 2))) $stream) 36 | "stream backend of att-eval with att-output") 37 | 38 | (is-expand 39 | '#.(emit-code (make-backend :stream) (att-output (att-variable 'foo))) 40 | 41 | '(write-string (if (stringp foo) 42 | (encode-for-tt foo) 43 | (encode-for-tt (princ-to-string foo))) $stream) 44 | "stream backend of att-variable with att-output") 45 | 46 | (is-expand 47 | '#.(emit-code (make-backend :stream) (att-output (att-variable 'foo :string))) 48 | '(write-string (encode-for-tt foo) $stream) 49 | "stream backend of att-variable with type with att-output") 50 | 51 | (is-expand 52 | '#.(emit-code (make-backend :stream) 53 | (att-if 54 | (att-eval t) 55 | (att-string "foo"))) 56 | 57 | '(if t 58 | "foo" 59 | nil) 60 | "stream backend of att-if with else omitted") 61 | 62 | (is-expand 63 | '#.(emit-code (make-backend :stream) 64 | (att-if 65 | (att-eval t) 66 | (att-string "foo") 67 | (att-string "bar"))) 68 | 69 | '(if t 70 | "foo" 71 | "bar") 72 | "stream backend of att-if") 73 | 74 | (is-expand 75 | '#.(emit-code (make-backend :stream) 76 | (att-loop 77 | (att-eval ''((:foo 1) (:foo 2) (:foo 3))) 78 | (att-variable 'foo))) 79 | 80 | '(loop :for $loopvar :in '((:foo 1) (:foo 2) (:foo 3)) 81 | :do foo ) 82 | "stream backend of loop") 83 | 84 | (finalize) 85 | -------------------------------------------------------------------------------- /t/pass.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.pass-test 8 | (:use :cl 9 | :zenekindarl.att 10 | :zenekindarl.pass 11 | :zenekindarl.util 12 | :cl-test-more)) 13 | (in-package :zenekindarl.pass-test) 14 | 15 | (plan nil) 16 | (diag "test pass") 17 | 18 | (is 19 | (flatten-pass 20 | (att-progn 21 | (att-string "start") 22 | (att-progn (att-variable 'foo)) 23 | (att-string "end")) 24 | ()) 25 | 26 | (att-progn 27 | (att-string "start") 28 | (att-variable 'foo) 29 | (att-string "end")) 30 | "flatten-pass for progn" 31 | :test #'att-equal) 32 | 33 | (is 34 | (flatten-pass 35 | (att-progn 36 | (att-string "start") 37 | (att-progn (att-progn (att-variable 'foo))) 38 | (att-string "end")) 39 | ()) 40 | 41 | (att-progn 42 | (att-string "start") 43 | (att-variable 'foo) 44 | (att-string "end")) 45 | "flatten-pass for nested progn" 46 | :test #'att-equal) 47 | 48 | (is 49 | (flatten-pass 50 | (att-progn 51 | (att-string "start") 52 | (att-if 53 | (att-eval t) 54 | (att-progn 55 | (att-string "foo is: ") 56 | (att-progn (att-variable 'foo)))) 57 | (att-string "end")) 58 | ()) 59 | 60 | (att-progn 61 | (att-string "start") 62 | (att-if 63 | (att-progn (att-eval t)) 64 | (att-progn 65 | (att-string "foo is: ") 66 | (att-variable 'foo)) 67 | (att-progn (att-nil))) 68 | (att-string "end")) 69 | "flatten-pass for if" 70 | :test #'att-equal) 71 | 72 | (is 73 | (flatten-pass 74 | (att-progn 75 | (att-string "start") 76 | (att-loop 77 | (att-eval t) 78 | (att-progn 79 | (att-string "foo is: ") 80 | (att-progn (att-variable 'foo))) 81 | (att-progn (att-variable 'foo))) 82 | (att-string "end")) 83 | ()) 84 | 85 | (att-progn 86 | (att-string "start") 87 | (att-loop 88 | (att-progn (att-eval t)) 89 | (att-progn 90 | (att-string "foo is: ") 91 | (att-variable 'foo)) 92 | (att-progn (att-variable 'foo))) 93 | (att-string "end")) 94 | "flatten-pass for loop" 95 | :test #'att-equal) 96 | 97 | (is 98 | (remove-progn-pass 99 | (att-progn (att-string "test")) 100 | ()) 101 | (att-string "test") 102 | "remove-progn-pass for simple progn" 103 | :test #'att-equal) 104 | 105 | (is 106 | (remove-progn-pass 107 | (att-if 108 | (att-progn (att-eval t)) 109 | (att-progn 110 | (att-string "foo is: ") 111 | (att-variable 'foo)) 112 | (att-progn (att-nil))) 113 | ()) 114 | (att-if 115 | (att-eval t) 116 | (att-progn 117 | (att-string "foo is: ") 118 | (att-variable 'foo))) 119 | "remove-progn-pass for if" 120 | :test #'att-equal) 121 | 122 | 123 | (is 124 | (append-sequence-pass 125 | (att-progn 126 | (att-string "foo") 127 | (att-string "bar")) 128 | ()) 129 | (att-progn (att-string "foobar")) 130 | "append-sequence-pass for string" 131 | :test #'att-equal) 132 | 133 | (finalize) 134 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/KeenS/zenekindarl.svg?branch=master)](https://travis-ci.org/KeenS/zenekindarl) 2 | [![Coverage Status](https://coveralls.io/repos/KeenS/zenekindarl/badge.svg?branch=master)](https://coveralls.io/r/KeenS/zenekindarl) 3 | 4 | # Zenekindarl 5 | Expected to be a fast, flexible, extensible, low memory usage, async, concurrent template engine. 6 | 7 | ## Usage 8 | Like this 9 | 10 | ```lisp 11 | (render "Hello {{var name}}!!" 12 | :name "κeen") 13 | ``` 14 | 15 | ```lisp 16 | (let ((renderer (compile-template-string :stream "Hello {{var name}}!!"))) 17 | (funcall renderer *standard-output* :name "κeen")) 18 | ``` 19 | 20 | . 21 | 22 | For more information, see docstring 23 | 24 | ## Instant Benchmark 25 | Zenekindarl perform **x16** as fast as a template engine in Python in the following instant benchmark. 26 | 27 | ![Benchmark](https://docs.google.com/spreadsheets/d/1M8x9dcK8ToL4-tfVUfGnCh_OOtttJpXxK905raA0eas/pubchart?oid=1882415724&format=image) 28 | 29 | Template engines | Time[sec] 30 | ---------------------|---------- 31 | Zenekindarl, SBCL 1.1.8 | 1.365 32 | Jinja2, Python 2.7.5 | 24.07 33 | 34 | The benchmark code for Zenekindarl: 35 | 36 | > (time 37 | (with-open-file (out #P"~/Desktop/out" :direction :output :if-exists :supersede) 38 | (let ((fun (zenekindarl:compile-template-string :stream "Hello {{var name}}!!"))) 39 | (loop repeat 1000000 40 | do (funcall fun out :name "κeen"))))) 41 | Evaluation took: 42 | 1.625 seconds of real time 43 | 1.364707 seconds of total run time (1.302198 user, 0.062509 system) 44 | [ Run times consist of 0.042 seconds GC time, and 1.323 seconds non-GC time. ] 45 | 84.00% CPU 46 | 1 form interpreted 47 | 3 lambdas converted 48 | 3,265,218,807 processor cycles 49 | 528,706,464 bytes consed 50 | 51 | The benchmark code for a template engine in Python: 52 | 53 | $ cat te.py 54 | from jinja2 import Template 55 | 56 | template = Template( u'Hello {{ name }}!!' ) 57 | 58 | f = open( 'out', 'w' ) 59 | for i in range( 1000000 ): 60 | f.write( template.render( name=u'κeen' ).encode( 'utf-8' ) ) 61 | 62 | $ time python te.py 63 | real 0m25.612s 64 | user 0m24.069s 65 | sys 0m0.190s 66 | 67 | ## Syntax 68 | 69 | ### Variable 70 | variables will be HTML escaped 71 | 72 | 73 | ```html 74 | {{var foo}} 75 | ``` 76 | 77 | ### Repeat 78 | 79 | ```html 80 | {{repeat 10}}hello{{endrepeat}} 81 | ``` 82 | 83 | ``` 84 | {{repeat n as i}}
  • {{var i}}th item
  • {{endrepeat}} 85 | ``` 86 | 87 | ### Loop 88 | 89 | ```html 90 |
      91 | {{loop items as item}} 92 |
    1. {{var item}}
    2. 93 | {{endloop}} 94 |
    95 | ``` 96 | 97 | ### If 98 | 99 | ``` 100 | {{if new-p}}New{{else}}Old{{endif}} 101 | ``` 102 | 103 | 104 | ### Insert 105 | 106 | ```html 107 | See code below 108 |
    109 | {{insert "snippet.lisp"}}
    110 | 
    111 | ``` 112 | 113 | ### Include 114 | 115 | 116 | ```html 117 | 120 | ``` 121 | 122 | 123 | ## Author 124 | 125 | * κeen 126 | 127 | ## Copyright 128 | 129 | Copyright (c) 2014 κeen 130 | -------------------------------------------------------------------------------- /src/backend/sequence.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.backend.sequence 8 | (:use :cl :zenekindarl.util :zenekindarl.att :zenekindarl.backend :zenekindarl.backend.stream) 9 | (:import-from :fast-io 10 | :with-fast-output 11 | :fast-write-sequence) 12 | (:import-from :babel 13 | :string-to-octets) 14 | (:export :string-backend 15 | :octet-backend)) 16 | (in-package :zenekindarl.backend.sequence) 17 | 18 | (defclass string-backend (stream-backend) 19 | ((string 20 | :accessor string-of 21 | :initarg :string))) 22 | 23 | (defmethod make-backend ((backend (eql :string)) &key string &allow-other-keys) 24 | (make-instance 'string-backend 25 | :string string)) 26 | 27 | (defmethod emit-lambda ((backend string-backend) att) 28 | (let* ((code (emit-code backend att))) 29 | (eval 30 | `(lambda ,(emit-parameters backend) 31 | (with-output-to-string (,(stream-of backend) ,(string-of backend)) 32 | ,code))))) 33 | 34 | 35 | (defclass octet-backend (string-backend octet-stream-backend) 36 | ((buffer% 37 | :accessor buffer-of 38 | :initform (gensym "buffer")))) 39 | 40 | (defmethod make-backend ((backend (eql :octets)) &key &allow-other-keys) 41 | (make-instance 'octet-backend)) 42 | 43 | (defmethod emit-code ((backend octet-backend) (obj att-output) &key output-p) 44 | (declare (ignore output-p)) 45 | (with-slots (arg) obj 46 | (with-slots (buffer%) backend 47 | (typecase arg 48 | (att-string 49 | `(fast-write-sequence ,(string-to-octets (emit-code backend arg :output-p t)) ,buffer%)) 50 | (att-variable 51 | (case (vartype arg) 52 | (:string 53 | `(fast-write-sequence (string-to-octets ,(emit-code backend arg :output-p t)) ,buffer%)) 54 | (:anything 55 | (if (auto-escape arg) 56 | `(fast-write-sequence (string-to-octets ,(emit-code backend arg :output-p t)) ,buffer%) 57 | `(fast-write-sequence (string-to-octets (let ((val ,(emit-code backend arg :output-p t))) 58 | (if (stringp val) 59 | val 60 | (princ-to-string val)))) 61 | ,buffer%))))) 62 | (att-leaf 63 | (if (auto-escape arg) 64 | `(fast-write-sequence (string-to-octets ,(emit-code backend arg :output-p t)) ,buffer%) 65 | `(fast-write-sequence (string-to-octets (let ((val ,(emit-code backend arg :output-p t))) 66 | (if (stringp val) 67 | val 68 | (princ-to-string val)))) 69 | ,buffer%))) 70 | (t (call-next-method)))))) 71 | 72 | (defmethod emit-lambda ((backend octet-backend) att) 73 | (let* ((code (emit-code backend att))) 74 | (eval 75 | `(lambda ,(emit-parameters backend) 76 | (with-fast-output (,(buffer-of backend)) 77 | ,code))))) 78 | -------------------------------------------------------------------------------- /src/parser.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage :zenekindarl.parser 3 | (:use :cl :zenekindarl.util :zenekindarl.token :zenekindarl.att :maxpc) 4 | (:export :=template)) 5 | (in-package :zenekindarl.parser) 6 | 7 | (defun id (prop) (=transform (=subseq (?satisfies prop)) #'car)) 8 | (defun ?token-string () (id #'token-string-p)) 9 | (defun ?token-variable () (id #'token-variable-p)) 10 | (defun ?token-if () (id #'token-if-p)) 11 | (defun ?token-else () (id #'token-else-p)) 12 | (defun ?token-end () (id #'token-end-p)) 13 | (defun ?token-loop () (id #'token-loop-p)) 14 | (defun ?token-repeat () (id #'token-repeat-p)) 15 | (defun ?token-include () (id #'token-include-p)) 16 | (defun ?token-insert () (id #'token-insert-p)) 17 | 18 | 19 | 20 | (defun =template-string () 21 | (=destructure (token-string) (=list (?token-string)) 22 | (att-output (att-string (token-str token-string))))) 23 | 24 | (defun =control-variable () 25 | (=destructure (token-variable) (=list (?token-variable)) 26 | (att-output (att-variable (token-value token-variable) 27 | :anything 28 | (token-auto-escape token-variable))))) 29 | 30 | (defun =control-if () 31 | (=destructure (token-if then else _) 32 | (=list (?token-if) 33 | 's/=template 34 | (%maybe (=transform (=list (?token-else) 's/=template) #'second)) 35 | (?token-end)) 36 | (att-if (if (symbolp (token-cond-clause token-if)) 37 | (att-variable (token-cond-clause token-if)) 38 | (att-eval (token-cond-clause token-if))) 39 | then 40 | (if else 41 | else 42 | (att-nil))))) 43 | 44 | (defun =control-loop () 45 | (=destructure (token-loop body _) (=list (?token-loop) 's/=template (?token-end)) 46 | (att-loop 47 | (if (symbolp (token-seq token-loop)) 48 | (att-variable (token-seq token-loop)) 49 | (att-constant (token-seq token-loop))) 50 | body 51 | (if (token-loop-sym token-loop) 52 | (att-variable (token-loop-sym token-loop)) 53 | (att-gensym "loopvar"))))) 54 | 55 | (defun =control-repeat () 56 | (=destructure (token-repeat body _) (=list (?token-repeat) 's/=template (?token-end)) 57 | (att-repeat 58 | (if (symbolp (token-times token-repeat)) 59 | (att-variable (token-times token-repeat)) 60 | (att-constant (token-times token-repeat))) 61 | body 62 | (if (token-repeat-sym token-repeat) 63 | (att-variable (token-repeat-sym token-repeat)) 64 | (att-gensym "repeatvar"))))) 65 | 66 | (defun =control-include () 67 | (=destructure (token-include) (=list (?token-include)) 68 | (parse (token-include-template token-include) 's/=template))) 69 | 70 | (defun =control-insert () 71 | (=destructure (token-insert) (=list (?token-insert)) 72 | (att-output (att-string (token-insert-string token-insert))))) 73 | 74 | 75 | (defun =template () 76 | (=destructure (tmp) (=list (%some 77 | (%or 78 | (=template-string) 79 | (=control-variable) 80 | (=control-if) 81 | (=control-loop) 82 | (=control-repeat) 83 | (=control-include) 84 | (=control-insert)))) 85 | (apply #'att-progn tmp))) 86 | (setf (fdefinition 's/=template) (=template)) 87 | -------------------------------------------------------------------------------- /src/zenekindarl.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl 8 | (:use 9 | :cl 10 | :zenekindarl.parse 11 | :zenekindarl.pass 12 | :zenekindarl.backend 13 | :zenekindarl.backend.stream 14 | :zenekindarl.backend.sequence 15 | :zenekindarl.backend.fast-io 16 | :zenekindarl.util) 17 | (:import-from :alexandria 18 | :read-file-into-string) 19 | (:nicknames :zen) 20 | (:export :compile-template-string 21 | :compile-template-file 22 | :render 23 | :render-file)) 24 | (in-package :zenekindarl) 25 | 26 | (defun compile-template-string (backend str &key (syntax :default) (env ())) 27 | "compiles `str' with `env' and emmit renderer with backend `backend'. 28 | `backend': Currently, one of :stream :octet-stream :string :octets :fast-io. 29 | `str': A template string. 30 | `syntax': Currently, :default only. 31 | `env': A plist of compile time information. Left nil. 32 | return: A keyword argumented lambda. 33 | If the backend is :stream or :octet-stream, it looks like (lambda (stream &key ...) ...). 34 | If the backend is :string or :octets, it looks like (lambda (&key ...) ...). 35 | If the backend is :fast-io, it looks like (lambda (fast-io-buffer &key ...) ...). 36 | Keys are free variables appear in the template." 37 | 38 | (let ((lam (emit-lambda (if (keywordp backend) 39 | (make-backend backend) 40 | (apply #'make-backend backend)) 41 | (apply-passes (parse-template-string str syntax) env))) 42 | (name (gensym "name"))) 43 | (setf (symbol-function name) lam) 44 | (compile name) 45 | (symbol-function name))) 46 | 47 | (defun compile-template-file (backend file &key (syntax :default) (env ())) 48 | "Read `file' into string and passes `compile-template-string'" 49 | (compile-template-string backend (read-file-into-string file) :syntax syntax :env env)) 50 | 51 | (defun render (template &rest args) 52 | "Parse template and render it with args. Like below: 53 | (render \"Hello {{var name}}!!\" :name \"κeen\") 54 | (render \"Hello {{var name}}!!\" :backend :octet-stream stream :name \"κeen\") 55 | . 56 | If args have `:backend backend' key-value pair, this function uses it. If not given the backend is :stream and stream is *standard-output*. 57 | And also if `:syntax syntax' is given, use it or default to :default. " 58 | (let* ((backend-given (ignore-errors (getf args :backend))) 59 | (backend (or backend-given :stream)) 60 | (syntax-given (ignore-errors (getf args :syntax))) 61 | (syntax (or syntax-given :default))) 62 | (ignore-errors 63 | (when backend-given 64 | (remf args :backend)) 65 | (when syntax-given 66 | (remf args :syntax))) 67 | (apply (compile-template-string backend template :syntax syntax) 68 | (if backend-given 69 | args 70 | (cons *standard-output* args))))) 71 | 72 | (defun render-file (template-file &rest args) 73 | "A wrapper of `render'" 74 | (apply #'render (read-file-into-string template-file) args)) 75 | 76 | 77 | #+(or) 78 | (render "Hello {{var name}}!!" :name "κeen") 79 | 80 | #+(or) 81 | (let ((renderer (compile-template-string :stream "Hello {{var name}}!!"))) 82 | (funcall renderer *standard-output* :name "κeen")) 83 | 84 | -------------------------------------------------------------------------------- /benchmark/bench.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | 7 | ;;; To run benchmark, simply cd to here and load this file. 8 | (require 'asdf) 9 | (require 'zenekindarl) 10 | (require 'html-template) 11 | (in-package :cl-user) 12 | (defpackage zenekindarl-bench 13 | (:use 14 | :cl 15 | :zenekindarl 16 | 17 | :html-template) 18 | (:import-from :fast-io 19 | :with-fast-output) 20 | (:import-from :alexandria 21 | :make-keyword)) 22 | (in-package :zenekindarl-bench) 23 | 24 | (defparameter *templates* `(("simple.tmpl" . ()) 25 | ("1var.tmpl" . (:name "κeen")) 26 | ("100var.tmpl" . ,(loop :for i :from 1 :to 100 27 | :append (list (make-keyword (format nil "FOO~a" i)) (format nil "bar~a" i)))) 28 | ("repeat.tmpl" . (:foos ',(loop :for i :from 1 :to 100 29 | :collect (format nil "foo~a" i)))))) 30 | 31 | 32 | (defmacro bench10000 (title form) 33 | `(progn 34 | (write-line ,title) 35 | (time (loop :repeat 10000 :do ,form)))) 36 | 37 | (defmacro bench/zenekindarl (tmpl args) 38 | (let* ((tmpl (pathname (concatenate 'string tmpl ".zenekindarl"))) 39 | (stream-renderer (compile-template-file :stream tmpl)) 40 | (octet-stream-renderer (compile-template-file :octet-stream tmpl)) 41 | (string-renderer (compile-template-file :string tmpl)) 42 | (octets-renderer (compile-template-file :octets tmpl)) 43 | (fast-io-renderer (compile-template-file :fast-io tmpl))) 44 | `(progn 45 | (write-line "zenekindarl") 46 | (with-open-file (/dev/null "/dev/null" :direction :output :if-exists :append) 47 | (bench10000 (format nil "compiled stream backend with ~a" ,tmpl) 48 | (funcall ,stream-renderer /dev/null ,@args)) 49 | (bench10000 (format nil "compiled string backend with ~a" ,tmpl) 50 | (write-string (funcall ,string-renderer ,@args) /dev/null))) 51 | (with-open-file (/dev/null "/dev/null" :element-type '(unsigned-byte 8) :direction :output :if-exists :append) 52 | (bench10000 (format nil "compiled octet stream backend with ~a" ,tmpl) 53 | (funcall ,octet-stream-renderer /dev/null ,@args)) 54 | (bench10000 (format nil "compiled octet backend with ~a" ,tmpl) 55 | (write-sequence (funcall ,octets-renderer ,@args) /dev/null)) 56 | (bench10000 (format nil "compiled fast-io backend with ~a" ,tmpl) 57 | (with-fast-output (buff /dev/null) (funcall ,fast-io-renderer buff ,@args))))))) 58 | 59 | (defmacro bench/html-template (tmpl args) 60 | (let* ((args (if (string= tmpl "repeat.tmpl") 61 | (list :foos (mapcar (lambda (x) (list :foo x)) (cadadr args))) 62 | args)) 63 | (tmpl (pathname (concatenate 'string tmpl ".html-template"))) 64 | (renderer (create-template-printer tmpl))) 65 | `(progn 66 | (write-line "html-template") 67 | (with-open-file (/dev/null "/dev/null" :direction :output :if-exists :append) 68 | (let ((*default-template-output* /dev/null)) 69 | (bench10000 (format nil "render ~a" ,tmpl) 70 | (fill-and-print-template ,renderer ',args))))))) 71 | 72 | (defmacro bench (tmpl args) 73 | `(progn 74 | (bench/zenekindarl ,tmpl ,args) 75 | (bench/html-template ,tmpl ,args))) 76 | 77 | #.`(progn 78 | ,@(loop :for (tmpl . args) :in *templates* 79 | :collect `(bench ,tmpl ,args))) 80 | -------------------------------------------------------------------------------- /src/lexer/default.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | (defpackage zenekindarl.lexer.default 3 | (:use :cl 4 | :zenekindarl.util 5 | :zenekindarl.token 6 | :zenekindarl.lexer) 7 | (:import-from :alexandria 8 | :if-let 9 | :iota 10 | :read-file-into-string) 11 | (:export :lex)) 12 | (in-package zenekindarl.lexer.default) 13 | (annot:enable-annot-syntax) 14 | 15 | 16 | (defun read-out (str start eof-value) 17 | (multiple-value-bind (atom end) 18 | (read-from-string str nil eof-value :start start) 19 | (if-let ((i (and (symbolp atom) 20 | (search "}}" str :start2 start :end2 end))) 21 | (*package* (find-package :zenekindarl.lexer.default))) 22 | (multiple-value-bind (atom end) 23 | (read-from-string str nil eof-value :start start :end i) 24 | (list t atom (+ end (length "}}")))) 25 | (list nil (if (symbolp atom) (intern (symbol-name atom) *package*) 26 | atom) 27 | end)))) 28 | 29 | (defun tokenize-variable (start end rest) 30 | (let ((plist (cdr rest))) 31 | (make-token-variable :start start :end end :value (car rest) 32 | :auto-escape (getf plist 'auto-escape t)))) 33 | 34 | (defun tokenize-if (start end rest) 35 | (make-token-if :start start :end end :cond-clause (car rest))) 36 | 37 | (defun tokenize-else (start end rest) 38 | @ignore rest 39 | (make-token-else :start start :end end)) 40 | 41 | (defun tokenize-end (start end rest) 42 | @ignore rest 43 | (make-token-end :start start :end end)) 44 | 45 | (defun tokenize-loop (start end rest) 46 | (destructuring-bind (seq as var) rest 47 | @ignore as 48 | (make-token-loop :start start :end end 49 | :seq seq :loop-sym var))) 50 | 51 | (defun tokenize-repeat (start end rest) 52 | (if (= (length rest) 1) 53 | (make-token-repeat :start start :end end :times (car rest)) 54 | (destructuring-bind (times as var) rest 55 | @ignore as 56 | (make-token-repeat :start start :end end :times times :repeat-sym var)))) 57 | 58 | (defun tokenize-include (start end rest) 59 | (make-token-include :start start :end end :include-template (lex (read-file-into-string (merge-pathnames (car rest))) :default))) 60 | 61 | (defun tokenize-insert (start end rest) 62 | (make-token-insert :start start :end end :insert-string (read-file-into-string (merge-pathnames (car rest))))) 63 | 64 | (defun tokenize (obj start end) 65 | (if (stringp obj) 66 | (make-token-string :start start :end end :str obj) 67 | (funcall (ecase (car obj) 68 | ((var) #'tokenize-variable) 69 | ((if) #'tokenize-if) 70 | ((else) #'tokenize-else) 71 | ((endif) #'tokenize-end) 72 | ((loop) #'tokenize-loop) 73 | ((endloop) #'tokenize-end) 74 | ((repeat) #'tokenize-repeat) 75 | ((endrepeat) #'tokenize-end) 76 | ((include) #'tokenize-include) 77 | ((insert) #'tokenize-insert)) 78 | start end 79 | (cdr obj)))) 80 | 81 | (defun tokens (str start) 82 | (loop 83 | :with eof-value := '#:eof 84 | :for i := start :then end 85 | :for (endp atom end) := (read-out str i eof-value) 86 | :if (not (eq eof-value atom)) :collect atom :into result 87 | :until endp 88 | :finally (return (cons result end)))) 89 | 90 | (defmethod lex (str (lexer (eql :default))) 91 | (labels ((aux (start result) 92 | (let* ((end (search "{{" str :test #'char= :start2 start))) 93 | (if end 94 | (let ((sub (tokenize (subseq str start end) start end)) 95 | (start (+ end 2))) 96 | (destructuring-bind (atoms . end) (tokens str start) 97 | (aux end (cons (tokenize atoms start end) (cons sub result))))) 98 | (reverse (cons (tokenize (subseq str start) start (length str)) result)))))) 99 | (aux 0 ()))) 100 | 101 | -------------------------------------------------------------------------------- /benchmark/100var.tmpl.zenekindarl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

    Hello

    9 |
      10 |
    1. {{ var foo1 }}
    2. 11 |
    3. {{ var foo2 }}
    4. 12 |
    5. {{ var foo3 }}
    6. 13 |
    7. {{ var foo4 }}
    8. 14 |
    9. {{ var foo5 }}
    10. 15 |
    11. {{ var foo6 }}
    12. 16 |
    13. {{ var foo7 }}
    14. 17 |
    15. {{ var foo8 }}
    16. 18 |
    17. {{ var foo9 }}
    18. 19 |
    19. {{ var foo10 }}
    20. 20 |
    21. {{ var foo11 }}
    22. 21 |
    23. {{ var foo12 }}
    24. 22 |
    25. {{ var foo13 }}
    26. 23 |
    27. {{ var foo14 }}
    28. 24 |
    29. {{ var foo15 }}
    30. 25 |
    31. {{ var foo16 }}
    32. 26 |
    33. {{ var foo17 }}
    34. 27 |
    35. {{ var foo18 }}
    36. 28 |
    37. {{ var foo19 }}
    38. 29 |
    39. {{ var foo20 }}
    40. 30 |
    41. {{ var foo21 }}
    42. 31 |
    43. {{ var foo22 }}
    44. 32 |
    45. {{ var foo23 }}
    46. 33 |
    47. {{ var foo24 }}
    48. 34 |
    49. {{ var foo25 }}
    50. 35 |
    51. {{ var foo26 }}
    52. 36 |
    53. {{ var foo27 }}
    54. 37 |
    55. {{ var foo28 }}
    56. 38 |
    57. {{ var foo29 }}
    58. 39 |
    59. {{ var foo30 }}
    60. 40 |
    61. {{ var foo31 }}
    62. 41 |
    63. {{ var foo32 }}
    64. 42 |
    65. {{ var foo33 }}
    66. 43 |
    67. {{ var foo34 }}
    68. 44 |
    69. {{ var foo35 }}
    70. 45 |
    71. {{ var foo36 }}
    72. 46 |
    73. {{ var foo37 }}
    74. 47 |
    75. {{ var foo38 }}
    76. 48 |
    77. {{ var foo39 }}
    78. 49 |
    79. {{ var foo40 }}
    80. 50 |
    81. {{ var foo41 }}
    82. 51 |
    83. {{ var foo42 }}
    84. 52 |
    85. {{ var foo43 }}
    86. 53 |
    87. {{ var foo44 }}
    88. 54 |
    89. {{ var foo45 }}
    90. 55 |
    91. {{ var foo46 }}
    92. 56 |
    93. {{ var foo47 }}
    94. 57 |
    95. {{ var foo48 }}
    96. 58 |
    97. {{ var foo49 }}
    98. 59 |
    99. {{ var foo50 }}
    100. 60 |
    101. {{ var foo51 }}
    102. 61 |
    103. {{ var foo52 }}
    104. 62 |
    105. {{ var foo53 }}
    106. 63 |
    107. {{ var foo54 }}
    108. 64 |
    109. {{ var foo55 }}
    110. 65 |
    111. {{ var foo56 }}
    112. 66 |
    113. {{ var foo57 }}
    114. 67 |
    115. {{ var foo58 }}
    116. 68 |
    117. {{ var foo59 }}
    118. 69 |
    119. {{ var foo60 }}
    120. 70 |
    121. {{ var foo61 }}
    122. 71 |
    123. {{ var foo62 }}
    124. 72 |
    125. {{ var foo63 }}
    126. 73 |
    127. {{ var foo64 }}
    128. 74 |
    129. {{ var foo65 }}
    130. 75 |
    131. {{ var foo66 }}
    132. 76 |
    133. {{ var foo67 }}
    134. 77 |
    135. {{ var foo68 }}
    136. 78 |
    137. {{ var foo69 }}
    138. 79 |
    139. {{ var foo70 }}
    140. 80 |
    141. {{ var foo71 }}
    142. 81 |
    143. {{ var foo72 }}
    144. 82 |
    145. {{ var foo73 }}
    146. 83 |
    147. {{ var foo74 }}
    148. 84 |
    149. {{ var foo75 }}
    150. 85 |
    151. {{ var foo76 }}
    152. 86 |
    153. {{ var foo77 }}
    154. 87 |
    155. {{ var foo78 }}
    156. 88 |
    157. {{ var foo79 }}
    158. 89 |
    159. {{ var foo80 }}
    160. 90 |
    161. {{ var foo81 }}
    162. 91 |
    163. {{ var foo82 }}
    164. 92 |
    165. {{ var foo83 }}
    166. 93 |
    167. {{ var foo84 }}
    168. 94 |
    169. {{ var foo85 }}
    170. 95 |
    171. {{ var foo86 }}
    172. 96 |
    173. {{ var foo87 }}
    174. 97 |
    175. {{ var foo88 }}
    176. 98 |
    177. {{ var foo89 }}
    178. 99 |
    179. {{ var foo90 }}
    180. 100 |
    181. {{ var foo91 }}
    182. 101 |
    183. {{ var foo92 }}
    184. 102 |
    185. {{ var foo93 }}
    186. 103 |
    187. {{ var foo94 }}
    188. 104 |
    189. {{ var foo95 }}
    190. 105 |
    191. {{ var foo96 }}
    192. 106 |
    193. {{ var foo97 }}
    194. 107 |
    195. {{ var foo98 }}
    196. 108 |
    197. {{ var foo99 }}
    198. 109 |
    199. {{ var foo100 }}
    200. 110 |
    111 | 112 | 113 | -------------------------------------------------------------------------------- /src/backend/stream.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.backend.stream 8 | (:use :cl :zenekindarl.util :zenekindarl.att :zenekindarl.backend) 9 | (:import-from :babel 10 | :string-to-octets) 11 | (:import-from :fast-io 12 | :with-fast-output 13 | :fast-write-sequence) 14 | (:export :stream-backend 15 | :octet-stream-backend 16 | :stream-of 17 | :buffer-of)) 18 | (in-package :zenekindarl.backend.stream) 19 | 20 | (defclass stream-backend (backend) 21 | ((stream% 22 | :accessor stream-of 23 | :initarg :stream 24 | :initform (gensym "stream")))) 25 | 26 | (defmethod make-backend ((backend (eql :stream)) &key &allow-other-keys) 27 | (make-instance 'stream-backend)) 28 | 29 | (defmethod emit-code ((backend stream-backend) (obj att-output) &key output-p) 30 | (declare (ignore output-p)) 31 | (with-slots (arg) obj 32 | (with-slots (stream%) backend 33 | (typecase arg 34 | (att-string 35 | `(write-string ,(emit-code backend arg :output-p t) ,stream%)) 36 | (att-variable 37 | (case (vartype arg) 38 | (:string 39 | `(write-string ,(emit-code backend arg :output-p t) ,stream%)) 40 | (:anything 41 | (if (auto-escape arg) 42 | `(write-string ,(emit-code backend arg :output-p t) ,stream%) 43 | `(princ ,(emit-code backend arg :output-p t) ,stream%))))) 44 | (att-leaf 45 | (if (auto-escape arg) 46 | `(write-string ,(emit-code backend arg :output-p t) ,stream%) 47 | `(princ ,(emit-code backend arg :output-p t) ,stream%))) 48 | (t (call-next-method)))))) 49 | 50 | (defmethod emit-lambda ((backend stream-backend) att) 51 | (let* ((code (emit-code backend att))) 52 | (eval 53 | `(lambda ,(cons (stream-of backend) (emit-parameters backend)) 54 | (declare (ignorable ,(stream-of backend))) 55 | ,code 56 | t)))) 57 | 58 | 59 | (defclass octet-stream-backend (stream-backend) 60 | ()) 61 | 62 | (defmethod make-backend ((backend (eql :octet-stream)) &key &allow-other-keys) 63 | (make-instance 'octet-stream-backend)) 64 | 65 | 66 | (defmethod emit-lambda ((backend octet-stream-backend) att) 67 | (let* ((code (emit-code backend att))) 68 | (eval 69 | `(lambda ,(cons (stream-of backend) (emit-parameters backend)) 70 | (declare (ignorable ,(stream-of backend))) 71 | ,code)))) 72 | 73 | (defmethod emit-code ((backend octet-stream-backend) (obj att-output) &key output-p) 74 | (declare (ignore output-p)) 75 | (with-slots (arg) obj 76 | (with-slots (stream%) backend 77 | (typecase arg 78 | (att-string 79 | `(write-sequence ,(string-to-octets (emit-code backend arg :output-p t)) ,stream%)) 80 | (att-variable 81 | (case (vartype arg) 82 | (:string 83 | `(write-sequence (string-to-octets ,(emit-code backend arg :output-p t)) ,stream%)) 84 | (:anything 85 | (if (auto-escape arg) 86 | `(write-sequence (string-to-octets ,(emit-code backend arg :output-p t)) ,stream%) 87 | `(write-sequence (string-to-octets (let ((val ,(emit-code backend arg :output-p t))) 88 | (if (stringp val) 89 | val 90 | (princ-to-string val)))) 91 | ,stream%))))) 92 | (att-leaf 93 | (if (auto-escape arg) 94 | `(write-sequence (string-to-octets ,(emit-code backend arg :output-p t)) ,stream%) 95 | `(write-sequence (string-to-octets (let ((val ,(emit-code backend arg :output-p t))) 96 | (if (stringp val) 97 | val 98 | (princ-to-string val)))) 99 | ,stream%))) 100 | (t (call-next-method)))))) 101 | -------------------------------------------------------------------------------- /t/zenekindarl.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl-test 8 | (:use :cl 9 | :zenekindarl 10 | :cl-test-more) 11 | (:import-from :babel 12 | :string-to-octets) 13 | (:import-from :fast-io 14 | :fast-output-stream 15 | :with-fast-output) 16 | (:import-from :flexi-streams 17 | :with-output-to-sequence)) 18 | (in-package :zenekindarl-test) 19 | 20 | ;; NOTE: To run this test file, execute `(asdf:test-system :zenekindarl)' in your Lisp. 21 | 22 | (defparameter *suites* 23 | '(("foo" () "foo" "simple string") 24 | ("" () "" "simple string containing html meta chars") 25 | ("bar {{var bar}}" (:bar "bar") "bar bar" "simple variable") 26 | ("bar {{var bar}}" (:bar "") "bar <bar>" "variable containing html meta char") 27 | ("bar{{repeat 3}} {{var bar}}{{endrepeat}}" (:bar "bar") "bar bar bar bar" "repeat") 28 | ("bar{{repeat n}} {{var bar}}{{endrepeat}}" (:n 3 :bar "bar") "bar bar bar bar" "repeat") 29 | ("{{loop items as i}}
  • {{var i}}
  • {{endloop}}" (:items ("uragasumi" "hakkaisan" "dassai")) "
  • uragasumi
  • hakkaisan
  • dassai
  • " "loop") 30 | ("{{loop items as i}}
  • {{var i}}
  • {{endloop}}" (:items ()) "" "loop with loopee being nil") 31 | ("
  • {{if new-p}}New! {{endif}}blahblah
  • " (:new-p t) "
  • New! blahblah
  • " "if") 32 | ("
  • {{if new-p}}New! {{endif}}blahblah
  • " (:new-p nil) "
  • blahblah
  • " "if with condition being nil") 33 | ("
  • {{if new-p}}New! {{else}}Old! {{endif}}blahblah
  • " (:new-p t)"
  • New! blahblah
  • " "if with else") 34 | ("
  • {{if new-p}}New! {{else}}Old! {{endif}}blahblah
  • " (:new-p nil) "
  • Old! blahblah
  • " "if with else and condition being nil") 35 | ("the content of foo is {{insert \"foo\"}}" () "the content of foo is {{repeat 2 as i}}bar{{endrepeat}}" "insert") 36 | ("the content of var is {{insert \"var\"}}" () "the content of var is {{var bar}}" "insert with insertee cotaining template string") 37 | ("the content of foo is {{include \"foo\"}}" () "the content of foo is barbar" "against include") 38 | ("the content of var is {{include \"var\"}}" (:bar "var") "the content of var is var" "include with includee cotaining template string"))) 39 | 40 | (setf *default-pathname-defaults* (asdf:system-relative-pathname 'zenekindarl "t/")) 41 | 42 | (plan nil) 43 | (diag "compile test with stream backend") 44 | (loop :for (template args result description) :in *suites* 45 | :do (ok (compile-template-string :stream template) description)) 46 | 47 | (diag "compile test with octet stream backend") 48 | (loop :for (template args result description) :in *suites* 49 | :do (ok (compile-template-string :octet-stream template) description)) 50 | 51 | (diag "compile test with string backend") 52 | (loop :for (template args result description) :in *suites* 53 | :do (ok (compile-template-string :string template) description)) 54 | 55 | (diag "compile test with octet backend") 56 | (loop :for (template args result description) :in *suites* 57 | :do (ok (compile-template-string :octets template) description)) 58 | 59 | (diag "compile test with fast-io backend") 60 | (loop :for (template args result description) :in *suites* 61 | :do (ok (compile-template-string :fast-io template) description)) 62 | 63 | (diag "render test with stream backend") 64 | (loop :for (template args result description) :in *suites* 65 | :do (is-print (apply #'render template args) result description)) 66 | 67 | (diag "render test with octet stream backend") 68 | (loop :for (template args result description) :in *suites* 69 | :do (is (with-output-to-sequence (s :element-type '(unsigned-byte 8)) 70 | (apply #'render template `(:backend :octet-stream ,s ,@args))) 71 | (string-to-octets result) :test #'equalp description)) 72 | 73 | (diag "render test with string backend") 74 | (loop :for (template args result description) :in *suites* 75 | :do (is (apply #'render template (cons :backend (cons :string args))) result description)) 76 | 77 | (diag "render test with octet backend") 78 | (loop :for (template args result description) :in *suites* 79 | :do (is (apply #'render template (cons :backend (cons :octets args))) (string-to-octets result) :test #'equalp description)) 80 | 81 | (diag "render test with fast-io backend") 82 | (loop :for (template args result description) :in *suites* 83 | :do (is (with-fast-output (buff) 84 | (apply #'render template (cons :backend (cons :fast-io (cons buff args))))) 85 | (string-to-octets result) :test #'equalp description)) 86 | 87 | (finalize) 88 | -------------------------------------------------------------------------------- /src/pass.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.pass 8 | (:use :cl :zenekindarl.util :zenekindarl.att) 9 | (:import-from :alexandria 10 | :if-let) 11 | (:import-from :optima 12 | :match) 13 | (:export :*optimizing-passes* 14 | :*necesarry-passes* 15 | :apply-passes 16 | :flatten-pass 17 | :fold-variables-pass 18 | :append-sequence-pass 19 | :remove-progn-pass)) 20 | (in-package :zenekindarl.pass) 21 | 22 | (defgeneric traverse-node (func obj) 23 | (:method (func (obj att-progn)) 24 | (funcall func 25 | (apply #'att-progn 26 | (mapcar (lambda (node) 27 | (traverse-node func node)) 28 | (nodes obj))))) 29 | (:method (func (obj att-if)) 30 | (funcall func 31 | (att-if 32 | (traverse-node func (cond-clause obj)) 33 | (traverse-node func (then-clause obj)) 34 | (traverse-node func (else-clause obj))))) 35 | (:method (func (obj att-output)) 36 | (funcall func (att-output (traverse-node func (arg obj))))) 37 | (:method (func (obj att-loop)) 38 | (funcall func 39 | (att-loop 40 | (traverse-node func (loop-seq obj)) 41 | (traverse-node func (body obj)) 42 | (traverse-node func (loop-var obj))))) 43 | (:method (func (obj att-repeat)) 44 | (funcall func 45 | (att-repeat 46 | (traverse-node func (repeat-times obj)) 47 | (traverse-node func (body obj)) 48 | (traverse-node func (repeat-var obj))))) 49 | (:method (func (obj att-node)) 50 | (funcall func obj))) 51 | 52 | (defgeneric append-att-node-aux (x y) 53 | (:method ((x att-progn) (y att-progn)) 54 | (apply #'att-progn (append (nodes x) (nodes y)))) 55 | (:method ((x att-string) (y att-string)) 56 | (att-string (concatenate 'string (value x) (value y)))) 57 | (:method ((x att-output) (y att-output)) 58 | (apply #'values (mapcar #'att-output (multiple-value-list (append-att-node-aux (arg x) (arg y)))))) 59 | (:method ((x att-nil) (y att-nil)) 60 | (declare (ignore x y)) 61 | (values)) 62 | (:method ((x att-nil) y) 63 | (declare (ignore x)) 64 | y) 65 | (:method (x (y att-nil)) 66 | (declare (ignore y)) 67 | x) 68 | (:method ((x att-node) (y att-node)) 69 | (values x y))) 70 | 71 | (defun append-att-node (&rest args) 72 | (nreverse 73 | (reduce (lambda (acc y) 74 | (revappend 75 | (multiple-value-list (append-att-node-aux (car acc) y)) 76 | (cdr acc))) 77 | args :initial-value (list (att-nil))))) 78 | 79 | (defgeneric flatten-impl (obj) 80 | (:method ((obj att-progn)) 81 | ;; the appended att nodes should be (#) 82 | (car (apply #'append-att-node (nodes obj)))) 83 | (:method ((obj att-node)) 84 | (att-progn obj))) 85 | 86 | (defun flatten-pass (obj env) 87 | (declare (ignore env)) 88 | (traverse-node #'flatten-impl obj)) 89 | 90 | (defgeneric remove-progn-impl (obj) 91 | (:method ((obj att-progn)) 92 | (match (nodes obj) 93 | (() (att-nil)) 94 | ((list x) x) 95 | (xs (apply #'att-progn xs)))) 96 | (:method ((obj att-node)) 97 | obj)) 98 | 99 | (defun remove-progn-pass (obj env) 100 | (declare (ignore env)) 101 | (traverse-node #'remove-progn-impl obj)) 102 | 103 | (defgeneric fold-variables-impl (obj vars) 104 | (:method ((obj att-variable) vars) 105 | (if-let ((value (getf vars (intern (symbol-name (varsym obj)) :keyword)))) 106 | (ecase (vartype obj) 107 | (:string 108 | (att-string value)) 109 | (:anything 110 | (att-string (format nil "~A" value)))) 111 | obj)) 112 | (:method ((obj att-node) vars) 113 | (declare (ignore vars)) 114 | obj)) 115 | 116 | (defun fold-variables-pass (obj env) 117 | (traverse-node (lambda (o) (fold-variables-impl o (getf env :known-args))) obj)) 118 | 119 | (defgeneric append-sequence-impl (obj) 120 | (:method ((obj att-progn)) 121 | (apply #'att-progn (apply #'append-att-node (nodes obj)))) 122 | (:method ((obj att-node)) 123 | obj)) 124 | 125 | (defun append-sequence-pass (obj env) 126 | (declare (ignore env)) 127 | (traverse-node #'append-sequence-impl obj)) 128 | 129 | (defparameter *optimizing-passes* (list #'fold-variables-pass #'flatten-pass #'remove-progn-pass #'append-sequence-pass)) 130 | (defparameter *necessary-passes* ()) 131 | 132 | (defun apply-passes (att env) 133 | (reduce (lambda (att pass) 134 | (funcall pass att env)) 135 | (append *optimizing-passes* *necessary-passes*) 136 | :initial-value att)) 137 | -------------------------------------------------------------------------------- /benchmark/100var.tmpl.html-template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |

    Hello

    9 |
      10 |
    1. 11 |
    2. 12 |
    3. 13 |
    4. 14 |
    5. 15 |
    6. 16 |
    7. 17 |
    8. 18 |
    9. 19 |
    10. 20 |
    11. 21 |
    12. 22 |
    13. 23 |
    14. 24 |
    15. 25 |
    16. 26 |
    17. 27 |
    18. 28 |
    19. 29 |
    20. 30 |
    21. 31 |
    22. 32 |
    23. 33 |
    24. 34 |
    25. 35 |
    26. 36 |
    27. 37 |
    28. 38 |
    29. 39 |
    30. 40 |
    31. 41 |
    32. 42 |
    33. 43 |
    34. 44 |
    35. 45 |
    36. 46 |
    37. 47 |
    38. 48 |
    39. 49 |
    40. 50 |
    41. 51 |
    42. 52 |
    43. 53 |
    44. 54 |
    45. 55 |
    46. 56 |
    47. 57 |
    48. 58 |
    49. 59 |
    50. 60 |
    51. 61 |
    52. 62 |
    53. 63 |
    54. 64 |
    55. 65 |
    56. 66 |
    57. 67 |
    58. 68 |
    59. 69 |
    60. 70 |
    61. 71 |
    62. 72 |
    63. 73 |
    64. 74 |
    65. 75 |
    66. 76 |
    67. 77 |
    68. 78 |
    69. 79 |
    70. 80 |
    71. 81 |
    72. 82 |
    73. 83 |
    74. 84 |
    75. 85 |
    76. 86 |
    77. 87 |
    78. 88 |
    79. 89 |
    80. 90 |
    81. 91 |
    82. 92 |
    83. 93 |
    84. 94 |
    85. 95 |
    86. 96 |
    87. 97 |
    88. 98 |
    89. 99 |
    90. 100 |
    91. 101 |
    92. 102 |
    93. 103 |
    94. 104 |
    95. 105 |
    96. 106 |
    97. 107 |
    98. 108 |
    99. 109 |
    100. 110 |
    111 | 112 | 113 | -------------------------------------------------------------------------------- /src/backend.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.backend 8 | (:use :cl :zenekindarl.util :zenekindarl.att) 9 | (:import-from :html-encode 10 | :encode-for-tt) 11 | (:export :backend 12 | :make-backend 13 | :emit-code 14 | :emit-lambda 15 | :emit-parameters 16 | :symbols)) 17 | 18 | (in-package :zenekindarl.backend) 19 | 20 | (defclass backend () 21 | ((symbols 22 | :accessor symbols 23 | :initform ()) 24 | (scopes 25 | :accessor scopes 26 | :initform ()))) 27 | 28 | (defgeneric make-backend (backend &key &allow-other-keys)) 29 | 30 | (defgeneric push-scope (backend) 31 | (:method (backend) 32 | (push () (scopes backend)))) 33 | (defgeneric pop-scope (backend) 34 | (:method (backend) 35 | (pop (scopes backend)))) 36 | 37 | (defgeneric add-to-scope (sym backend) 38 | (:method (sym backend) 39 | (push sym (car (scopes backend))))) 40 | (defgeneric find-from-scope (sym backend) 41 | (:method (sym backend) 42 | (loop :for scope :in (scopes backend) 43 | :thereis (member sym scope)))) 44 | 45 | (defgeneric http-escape (obj sexp) 46 | (:method (obj sexp) 47 | (declare (ignore obj)) 48 | (error "Don't know how to escape ~a" sexp)) 49 | (:method ((obj att-leaf) sexp) 50 | (declare (ignore obj)) 51 | `(encode-for-tt (princ-to-string ,sexp)))) 52 | 53 | (defgeneric emit-code (backend obj &key output-p) 54 | (:method (backend obj &key output-p) 55 | (declare (ignore output-p)) 56 | (error "The backend ~A of ~A is not implemented" backend obj))) 57 | 58 | (defmethod emit-code :around (backend (obj att-leaf) &key output-p) 59 | (if (and output-p (auto-escape obj)) 60 | (http-escape obj (call-next-method backend obj)) 61 | (call-next-method backend obj))) 62 | ;;; You are to implement the backend specific `emit-code' for `att-output' 63 | ;; (defmethod emit-code (backend (obj att-output))) 64 | 65 | (defmethod emit-code (backend (obj att-string) &key output-p) 66 | (declare (ignore backend output-p)) 67 | (value obj)) 68 | 69 | (defmethod http-escape ((obj att-string) sexp) 70 | (declare (ignore obj)) 71 | (encode-for-tt sexp)) 72 | 73 | (defmethod emit-code (backend (obj att-variable) &key output-p) 74 | (declare (ignore output-p)) 75 | (let ((sym (varsym obj))) 76 | (unless (find-from-scope sym backend) 77 | (pushnew sym (symbols backend))) 78 | sym)) 79 | 80 | (defmethod http-escape ((obj att-variable) sexp) 81 | (if (eq (vartype obj) :string) 82 | `(encode-for-tt ,sexp) 83 | `(if (stringp ,sexp) 84 | (encode-for-tt ,sexp) 85 | ,(call-next-method obj sexp)))) 86 | 87 | (defmethod emit-code (backend (obj att-constant) &key output-p) 88 | (declare (ignore backend output-p)) 89 | `',(value obj)) 90 | 91 | (defmethod emit-code (backend (obj att-eval) &key output-p) 92 | (declare (ignore backend output-p)) 93 | (sexp obj)) 94 | 95 | (defmethod emit-code (backend (obj att-nil) &key output-p) 96 | (declare (ignore backend obj output-p)) 97 | nil) 98 | 99 | (defmethod emit-code (backend (obj att-progn) &key output-p) 100 | (cons 'progn (mapcar (lambda (node) 101 | ;; :FIXME: mark output-p only the last one 102 | (emit-code backend node :output-p output-p)) 103 | (nodes obj)))) 104 | 105 | (defmethod emit-code (backend (obj att-if) &key output-p) 106 | (with-slots (cond-clause then-clause else-clause) obj 107 | (list 'if 108 | (emit-code backend cond-clause) 109 | (emit-code backend then-clause :output-p output-p) 110 | (emit-code backend else-clause :output-p output-p)))) 111 | 112 | (defmethod emit-code (backend (obj att-loop) &key output-p) 113 | (with-slots (loop-seq body loop-var) obj 114 | (let* ((seq (emit-code backend loop-seq)) 115 | (sym (varsym loop-var))) 116 | (push-scope backend) 117 | (add-to-scope sym backend) 118 | `(loop 119 | ;; :FIXME: dirty hack 120 | :for ,sym 121 | :in ,seq 122 | :do ,(emit-code backend body :output-p output-p))))) 123 | 124 | (defmethod emit-code (backend (obj att-repeat) &key output-p) 125 | (with-slots (repeat-times body repeat-var) obj 126 | (let* ((times (emit-code backend repeat-times)) 127 | (sym (varsym repeat-var))) 128 | (push-scope backend) 129 | (add-to-scope sym backend) 130 | `(dotimes (,sym ,times) 131 | ;; :FIXME: dirty hack 132 | ,(emit-code backend body :output-p output-p))))) 133 | 134 | (defgeneric emit-parameters (backend) 135 | (:method (backend) 136 | (let ((syms (symbols backend))) 137 | (if syms `(&key ,@syms) ())))) 138 | 139 | (defgeneric emit-lambda (backend att) 140 | (:method (backend att) 141 | (let* ((code (emit-code backend att))) 142 | (eval 143 | `(lambda ,(emit-parameters backend) 144 | ,code 145 | t))))) 146 | -------------------------------------------------------------------------------- /t/att.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.att-test 8 | (:use :cl 9 | :zenekindarl.att 10 | :zenekindarl.util 11 | :cl-test-more)) 12 | (in-package :zenekindarl.att-test) 13 | 14 | ;; NOTE: To run this test file, execute `(asdf:test-system :zenekindarl)' in your Lisp. 15 | 16 | (plan nil) 17 | (diag "att tests") 18 | (is (type-of (att-string "string")) 19 | 'att-string 20 | "att-string constructor") 21 | (is (att-string "string") (att-string "string") 22 | "att-string equality" 23 | :test #'att-equal) 24 | 25 | (is (type-of (att-variable 'var)) 26 | 'att-variable 27 | "att-variable constructor") 28 | (is (att-variable 'var) 29 | (att-variable 'var) 30 | "att-variable equality" 31 | :test #'att-equal) 32 | 33 | (is (type-of (att-variable 'var :string)) 34 | 'att-variable 35 | "att-string constructor") 36 | (is (att-variable 'var :string) 37 | (att-variable 'var :string) 38 | "att-string equality" 39 | :test #'att-equal) 40 | (isnt (att-variable 'var :string) 41 | (att-variable 'var :anything) 42 | "att-string equality with different type" 43 | :test #'att-equal) 44 | (is (att-variable 'var) 45 | (att-variable 'var :anything) 46 | "att-string equality with type omitted" 47 | :test #'att-equal) 48 | 49 | 50 | (is (type-of (att-gensym "var")) 51 | 'att-gensym 52 | "att-gensym constructor") 53 | (is (att-gensym "var") 54 | (att-gensym "var") 55 | "att-gensym equality" 56 | :test #'att-equal) 57 | 58 | (is (type-of (att-gensym "var" :string)) 59 | 'att-gensym 60 | "att-string constructor") 61 | (is (att-gensym "var" :string) 62 | (att-gensym "var" :string) 63 | "att-string equality" 64 | :test #'att-equal) 65 | (isnt (att-gensym "var" :string) 66 | (att-gensym "var" :anything) 67 | "att-string equality with different type" 68 | :test #'att-equal) 69 | (is (att-gensym "var") 70 | (att-gensym "var" :anything) 71 | "att-string equality with type omitted" 72 | :test #'att-equal) 73 | 74 | (is (type-of (att-eval '(+ 1 2))) 75 | 'att-eval 76 | "att-eval constructor") 77 | (is (att-eval '(+ 1 2)) 78 | (att-eval '(+ 1 2)) 79 | "att-eval equality" 80 | :test #'att-equal) 81 | 82 | (is (type-of (att-output (att-string "hello"))) 83 | 'att-output 84 | "att-output constructor") 85 | (is (att-output (att-string "hello")) 86 | (att-output (att-string "hello")) 87 | "att-output equality" 88 | :test #'att-equal) 89 | 90 | (is (type-of (att-progn (att-string "string") 91 | (att-string "string2"))) 92 | 'att-progn 93 | "att-progn constructor") 94 | (is (att-progn (att-string "string") 95 | (att-string "string2")) 96 | (att-progn (att-string "string") 97 | (att-string "string2")) 98 | "att-progn equality" 99 | :test #'att-equal) 100 | 101 | (is (type-of (att-if 102 | (att-variable 'var) 103 | (att-string "then"))) 104 | 'att-if 105 | "att-if constructor with else omitted") 106 | (is (att-if 107 | (att-variable 'var) 108 | (att-string "then")) 109 | (att-if 110 | (att-variable 'var) 111 | (att-string "then")) 112 | "att-if equality with else omitted" 113 | :test #'att-equal) 114 | (is (type-of (att-if 115 | (att-variable 'var) 116 | (att-string "then") 117 | (att-string "else"))) 118 | 'att-if 119 | "att-if equality") 120 | (is (att-if 121 | (att-variable 'var) 122 | (att-string "then") 123 | (att-string "else")) 124 | (att-if 125 | (att-variable 'var) 126 | (att-string "then") 127 | (att-string "else")) 128 | "att-if equality" 129 | :test #'att-equal) 130 | 131 | (is (type-of (att-loop 132 | (att-constant '(list (:foo 1) (:foo 2) (:foo 3))) 133 | (att-variable 'foo))) 134 | 'att-loop 135 | "att-loop constructor with loop variable omitted") 136 | (is (att-loop 137 | (att-constant '(list (:foo 1) (:foo 2) (:foo 3))) 138 | (att-variable 'foo)) 139 | (att-loop 140 | (att-constant '(list (:foo 1) (:foo 2) (:foo 3))) 141 | (att-variable 'foo)) 142 | "att-loop equality with loop variable omitted" 143 | :test #'att-equal) 144 | 145 | (is (type-of (att-loop 146 | (att-constant '(list 1 2 3)) 147 | (att-variable 'foo) 148 | (att-variable 'foo))) 149 | 'att-loop 150 | "att-loop constructor") 151 | (is (att-loop 152 | (att-constant '(list 1 2 3)) 153 | (att-variable 'foo) 154 | (att-variable 'foo)) 155 | (att-loop 156 | (att-constant '(list 1 2 3)) 157 | (att-variable 'foo) 158 | (att-variable 'foo)) 159 | "att-loop equality" 160 | :test #'att-equal) 161 | 162 | (is (type-of (att-include "template.tmpl")) 163 | 'att-include 164 | "att-include constructor") 165 | (is (att-include "template.tmpl") 166 | (att-include "template.tmpl") 167 | "att-include equality" 168 | :test #'att-equal) 169 | 170 | ;; blah blah blah. 171 | 172 | (finalize) 173 | -------------------------------------------------------------------------------- /t/parse.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.parse-test 8 | (:use :cl 9 | :zenekindarl.att 10 | :zenekindarl.parse 11 | :zenekindarl.util 12 | :cl-test-more)) 13 | (in-package :zenekindarl.parse-test) 14 | 15 | (plan nil) 16 | (diag "test parse") 17 | 18 | (is (parse-template-string "aaa") 19 | (att-progn (att-output (att-string "aaa"))) 20 | "simple text" 21 | :test #'att-equal) 22 | 23 | (is (parse-template-string "{{var sym}}") 24 | (att-progn 25 | (att-output (att-string "")) 26 | (att-output (att-variable 'zenekindarl.lexer.default::sym)) 27 | (att-output (att-string ""))) 28 | "var" 29 | :test #'att-equal) 30 | 31 | (is (parse-template-string "{{if cond}}aaa{{endif}}") 32 | (att-progn 33 | (att-output (att-string "")) 34 | (att-if (att-variable 'zenekindarl.lexer.default::cond) 35 | (att-progn (att-output (att-string "aaa"))) 36 | (att-nil)) 37 | (att-output (att-string ""))) 38 | "if" 39 | :test #'att-equal) 40 | 41 | (is (parse-template-string "{{if cond}}aaa{{else}}bbb{{endif}}") 42 | (att-progn 43 | (att-output (att-string "")) 44 | (att-if (att-variable 'zenekindarl.lexer.default::cond) 45 | (att-progn (att-output (att-string "aaa"))) 46 | (att-progn (att-output (att-string "bbb")))) 47 | (att-output (att-string ""))) 48 | "if else" 49 | :test #'att-equal) 50 | 51 | (is (parse-template-string "{{repeat 10}}
  • item
  • {{endrepeat}}") 52 | (att-progn 53 | (att-output (att-string "")) 54 | (att-repeat (att-constant 10) 55 | (att-progn (att-output (att-string "
  • item
  • "))) 56 | (att-gensym "repeatvar")) 57 | (att-output (att-string ""))) 58 | "repeat" 59 | :test #'att-equal) 60 | 61 | (is (parse-template-string "{{repeat 10 as i}}
  • item{{var i}}
  • {{endrepeat}}") 62 | (att-progn 63 | (att-output (att-string "")) 64 | (att-repeat (att-constant 10) 65 | (att-progn (att-output (att-string "
  • item")) 66 | (att-output (att-variable 'zenekindarl.lexer.default::i)) 67 | (att-output (att-string "
  • "))) 68 | (att-variable 'zenekindarl.lexer.default::i)) 69 | (att-output (att-string ""))) 70 | "repeat with index" 71 | :test #'att-equal) 72 | 73 | (is (parse-template-string "{{repeat 10 as i }}
  • item{{var i}}
  • {{endrepeat}}") 74 | (att-progn 75 | (att-output (att-string "")) 76 | (att-repeat (att-constant 10) 77 | (att-progn (att-output (att-string "
  • item")) 78 | (att-output (att-variable 'zenekindarl.lexer.default::i)) 79 | (att-output (att-string "
  • "))) 80 | (att-variable 'zenekindarl.lexer.default::i)) 81 | (att-output (att-string ""))) 82 | "repeat with index with trailing space" 83 | :test #'att-equal) 84 | 85 | (is (parse-template-string "{{repeat n as i }}
  • item{{var i}}
  • {{endrepeat}}") 86 | (att-progn 87 | (att-output (att-string "")) 88 | (att-repeat (att-variable 'zenekindarl.lexer.default::n) 89 | (att-progn (att-output (att-string "
  • item")) 90 | (att-output (att-variable 'zenekindarl.lexer.default::i)) 91 | (att-output (att-string "
  • "))) 92 | (att-variable 'zenekindarl.lexer.default::i)) 93 | (att-output (att-string ""))) 94 | "repeat on variable with index with trailing space" 95 | :test #'att-equal) 96 | 97 | (is (parse-template-string "{{loop seq as i}}
  • item{{var i}}
  • {{endloop}}") 98 | (att-progn 99 | (att-output (att-string "")) 100 | (att-loop (att-variable 'zenekindarl.lexer.default::seq) 101 | (att-progn (att-output (att-string "
  • item")) 102 | (att-output (att-variable 'zenekindarl.lexer.default::i)) 103 | (att-output (att-string "
  • "))) 104 | (att-variable 'zenekindarl.lexer.default::i)) 105 | (att-output (att-string ""))) 106 | "loop" 107 | :test #'att-equal) 108 | 109 | (is (parse-template-string "{{loop seq as i }}
  • item{{var i}}
  • {{endloop}}") 110 | (att-progn 111 | (att-output (att-string "")) 112 | (att-loop (att-variable 'zenekindarl.lexer.default::seq) 113 | (att-progn (att-output (att-string "
  • item")) 114 | (att-output (att-variable 'zenekindarl.lexer.default::i)) 115 | (att-output (att-string "
  • "))) 116 | (att-variable 'zenekindarl.lexer.default::i)) 117 | (att-output (att-string ""))) 118 | "loop with trailing space" 119 | :test #'att-equal) 120 | 121 | (is (parse-template-string "the content of foo is {{insert \"foo\"}}") 122 | (att-progn 123 | (att-output (att-string "the content of foo is ")) 124 | (att-output (att-string "{{repeat 2 as i}}bar{{endrepeat}}")) 125 | (att-output (att-string ""))) 126 | "insert" 127 | :test #'att-equal) 128 | 129 | (is (parse-template-string "the content of foo is {{include \"foo\"}}") 130 | (att-progn 131 | (att-output 132 | (att-string "the content of foo is ")) 133 | (att-progn 134 | (att-output (att-string "")) 135 | (att-repeat 136 | (att-constant 2) 137 | (att-progn (att-output (att-string "bar"))) 138 | (att-variable 'zenekindarl.lexer.default::i)) 139 | (att-output (att-string ""))) 140 | (att-output (att-string ""))) 141 | "include" 142 | :test #'att-equal) 143 | (finalize) 144 | 145 | -------------------------------------------------------------------------------- /benchmark/master_all.bench: -------------------------------------------------------------------------------- 1 | This is SBCL 1.2.13.89-31d4aef, an implementation of ANSI Common Lisp. 2 | More information about SBCL is available at . 3 | 4 | SBCL is free software, provided as is, with absolutely no warranty. 5 | It is mostly in the public domain; some portions are provided under 6 | BSD-style licenses. See the CREDITS and COPYING files in the 7 | distribution for more information. 8 | zenekindarl 9 | compiled stream backend with simple.tmpl.zenekindarl 10 | Evaluation took: 11 | 0.028 seconds of real time 12 | 0.028000 seconds of total run time (0.028000 user, 0.000000 system) 13 | 100.00% CPU 14 | 65,772,876 processor cycles 15 | 0 bytes consed 16 | 17 | compiled string backend with simple.tmpl.zenekindarl 18 | Evaluation took: 19 | 0.051 seconds of real time 20 | 0.052000 seconds of total run time (0.052000 user, 0.000000 system) 21 | 101.96% CPU 22 | 124,215,844 processor cycles 23 | 19,678,432 bytes consed 24 | 25 | compiled octet stream backend with simple.tmpl.zenekindarl 26 | Evaluation took: 27 | 0.009 seconds of real time 28 | 0.012000 seconds of total run time (0.012000 user, 0.000000 system) 29 | 133.33% CPU 30 | 22,722,600 processor cycles 31 | 0 bytes consed 32 | 33 | compiled octet backend with simple.tmpl.zenekindarl 34 | Evaluation took: 35 | 0.020 seconds of real time 36 | 0.020000 seconds of total run time (0.020000 user, 0.000000 system) 37 | 100.00% CPU 38 | 46,782,176 processor cycles 39 | 5,450,848 bytes consed 40 | 41 | compiled fast-io backend with simple.tmpl.zenekindarl 42 | Evaluation took: 43 | 0.011 seconds of real time 44 | 0.012000 seconds of total run time (0.012000 user, 0.000000 system) 45 | 109.09% CPU 46 | 27,682,560 processor cycles 47 | 949,328 bytes consed 48 | 49 | WARNING: New template printer for #P"simple.tmpl.html-template" created 50 | html-template 51 | render simple.tmpl.html-template 52 | Evaluation took: 53 | 0.040 seconds of real time 54 | 0.040000 seconds of total run time (0.040000 user, 0.000000 system) 55 | 100.00% CPU 56 | 16 lambdas converted 57 | 96,169,564 processor cycles 58 | 883,840 bytes consed 59 | 60 | zenekindarl 61 | compiled stream backend with 1var.tmpl.zenekindarl 62 | Evaluation took: 63 | 0.047 seconds of real time 64 | 0.048000 seconds of total run time (0.048000 user, 0.000000 system) 65 | 102.13% CPU 66 | 112,512,712 processor cycles 67 | 5,270,352 bytes consed 68 | 69 | compiled string backend with 1var.tmpl.zenekindarl 70 | Evaluation took: 71 | 0.078 seconds of real time 72 | 0.080000 seconds of total run time (0.080000 user, 0.000000 system) 73 | 102.56% CPU 74 | 187,137,124 processor cycles 75 | 31,528,544 bytes consed 76 | 77 | compiled octet stream backend with 1var.tmpl.zenekindarl 78 | Evaluation took: 79 | 0.059 seconds of real time 80 | 0.060000 seconds of total run time (0.060000 user, 0.000000 system) 81 | 101.69% CPU 82 | 142,600,360 processor cycles 83 | 5,601,968 bytes consed 84 | 85 | compiled octet backend with 1var.tmpl.zenekindarl 86 | Evaluation took: 87 | 0.167 seconds of real time 88 | 0.168000 seconds of total run time (0.156000 user, 0.012000 system) 89 | [ Run times consist of 0.092 seconds GC time, and 0.076 seconds non-GC time. ] 90 | 100.60% CPU 91 | 398,850,904 processor cycles 92 | 14,696,608 bytes consed 93 | 94 | compiled fast-io backend with 1var.tmpl.zenekindarl 95 | Evaluation took: 96 | 0.067 seconds of real time 97 | 0.068000 seconds of total run time (0.064000 user, 0.004000 system) 98 | 101.49% CPU 99 | 161,409,576 processor cycles 100 | 6,541,200 bytes consed 101 | 102 | WARNING: New template printer for #P"1var.tmpl.html-template" created 103 | html-template 104 | render 1var.tmpl.html-template 105 | Evaluation took: 106 | 0.090 seconds of real time 107 | 0.088000 seconds of total run time (0.080000 user, 0.008000 system) 108 | 97.78% CPU 109 | 213,243,288 processor cycles 110 | 5,450,912 bytes consed 111 | 112 | zenekindarl 113 | compiled stream backend with 100var.tmpl.zenekindarl 114 | Evaluation took: 115 | 2.461 seconds of real time 116 | 2.448000 seconds of total run time (2.376000 user, 0.072000 system) 117 | [ Run times consist of 0.216 seconds GC time, and 2.232 seconds non-GC time. ] 118 | 99.47% CPU 119 | 5,893,828,288 processor cycles 120 | 527,999,152 bytes consed 121 | 122 | compiled string backend with 100var.tmpl.zenekindarl 123 | Evaluation took: 124 | 2.423 seconds of real time 125 | 2.412000 seconds of total run time (2.380000 user, 0.032000 system) 126 | [ Run times consist of 0.144 seconds GC time, and 2.268 seconds non-GC time. ] 127 | 99.55% CPU 128 | 5,801,216,352 processor cycles 129 | 753,058,640 bytes consed 130 | 131 | compiled octet stream backend with 100var.tmpl.zenekindarl 132 | Evaluation took: 133 | 4.831 seconds of real time 134 | 4.812000 seconds of total run time (4.780000 user, 0.032000 system) 135 | [ Run times consist of 0.124 seconds GC time, and 4.688 seconds non-GC time. ] 136 | 99.61% CPU 137 | 11,567,130,530 processor cycles 138 | 559,998,144 bytes consed 139 | 140 | compiled octet backend with 100var.tmpl.zenekindarl 141 | Evaluation took: 142 | 3.124 seconds of real time 143 | 3.108000 seconds of total run time (3.064000 user, 0.044000 system) 144 | [ Run times consist of 0.116 seconds GC time, and 2.992 seconds non-GC time. ] 145 | 99.49% CPU 146 | 7,480,992,676 processor cycles 147 | 641,442,256 bytes consed 148 | 149 | compiled fast-io backend with 100var.tmpl.zenekindarl 150 | Evaluation took: 151 | 4.991 seconds of real time 152 | 4.964000 seconds of total run time (4.936000 user, 0.028000 system) 153 | [ Run times consist of 0.112 seconds GC time, and 4.852 seconds non-GC time. ] 154 | 99.46% CPU 155 | 11,950,776,700 processor cycles 156 | 560,970,096 bytes consed 157 | 158 | WARNING: New template printer for #P"100var.tmpl.html-template" created 159 | html-template 160 | render 100var.tmpl.html-template 161 | Evaluation took: 162 | 2.600 seconds of real time 163 | 2.588000 seconds of total run time (2.576000 user, 0.012000 system) 164 | 99.54% CPU 165 | 6,224,760,576 processor cycles 166 | 0 bytes consed 167 | 168 | zenekindarl 169 | compiled stream backend with repeat.tmpl.zenekindarl 170 | Evaluation took: 171 | 2.168 seconds of real time 172 | 2.152000 seconds of total run time (2.132000 user, 0.020000 system) 173 | [ Run times consist of 0.132 seconds GC time, and 2.020 seconds non-GC time. ] 174 | 99.26% CPU 175 | 5,192,219,250 processor cycles 176 | 527,999,232 bytes consed 177 | 178 | compiled string backend with repeat.tmpl.zenekindarl 179 | Evaluation took: 180 | 2.026 seconds of real time 181 | 2.020000 seconds of total run time (1.988000 user, 0.032000 system) 182 | [ Run times consist of 0.140 seconds GC time, and 1.880 seconds non-GC time. ] 183 | 99.70% CPU 184 | 4,849,781,020 processor cycles 185 | 627,002,352 bytes consed 186 | 187 | compiled octet stream backend with repeat.tmpl.zenekindarl 188 | Evaluation took: 189 | 5.269 seconds of real time 190 | 5.252000 seconds of total run time (5.216000 user, 0.036000 system) 191 | [ Run times consist of 0.152 seconds GC time, and 5.100 seconds non-GC time. ] 192 | 99.68% CPU 193 | 12,616,641,016 processor cycles 194 | 559,997,760 bytes consed 195 | 196 | compiled octet backend with repeat.tmpl.zenekindarl 197 | Evaluation took: 198 | 2.934 seconds of real time 199 | 2.916000 seconds of total run time (2.880000 user, 0.036000 system) 200 | [ Run times consist of 0.156 seconds GC time, and 2.760 seconds non-GC time. ] 201 | 99.39% CPU 202 | 7,025,850,892 processor cycles 203 | 583,192,976 bytes consed 204 | 205 | compiled fast-io backend with repeat.tmpl.zenekindarl 206 | Evaluation took: 207 | 5.707 seconds of real time 208 | 5.684000 seconds of total run time (5.672000 user, 0.012000 system) 209 | [ Run times consist of 0.144 seconds GC time, and 5.540 seconds non-GC time. ] 210 | 99.60% CPU 211 | 13,666,675,424 processor cycles 212 | 560,970,000 bytes consed 213 | 214 | WARNING: New template printer for #P"repeat.tmpl.html-template" created 215 | html-template 216 | render repeat.tmpl.html-template 217 | Evaluation took: 218 | 2.273 seconds of real time 219 | 2.268000 seconds of total run time (2.264000 user, 0.004000 system) 220 | [ Run times consist of 0.016 seconds GC time, and 2.252 seconds non-GC time. ] 221 | 99.78% CPU 222 | 5,443,603,096 processor cycles 223 | 48,168,960 bytes consed 224 | 225 | -------------------------------------------------------------------------------- /src/att.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of zenekindarl project. 3 | Copyright (c) 2014 κeen 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage zenekindarl.att 8 | (:use :cl :zenekindarl.util) 9 | (:export :print-object 10 | :att-node 11 | :att-leaf 12 | :auto-escape 13 | :att-control 14 | 15 | :att-string 16 | :value 17 | 18 | :att-variable 19 | :varsym 20 | :vartype 21 | 22 | :att-gensym 23 | :gensym-string 24 | 25 | :att-eval 26 | :sexp 27 | 28 | :att-output 29 | :arg 30 | 31 | :att-constant 32 | 33 | :att-nil 34 | 35 | :att-progn 36 | :nodes 37 | 38 | :att-if 39 | :cond-clause 40 | :then-clause 41 | :else-clause 42 | 43 | :att-loop 44 | :loop-seq 45 | :loop-var 46 | :body 47 | 48 | :att-repeat 49 | :repeat-times 50 | :repeat-var 51 | :body 52 | 53 | :att-include 54 | :path 55 | 56 | :att-equal)) 57 | (in-package :zenekindarl.att) 58 | 59 | ;;; Abstract Template Tree 60 | (defclass att-node () 61 | ()) 62 | (defclass att-leaf (att-node) 63 | ((auto-escape 64 | :type '(or null t) 65 | :accessor auto-escape 66 | :initarg :auto-escape 67 | :initform t))) 68 | (defclass att-control (att-node) 69 | ()) 70 | 71 | (defgeneric att-equal (x y) 72 | (:method (x y) 73 | (declare (ignore x y)) 74 | nil)) 75 | 76 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 77 | ;;; att-string 78 | (defclass att-string (att-leaf) 79 | ((value 80 | :type 'string 81 | :accessor value 82 | :initarg :value) 83 | (auto-escape 84 | :initform nil))) 85 | 86 | (defmethod print-object ((obj att-string) stream) 87 | (format stream "#" (value obj))) 88 | 89 | (defun att-string (str) 90 | (make-instance 'att-string :value str)) 91 | 92 | (defmethod att-equal ((x att-string) (y att-string)) 93 | (string= (value x) (value y))) 94 | 95 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 96 | ;;; att-variable 97 | (defclass att-variable (att-leaf) 98 | ((varsym 99 | :type 'symbol 100 | :accessor varsym 101 | :initarg :varsym) 102 | (vartype 103 | :type '(or :string :anything) 104 | :accessor vartype 105 | :initarg :vartype 106 | :initform :anything))) 107 | 108 | (defmethod print-object ((obj att-variable) stream) 109 | (format stream "#" (varsym obj))) 110 | 111 | (defun att-variable (sym &optional (type :anything) (auto-escape nil auto-escape-p)) 112 | (if auto-escape-p 113 | (make-instance 'att-variable 114 | :varsym sym 115 | :vartype type 116 | :auto-escape auto-escape) 117 | (make-instance 'att-variable 118 | :varsym sym 119 | :vartype type))) 120 | 121 | (defmethod att-equal ((x att-variable) (y att-variable)) 122 | (and (eql (varsym x) (varsym y)) 123 | (eql (vartype x) (vartype y)))) 124 | 125 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 126 | ;;; att-gensym 127 | (defclass att-gensym (att-variable) 128 | ((gensym-string 129 | :type '(or null string) 130 | :accessor gensym-string 131 | :initarg :gensym-string))) 132 | 133 | (defmethod print-object ((obj att-gensym) stream) 134 | (format stream "#" (gensym-string obj))) 135 | 136 | (defun att-gensym (gensym-string &optional (type :anything)) 137 | (make-instance 'att-gensym 138 | :varsym (gensym gensym-string) 139 | :vartype type 140 | :gensym-string gensym-string)) 141 | 142 | (defmethod att-equal ((x att-gensym) (y att-gensym)) 143 | (and (string= (gensym-string x) (gensym-string y)) 144 | (eql (vartype x) (vartype y)))) 145 | 146 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 147 | ;;; att-constant 148 | (defclass att-constant (att-leaf) 149 | ((value 150 | :accessor value 151 | :initarg :value) 152 | (auto-escape 153 | :initform nil))) 154 | 155 | (defmethod print-object ((obj att-constant) stream) 156 | (format stream "#" (value obj))) 157 | 158 | (defun att-constant (val) 159 | (make-instance 'att-constant 160 | :value val)) 161 | 162 | (defmethod att-equal ((x att-constant) (y att-constant)) 163 | (equal (value x) (value y))) 164 | 165 | 166 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 167 | ;;; att-eval 168 | (defclass att-eval (att-leaf) 169 | ((sexp 170 | :accessor sexp 171 | :initarg :sexp))) 172 | 173 | (defmethod print-object ((obj att-eval) stream) 174 | (format stream "#" (sexp obj))) 175 | 176 | (defun att-eval (sexp) 177 | (make-instance 'att-eval :sexp sexp)) 178 | 179 | (defmethod att-equal ((x att-eval) (y att-eval)) 180 | (equalp (sexp x) (sexp y))) 181 | 182 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 183 | ;;; att-nil 184 | (defclass att-nil (att-leaf) 185 | ()) 186 | 187 | (defun att-nil () 188 | (make-instance 'att-nil)) 189 | 190 | (defmethod att-equal ((x att-nil) (y att-nil)) 191 | t) 192 | 193 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 194 | ;;; att-eval-to-output 195 | (defclass att-output (att-control) 196 | ((arg 197 | :accessor arg 198 | :initarg :arg))) 199 | 200 | (defmethod print-object ((obj att-output) stream) 201 | (format stream "#" (arg obj))) 202 | 203 | (defun att-output (arg) 204 | (make-instance 'att-output :arg arg)) 205 | 206 | (defmethod att-equal ((x att-output) (y att-output)) 207 | (att-equal (arg x) (arg y))) 208 | 209 | 210 | 211 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 212 | ;;; att-progn 213 | (defclass att-progn (att-control) 214 | ((nodes 215 | :type 'list 216 | :accessor nodes 217 | :initarg :nodes))) 218 | 219 | (defmethod print-object ((obj att-progn) stream) 220 | (format stream "#" (nodes obj))) 221 | 222 | (defun att-progn (&rest nodes) 223 | (make-instance 'att-progn 224 | :nodes nodes)) 225 | 226 | (defmethod att-equal ((x att-progn) (y att-progn)) 227 | (loop 228 | :for a :in (nodes x) 229 | :for b :in (nodes y) 230 | :always (att-equal a b))) 231 | 232 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 233 | ;;; att-if 234 | (defclass att-if (att-control) 235 | ((cond-clause 236 | :accessor cond-clause 237 | :initarg :cond) 238 | (then-clause 239 | :type 'att-node 240 | :accessor then-clause 241 | :initarg :then) 242 | (else-clause 243 | :type 'att-node 244 | :accessor else-clause 245 | :initarg :else 246 | :initform (att-nil)))) 247 | 248 | (defmethod print-object ((obj att-if) stream) 249 | (with-slots (cond-clause then-clause else-clause) obj 250 | (format stream "#" cond-clause then-clause else-clause))) 251 | 252 | (defun att-if (cond-clause then-clause &optional (else-clause (att-nil))) 253 | (make-instance 'att-if 254 | :cond cond-clause 255 | :then then-clause 256 | :else else-clause)) 257 | 258 | (defmethod att-equal ((x att-if) (y att-if)) 259 | (and (att-equal (cond-clause x) 260 | (cond-clause y)) 261 | (att-equal (then-clause x) 262 | (then-clause y)) 263 | (att-equal (else-clause x) 264 | (else-clause y)))) 265 | 266 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 267 | ;;; att-loop 268 | (defclass att-loop (att-control) 269 | ((loop-seq 270 | :type 'att-leaf 271 | :accessor loop-seq 272 | :initarg :loop-seq) 273 | (loop-var 274 | :type 'att-variable 275 | :accessor loop-var 276 | :initarg :loop-var) 277 | (body 278 | :type 'att-node 279 | :accessor body 280 | :initarg :body))) 281 | 282 | (defmethod print-object ((obj att-loop) stream) 283 | (with-slots (loop-seq loop-var body) obj 284 | (if loop-var 285 | (format stream "#" loop-var loop-seq body) 286 | (format stream "#" loop-seq body)))) 287 | 288 | (defun att-loop (loop-seq body &optional (loop-var (att-gensym "loopvar"))) 289 | (make-instance 'att-loop 290 | :loop-seq loop-seq 291 | :body body 292 | :loop-var loop-var)) 293 | 294 | (defmethod att-equal ((x att-loop) (y att-loop)) 295 | (and (att-equal (loop-seq x) 296 | (loop-seq y)) 297 | (att-equal (loop-var x) 298 | (loop-var y)) 299 | (att-equal (body x) 300 | (body y)))) 301 | 302 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 303 | ;;; att-repeat 304 | (defclass att-repeat (att-control) 305 | ((repeat-times 306 | :type 'att-leaf 307 | :accessor repeat-times 308 | :initarg :repeat-times) 309 | (repeat-var 310 | :type 'att-variable 311 | :accessor repeat-var 312 | :initarg :repeat-var) 313 | (body 314 | :type 'att-node 315 | :accessor body 316 | :initarg :body))) 317 | 318 | (defmethod print-object ((obj att-repeat) stream) 319 | (with-slots (repeat-times repeat-var body) obj 320 | (if repeat-var 321 | (format stream "#" repeat-var repeat-times body) 322 | (format stream "#" repeat-times body)))) 323 | 324 | (defun att-repeat (repeat-times body &optional (repeat-var (att-gensym "repeatvar"))) 325 | (make-instance 'att-repeat 326 | :repeat-times repeat-times 327 | :body body 328 | :repeat-var repeat-var)) 329 | 330 | (defmethod att-equal ((x att-repeat) (y att-repeat)) 331 | (and (att-equal (repeat-times x) 332 | (repeat-times y)) 333 | (att-equal (repeat-var x) 334 | (repeat-var y)) 335 | (att-equal (body x) 336 | (body y)))) 337 | 338 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 339 | ;;; att-include 340 | (defclass att-include (att-control) 341 | ((path 342 | :initarg :path 343 | :accessor path))) 344 | 345 | (defmethod print-object ((obj att-include) stream) 346 | (format stream "#" (slot-value obj 'path))) 347 | 348 | (defun att-include (path) 349 | (make-instance 'att-include :path path)) 350 | 351 | (defmethod att-equal ((x att-include) (y att-include)) 352 | ;; :FIXME: treat relative and absolute pathes 353 | (string= (path x) (path y))) 354 | --------------------------------------------------------------------------------