Update domain name to kpe.io
[lml.git] / base.lisp
index 85a59117fc1b81e94cf59e178e1a86de1011a5da..77ffa089e5d0fe726ec731b24f46bd6503ebffee 100644 (file)
--- a/base.lisp
+++ b/base.lisp
@@ -1,13 +1,13 @@
-;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
 ;;;; *************************************************************************
 ;;;; FILE IDENTIFICATION
 ;;;;
-;;;; Name:          lml.cl
+;;;; Name:          base.lisp
 ;;;; Purpose:       Lisp Markup Language functions
 ;;;; Programmer:    Kevin M. Rosenberg
 ;;;; Date Started:  Aug 2002
 ;;;;
-;;;; $Id: base.lisp,v 1.1 2002/12/29 09:10:41 kevin Exp $
+;;;; $Id$
 ;;;;
 ;;;; This file, part of LML, is Copyright (c) 2002 by Kevin M. Rosenberg
 ;;;;
 ;;;; (http://www.gnu.org/licenses/gpl.html)
 ;;;; *************************************************************************
 
-(declaim (optimize (debug 3) (speed 3) (safety 3) (compilation-speed 0)))
-(in-package :lml)
+(in-package #:lml)
 
 (defun html4-prologue-string ()
   "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">")
 
 (defun xml-prologue-string ()
-  "<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"yes\"?>")
+  "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")
 
 (defun xhtml-prologue-string ()
-  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
+  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml10-strict.dtd\">")
 
 (defvar *print-spaces* nil)
 (defvar *indent* 0)
 (defun reset-indent ()
   (setq *indent* 0))
 
-(defun lml-print (str &rest args)
+(defun lml-format (str &rest args)
   (when (streamp *html-output*)
     (when *print-spaces* (indent-spaces *indent* *html-output*))
     (if args
-       (apply #'format *html-output* str args)
-      (princ str *html-output*))
-    (when *print-spaces* (format *html-output* "~%"))
-    (values)))
+        (apply #'format *html-output* str args)
+      (write-string str *html-output*))
+    (when *print-spaces* (write-char #\newline *html-output*))))
 
-(defmacro lml-line (str &rest args)
-  `(lml-print ,str ,@args))
+(defun lml-princ (s)
+  (princ s *html-output*))
+
+(defun lml-print (s)
+  (format *html-output* "~A~%" s))
+
+(defun lml-write-char (char)
+  (write-char char *html-output*))
+
+(defun lml-write-string (str)
+  (write-string str *html-output*))
 
 (defun lml-print-date (date)
-  (lml-print (date-string date)))
+  (lml-write-string (date-string date)))
 
 (defmacro lml-exec-body (&body forms)
   `(progn
     ,@(mapcar
        #'(lambda (form)
-          (etypecase form
-            (string
-             `(lml-print ,form))
-            (number
-             `(lml-print "~D" ,form))
-            (symbol
-             (when form
-             `(lml-print (string-downcase (symbol-name ,form)))))
-            (cons
-             form)))
+           (etypecase form
+             (string
+              `(lml-princ ,form))
+             (number
+              `(lml-format "~D" ,form))
+             (symbol
+              (when form
+              `(lml-princ ,form)))
+             (cons
+              form)))
        forms)))
 
 (defmacro with-attr-string (tag attr-string &body body)
   (let ((attr (gensym)))
   `(let ((,attr ,attr-string))
-     (lml-print "<~(~A~)~A>" ',tag
-             (if (and (stringp ,attr) (plusp (length ,attr)))
-                 (format nil "~A" ,attr)
-               ""))
+     (lml-format "<~(~A~)~A>" ',tag
+              (if (and (stringp ,attr) (plusp (length ,attr)))
+                  (format nil " ~A" ,attr)
+                ""))
      (incf *indent*)
      (lml-exec-body ,@body)
      (decf *indent*)
-     (lml-print "</~(~A~)>" ',tag))))
+     (lml-format "</~(~A~)>" ',tag))))
+
+(defmacro with-no-endtag-attr-string (tag attr-string)
+  (let ((attr (gensym)))
+  `(let ((,attr ,attr-string))
+     (lml-format "<~(~A~)~A />" ',tag
+              (if (and (stringp ,attr) (plusp (length ,attr)))
+                  (format nil " ~A" ,attr)
+                  "")))))
 
 (defun one-keyarg-string (key value)
   "Return attribute string for keys"
   (format nil "~(~A~)=\"~A\"" key
-         (typecase value
-           (symbol
-            (string-downcase (symbol-name value)))
-           (string
-            value)
-           (t
-            (eval value)))))
+          (typecase value
+            (symbol
+             (string-downcase (symbol-name value)))
+            (string
+             value)
+            (t
+             (eval value)))))
 
 (defmacro with-keyargs (tag keyargs &body body)
   (let ((attr (gensym))
-       (kv (gensym)))
+        (kv (gensym)))
   `(progn
      (let ((,attr '()))
-       (dolist (,kv ',keyargs)
-        (awhen (cadr ,kv)
-          (push (one-keyarg-string (car ,kv) it) ,attr)))
+       (dolist (,kv ,keyargs)
+         (awhen (cdr ,kv)
+           (push (one-keyarg-string (car ,kv) it) ,attr)))
        (with-attr-string ,tag (list-to-spaced-string (nreverse ,attr)) ,@body)))))
 
+(defmacro with-no-endtag-keyargs (tag keyargs)
+  (let ((attr (gensym))
+        (kv (gensym)))
+  `(progn
+     (let ((,attr '()))
+       (dolist (,kv ,keyargs)
+         (awhen (cdr ,kv)
+           (push (one-keyarg-string (car ,kv) it) ,attr)))
+       (with-no-endtag-attr-string ,tag (list-to-spaced-string (nreverse ,attr)))))))
+
+(defmacro bind-one-keyarg (keyarg)
+  `(list ,(car keyarg) ,(cdr keyarg)))
+
+(defmacro bind-all-keyargs (keyargs)
+  "Convert a list of keyarg pairs and convert eval/bind arguments"
+  (let* ((npairs (length keyargs))
+         (syms (make-array npairs))
+         (ipair 0)
+         (ipair2 0))
+    (declare (dynamic-extent syms))
+    (dotimes (i npairs)
+      (setf (aref syms i) (gensym)))
+    `(let ,(mapcar #'(lambda (ka)
+                       (prog1
+                           (list (aref syms ipair) (cdr ka))
+                         (incf ipair)))
+                   keyargs)
+      (list ,@(mapcar #'(lambda (ka)
+                          (prog1
+                              `(cons ,(car ka) ,(aref syms ipair2))
+                            (incf ipair2)))
+                      keyargs)))))
+
 (defmacro with (tag &rest args)
+  "Return a list of keyargs and also the body of LML form"
   (let ((body '())
-       (keyargs '())
-       (n (length args)))
-    (do ((i 0 (1+ i)))
-       ((> i (1- n)))
-      (let ((arg (nth i args))
-           (value (when (< (1+ i) n)
-                    (nth (1+ i) args))))
-       (if (keyword-symbol? arg)
-           (progn
-             (push (list arg value) keyargs)
-             (incf i))
-         (push arg body))))
-    `(with-keyargs ,tag ,keyargs ,@(nreverse body))))
-
-
-(defmacro keyargs-string (&rest args)
-  "Returns a string of attributes and values. Result contains a leading space."
-  (let ((keyarg-list '()))
-    (loop for ( name val ) on args by #'cddr
-         do
-         (when val
-           (push (one-keyarg-string name val) keyarg-list)))
-    (list-to-spaced-string (nreverse keyarg-list))))
-  
+        (keyargs '())
+        (bound-keyargs (gensym)))
+    (do* ((n (length args))
+          (i 0 (+ 2 i))
+          (arg (nth i args) (nth i args))
+          (value (when (< (1+ i) n)
+                   (nth (1+ i) args))
+                 (when (< (1+ i) n)
+                   (nth (1+ i) args))))
+         ((or (not (keyword-symbol? arg))
+              (>= i n))
+          (dotimes (j (- n i))
+            (push (nth (+ i j) args) body)))
+      (push (cons arg value) keyargs))
+    (setq keyargs (nreverse keyargs))
+    (setq body (nreverse body))
+    `(let ((,bound-keyargs ,(macroexpand `(bind-all-keyargs ,keyargs))))
+       ,(macroexpand `(with-keyargs ,tag ,bound-keyargs ,@body)))))
+
+(defmacro with-no-endtag (tag &rest args)
+  "Return a list of keyargs body of LML form"
+  (let ((keyargs '())
+        (bound-keyargs (gensym)))
+    (do* ((n (length args))
+          (i 0 (+ 2 i))
+          (arg (nth i args) (nth i args))
+          (value (when (< (1+ i) n)
+                   (nth (1+ i) args))
+                 (when (< (1+ i) n)
+                   (nth (1+ i) args))))
+        ((or (not (keyword-symbol? arg))
+             (>= i n)))
+      (push (cons arg value) keyargs))
+    (setq keyargs (nreverse keyargs))
+    `(let ((,bound-keyargs ,(macroexpand `(bind-all-keyargs ,keyargs))))
+      ,(macroexpand `(with-no-endtag-keyargs ,tag ,bound-keyargs)))))
+
+(defmacro jscript (&body body)
+  `(with script :language "JavaScript" :type "text/javascript"
+         ,@body))
 
 (defmacro xhtml-prologue ()
   `(progn
-     (lml-print "~A~%" (xml-prologue-string))
-     (lml-print "~A~%" (xhtml-prologue-string))))
+     (lml-format "~A~%" (xml-prologue-string))
+     (lml-format "~A~%" (xhtml-prologue-string))))
 
-(defmacro link (dest &body body)
+(defmacro alink (dest &body body)
   `(with a :href ,dest ,@body))
 
-(defmacro link-c (class dest &body body)
-  `(with a :href ,dest :class ,class ,@body))
+(defmacro alink-c (class dest &body body)
+  `(with a :href ,dest :class (quote ,class) ,@body))
 
-(defmacro img (dest &key class id alt style width height align)
-  (let ((attr
-        (eval `(keyargs-string :class ,class :id ,id :alt ,alt :style ,style
-                            :width ,width :height ,height :align ,align))))
-    `(lml-print ,(format nil "<img src=\"~A\"~A />" dest attr))))
+(defmacro img (dest &rest args)
+  `(with-no-endtag img :src ,dest ,@args))
 
-(defmacro input (&key name class id type style size maxlength value checked)
-  (let ((attr
-        (eval `(keyargs-string :name ,name :class ,class :id ,id :style ,style
-                            :size ,size :maxlength ,maxlength :value ,value
-                            :type ,type :checked ,checked))))
-    `(lml-print ,(format nil "<input~A />" attr))))
+(defmacro input (&rest args)
+  `(with-no-endtag input ,@args))
 
-(defmacro meta (name content)
-  `(with meta :name ,name :content ,content))
+(defmacro link (&rest args)
+  `(with-no-endtag link ,@args))
 
-(defmacro meta-key (&key name content http-equiv)
-  `(with meta :name ,name :content ,content :http-equiv ,http-equiv))
+(defmacro meta (&rest args)
+  `(with-no-endtag meta ,@args))
 
-(defmacro br ()
-  `(lml-print "<br />"))
+(defmacro br (&rest args)
+  `(with-no-endtag br ,@args))
 
-(defmacro hr ()
-  `(lml-print "<hr />"))
+(defmacro hr (&rest args)
+  `(with-no-endtag hr ,@args))
 
 (defmacro lml-tag-macro (tag)
   `(progn
   (let ((name (intern (format nil "~A-~A" tag :c))))
     `(progn
        (defmacro ,name (&body body)
-        `(with ,',tag :class ,(car body) ,@(cdr body)))
+         `(with ,',tag :class (quote ,(car body)) ,@(cdr body)))
        (export ',name))))
 
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defparameter *macro-list*
-    '(a div span h1 h2 h3 h4 h5 h6 i b p li ul ol table tbody td tr body head
-         html title pre tt u dl dt dd kbd code form))
-  (export '(link link-c br hr img input meta meta-key))
+    '(a div span h1 h2 h3 h4 h5 h6 i b p li ul ol table tbody td th tr body
+      head html title pre tt u dl dt dd kbd code form textarea blockquote
+      var strong small samp big cite address dfn em q area del ins
+      object param caption col colgroup script noscript))
+  (export '(jscript alink alink-c br hr img input meta link meta-key))
   (export *macro-list*))
 
 (loop for i in *macro-list*
 
 (defmacro page (out-file &body body)
   `(with-open-file (*html-output*
-                   (lml-file-name ,out-file :output)
-                   :direction :output
-                   :if-exists :supersede)
+                    (lml-file-name ',out-file :output)
+                    :direction :output
+                    :if-exists :supersede)
      (xhtml-prologue)
      (html :xmlns "http://www.w3.org/1999/xhtml"
        ,@body)))
 (defun new-string ()
   (make-array 1024 :fill-pointer 0 :adjustable t :element-type 'character))
 
-(set-macro-character #\[
-  #'(lambda (stream char)
-      (declare (ignore char))
-      (let ((forms '())
-           (curr-string (new-string))
-           (paren-level 0)
-           (got-comma nil))
-       (declare (type fixnum paren-level))
-       (do ((ch (read-char stream t nil t) (read-char stream t nil t)))
-           ((eql ch #\]))
-         (if got-comma
-             (if (eql ch #\()
-                 ;; Starting top-level ,(
-                 (progn
-                   #+cmu
-                   (setf curr-string (coerce curr-string `(simple-array character (*))))
-       
-                   (push `(lml-print ,curr-string) forms)
-                   (setq curr-string (new-string))
-                   (setq got-comma nil)
-                   (vector-push #\( curr-string)
-                   (do ((ch (read-char stream t nil t) (Read-char stream t nil t)))
-                       ((and (eql ch #\)) (zerop paren-level)))
-                     (when (eql ch #\])
-                       (format *trace-output* "Syntax error reading #\]")
-                       (return nil))
-                     (case ch
-                       (#\(
-                        (incf paren-level))
-                       (#\)
-                        (decf paren-level)))
-                     (vector-push-extend ch curr-string))
-                   (vector-push-extend #\) curr-string)
-                   (let ((eval-string (read-from-string curr-string))
-                         (res (gensym)))
-                     (push
-                      `(let ((,res ,eval-string))
-                         (when ,res
-                           (lml-print ,res)))
-                      forms))
-                   (setq curr-string (new-string)))
-               ;; read comma, then non #\( char
-               (progn
-                 (unless (eql ch #\,)
-                   (setq got-comma nil))
-                 (vector-push-extend #\, curr-string) ;; push previous command
-                 (vector-push-extend ch curr-string)))
-           ;; previous character is not a comma
-           (if (eql ch #\,)
-               (setq got-comma t)
-             (progn
-               (setq got-comma nil)
-               (vector-push-extend ch curr-string)))))
-
-       #+cmu
-       (setf curr-string (coerce curr-string `(simple-array character (*))))
-       
-       (push `(lml-print ,curr-string) forms)
-       `(progn ,@(nreverse forms)))))
-
-                    
+
+
+