]> gitweb.factorcode.org Git - factor.git/blob - basis/json/json.factor
factor: use more ?if
[factor.git] / basis / json / json.factor
1 ! Copyright (C) 2006 Chris Double, 2008 Peter Burns, 2009 Philipp Winkler
2
3 USING: accessors ascii assocs combinators formatting hashtables
4 io io.encodings.utf16.private io.encodings.utf8 io.files
5 io.streams.string kernel kernel.private math math.order
6 math.parser mirrors namespaces sbufs sequences sequences.private
7 strings summary tr words ;
8
9 IN: json
10
11 SINGLETON: json-null
12
13 ERROR: json-error ;
14
15 ERROR: json-fp-special-error value ;
16
17 M: json-fp-special-error summary
18     drop "JSON serialization: illegal float:" ;
19
20 : if-json-null ( x if-null else -- )
21     [ dup json-null? ]
22     [ [ drop ] prepose ]
23     [ ] tri* if ; inline
24
25 : when-json-null ( x if-null -- ) [ ] if-json-null ; inline
26
27 : unless-json-null ( x else -- ) [ ] swap if-json-null ; inline
28
29 <PRIVATE
30
31 ERROR: not-a-json-number string ;
32
33 SYMBOL: json-depth
34
35 : json-number ( char stream -- num char )
36     [ 1string ] [ "\s\t\r\n,:}]" swap stream-read-until ] bi*
37     [
38         append {
39             { "Infinity" [ 1/0. ] }
40             { "-Infinity" [ -1/0. ] }
41             { "NaN" [ 0/0. ] }
42             [ [ string>number ] [ not-a-json-number ] ?unless ]
43         } case
44     ] dip ;
45
46 : json-expect ( token stream -- )
47     [ dup length ] [ stream-read ] bi* = [ json-error ] unless ; inline
48
49 DEFER: (read-json-string)
50
51 : decode-utf16-surrogate-pair ( hex1 hex2 -- char )
52     [ 0x3ff bitand ] bi@ [ 10 shift ] dip bitor 0x10000 + ;
53
54 : stream-read-4hex ( stream -- hex ) 4 swap stream-read hex> ;
55
56 : first-surrogate? ( hex -- ? ) 0xd800 0xdbff between? ;
57
58 : read-second-surrogate ( stream -- hex )
59     "\\u" over json-expect stream-read-4hex ;
60
61 : read-json-escape-unicode ( stream -- char )
62     [ stream-read-4hex ] keep over first-surrogate? [
63         read-second-surrogate decode-utf16-surrogate-pair
64     ] [ drop ] if ;
65
66 : (read-json-escape) ( stream accum -- accum )
67     { sbuf } declare
68     over stream-read1 {
69         { CHAR: \" [ CHAR: \" ] }
70         { CHAR: \\ [ CHAR: \\ ] }
71         { CHAR: / [ CHAR: / ] }
72         { CHAR: b [ CHAR: \b ] }
73         { CHAR: f [ CHAR: \f ] }
74         { CHAR: n [ CHAR: \n ] }
75         { CHAR: r [ CHAR: \r ] }
76         { CHAR: t [ CHAR: \t ] }
77         { CHAR: u [ over read-json-escape-unicode ] }
78         [ ]
79     } case [ suffix! (read-json-string) ] [ json-error ] if* ;
80
81 : (read-json-string) ( stream accum -- accum )
82     { sbuf } declare
83     "\\\"" pick stream-read-until [ append! ] dip
84     CHAR: \" = [ nip ] [ (read-json-escape) ] if ;
85
86 : read-json-string ( stream -- str )
87     "\\\"" over stream-read-until CHAR: \" =
88     [ nip ] [ >sbuf (read-json-escape) "" like ] if ;
89
90 : second-last-unsafe ( seq -- second-last )
91     [ length 2 - ] [ nth-unsafe ] bi ; inline
92
93 : pop-unsafe ( seq -- elt )
94     index-of-last [ nth-unsafe ] [ shorten ] 2bi ; inline
95
96 : check-length ( seq n -- seq )
97     [ dup length ] [ >= ] bi* [ json-error ] unless ; inline
98
99 : v-over-push ( accum -- accum )
100     2 check-length dup [ pop-unsafe ] [ last-unsafe ] bi push ;
101
102 : v-pick-push ( accum -- accum )
103     3 check-length dup [ pop-unsafe ] [ second-last-unsafe ] bi push ;
104
105 : v-close ( accum -- accum )
106     dup last V{ } = not [ v-over-push ] when ;
107
108 : json-open-array ( accum -- accum )
109     V{ } clone suffix! ;
110
111 : json-open-hash ( accum -- accum )
112     V{ } clone suffix! V{ } clone suffix! ;
113
114 : json-close-array ( accum -- accum )
115     v-close dup pop { } like suffix! ;
116
117 : json-close-hash ( accum -- accum )
118     v-close dup dup [ pop ] bi@ swap H{ } zip-as suffix! ;
119
120 : scan ( stream accum char -- stream accum )
121     ! 2dup 1string swap . . ! Great for debug...
122     {
123         { CHAR: \" [ over read-json-string suffix! ] }
124         { CHAR: [  [ 1 json-depth +@ json-open-array ] }
125         { CHAR: ,  [ v-over-push ] }
126         { CHAR: ]  [ -1 json-depth +@ json-close-array ] }
127         { CHAR: {  [ json-open-hash ] }
128         { CHAR: :  [ v-pick-push ] }
129         { CHAR: }  [ json-close-hash ] }
130         { CHAR: \s [ ] }
131         { CHAR: \t [ ] }
132         { CHAR: \r [ ] }
133         { CHAR: \n [ ] }
134         { CHAR: t  [ "rue" pick json-expect t suffix! ] }
135         { CHAR: f  [ "alse" pick json-expect f suffix! ] }
136         { CHAR: n  [ "ull" pick json-expect json-null suffix! ] }
137         [ pick json-number [ suffix! ] dip [ scan ] when*  ]
138     } case ;
139
140 : json-read-input ( stream -- objects )
141     0 json-depth [
142         V{ } clone over '[ _ stream-read1 ] [ scan ] while* nip
143         json-depth get zero? [ json-error ] unless
144     ] with-variable ;
145
146 : get-json ( objects  --  obj )
147     dup length 1 = [ first ] [ json-error ] if ;
148
149 PRIVATE>
150
151 : read-json ( -- objects )
152     input-stream get json-read-input ;
153
154 GENERIC: json> ( string -- object )
155
156 M: string json>
157     [ read-json get-json ] with-string-reader ;
158
159 : path>json ( path -- json )
160     utf8 [ read-json get-json ] with-file-reader ;
161
162 : path>jsons ( path -- jsons )
163     utf8 [ read-json ] with-file-reader ;
164
165 SYMBOL: json-allow-fp-special?
166 f json-allow-fp-special? set-global
167
168 SYMBOL: json-friendly-keys?
169 t json-friendly-keys? set-global
170
171 SYMBOL: json-coerce-keys?
172 t json-coerce-keys? set-global
173
174 SYMBOL: json-escape-slashes?
175 f json-escape-slashes? set-global
176
177 SYMBOL: json-escape-unicode?
178 f json-escape-unicode? set-global
179
180 ! Writes the object out to a stream in JSON format
181 GENERIC#: stream-json-print 1 ( obj stream -- )
182
183 : json-print ( obj -- )
184     output-stream get stream-json-print ;
185
186 : >json ( obj -- string )
187     ! Returns a string representing the factor object in JSON format
188     [ json-print ] with-string-writer ;
189
190 M: f stream-json-print
191     [ drop "false" ] [ stream-write ] bi* ;
192
193 M: t stream-json-print
194     [ drop "true" ] [ stream-write ] bi* ;
195
196 M: json-null stream-json-print
197     [ drop "null" ] [ stream-write ] bi* ;
198
199 <PRIVATE
200
201 : json-print-generic-escape-surrogate-pair ( stream char -- stream )
202     0x10000 - [ encode-first ] [ encode-second ] bi
203     "\\u%02x%02x\\u%02x%02x" sprintf over stream-write ;
204
205 : json-print-generic-escape-bmp ( stream char -- stream )
206     "\\u%04x" sprintf over stream-write ;
207
208 : json-print-generic-escape ( stream char -- stream )
209     dup 0xffff > [
210         json-print-generic-escape-surrogate-pair
211     ] [
212         json-print-generic-escape-bmp
213     ] if ;
214
215 PRIVATE>
216
217 M: string stream-json-print
218     CHAR: \" over stream-write1 swap [
219         {
220             { CHAR: \"  [ "\\\"" over stream-write ] }
221             { CHAR: \\ [ "\\\\" over stream-write ] }
222             { CHAR: /  [
223                 json-escape-slashes? get
224                 [ "\\/" over stream-write ]
225                 [ CHAR: / over stream-write1 ] if
226             ] }
227             { CHAR: \b [ "\\b" over stream-write ] }
228             { CHAR: \f [ "\\f" over stream-write ] }
229             { CHAR: \n [ "\\n" over stream-write ] }
230             { CHAR: \r [ "\\r" over stream-write ] }
231             { CHAR: \t [ "\\t" over stream-write ] }
232             { 0x2028   [ "\\u2028" over stream-write ] }
233             { 0x2029   [ "\\u2029" over stream-write ] }
234             [
235                 {
236                     { [ dup printable? ] [ f ] }
237                     { [ dup control? ] [ t ] }
238                     [ json-escape-unicode? get ]
239                 } cond [
240                     json-print-generic-escape
241                 ] [
242                     over stream-write1
243                 ] if
244             ]
245         } case
246     ] each CHAR: \" swap stream-write1 ;
247
248 M: integer stream-json-print
249     [ number>string ] [ stream-write ] bi* ;
250
251 : float>json ( float -- string )
252     dup fp-special? [
253         json-allow-fp-special? get [ json-fp-special-error ] unless
254         {
255             { [ dup fp-nan? ] [ drop "NaN" ] }
256             { [ dup 1/0. = ] [ drop "Infinity" ] }
257             { [ dup -1/0. = ] [ drop "-Infinity" ] }
258         } cond
259     ] [
260         number>string
261     ] if ;
262
263 M: float stream-json-print
264     [ float>json ] [ stream-write ] bi* ;
265
266 M: real stream-json-print
267     [ >float number>string ] [ stream-write ] bi* ;
268
269 M: sequence stream-json-print
270     CHAR: [ over stream-write1 swap
271     over '[ CHAR: , _ stream-write1 ]
272     pick '[ _ stream-json-print ] interleave
273     CHAR: ] swap stream-write1 ;
274
275 <PRIVATE
276
277 TR: json-friendly "-" "_" ;
278
279 GENERIC: json-coerce ( obj -- str )
280 M: f json-coerce drop "false" ;
281 M: t json-coerce drop "true" ;
282 M: json-null json-coerce drop "null" ;
283 M: string json-coerce ;
284 M: integer json-coerce number>string ;
285 M: float json-coerce float>json ;
286 M: real json-coerce >float number>string ;
287
288 :: json-print-assoc ( obj stream -- )
289     CHAR: { stream stream-write1 obj >alist
290     [ CHAR: , stream stream-write1 ]
291     json-friendly-keys? get
292     json-coerce-keys? get '[
293         first2 [
294             dup string?
295             [ _ [ json-friendly ] when ]
296             [ _ [ json-coerce ] when ] if
297             stream stream-json-print
298         ] [
299             CHAR: : stream stream-write1
300             stream stream-json-print
301         ] bi*
302     ] interleave
303     CHAR: } stream stream-write1 ;
304
305 PRIVATE>
306
307 M: tuple stream-json-print
308     [ <mirror> ] dip json-print-assoc ;
309
310 M: hashtable stream-json-print json-print-assoc ;
311
312 M: word stream-json-print
313     [ name>> ] dip stream-json-print ;