Steve Losh avatar Steve Losh committed dc30e66

More parser work.

Comments (0)

Files changed (2)

src/dram/parser.clj

   (either (attempt (literal-string-escape))
           (token (complement #{\"}))))
 
+(defparser literal-string-empty []
+  (string "\"\"")
+  (always ""))
+
+(defparser literal-string-nonempty []
+  (between (char \") (char \")
+           (let->> [contents (many1 (literal-string-char))]
+             (always (apply str contents)))))
+
 (defparser literal-string []
-  (between (char \") (char \")
-           (let->> [contents (many (literal-string-char))]
-             (always (apply str contents)))))
+  (either (attempt (literal-string-empty))
+          (attempt (literal-string-nonempty))))
+
 
 ; Literals --------------------------------------------------------------------
 (defparser literal []
 
 ; Variables -------------------------------------------------------------------
 (defparser variable-open []
-  (times 2 (char \{))
+  (string "{{")
   (optional-whitespace)
   (always nil))
 
 (defparser variable-close []
   (optional-whitespace)
-  (times 2 (char \}))
+  (string "}}")
   (always nil))
 
 (defparser variable []
            (choice (literal))))
 
 
+; Tags ------------------------------------------------------------------------
+(defparser tag-open []
+  (string "{%")
+  (optional-whitespace)
+  (always nil))
+
+(defparser tag-close []
+  (optional-whitespace)
+  (string "%}")
+  (always nil))
+
+(defparser tag-guts []
+  (always nil))
+
+(defparser tag []
+  (between (tag-open) (tag-close)
+           (tag-guts)))
+
+
+
+; Extends ---------------------------------------------------------------------
+(defparser tag-extends []
+  (between (tag-open) (tag-close)
+           (let->> [_ (string "extends")
+                    _ (required-whitespace)
+                    path (literal-string-nonempty)]
+             (always {:type :extends
+                      :path path}))))
+
+; Block -----------------------------------------------------------------------
+(defparser tag-block-name-char []
+  (choice (letter)
+          (digit)
+          (token #{\- \_})))
+
+(defparser tag-block-name []
+  (let->> [fch (letter)
+           chs (many (tag-block-name-char))]
+    (always (apply str (concat [fch] chs)))))
+
+(defparser tag-block-open []
+  (between (tag-open) (tag-close)
+           (let->> [_ (string "block")
+                    _ (required-whitespace)
+                    name (tag-block-name)]
+             (always {:name name}))))
+
+(defparser tag-block-close []
+  (between (tag-open) (tag-close)
+           (let->> [_ (string "endblock")]
+             (always nil))))
+
+
 
 ; Main ------------------------------------------------------------------------
 (defn parse

test/dram/test/parser.clj

     (parses-as "{{42 }}" (p/variable) 42)
     (parses-as "{{42       }}" (p/variable) 42)
     (parses-as "{{\n\t\n\t42}}" (p/variable) 42)))
+
+(deftest extends-test
+  (testing "{% extends ... %} parses to its own custom AST element."
+    (parses-as "{% extends \"p\" %}" (p/tag-extends) {:type :extends :path "p"})
+    (parses-as "{% extends \"foo/bar\" %}" (p/tag-extends) {:type :extends
+                                                            :path "foo/bar"}))
+  (testing "{% extends ... %} requires a non-empty argument."
+    (is-error "{% extends \"\" %}" (p/tag-extends))
+    (is-error "{% extends %}" (p/tag-extends)))
+  (testing "{% extends ... %} doesn't accept garbage."
+    (is-error "{% extends foo %}" (p/tag-extends))
+    (is-error "{% extends \"foo\" foo %}" (p/tag-extends))
+    (is-error "{% extends foo \"foo\" %}" (p/tag-extends))
+    (is-error "{% extends foo\"foo\" %}" (p/tag-extends))
+    (is-error "{% extends 43 %}" (p/tag-extends))
+    (is-error "{% extends foo/bar %}" (p/tag-extends))
+    (is-error "{% extends the quick brown fox %}" (p/tag-extends))))
+
+(deftest block-test
+  (testing "{% block ... %} parses to an intermediate AST element."
+    (parses-as "{% block cats %}" (p/tag-block-open) {:name "cats"})
+    (parses-as "{% block boots-and-cats %}" (p/tag-block-open)
+               {:name "boots-and-cats"})
+    (parses-as "{% block hello-world_585 %}" (p/tag-block-open)
+               {:name "hello-world_585"})
+    (parses-as "{% block a %}" (p/tag-block-open) {:name "a"})
+    (parses-as "{% block a_ %}" (p/tag-block-open) {:name "a_"}))
+  (testing "{% block ... %} requires valid block names."
+    (is-error "{% block 1 %}" (p/tag-block-open))
+    (is-error "{% block -1 %}" (p/tag-block-open))
+    (is-error "{% block -foo %}" (p/tag-block-open))
+    (is-error "{% block __foo %}" (p/tag-block-open))
+    (is-error "{% block 12dogs %}" (p/tag-block-open))
+    (is-error "{% block c&ats %}" (p/tag-block-open))
+    (is-error "{% block boots and cats %}" (p/tag-block-open))
+    (is-error "{% block \"rochester-made\" %}" (p/tag-block-open))
+    (is-error "{% block dogs* %}" (p/tag-block-open))
+    (is-error "{% block dogs% %}" (p/tag-block-open))
+    (is-error "{% block dogs} %}" (p/tag-block-open)))
+  (testing "{% block ... %} allows wonky whitespace."
+    (parses-as "{%block foo%}" (p/tag-block-open) {:name "foo"})
+    (parses-as "{%   block foo%}" (p/tag-block-open) {:name "foo"})
+    (parses-as "{%block      foo     %}" (p/tag-block-open) {:name "foo"})
+    (parses-as "{%\n\nblock\tfoo\n%}" (p/tag-block-open) {:name "foo"}))
+  (testing "{% block ... %} REQUIRES whitespace between block and the name."
+    (is-error "{% blockfoo %}" (p/tag-block-open)))
+  (testing "{% endblock %} parses to nil and allows weird whitespace."
+    (parses-as "{% endblock %}" (p/tag-block-close) nil)
+    (parses-as "{%\nendblock\t%}" (p/tag-block-close) nil)
+    (parses-as "{%endblock %}" (p/tag-block-close) nil)
+    (parses-as "{% endblock%}" (p/tag-block-close) nil))
+  (testing "{% endblock %} does NOT take a block name (for now)."
+    (is-error "{% endblock foo %}" (p/tag-block-open))))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.