1 ;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
6 ;;;; Purpose: Lisp Markup Language functions
7 ;;;; Programmer: Kevin M. Rosenberg
8 ;;;; Date Started: Aug 2002
10 ;;;; $Id: base.lisp,v 1.4 2003/01/14 08:41:22 kevin Exp $
12 ;;;; This file, part of LML, is Copyright (c) 2002 by Kevin M. Rosenberg
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 ;;;; *************************************************************************
19 (declaim (optimize (debug 3) (speed 3) (safety 3) (compilation-speed 0)))
22 (defun html4-prologue-string ()
23 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">")
25 (defun xml-prologue-string ()
26 "<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"yes\"?>")
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\">")
31 (defvar *print-spaces* nil)
33 (defun reset-indent ()
36 (defun lml-print (str &rest args)
37 (when (streamp *html-output*)
38 (when *print-spaces* (indent-spaces *indent* *html-output*))
40 (apply #'format *html-output* str args)
41 (princ str *html-output*))
42 (when *print-spaces* (format *html-output* "~%"))
45 (defmacro lml-line (str &rest args)
46 `(lml-print ,str ,@args))
48 (defun lml-print-date (date)
49 (lml-print (date-string date)))
51 (defmacro lml-exec-body (&body forms)
59 `(lml-print "~D" ,form))
67 (defmacro with-attr-string (tag attr-string &body body)
68 (let ((attr (gensym)))
69 `(let ((,attr ,attr-string))
70 (lml-print "<~(~A~)~A>" ',tag
71 (if (and (stringp ,attr) (plusp (length ,attr)))
72 (format nil "~A" ,attr)
75 (lml-exec-body ,@body)
77 (lml-print "</~(~A~)>" ',tag))))
79 (defmacro with-no-endtag-attr-string (tag attr-string)
80 (let ((attr (gensym)))
81 `(let ((,attr ,attr-string))
82 (lml-print "<~(~A~)~A />" ',tag
83 (if (and (stringp ,attr) (plusp (length ,attr)))
84 (format nil "~A" ,attr)
87 (defun one-keyarg-string (key value)
88 "Return attribute string for keys"
89 (format nil "~(~A~)=\"~A\"" key
92 (string-downcase (symbol-name value)))
98 (defmacro with-keyargs (tag keyargs &body body)
103 (dolist (,kv ,keyargs)
105 (push (one-keyarg-string (car ,kv) it) ,attr)))
106 (with-attr-string ,tag (list-to-spaced-string (nreverse ,attr)) ,@body)))))
108 (defmacro with-no-endtag-keyargs (tag keyargs)
109 (let ((attr (gensym))
113 (dolist (,kv ,keyargs)
115 (push (one-keyarg-string (car ,kv) it) ,attr)))
116 (with-no-endtag-attr-string ,tag (list-to-spaced-string (nreverse ,attr)))))))
118 (defmacro bind-one-keyarg (keyarg)
119 `(list ,(car keyarg) ,(cdr keyarg)))
121 (defmacro bind-all-keyargs (keyargs)
122 "Convert a list of keyarg pairs and convert eval/bind arguments"
123 (let* ((npairs (length keyargs))
124 (syms (make-array npairs))
127 (declare (dynamic-extent syms))
129 (setf (aref syms i) (gensym)))
130 `(let ,(mapcar #'(lambda (ka)
132 (list (aref syms ipair) (cdr ka))
135 (list ,@(mapcar #'(lambda (ka)
137 `(cons ,(car ka) ,(aref syms ipair2))
141 (defmacro with (tag &rest args)
142 "Return a list of keyargs and also the body of LML form"
145 (bound-keyargs (gensym)))
146 (do* ((n (length args))
148 (arg (nth i args) (nth i args))
149 (value (when (< (1+ i) n)
153 ((or (not (keyword-symbol? arg))
156 (push (nth (+ i j) args) body)))
157 (push (cons arg value) keyargs))
158 (setq keyargs (nreverse keyargs))
159 (setq body (nreverse body))
160 `(let ((,bound-keyargs ,(macroexpand `(bind-all-keyargs ,keyargs))))
161 ,(macroexpand `(with-keyargs ,tag ,bound-keyargs ,@body)))))
163 (defmacro with-no-endtag (tag &rest args)
164 "Return a list of keyargs body of LML form"
166 (bound-keyargs (gensym)))
167 (do* ((n (length args))
169 (arg (nth i args) (nth i args))
170 (value (when (< (1+ i) n)
174 ((or (not (keyword-symbol? arg))
176 (push (cons arg value) keyargs))
177 (setq keyargs (nreverse keyargs))
178 `(let ((,bound-keyargs ,(macroexpand `(bind-all-keyargs ,keyargs))))
179 ,(macroexpand `(with-no-endtag-keyargs ,tag ,bound-keyargs)))))
181 (defmacro xhtml-prologue ()
183 (lml-print "~A~%" (xml-prologue-string))
184 (lml-print "~A~%" (xhtml-prologue-string))))
186 (defmacro link (dest &body body)
187 `(with a :href ,dest ,@body))
189 (defmacro link-c (class dest &body body)
190 `(with a :href ,dest :class (quote ,class) ,@body))
192 (defmacro img (dest &rest args)
193 `(with-no-endtag :src ,dest ,@args))
195 (defmacro input (&rest args)
196 `(with-no-endtag input ,@args))
198 (defmacro meta (name content)
199 `(with meta :name ,name :content ,content))
201 (defmacro meta-key (&key name content http-equiv)
202 `(with meta :name ,name :content ,content :http-equiv ,http-equiv))
204 (defmacro br (&rest args)
205 `(with-no-endtag br ,@args))
207 (defmacro hr (&rest args)
208 `(with-no-endtag hr ,@args))
210 (defmacro lml-tag-macro (tag)
212 (defmacro ,tag (&body body)
213 `(with ,',tag ,@body))
216 (defmacro lml-tag-class-macro (tag)
217 (let ((name (intern (format nil "~A-~A" tag :c))))
219 (defmacro ,name (&body body)
220 `(with ,',tag :class (quote ,(car body)) ,@(cdr body)))
223 (eval-when (:compile-toplevel :load-toplevel :execute)
224 (defparameter *macro-list*
225 '(a div span h1 h2 h3 h4 h5 h6 i b p li ul ol table tbody td tr body head
226 html title pre tt u dl dt dd kbd code form))
227 (export '(link link-c br hr img input meta meta-key))
228 (export *macro-list*))
230 (loop for i in *macro-list*
232 (eval `(lml-tag-macro ,i))
233 (eval `(lml-tag-class-macro ,i)))
235 (defmacro print-page (title &body body)
241 (defmacro page (out-file &body body)
242 `(with-open-file (*html-output*
243 (lml-file-name ,out-file :output)
245 :if-exists :supersede)
247 (html :xmlns "http://www.w3.org/1999/xhtml"
251 (make-array 1024 :fill-pointer 0 :adjustable t :element-type 'character))
253 (set-macro-character #\[
254 #'(lambda (stream char)
255 (declare (ignore char))
257 (curr-string (new-string))
260 (declare (type fixnum paren-level))
261 (do ((ch (read-char stream t nil t) (read-char stream t nil t)))
265 ;; Starting top-level ,(
268 (setf curr-string (coerce curr-string `(simple-array character (*))))
270 (push `(lml-print ,curr-string) forms)
271 (setq curr-string (new-string))
273 (vector-push #\( curr-string)
274 (do ((ch (read-char stream t nil t) (Read-char stream t nil t)))
275 ((and (eql ch #\)) (zerop paren-level)))
277 (format *trace-output* "Syntax error reading #\]")
284 (vector-push-extend ch curr-string))
285 (vector-push-extend #\) curr-string)
286 (let ((eval-string (read-from-string curr-string))
289 `(let ((,res ,eval-string))
293 (setq curr-string (new-string)))
294 ;; read comma, then non #\( char
297 (setq got-comma nil))
298 (vector-push-extend #\, curr-string) ;; push previous command
299 (vector-push-extend ch curr-string)))
300 ;; previous character is not a comma
305 (vector-push-extend ch curr-string)))))
308 (setf curr-string (coerce curr-string `(simple-array character (*))))
310 (push `(lml-print ,curr-string) forms)
311 `(progn ,@(nreverse forms)))))