Skip to content

Instantly share code, notes, and snippets.

@emctague
Created September 16, 2021 17:25
Show Gist options
  • Save emctague/d7c6b37a9e9d9fb863fe0ba0363adcf5 to your computer and use it in GitHub Desktop.
Save emctague/d7c6b37a9e9d9fb863fe0ba0363adcf5 to your computer and use it in GitHub Desktop.
LHTML - Convert S-Expressions to HTML
; LHTML
; -----
;
; Generate HTML code from a sequence of lisp s-expressions.
; This operates on stdin and prints to stdout.
;
; (For clisp.)
;
; Please for the love of all that is good do not use this in production.
; The code is barely readable!
;
; Example input:
;
; (div :id "my-div" :style "font-family: sans-serif;"
; (img :src "icon.png" /)
; (p (b "This") "is content that ends up inside the element!"))
;
;
; Example output:
;
; <div id="my-div" style="font-family: sans-serif;">
; <img src="icon.png"/>
; <p>
; <b>
; This
; </b>
; is content that ends up inside the element!
; </p>
; </div>
(defun ind (indent)
"Print (indent * 4) spaces to standard output"
(format t "~va" (* indent 4) ""))
; Syntax:
; (tag-name [:attribute-name "attribute-value"]* [[child-node]* | [/]])
; - :attribute-name "attribute-value" pairs become attributes on the tag
; - child-node values, either strings with text content or nested elements,
; are inserted into this element
; - '/' token, if provided instead of children, makes this element have no
; children or closing tagt
(defun decode-element (seq)
"Parse an element s-expression into (flag for if '/' is present, list of attribute k/v pairs, list of child nodes)"
(let ((prevkey nil) (slashfound nil))
(loop for i in seq
if prevkey collect (list prevkey i) into keywords and do (setq prevkey nil)
else if (eql i '/) do (setq slashfound t)
else if (keywordp i) do (setq prevkey i)
else collect i into children
finally (return (list slashfound keywords children)))))
(defun element (indent elem)
"Convert an element s-expression to HTML. See decode-element for formatting."
(destructuring-bind (slashfound keywords children) (decode-element (cdr elem))
(format t "<~(~a~)~{ ~{~(~a~)=\"~a\"~}~}~:[~;/~]>~%" (car elem) keywords slashfound)
(if (not slashfound) (progn
(loop for ch in children do (node (+ indent 1) ch))
(ind indent)
(format t "</~(~a~)>~%" (car elem))))))
(defun node (indent elem)
"Parse an s-expression into HTML content - either a string or parenthesized expression."
(ind indent)
(cond
((stringp elem) (format t "~a~%" elem))
((listp elem) (element indent elem))
(t (format t "Error: what is ~a?~%" elem))))
; Begin Read Loop
(loop for line = (read t NIL)
while line do
(node 0 line))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment