aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Done <chrisdone@gmail.com>2009-11-07 09:37:38 +0000
committerChris Done <chrisdone@gmail.com>2009-11-07 09:37:38 +0000
commit727fc744b8fb619bbd6d5861e53b43c83ff2831d (patch)
treef502eb7d9be7d92bc2a61606f0fdabac15db7a3b
parentf25237af8c869a4f44a48858379e1d73dc66e6a8 (diff)
downloademmet-mode-727fc744b8fb619bbd6d5861e53b43c83ff2831d.tar.lz
emmet-mode-727fc744b8fb619bbd6d5861e53b43c83ff2831d.tar.xz
emmet-mode-727fc744b8fb619bbd6d5861e53b43c83ff2831d.zip
Now with multipliers (li*5>span), a mini test suite and qualified symbol names
-rw-r--r--zencoding.el302
1 files changed, 198 insertions, 104 deletions
diff --git a/zencoding.el b/zencoding.el
index 9737dad..bdcdba5 100644
--- a/zencoding.el
+++ b/zencoding.el
@@ -1,45 +1,83 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Demos
+
+;; (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>
+
+;; (transform (car (expr "body.sub-page>div#news.content.a+div#news.content.a")))
+;; => [indentation added with xml-mode]
+;; <body class="sub-page">
+;; <div id="news" class="content a">
+;; </div>
+;; <div id="news" class="content a">
+;; </div>
+;; </body>
+
+;; (transform (car (expr "a#q.x>b#q.x*2")))
+;; => <a id="q" class="x"><b id="q" class="x"></b><b id="q" class="x"></b></a>
+
+;; See ``Test cases'' section for a complete set of expression types.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Generic parsing macros and utilities
-(defmacro aif (test-form then-form &rest else-forms)
+(defmacro zencoding-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)
+(defmacro zencoding-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)
+(defmacro zencoding-parse (regex nums label &rest body)
"Parse according to a regex and update the `input' variable."
- `(aif (regex ,regex input ',(number-sequence 0 nums))
+ `(zencoding-aif (zencoding-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)
+(defmacro zencoding-run (parser then-form &rest else-forms)
"Run a parser and update the input properly, extract the parsed
expression."
- `(pif (,parser input)
+ `(zencoding-pif (,parser input)
(let ((input (cdr it))
(expr (car it)))
,then-form)
,@else-forms))
-(defmacro por (parser1 parser2 then-form &rest else-forms)
+(defmacro zencoding-por (parser1 parser2 then-form &rest else-forms)
"OR two parsers. Try one parser, if it fails try the next."
- `(pif (,parser1 input)
+ `(zencoding-pif (,parser1 input)
(let ((input (cdr it))
(expr (car it)))
,then-form)
- (pif (,parser2 input)
+ (zencoding-pif (,parser2 input)
(let ((input (cdr it))
(expr (car it)))
,then-form)
,@else-forms)))
-(defun regex (regexp string refs)
+(defun zencoding-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))
@@ -49,30 +87,40 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Zen coding parsers
-(defun expr (input)
- "Parse a zen coding expression."
- (run siblings
+(defun zencoding-expr (input)
+ "Parse a zen coding expression. This pretty much defines precedence."
+ (zencoding-run zencoding-siblings
it
- (run parent-child
+ (zencoding-run zencoding-parent-child
it
- (run pexpr
+ (zencoding-run zencoding-multiplier
it
- (run tag
+ (zencoding-run zencoding-pexpr
it
- '(error "no match, expecting ( or a-zA-Z0-9"))))))
+ (zencoding-run zencoding-tag
+ it
+ '(error "no match, expecting ( or a-zA-Z0-9")))))))
-(defun tag (input)
+(defun zencoding-multiplier (input)
+ (zencoding-por zencoding-pexpr zencoding-tag
+ (let ((multiplier expr))
+ (zencoding-parse "\\*\\([0-9]+\\)" 2 "*n where n is a number"
+ (let ((multiplicand (read (elt it 1))))
+ `((list ,(make-list multiplicand multiplier)) . ,input))))
+ '(error "expected *n multiplier")))
+
+(defun zencoding-tag (input)
"Parse a tag."
- (run tagname
+ (zencoding-run zencoding-tagname
(let ((result it)
(tagname (cdr expr)))
- (run identifier
- (tag-classes `(tag ,tagname ((id ,(cddr expr)))) input)
- (tag-classes `(tag ,tagname ()) input)))
+ (zencoding-run zencoding-identifier
+ (zencoding-tag-classes `(tag ,tagname ((id ,(cddr expr)))) input)
+ (zencoding-tag-classes `(tag ,tagname ()) input)))
'(error "expected tagname")))
-(defun tag-classes (tag input)
- (run classes
+(defun zencoding-tag-classes (tag input)
+ (zencoding-run zencoding-classes
(let ((tagname (cadr tag))
(props (caddr tag))
(classes `(class ,(mapconcat
@@ -83,68 +131,91 @@
`((tag ,tagname ,(append props (list classes))) . ,input))
`(,tag . ,input)))
-(defun tagname (input)
+(defun zencoding-tagname (input)
"Parse a tagname a-zA-Z0-9 tagname (e.g. html/head/xsl:if/br)."
- (parse "\\([a-zA-Z0-9:-]+\\)" 2 "tagname, a-zA-Z0-9"
+ (zencoding-parse "\\([a-zA-Z0-9:-]+\\)" 2 "tagname, a-zA-Z0-9"
`((tagname . ,(elt it 1)) . ,input)))
-(defun pexpr (input)
+(defun zencoding-pexpr (input)
"A zen coding expression with parentheses around it."
- (parse "(" 1 "("
- (run expr
- (aif (regex ")" input '(0 1))
+ (zencoding-parse "(" 1 "("
+ (zencoding-run zencoding-expr
+ (zencoding-aif (zencoding-regex ")" input '(0 1))
`(,expr . ,(elt it 1))
'(error "expecting `)'")))))
-(defun parent-child (input)
+(defun zencoding-parent-child (input)
"Parse an tag>e expression, where `n' is an tag and `e' is any
expression."
- (run tag
- (let ((parent expr))
- (parse ">" 1 ">"
- (run expr
- (let ((child expr))
- `((parent-child ,parent ,child) . ,input))
- '(error "expected child"))))
- '(error "expected parent")))
+ (zencoding-run zencoding-multiplier
+ (let* ((items (cadr expr))
+ (rest (zencoding-child-sans expr input)))
+ (if (not (eq (car rest) 'error))
+ (let ((child (car rest))
+ (input (cdr rest)))
+ (cons (cons 'list
+ (cons (mapcar (lambda (parent)
+ `(parent-child ,parent ,child))
+ items)
+ nil))
+ input))
+ '(error "expected child")))
+ (zencoding-run zencoding-tag
+ (zencoding-child expr input)
+ '(error "expected parent"))))
+
+(defun zencoding-child-sans (parent input)
+ (zencoding-parse ">" 1 ">"
+ (zencoding-run zencoding-expr
+ it
+ '(error "expected child"))))
+
+(defun zencoding-child (parent input)
+ (zencoding-parse ">" 1 ">"
+ (zencoding-run zencoding-expr
+ (let ((child expr))
+ `((parent-child ,parent ,child) . ,input))
+ '(error "expected child"))))
-(defun sibling (input)
- (por pexpr tag
+(defun zencoding-sibling (input)
+ (zencoding-por zencoding-pexpr zencoding-multiplier
it
- '(error "expected sibling")))
+ (zencoding-run zencoding-tag
+ it
+ '(error "expected sibling"))))
-(defun siblings (input)
+(defun zencoding-siblings (input)
"Parse an e+e expression, where e is an tag or a pexpr."
- (por pexpr tag
+ (zencoding-run zencoding-sibling
(let ((parent expr))
- (parse "\\+" 1 "+"
- (por siblings sibling
+ (zencoding-parse "\\+" 1 "+"
+ (zencoding-run zencoding-expr
(let ((child expr))
- `((siblings ,parent ,child) . ,input))
+ `((zencoding-siblings ,parent ,child) . ,input))
'(error "expected second sibling"))))
'(error "expected first sibling")))
-(defun name (input)
+(defun zencoding-name (input)
"Parse a class or identifier name, e.g. news, footer, mainimage"
- (parse "\\([a-zA-Z][a-zA-Z0-9-_]*\\)" 2 "class or identifer name"
+ (zencoding-parse "\\([a-zA-Z][a-zA-Z0-9-_]*\\)" 2 "class or identifer name"
`((name . ,(elt it 1)) . ,input)))
-(defun class (input)
+(defun zencoding-class (input)
"Parse a classname expression, e.g. .foo"
- (parse "\\." 1 "."
- (run name
+ (zencoding-parse "\\." 1 "."
+ (zencoding-run zencoding-name
`((class ,expr) . ,input)
'(error "expected class name"))))
-(defun identifier (input)
+(defun zencoding-identifier (input)
"Parse an identifier expression, e.g. #foo"
- (parse "#" 1 "#"
- (run name `((identifier . ,expr) . ,input))))
+ (zencoding-parse "#" 1 "#"
+ (zencoding-run zencoding-name `((identifier . ,expr) . ,input))))
-(defun classes (input)
+(defun zencoding-classes (input)
"Parse many classes."
- (run class
- (pif (classes input)
+ (zencoding-run zencoding-class
+ (zencoding-pif (zencoding-classes input)
`((classes . ,(cons expr (cdar it))) . ,(cdr it))
`((classes . ,(list expr)) . ,input))
'(error "expected class")))
@@ -152,60 +223,83 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Zen coding transformer from AST to HTML
-(defun make-tag (tag &optional content)
+(defun zencoding-make-tag (tag &optional content)
(let ((name (car tag))
(props (apply 'concat (mapcar
- (lambda (prop)
- (concat " " (symbol-name (car prop))
- "=\"" (cadr prop) "\""))
- (cadr tag)))))
- (concat "<" name props ">\n"
+ (lambda (prop)
+ (concat " " (symbol-name (car prop))
+ "=\"" (cadr prop) "\""))
+ (cadr tag)))))
+ (concat "<" name props ">"
(if content content "")
- "</" name ">\n")))
+ "</" name ">")))
-(defun transform (ast)
+(defun zencoding-transform (ast)
(let ((type (car ast)))
(cond
+ ((eq type 'list)
+ (mapconcat 'zencoding-transform (cadr ast) ""))
((eq type 'tag)
- (make-tag (cdr ast)))
- ((eq type 'tagname) (make-tag (cdr ast)))
+ (zencoding-make-tag (cdr ast)))
((eq type 'parent-child)
(let ((parent (cdadr ast))
- (children (transform (caddr ast))))
- (make-tag parent children)))
- ((eq type 'siblings)
- (let ((sib1 (transform (cadr ast)))
- (sib2 (transform (caddr ast))))
+ (children (zencoding-transform (caddr ast))))
+ (zencoding-make-tag parent children)))
+ ((eq type 'zencoding-siblings)
+ (let ((sib1 (zencoding-transform (cadr ast)))
+ (sib2 (zencoding-transform (caddr ast))))
(concat sib1 sib2))))))
-;; 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>
-
-;; (transform (car (expr "body.sub-page>div#news.content.a+div#news.content.a")))
-;; => [indentation added with xml-mode]
-;; <body class="sub-page">
-;; <div id="news" class="content a">
-;; </div>
-;; <div id="news" class="content a">
-;; </div>
-;; </body>
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Test-cases
+(defun zencoding-test-cases ()
+ (let ((tests '(;; Tags
+ ("a" "<a></a>")
+ ("a.x" "<a class=\"x\"></a>")
+ ("a#q.x" "<a id=\"q\" class=\"x\"></a>")
+ ("a#q.x.y.z" "<a id=\"q\" class=\"x y z\"></a>")
+ ;; Siblings
+ ("a+b" "<a></a><b></b>")
+ ("a+b+c" "<a></a><b></b><c></c>")
+ ("a.x+b" "<a class=\"x\"></a><b></b>")
+ ("a#q.x+b" "<a id=\"q\" class=\"x\"></a><b></b>")
+ ("a#q.x.y.z+b" "<a id=\"q\" class=\"x y z\"></a><b></b>")
+ ("a#q.x.y.z+b#p.l.m.n" "<a id=\"q\" class=\"x y z\"></a><b id=\"p\" class=\"l m n\"></b>")
+ ;; Parent > child
+ ("a>b" "<a><b></b></a>")
+ ("a>b>c" "<a><b><c></c></b></a>")
+ ("a.x>b" "<a class=\"x\"><b></b></a>")
+ ("a#q.x>b" "<a id=\"q\" class=\"x\"><b></b></a>")
+ ("a#q.x.y.z>b" "<a id=\"q\" class=\"x y z\"><b></b></a>")
+ ("a#q.x.y.z>b#p.l.m.n" "<a id=\"q\" class=\"x y z\"><b id=\"p\" class=\"l m n\"></b></a>")
+ ("a>b+c" "<a><b></b><c></c></a>")
+ ("a>b+c>d" "<a><b></b><c><d></d></c></a>")
+ ;; Multiplication
+ ("a*1" "<a></a>")
+ ("a*2" "<a></a><a></a>")
+ ("a*2+b*2" "<a></a><a></a><b></b><b></b>")
+ ("a*2>b*2" "<a><b></b><b></b></a><a><b></b><b></b></a>")
+ ("a>b*2" "<a><b></b><b></b></a>")
+ ("a#q.x>b#q.x*2" "<a id=\"q\" class=\"x\"><b id=\"q\" class=\"x\"></b><b id=\"q\" class=\"x\"></b></a>")
+ ;; ;; Parentheses
+ ("(a)" "<a></a>")
+ ("(a)+(b)" "<a></a><b></b>")
+ ("a>(b)" "<a><b></b></a>")
+ ("(a>b)>c" "<a><b></b></a>")
+ ("(a>b)+c" "<a><b></b></a><c></c>")
+ ("z+(a>b)+c+k" "<z></z><a><b></b></a><c></c><k></k>")
+ ("(a)*2" "<a></a><a></a>")
+ ("((a)*2)" "<a></a><a></a>")
+ ("((a)*2)" "<a></a><a></a>")
+ ("(a>b)*2" "<a><b></b></a><a><b></b></a>")
+ ("(a+b)*2" "<a></a><b></b><a></a><b></b>"))))
+ (mapcar (lambda (input)
+ (let ((expected (cadr input))
+ (actual (zencoding-transform (car (zencoding-expr (car input))))))
+ (if (not (equal expected actual))
+ (error (concat "Assertion " (car input) " failed:"
+ expected
+ " == "
+ actual)))))
+ tests)
+ (concat (number-to-string (length tests)) " tests performed. All OK.")))