├── 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 |
10 | - {{ loop foos as foo }} {{var foo}} {{ endloop }}
11 |
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 | [](https://travis-ci.org/KeenS/zenekindarl)
2 | [](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 | 
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 | - {{var item}}
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 | - {{ var foo1 }}
11 | - {{ var foo2 }}
12 | - {{ var foo3 }}
13 | - {{ var foo4 }}
14 | - {{ var foo5 }}
15 | - {{ var foo6 }}
16 | - {{ var foo7 }}
17 | - {{ var foo8 }}
18 | - {{ var foo9 }}
19 | - {{ var foo10 }}
20 | - {{ var foo11 }}
21 | - {{ var foo12 }}
22 | - {{ var foo13 }}
23 | - {{ var foo14 }}
24 | - {{ var foo15 }}
25 | - {{ var foo16 }}
26 | - {{ var foo17 }}
27 | - {{ var foo18 }}
28 | - {{ var foo19 }}
29 | - {{ var foo20 }}
30 | - {{ var foo21 }}
31 | - {{ var foo22 }}
32 | - {{ var foo23 }}
33 | - {{ var foo24 }}
34 | - {{ var foo25 }}
35 | - {{ var foo26 }}
36 | - {{ var foo27 }}
37 | - {{ var foo28 }}
38 | - {{ var foo29 }}
39 | - {{ var foo30 }}
40 | - {{ var foo31 }}
41 | - {{ var foo32 }}
42 | - {{ var foo33 }}
43 | - {{ var foo34 }}
44 | - {{ var foo35 }}
45 | - {{ var foo36 }}
46 | - {{ var foo37 }}
47 | - {{ var foo38 }}
48 | - {{ var foo39 }}
49 | - {{ var foo40 }}
50 | - {{ var foo41 }}
51 | - {{ var foo42 }}
52 | - {{ var foo43 }}
53 | - {{ var foo44 }}
54 | - {{ var foo45 }}
55 | - {{ var foo46 }}
56 | - {{ var foo47 }}
57 | - {{ var foo48 }}
58 | - {{ var foo49 }}
59 | - {{ var foo50 }}
60 | - {{ var foo51 }}
61 | - {{ var foo52 }}
62 | - {{ var foo53 }}
63 | - {{ var foo54 }}
64 | - {{ var foo55 }}
65 | - {{ var foo56 }}
66 | - {{ var foo57 }}
67 | - {{ var foo58 }}
68 | - {{ var foo59 }}
69 | - {{ var foo60 }}
70 | - {{ var foo61 }}
71 | - {{ var foo62 }}
72 | - {{ var foo63 }}
73 | - {{ var foo64 }}
74 | - {{ var foo65 }}
75 | - {{ var foo66 }}
76 | - {{ var foo67 }}
77 | - {{ var foo68 }}
78 | - {{ var foo69 }}
79 | - {{ var foo70 }}
80 | - {{ var foo71 }}
81 | - {{ var foo72 }}
82 | - {{ var foo73 }}
83 | - {{ var foo74 }}
84 | - {{ var foo75 }}
85 | - {{ var foo76 }}
86 | - {{ var foo77 }}
87 | - {{ var foo78 }}
88 | - {{ var foo79 }}
89 | - {{ var foo80 }}
90 | - {{ var foo81 }}
91 | - {{ var foo82 }}
92 | - {{ var foo83 }}
93 | - {{ var foo84 }}
94 | - {{ var foo85 }}
95 | - {{ var foo86 }}
96 | - {{ var foo87 }}
97 | - {{ var foo88 }}
98 | - {{ var foo89 }}
99 | - {{ var foo90 }}
100 | - {{ var foo91 }}
101 | - {{ var foo92 }}
102 | - {{ var foo93 }}
103 | - {{ var foo94 }}
104 | - {{ var foo95 }}
105 | - {{ var foo96 }}
106 | - {{ var foo97 }}
107 | - {{ var foo98 }}
108 | - {{ var foo99 }}
109 | - {{ var foo100 }}
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")) "uragasumihakkaisandassai" "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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------