diff options
-rw-r--r-- | zencoding.el | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/zencoding.el b/zencoding.el new file mode 100644 index 0000000..6b0efb7 --- /dev/null +++ b/zencoding.el @@ -0,0 +1,144 @@ +;; Demo + +;; (transform (car (expr "a>b>c+d+e"))) +;; => <a><b><c></c><d></d><e></e></b></a> + +;; (transform (car (expr "html>head+(body>p)"))) +;; => <html><head></head><body><p></p></body></html> + +;; (transform (car (expr "html>head+(body>p+(ul>li))"))) +;; => [indentation added with xml-mode] +;; <html> +;; <head> +;; </head> +;; <body> +;; <p> +;; </p> +;; <ul> +;; <li> +;; </li> +;; </ul> +;; </body> +;; </html> + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Generic parsing macros and utilities + +(defmacro aif (test-form then-form &rest else-forms) + "Anaphoric if. Temporary variable `it' is the result of test-form." + `(let ((it ,test-form)) + (if it ,then-form ,@else-forms))) + +(defmacro pif (test-form then-form &rest else-forms) + "Parser anaphoric if. Temporary variable `it' is the result of test-form." + `(let ((it ,test-form)) + (if (not (eq 'error (car it))) ,then-form ,@else-forms))) + +(defmacro parse (regex nums label &rest body) + "Parse according to a regex and update the `input' variable." + `(aif (regex ,regex input ',(number-sequence 0 nums)) + (let ((input (elt it ,nums))) + ,@body) + `,`(error ,(concat "expected " ,label)))) + +(defmacro run (parser then-form &rest else-forms) + "Run a parser and update the input properly, extract the parsed + expression." + `(pif (,parser input) + (let ((input (cdr it)) + (expr (car it))) + ,then-form) + ,@else-forms)) + +(defmacro por (parser1 parser2 then-form &rest else-forms) + "OR two parsers. Try one parser, if it fails try the next." + `(pif (,parser1 input) + (let ((input (cdr it)) + (expr (car it))) + ,then-form) + (pif (,parser2 input) + (let ((input (cdr it)) + (expr (car it))) + ,then-form) + ,@else-forms))) + +(defun regex (regexp string refs) + "Return a list of (`ref') matches for a `regex' on a `string' or nil." + (if (string-match (concat "^" regexp "\\(.*\\)$") string) + (mapcar (lambda (ref) (match-string ref string)) + (if (sequencep refs) refs (list refs))) + nil)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Zen coding parsers + +(defun expr (input) + "Parse a zen coding expression." + (pif (siblings input) + it + (pif (parent-child input) + it + (pif (pexpr input) + it + (pif (atom input) + it + '(error "no match, expecting ( or a-zA-Z0-9")))))) + +(defun atom (input) + "Parse a simple a-zA-Z0-9 atom (e.g. html/head/xsl:if/br)." + (parse "\\([a-zA-Z0-9]+\\)" 2 "atom, a-zA-Z0-9" + `((atom . ,(elt it 1)) . ,input))) + +(defun pexpr (input) + "A zen coding expression with parentheses around it." + (parse "(" 1 "(" + (run expr + (aif (regex ")" input '(0 1)) + `(,expr . ,(elt it 1)) + '(error "expecting `)'"))))) + +(defun parent-child (input) + "Parse an atom>e expression, where `n' is an atom and `e' is any + expression." + (run atom + (let ((parent expr)) + (parse ">" 1 ">" + (run expr + (let ((child expr)) + `((parent-child ,parent ,child) . ,input)) + '(error "expected child")))) + '(error "expected parent"))) + +(defun sibling (input) + (por pexpr atom + it + '(error "expected sibling"))) + +(defun siblings (input) + "Parse an e+e expression, where e is an atom." + (por pexpr atom + (let ((parent expr)) + (parse "+" 1 "+" + (por siblings sibling + (let ((child expr)) + `((siblings ,parent ,child) . ,input)) + '(error "expected second sibling")))) + '(error "expected first sibling"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Zen coding transformer from AST to HTML + +(defun make-tag (name &optional content) + (concat "<" name ">\n" (if content content "") "</" name ">\n")) + +(defun transform (ast) + (cond + ((eq (car ast) 'atom) (make-tag (cdr ast))) + ((eq (car ast) 'parent-child) + (let ((parent (cdadr ast)) + (children (transform (caddr ast)))) + (make-tag parent children))) + ((eq (car ast) 'siblings) + (let ((sib1 (transform (cadr ast))) + (sib2 (transform (caddr ast)))) + (concat sib1 sib2)))))
\ No newline at end of file |