Skip to content

Commit 5de47d4

Browse files
committed
First functioning version of json-zetta
0 parents  commit 5de47d4

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/pom.xml
2+
*jar
3+
/lib
4+
/classes
5+
/native
6+
/.lein-failures
7+
/checkouts
8+
/.lein-deps-sum

README

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# json-zetta
2+
3+
FIXME: write description
4+
5+
## Usage
6+
7+
FIXME: write
8+
9+
## License
10+
11+
Copyright (C) 2012 FIXME
12+
13+
Distributed under the Eclipse Public License, the same as Clojure.

project.clj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(defproject org.van-clj/json-zetta "1.0.0-SNAPSHOT"
2+
:description "FIXME: write description"
3+
:dependencies [[org.clojure/clojure "1.3.0"]
4+
[org.van-clj/zetta-parser "0.0.1-SNAPSHOT"]])

src/zetta/json.clj

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
(ns zetta.json
2+
^{
3+
:author "Roman Gonzalez"
4+
:doc "A JSON parser combinator."
5+
}
6+
7+
(:refer-clojure :exclude [get char])
8+
(:require [clojure.string :as str])
9+
10+
(:use [zetta.core
11+
:only (always fail-parser do-parser with-parser <$> <* *> <|>)]
12+
[zetta.parser.seq
13+
:only (satisfy? char whitespace string number
14+
get put want-input?)]
15+
[zetta.combinators
16+
:only (sep-by skip-many)]))
17+
18+
(defrecord ContinueScan [buffer])
19+
(defrecord ScanFinished [item-count remainder])
20+
21+
(defn- continue-scan? [scanner]
22+
(instance? ContinueScan scanner))
23+
24+
(defn- scan-finished? [scanner]
25+
(instance? ScanFinished scanner))
26+
27+
(defn scan [state0 p]
28+
(letfn [
29+
(scanner [state0 item-count items0]
30+
(let [item (first items0)
31+
items (rest items0)]
32+
(if (nil? item)
33+
; ^ when there is no elements in the buffer, we need
34+
; to ask for more (this is done in the go function)
35+
(ContinueScan. state0)
36+
(let [state1 (p state0 item)]
37+
(if (nil? state1)
38+
; ^ when p returns nil, we stop the scan
39+
(ScanFinished. item-count items)
40+
(recur state1 (+ 1 item-count) items))))))
41+
42+
(process-scanner [scan-acc state0]
43+
(do-parser
44+
[input get
45+
:let [scan-result (scanner state0 0 input)]
46+
:cond [
47+
48+
(continue-scan? scan-result) [
49+
_ (put [])
50+
more (want-input?)
51+
52+
:if more
53+
:then [
54+
result (process-scanner (concat scan-acc input)
55+
(:buffer scan-result))
56+
]
57+
:else [
58+
result (always (concat scan-acc input))
59+
]]
60+
61+
(scan-finished? scan-result) [
62+
_ (put (:remainder scan-result))
63+
result (always
64+
(concat (take (:item-count scan-result) input)
65+
scan-acc))]]]
66+
67+
result))
68+
]
69+
(do-parser
70+
[scans (process-scanner [] state0)
71+
:cond [
72+
(= (count scans) 1) [ result (always (first scans)) ]
73+
:else [ result (always (concat scans)) ]]]
74+
result)))
75+
76+
(def skip-spaces (skip-many whitespace))
77+
78+
(declare js-object_ js-array_ js-string_)
79+
80+
(def js-value
81+
(let [
82+
most (do-parser [
83+
ch (char #{\{ \[ \" \f \t \n})
84+
:cond [
85+
(= ch \{) [ result js-object_ ]
86+
(= ch \[) [ result js-array_ ]
87+
(= ch \") [ result js-string_ ]
88+
(= ch \f) [ result (*> (string "alse") (always false)) ]
89+
(= ch \t) [ result (*> (string "rue") (always true)) ]
90+
(= ch \n) [ result (*> (string "ull") (always nil)) ]
91+
:else [ result (fail-parser "the imposible happened!") ]]
92+
]
93+
result)]
94+
(with-parser
95+
(<|> most number))))
96+
97+
98+
(def ^:private js-string_
99+
(letfn [
100+
(scan-step [prev-was-backslash? current-char]
101+
(if prev-was-backslash?
102+
false
103+
(if (= current-char \")
104+
nil
105+
(= current-char \\))))
106+
]
107+
(with-parser
108+
(<$> str/join (scan false scan-step)))))
109+
110+
(def js-string
111+
(with-parser
112+
(*> (char \") js-string_)))
113+
114+
115+
(defn- js-array-values [val-parser]
116+
(with-parser
117+
(*> skip-spaces
118+
(<* (sep-by (<* val-parser skip-spaces)
119+
(*> (char \,) skip-spaces))
120+
(char \])))))
121+
122+
(def ^:private js-array_
123+
(with-parser
124+
(js-array-values js-value)))
125+
126+
(def js-array
127+
(with-parser
128+
(*> (char \[) js-array_)))
129+
130+
(defn- js-object-values [key-parser val-parser]
131+
(let [parse-pair (with-parser
132+
(<$> vector (<* key-parser skip-spaces)
133+
(*> (char \:)
134+
skip-spaces
135+
val-parser)))]
136+
(with-parser
137+
(<$> (comp #(into {} %)
138+
#(map (fn [[k v]] [(keyword k) v]) %))
139+
(*> skip-spaces
140+
(<* (sep-by (<* parse-pair skip-spaces)
141+
(*> (char \,) skip-spaces))
142+
(char \})))))))
143+
144+
(def ^:private js-object_
145+
(with-parser
146+
(js-object-values js-string js-value)))
147+
148+
(def js-object
149+
(with-parser
150+
(*> (char \{) js-object_)))
151+
152+
(def json
153+
(do-parser [
154+
ch (*> skip-spaces (char #{\{ \[}))
155+
:if (= ch \{)
156+
:then [ result js-object_ ]
157+
:else [ result js-array_ ]]
158+
result))
159+
160+

test/zetta/tests/json.clj

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
(ns zetta.tests.json
2+
(:use clojure.test)
3+
4+
(:use zetta.core
5+
zetta.json))
6+
7+
(deftest test-js-string
8+
(let [result (parse-once js-string "\"hello world\"")]
9+
(is (done? result))
10+
(is (= "hello world" (:result result)))))
11+
12+
(deftest test-js-array
13+
(let [result (parse-once js-array "[\"hello\",
14+
[32, 42],
15+
43,
16+
\"other\"]")]
17+
(is (done? result))
18+
(is (= ["hello" [32 42] 43 "other"] (:result result)))))
19+
20+
(deftest test-js-object
21+
(let [result (parse-once js-object "{ \"name\": \"John Doe\",
22+
\"professional\": false,
23+
\"background\": null,
24+
\"age\": 43,
25+
\"phones\": [\"555-5555\",
26+
\"666-6666\"],
27+
\"address\": {
28+
\"suite\": \"3241\",
29+
\"street\":
30+
\"90210 \\\"Beverly\\\" Hills\",
31+
\"city\": \"Los Angeles\",
32+
\"state\": \"California\",
33+
\"country\": \"USA\"}}")]
34+
(is (done? result))
35+
(is (= { :name "John Doe"
36+
:age 43
37+
:professional false
38+
:background nil
39+
:phones ["555-5555" "666-6666"]
40+
:address {
41+
:suite "3241"
42+
:street "90210 \"Beverly\" Hills"
43+
:city "Los Angeles"
44+
:state "California"
45+
:country "USA"}}))))
46+

0 commit comments

Comments
 (0)