c3542bb423a88a28dc3ee5d481484b80a138f826
[lml.git] / lml.cl
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          lml.cl
6 ;;;; Purpose:       Lisp Markup Language functions
7 ;;;; Programmer:    Kevin M. Rosenberg
8 ;;;; Date Started:  Aug 2002
9 ;;;;
10 ;;;; $Id: lml.cl,v 1.13 2002/09/16 10:18:19 kevin Exp $
11 ;;;;
12 ;;;; This file, part of LML, is Copyright (c) 2002 by Kevin M. Rosenberg
13 ;;;;
14 ;;;; LML users are granted the rights to distribute and use this software
15 ;;;; as governed by the terms of the GNU General Public License v2
16 ;;;; (http://www.gnu.org/licenses/gpl.html)
17 ;;;; *************************************************************************
18
19 (declaim (optimize (debug 3) (speed 3) (safety 1) (compilation-speed 0)))
20 (in-package :lml)
21
22 (defun html4-prologue-string ()
23   "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">")
24
25 (defun xml-prologue-string ()
26   "<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"yes\"?>")
27
28 (defun xhtml-prologue-string ()
29   "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
30
31 (defvar *print-spaces* nil)
32 (defvar *indent* 0)
33 (defun reset-indent ()
34   (setq *indent* 0))
35
36 (defun lml-print (str &rest args)
37   (when (streamp *html-output*)
38     (when *print-spaces* (indent-spaces *indent* *html-output*))
39     (if args
40         (apply #'format *html-output* str args)
41       (princ str *html-output*))
42     (when *print-spaces* (format *html-output* "~%"))
43     (values)))
44
45 (defmacro lml-line (str &rest args)
46   `(lml-print ,str ,@args))
47
48 (defun lml-print-date (date)
49   (lml-print (date-string date)))
50
51 (defmacro lml-exec-body (&body forms)
52   `(progn
53     ,@(mapcar
54        #'(lambda (form)
55            (etypecase form
56              (string
57               `(lml-print ,form))
58              (number
59               `(lml-print "~D" ,form))
60              (symbol
61               `(lml-print (string-downcase (symbol-name ,form))))
62              (nil
63               nil)
64              (cons
65               form)))
66        forms)))
67
68 (defmacro with-attr-string (tag attr-string &body body)
69   (let ((attr (gensym)))
70   `(let ((,attr ,attr-string))
71      (lml-print "<~(~A~)~A>" ',tag
72               (if (and (stringp ,attr) (plusp (length ,attr)))
73                   (format nil "~A" ,attr)
74                 ""))
75      (incf *indent*)
76      (lml-exec-body ,@body)
77      (decf *indent*)
78      (lml-print "</~(~A~)>" ',tag))))
79
80 (defun one-keyarg-string (key value)
81   "Return attribute string for keys"
82   (format nil "~(~A~)=\"~A\"" key
83           (typecase value
84             (symbol
85              (string-downcase (symbol-name value)))
86             (string
87              value)
88             (t
89              (eval value)))))
90
91 (defmacro with-keyargs (tag keyargs &body body)
92   (let ((attr (gensym))
93         (kv (gensym)))
94   `(progn
95      (let ((,attr '()))
96        (dolist (,kv ',keyargs)
97          (awhen (cadr ,kv)
98            (push (one-keyarg-string (car ,kv) it) ,attr)))
99        (with-attr-string ,tag (list-to-spaced-string (nreverse ,attr)) ,@body)))))
100
101 (defmacro with (tag &rest args)
102   (let ((body '())
103         (keyargs '())
104         (n (length args)))
105     (do ((i 0 (1+ i)))
106         ((> i (1- n)))
107       (let ((arg (nth i args))
108             (value (when (< (1+ i) n)
109                      (nth (1+ i) args))))
110         (if (keyword-symbol? arg)
111             (progn
112               (push (list arg value) keyargs)
113               (incf i))
114           (push arg body))))
115     `(with-keyargs ,tag ,keyargs ,@(nreverse body))))
116
117
118 (defmacro keyargs-string (&rest args)
119   "Returns a string of attributes and values. Result contains a leading space."
120   (let ((keyarg-list '()))
121     (loop for ( name val ) on args by #'cddr
122           do
123           (when val
124             (push (one-keyarg-string name val) keyarg-list)))
125     (list-to-spaced-string (nreverse keyarg-list))))
126   
127
128 (defmacro xhtml-prologue ()
129   `(progn
130      (lml-print "~A~%" (xml-prologue-string))
131      (lml-print "~A~%" (xhtml-prologue-string))))
132
133 (defmacro link (dest &body body)
134   `(with a :href ,dest ,@body))
135
136 (defmacro link-c (class dest &body body)
137   `(with a :href ,dest :class ,class ,@body))
138
139 (defmacro img (dest &key class id alt style width height align)
140   (let ((attr
141          (eval `(keyargs-string :class ,class :id ,id :alt ,alt :style ,style
142                              :width ,width :height ,height :align ,align))))
143     `(lml-print ,(format nil "<img src=\"~A\"~A />" dest attr))))
144
145 (defmacro input (&key name class id type style size maxlength value)
146   (let ((attr
147          (eval `(keyargs-string :name ,name :class ,class :id ,id :style ,style
148                              :size ,size :maxlength ,maxlength :value ,value
149                              :type ,type))))
150     `(lml-print ,(format nil "<input~A />" attr))))
151
152 (defmacro meta (name content)
153   `(with meta :name ,name :content ,content))
154
155 (defmacro meta-key (&key name content http-equiv)
156   `(with meta :name ,name :content ,content :http-equiv ,http-equiv))
157
158 (defmacro br ()
159   `(lml-print "<br />"))
160
161 (defmacro hr ()
162   `(lml-print "<hr />"))
163
164 (defmacro lml-tag-macro (tag)
165   `(progn
166      (defmacro ,tag (&body body)
167        `(with ,',tag ,@body))
168      (export ',tag)))
169
170 (defmacro lml-tag-class-macro (tag)
171   (let ((name (intern (format nil "~A-~A" tag :c))))
172     `(progn
173        (defmacro ,name (&body body)
174          `(with ,',tag :class ,(car body) ,@(cdr body)))
175        (export ',name))))
176
177 (eval-when (:compile-toplevel :load-toplevel :execute)
178   (defparameter *macro-list*
179     '(a div span h1 h2 h3 h4 h5 h6 i b p li ul ol table tbody td tr body head
180           html title pre tt u dl dt dd kbd code form))
181   (export '(link link-c br hr img input meta meta-key))
182   (export *macro-list*))
183
184 (loop for i in *macro-list*
185       do
186       (eval `(lml-tag-macro ,i))
187       (eval `(lml-tag-class-macro ,i)))
188
189 (defmacro print-page (title &body body)
190   `(html
191     (head
192      (title ,title))
193     (body ,@body)))
194
195 (defmacro page (out-file &body body)
196   `(with-open-file (*html-output*
197                     (lml-file-name ,out-file :output)
198                     :direction :output
199                     :if-exists :supersede)
200      (xhtml-prologue)
201      (html :xmlns "http://www.w3.org/1999/xhtml"
202        ,@body)))
203
204 (defun new-string ()
205   (make-array 1024 :fill-pointer 0 :adjustable t :element-type 'character))
206
207 (set-macro-character #\[
208   #'(lambda (stream char)
209       (declare (ignore char))
210       (let ((forms '())
211             (curr-string (new-string))
212             (paren-level 0)
213             (got-comma nil))
214         (do ((ch (read-char stream t nil t) (read-char stream t nil t)))
215             ((eql ch #\]))
216           (if got-comma
217               (if (eql ch #\()
218                   ;; Starting top-level ,(
219                   (progn
220                     (push `(lml-print ,curr-string) forms)
221                     (setq curr-string (new-string))
222                     (setq got-comma nil)
223                     (vector-push #\( curr-string)
224                     (do ((ch (read-char stream t nil t) (Read-char stream t nil t)))
225                         ((and (eql ch #\)) (zerop paren-level)))
226                       (when (eql ch #\])
227                         (format *trace-output* "Syntax error reading #\]")
228                         (return nil))
229                       (case ch
230                         (#\(
231                          (incf paren-level))
232                         (#\)
233                          (decf paren-level)))
234                       (vector-push-extend ch curr-string))
235                     (vector-push-extend #\) curr-string)
236                     (let ((eval-string (read-from-string curr-string))
237                           (res (gensym)))
238                       (push
239                        `(let ((,res ,eval-string))
240                           (when ,res
241                             (lml-print ,res)))
242                        forms))
243                     (setq curr-string (new-string)))
244                 ;; read comma, then non #\( char
245                 (progn
246                   (unless (eql ch #\,)
247                     (setq got-comma nil))
248                   (vector-push-extend #\, curr-string) ;; push previous command
249                   (vector-push-extend ch curr-string)))
250             ;; previous character is not a comma
251             (if (eql ch #\,)
252                 (setq got-comma t)
253               (progn
254                 (setq got-comma nil)
255                 (vector-push-extend ch curr-string)))))
256         (push `(lml-print ,curr-string) forms)
257         `(progn ,@(nreverse forms)))))
258
259