From wchunye at common-lisp.net Sat Sep 11 15:50:42 2010 From: wchunye at common-lisp.net (wchunye) Date: Sat, 11 Sep 2010 11:50:42 -0400 Subject: [ginseng-cvs] CVS ginseng Message-ID: Update of /project/ginseng/cvsroot/ginseng In directory cl-net:/tmp/cvs-serv24595 Modified Files: ginseng.asd Log Message: update documents and examples --- /project/ginseng/cvsroot/ginseng/ginseng.asd 2010/08/11 12:02:30 1.2 +++ /project/ginseng/cvsroot/ginseng/ginseng.asd 2010/09/11 15:50:42 1.3 @@ -16,8 +16,8 @@ ((:module src :serial t :components ((:file "package") + (:file "pattern-match" :depends-on ("package")) (:file "my-utils" :depends-on ("package")) - (:file "dispatcher" :depends-on ("my-utils")) - (:file "application" :depends-on ("my-utils")) + (:file "ginseng" :depends-on ("my-utils")) )))) From wchunye at common-lisp.net Sat Sep 11 15:50:42 2010 From: wchunye at common-lisp.net (wchunye) Date: Sat, 11 Sep 2010 11:50:42 -0400 Subject: [ginseng-cvs] CVS ginseng/examples Message-ID: Update of /project/ginseng/cvsroot/ginseng/examples In directory cl-net:/tmp/cvs-serv24595/examples Modified Files: app-inc-counter.lisp hello-world.lisp sum-of.lisp Log Message: update documents and examples --- /project/ginseng/cvsroot/ginseng/examples/app-inc-counter.lisp 2010/08/11 12:02:36 1.2 +++ /project/ginseng/cvsroot/ginseng/examples/app-inc-counter.lisp 2010/09/11 15:50:42 1.3 @@ -30,8 +30,6 @@ (<:as-html " ") (<:a :href (dynamic-url (counter-main (1- counter))) "--") )))) - - (defun http-add-two-numbers (&optional (next-action nil)) (invoke-next-action next-action :main #'(lambda () (add-two-numbers-main)))) (defun add-two-numbers-main () @@ -41,16 +39,14 @@ (<:title "Add two number")) (<:body (<:h1 "Please input the first number:") - (let ((cf (make-callback-factory)) - (first-number 0)) + (let ((first-number 0)) (<:form :action (dynamic-url - (apply-callbacks cf) + (apply-callbacks) (input-next-number first-number)) (<:input :type :text - :name (create-callback cf - #'(lambda (v) (setq first-number v)) - :type 'integer)) - )))))) + :name (with-call-back (v :type 'integer) + (setf first-number v)) + ))))))) (defun input-next-number(first-number) (yaclml:with-yaclml-output-to-string (<:html @@ -59,16 +55,13 @@ (<:body (<:h1 (<:as-html "Add to " first-number "." " Please input the second number:")) - (let ((cf (make-callback-factory)) - (second-number 0)) + (let ((second-number 0)) (<:form :action (dynamic-url - (apply-callbacks cf) + (apply-callbacks) (add-the-two-numbers first-number second-number)) (<:input :type :text - :name (create-callback cf - #'(lambda (v) (setq second-number v)) - :type 'integer)) - )))))) + :name (bindf second-number 'integer))) + ))))) (defun add-the-two-numbers ( a b ) (yaclml:with-yaclml-output-to-string (<:html --- /project/ginseng/cvsroot/ginseng/examples/hello-world.lisp 2010/08/11 12:02:36 1.2 +++ /project/ginseng/cvsroot/ginseng/examples/hello-world.lisp 2010/09/11 15:50:42 1.3 @@ -32,4 +32,4 @@ (<:ol (dolist (arg args) (<:li (<:as-html arg))) - )))))) \ No newline at end of file + )))))) --- /project/ginseng/cvsroot/ginseng/examples/sum-of.lisp 2010/08/09 12:27:25 1.1 +++ /project/ginseng/cvsroot/ginseng/examples/sum-of.lisp 2010/09/11 15:50:42 1.2 @@ -1,17 +1,9 @@ (in-package :ginseng-examples) (defun http-sum-of(&rest args) - (with-yaclml-output-to-string - (<:html - (<:head - (<:title "Sum of numbers")) - (<:body - (<:h1 "Sum of numbers") + (with-html "sum of numbers" + (let ((a-list-of-number (mapcar #'(lambda (x) + (or (parse-integer x :junk-allowed t) 0)) + args))) (<:p - (<:as-html (format nil "~{~A~^+~}" args) "=" - (apply #'+ - (mapcar #'(lambda (x) - (or - (parse-integer x - :junk-allowed t) - 0)) - args)))))))) \ No newline at end of file + (<:as-html (format nil "~{~A~^+~}" a-list-of-number) "=" + (apply #'+ a-list-of-number)))))) \ No newline at end of file From wchunye at common-lisp.net Sat Sep 11 15:50:42 2010 From: wchunye at common-lisp.net (wchunye) Date: Sat, 11 Sep 2010 11:50:42 -0400 Subject: [ginseng-cvs] CVS ginseng/src Message-ID: Update of /project/ginseng/cvsroot/ginseng/src In directory cl-net:/tmp/cvs-serv24595/src Modified Files: my-utils.lisp package.lisp pattern-match.lisp Log Message: update documents and examples --- /project/ginseng/cvsroot/ginseng/src/my-utils.lisp 2010/08/09 12:27:25 1.1 +++ /project/ginseng/cvsroot/ginseng/src/my-utils.lisp 2010/09/11 15:50:42 1.2 @@ -14,12 +14,60 @@ `(setq it ,s)) body) it))) +(defmacro aand (&body body) + `(let ((it (and (boundp 'it) it))) + (and + ,@(mapcar #'(lambda (s) + `(setq it ,s)) + body)))) (let ((output *standard-output*)) (defun my-debug (&rest args) (format output "~&WCY-DEBUG:") (apply #'format output args))) - -(defun make-unique-id () - (hunchentoot::md5-hex - (hunchentoot::create-random-string 10 36))) \ No newline at end of file +(defun default-main() + " + + + GINSENG DEFAULT ACTION FOR DEBUG. + + +

DEFAULT ACTION IS NOT DEFINED

+ +") + +(defvar *last-request* nil) +(defvar *last-reply* nil) +(defvar *last-session* nil) +(defvar *last-app-name* nil) +(defvar *last-k* nil) +(defvar *last-environment* nil) +(defvar *last-acceptor* nil) +(defmacro with-last-environment (&body body) + `(let* ((hunchentoot::*acceptor* *last-acceptor*) + (hunchentoot::*request* *last-request*) + (hunchentoot:*reply* *last-request*) + (hunchentoot::*acceptor* (hunchentoot:request-acceptor ginseng::*last-request*)) + (hunchentoot::*session* *last-session*) + (*ginseng-app-name* *last-app-name*) + (*environment* *last-environment*) + (*k* *last-k*) + ) + , at body)) +(defun save-last-environment () + (setq *last-acceptor* hunchentoot::*acceptor* + *last-request* hunchentoot:*request* + *last-reply* hunchentoot:*reply* + *last-session* hunchentoot:*session* + *last-app-name* *ginseng-app-name* + *last-k* *k* + *last-environment* *environment* + )) +(defun clear-package(package) + (when (not (packagep package)) + (setq package (find-package package))) + (dolist (s (let (r) + (do-symbols (var package) + (push var r)) r)) + (unintern s package))) + \ No newline at end of file --- /project/ginseng/cvsroot/ginseng/src/package.lisp 2010/08/11 12:02:40 1.2 +++ /project/ginseng/cvsroot/ginseng/src/package.lisp 2010/09/11 15:50:42 1.3 @@ -1,25 +1,24 @@ (in-package :cl-user) +(defpackage :pattern-match + (:use :common-lisp) + (:export :match :match-values)) (defpackage :ginseng - (:use :cl :iterate :yaclml) - (:export :my-debug :aprogn :it :start-server :stop-server + (:use :cl + :iterate + :pattern-match + :yaclml) + (:export :my-debug :aprogn :awhen :it + :start-hunchentoot :stop-hunchentoot + :start-ginseng :stop-ginseng :dynamic-url :relative-url-to-app :invoke-next-action - :make-callback-factory - :create-callback - :apply-callbacks + :bindf + :with-call-back + :standard-page :env-var :with-rebinds - ) - (:import-from :hunchentoot - :*dispatch-table* - :create-prefix-dispatcher - :start - :stop - :acceptor - :script-name* - :*request* - :session-value - :get-parameters* - :post-parameters* - )) + :with-env-vars + :with-session-vars + )) + --- /project/ginseng/cvsroot/ginseng/src/pattern-match.lisp 2010/08/09 12:27:25 1.1 +++ /project/ginseng/cvsroot/ginseng/src/pattern-match.lisp 2010/09/11 15:50:42 1.2 @@ -1,44 +1,150 @@ -(in-package :ginseng) -(defun generate-code (pattern in body) - (cond - ((null pattern) - `(if (null ,in) - ,body)) - ((consp pattern) - (let ((next-car (gensym)) - (next-cdr (gensym))) - - `(if (consp ,in) - (let ((,next-car (car ,in)) - (,next-cdr (cdr ,in))) - (declare (ignorable ,next-car ,next-cdr)) - ,(generate-code (car pattern) next-car - (generate-code (cdr pattern) next-cdr body)))))) - ((symbolp pattern) - `(let ((,pattern ,in)) - (declare (ignorable ,pattern)) - ,body) - ))) -(defmacro p(pattern) - (let ((input (gensym))) - `#'(lambda (,input) - ,(generate-code pattern input t)))) -(defun match-helper (expr test-body) - (if (null test-body) nil - (let ((ok (gensym)) - (v (gensym)) - (tmp-args (gensym))) - `(multiple-value-bind (,ok ,v) - (funcall - #'(lambda (,tmp-args) - ,(generate-code (caar test-body) - tmp-args (append (list 'values t) (cdar test-body)))) - ,expr) - (if ,ok - ,v - ,(match-helper expr (cdr test-body))))))) -(defmacro match (expr test-body) - (let ((expr-v (gensym))) - `(let ((,expr-v ,expr)) - ,(match-helper expr-v test-body)))) - +(in-package :pattern-match) +(defvar *bindings* nil) +(defun generate-code-null (pattern arg body-cont) + (if (null pattern) + #'(lambda () + `(if 'generate-code-null + (if (null ,arg) ,(funcall body-cont)))))) +(defun generate-code-ignore (pattern arg body-cont) + (declare (ignore arg)) + (if (and (symbolp pattern) + (string= (symbol-name pattern) "_")) + #'(lambda () + `(if 'generate-code-ignore + (progn ,(funcall body-cont)))))) +(defun generate-code-T (pattern arg body-cont) + (if (eq pattern T) + #'(lambda () + `(if 'generate-code-T + (if (eq ,arg T) ,(funcall body-cont)))))) +(defun generate-code-string (pattern arg body-cont) + (if (stringp pattern) + #'(lambda () + `(if 'generate-code-string + (if (string= ,arg ,pattern) ,(funcall body-cont)))))) +(defun generate-code-keywords (pattern arg body-cont) + (if (keywordp pattern) + #'(lambda () + `(if 'generate-code-keywords + (if (eq ,pattern ,arg) ,(funcall body-cont)))))) +(defun generate-code-quote (pattern arg body-cont) + (if (and (listp pattern) + (eq (car pattern) 'cl:quote)) + #'(lambda () + `(if 'generate-code-quote + (if (equal ',(cadr pattern) ,arg) ,(funcall body-cont)))))) +(defun generate-code-symbol (pattern arg body-cont) + (if (symbolp pattern) + #'(lambda () + (let ((is-bound (find pattern *bindings*))) + (if is-bound + `(if 'generate-code-bound-symbol + (if (equal ,pattern ,arg) + ,(funcall body-cont))) + (let ((*bindings* (cons pattern *bindings*))) + `(if 'generate-code-unbound-symbol + (let ((,pattern ,arg)) + (declare (ignorable ,pattern)) + ,(funcall body-cont))))))))) +(defun generate-code-vector (pattern arg body-cont) + (if (vectorp pattern) + (let ((len (length pattern)) + (vars (map 'vector #'(lambda (v) (declare (ignore v)) (gensym "VEC")) pattern)) + (code-cont body-cont)) + (loop + for i downfrom (1- len) to 0 + do (progn + (setf code-cont + (let ((code-cont code-cont) + (i i)) + #'(lambda () + `(let ((,(aref vars i) (aref ,arg ,i))) + ,(funcall (generate-code (aref pattern i) (aref vars i) code-cont)))))))) + #'(lambda () + `(if 'generate-code-vector + (if (and (vectorp ,arg) + (= (length ,arg) ,len)) + ,(funcall code-cont))))))) +(defun generate-code-consp (pattern arg body-cont) + (if (consp pattern) + (let ((next-car (gensym)) + (next-cdr (gensym))) + #'(lambda () + `(if 'generate-code-consp + (if (consp ,arg) + (let ((,next-car (car ,arg)) + (,next-cdr (cdr ,arg))) + (declare (ignorable ,next-car ,next-cdr)) + ,(let ((cdr-code-body-cont (generate-code (cdr pattern) next-cdr body-cont))) + (funcall (generate-code (car pattern) next-car cdr-code-body-cont)))))))))) +(defun generate-code-default (pattern arg body-cont) + #'(lambda () + `(if 'generate-code-default + (if (equal ,pattern ,arg) + ,(funcall body-cont))))) +(defparameter *generate-code-functions* + (list #'generate-code-null + #'generate-code-ignore + #'generate-code-T + #'generate-code-string + #'generate-code-keywords + #'generate-code-quote + #'generate-code-symbol + #'generate-code-vector + #'generate-code-consp + #'generate-code-default + )) +(defun generate-code (pattern arg cont) + "ARG is the formal argument of the generated function. GENERATE-CODE +returns a generated function which matchs the PATTERN against ARG. If +match, the generated function evaluated the BODY and return the +result. " + (let (next-cont) + (dolist (f *generate-code-functions*) + (setf next-cont + (or next-cont (funcall f pattern arg cont)))) + (assert next-cont) + next-cont)) + +(defun match-helper (expr pattern-body) + "MATCH evaluate first expression and EXPR is the symbol whose value is the +results of the evaluation. PATTERN-BODY is a list of patterns as follows + ( (PATTERN_1 BODY_1) + (PATTERN_2 BODY_2) + .... + ) + +" + (if (null pattern-body) nil + (let ((ok (gensym)) + (v (gensym)) + (tmp-args (gensym)) + (pattern (caar pattern-body)) + (body (cdar pattern-body)) + guard) + (when (and (listp (car body)) (eq (caar body) :guard)) + ;; if the first element of body is :guard, then the next element is + ;; the GUARD expression. + (setq guard (cadr (pop body)))) + `(multiple-value-bind (,v ,ok) ;; if pattern is matched, OK is t, V is + ;; the form which is ready for + ;; evaluating. + (funcall + #'(lambda (,tmp-args) ;; this function return two values which is catched by OK and V. + ,(let ((*bindings* nil)) + (funcall + (generate-code pattern tmp-args #'(lambda () + (if guard + `(when ,guard (values (progn , at body) t)) + `(values (progn , at body) t))))))) + ,expr) + (if ,ok ,v + ,(match-helper expr (cdr pattern-body))))))) +(defmacro match (expr &rest test-body) + (let ((expr-v (gensym))) + `(let ((,expr-v ,expr)) + ,(match-helper expr-v test-body)))) +(defmacro match-values (multi-values-expr &rest test-body) + (let ((expr-v (gensym))) + `(let ((,expr-v (multiple-value-list ,multi-values-expr))) + ,(match-helper expr-v test-body)))) \ No newline at end of file From wchunye at common-lisp.net Sat Sep 11 15:53:52 2010 From: wchunye at common-lisp.net (wchunye) Date: Sat, 11 Sep 2010 11:53:52 -0400 Subject: [ginseng-cvs] CVS ginseng/examples Message-ID: Update of /project/ginseng/cvsroot/ginseng/examples In directory cl-net:/tmp/cvs-serv24891 Added Files: examples.lisp Log Message: add examples.lisp --- /project/ginseng/cvsroot/ginseng/examples/examples.lisp 2010/09/11 15:53:52 NONE +++ /project/ginseng/cvsroot/ginseng/examples/examples.lisp 2010/09/11 15:53:52 1.1 (in-package :ginseng-examples) (defun http-hello-world-1() " Hello World

Hello World

") (defun http-hello-world-2() (with-yaclml-output-to-string (<:html (<:head (<:title "Hello World")) (<:body (<:h1 "Hello World"))))) (defun http-hello-world-3() (standard-page (:title "Hello World") (<:h1 "Hello World"))) (defun http-sum-of(&rest args) (standard-page () (let ((a-list-of-number (mapcar #'(lambda (x) (or (parse-integer x :junk-allowed t) 0)) args))) (<:p (<:as-html (format nil "~{~A~^+~}" a-list-of-number) "=" (apply #'+ a-list-of-number)))))) (defun http-counter(&optional (next-action nil)) (invoke-next-action next-action :main #'(lambda () (counter-main 0)))) (defun counter-main (counter) (standard-page () (<:p (<:as-html counter)) (<:p(<:a :href (dynamic-url (counter-main (1+ counter))) "++") (<:as-html " ") (<:a :href (dynamic-url (counter-main (1- counter))) "--")))) (defun http-greet-1 (&optional next-action) (invoke-next-action next-action :main #'greet-1)) (defun greet-1 () (let (name) (standard-page () (<:form :action (dynamic-url (how-are-you name)) (<:p "What's your name?" (<:input :type :input :name (with-call-back (var) (setf name var)))) (<:p (<:input :type :submit :name "OK" :value "OK")))))) (defun how-are-you (name) (standard-page () (<:p "How are you, " (<:as-html name) "?") (<:a :href "." "try again"))) (defun http-greet-2 (&optional next-action) (invoke-next-action next-action :main #'greet-1)) (defun greet-2 () (let* (name (form-id (dynamic-url (how-are-you name))) (name-id (with-call-back (var) (setf name var)))) (standard-page () (<:form :action form-id (<:p "What's your name?" (<:input :type :input :name name-id)) (<:p (<:input :type :submit :name "OK" :value "OK")))))) (defun http-greet-3 (&optional next-action) (invoke-next-action next-action :main #'greet-1)) (defun greet-3 () (let* (name (form-id (dynamic-url (how-are-you name))) (name-id (bindf name))) (standard-page () (<:form :action form-id (<:p "What's your name?" (<:input :type :input :name name-id)) (<:p (<:input :type :submit :name "OK" :value "OK")))))) (defun http-greet-4 (&optional next-action) (invoke-next-action next-action :main #'greet-4)) (defun greet-4 () (let* (name (form-id (dynamic-url (how-are-you name))) (bob-id (with-call-back () (setf name "Bob"))) (john-id (with-call-back () (setf name "John"))) (tom-id (with-call-back () (setf name "Tom")))) (standard-page () (<:form :action form-id (<:p "What's your name?") (<:p (<:input :type :submit :name bob-id :value "Bob") (<:input :type :submit :name john-id :value "John") (<:input :type :submit :name tom-id :value "Tom")))))) (defun http-greet-5 (&optional next-action) (invoke-next-action next-action :main #'greet-5)) (defun greet-5 () (let* ((names (make-list 3 :initial-element "default-name")) (form-id (dynamic-url (how-are-you (format nil "~{~A~^,~}" (remove nil names))))) (bob-id (bindf (nth 0 names))) (john-id (bindf (nth 1 names))) (tom-id (bindf (nth 2 names))) ) (standard-page () (<:form :action form-id (<:p "What's your name?") (<:p (<:input :type :checkbox :name bob-id :value "Bob") "Bob") (<:p (<:input :type :checkbox :name john-id :value "John") "John") (<:p (<:input :type :checkbox :name tom-id :value "Tom") "Tom") (<:p (<:input :type :submit :name "OK" :value "OK")) )))) (defun http-greet-6 (&optional next-action) (invoke-next-action next-action :main #'greet-6)) (defun greet-6 () (let* ((names (make-list 3 :initial-element "default-name")) (form-id (dynamic-url (how-are-you (format nil "~{~A~^,~}" (remove "0" names :test #'equal))))) (bob-id (bindf (nth 0 names))) (john-id (bindf (nth 1 names))) (tom-id (bindf (nth 2 names))) ) (standard-page () (<:form :action form-id (<:p "What's your name?") (<:p (<:input :type :checkbox :name bob-id :value "Bob") (<:input :type :hidden :name bob-id :value "0") "Bob") (<:p (<:input :type :checkbox :name john-id :value "John") (<:input :type :hidden :name john-id :value "0") "John") (<:p (<:input :type :checkbox :name tom-id :value "Tom") (<:input :type :hidden :name tom-id :value "0") "Tom") (<:p (<:input :type :submit :name "OK" :value "OK")) )))) (defun http-greet-7 (&optional next-action) (invoke-next-action next-action :main #'greet-7)) (defun greet-7 () (let* ((names (list "Bob" "John" "Tom")) (form-id (dynamic-url (how-are-you (format nil "~{~A~^,~}" (remove "0" names :test #'equal))))) (names-id (with-call-back (var :type 'list) (setf names var))) ) (standard-page () (<:form :action form-id (<:p "What's your name?") (<:input :type :hidden :name names-id :value "0") (loop for n in names do (<:p (<:input :type :checkbox :name names-id :value n) (<:as-html n))) (<:p (<:input :type :submit :name "OK" :value "OK")) )))) (defun http-greet-8 (&optional next-action) (invoke-next-action next-action :main #'greet-8)) (defun greet-8 () (let* ((names (list "Bob" "John" "Tom")) (form-id (dynamic-url (how-are-you (format nil "~{~A~^,~}" (remove "0" names :test #'equal))))) (names-id (bindf names :type 'list)) ) (standard-page () (<:form :action form-id (<:p "What's your name?") (<:input :type :hidden :name names-id :value "0") (loop for n in names do (<:p (<:input :type :checkbox :name names-id :value n) (<:as-html n))) (<:p (<:input :type :submit :name "OK" :value "OK")) )))) From wchunye at common-lisp.net Sat Sep 11 16:10:22 2010 From: wchunye at common-lisp.net (wchunye) Date: Sat, 11 Sep 2010 12:10:22 -0400 Subject: [ginseng-cvs] CVS ginseng Message-ID: Update of /project/ginseng/cvsroot/ginseng In directory cl-net:/tmp/cvs-serv30287 Modified Files: ginseng-examples.asd Log Message: add examples.lisp --- /project/ginseng/cvsroot/ginseng/ginseng-examples.asd 2010/08/09 12:27:25 1.1 +++ /project/ginseng/cvsroot/ginseng/ginseng-examples.asd 2010/09/11 16:10:22 1.2 @@ -11,9 +11,6 @@ ((:module examples :serial t :components ((:file "package") - (:file "hello-world" :depends-on ("package")) - (:file "sum-of" :depends-on ("package")) - (:file "index" :depends-on ("package")) - (:file "app-inc-counter" :depends-on ("package")) + (:file "examples" :depends-on ("package")) )))) From wchunye at common-lisp.net Sat Sep 11 16:10:46 2010 From: wchunye at common-lisp.net (wchunye) Date: Sat, 11 Sep 2010 12:10:46 -0400 Subject: [ginseng-cvs] CVS ginseng/examples Message-ID: Update of /project/ginseng/cvsroot/ginseng/examples In directory cl-net:/tmp/cvs-serv30329 Modified Files: examples.lisp Log Message: add examples.lisp --- /project/ginseng/cvsroot/ginseng/examples/examples.lisp 2010/09/11 15:53:52 1.1 +++ /project/ginseng/cvsroot/ginseng/examples/examples.lisp 2010/09/11 16:10:46 1.2 @@ -194,4 +194,41 @@ :value "OK")) )))) +(defun http-add-two-numbers (&optional (next-action nil)) + (invoke-next-action next-action :main #'add-two-numbers)) +(defun add-two-numbers () + (let* ((first-number 0) + (form-id (dynamic-url + (input-next-number first-number))) + (first-number-call-back-id (bindf first-number :type 'integer)) + ) + (standard-page () + (<:form :action form-id + (<:p "Please input the first number to add:" + (<:input :type :text + :name first-number-call-back-id + )) + (<:p (<:input :type :submit + :name "OK" + :value "OK")))))) +(defun input-next-number(first-number) + (let* ((second-number 0) + (form-id (dynamic-url + (sum-of-the-two-numbers first-number second-number))) + (second-number-call-back-id (bindf second-number :type 'integer)) + ) + (standard-page () + (<:form :action form-id + (<:p (<:as-html "Add to " first-number "." + " Please input the second number:") + (<:input :type :text + :name second-number-call-back-id)) + (<:p (<:input :type :submit + :name "OK" + :value "OK")))))) +(defun sum-of-the-two-numbers (a b ) + (standard-page () + (<:p (<:as-html a "+" b "=" (+ a b))) + (<:p (<:a :href "." "try again")))) +