67c1b2e1400d2b395c0c4e4faa2667d960de3bb1
[scpubgit/TenDotTcl.git] / json.tcl
1 namespace eval ten::json {
2
3   namespace eval deparse {
4
5     variable quotes [
6       list "\"" "\\\"" / \\/ \\ \\\\ \b \\b \f \\f \n \\n \r \\r \t \\t
7     ]
8
9     variable indent
10     variable indentBy 0
11     variable indentIncr
12     variable nl
13
14     proc indent_one {} {
15       variable indentIncr
16       variable indentBy [ expr $indentBy + $indentIncr ]
17       variable indent [ string repeat " " $indentBy ]
18     }
19
20     proc outdent_one {} {
21       variable indentIncr
22       variable indentBy [ expr $indentBy - $indentIncr ]
23       variable indent [ string repeat " " $indentBy ]
24     }
25
26     proc str {str} {
27       variable quotes
28       return \"[ string map $quotes $str ]\"
29     }
30
31     proc num {num} {
32       return $num
33     }
34
35     proc list {args} {
36       variable indent
37       variable nl
38       set out \[
39       indent_one
40       foreach el [lrange $args 0 end] {
41         append out $nl$indent
42         append out [ deparse $el ],
43       }
44       outdent_one
45       append out $nl$indent\]
46       return $out
47     }
48
49     proc obj {args} {
50       variable indent
51       variable nl
52       set out \{
53       indent_one
54       dict for {k v} $args {
55         append out $nl$indent[ str $k ]:\ [ deparse $v ],
56       }
57       outdent_one
58       append out $nl$indent\}
59     }
60
61     proc deparse {data} {
62       switch -regexp [lindex $data 0] {
63         ^true|false|null$ { uplevel 1 return [lindex $data 0] }
64         ^num|str|obj|list$ {}
65         default { error [ concat "Invalid JSON type " [lindex $data 0 0] ] }
66       }
67       eval $data
68     }
69   }
70
71   proc deparse_json {data {indentIncr 2}} {
72     set deparse::indentBy 0
73     set deparse::indentIncr $indentIncr
74     if [expr $indentIncr == 0] {
75       set deparse::nl ""
76     } else {
77       set deparse::nl "\n"
78     }
79     deparse::deparse $data
80   }
81
82   namespace eval tclify {
83
84     proc str {str} { return $str }
85
86     proc num {num} { return $num }
87
88     proc obj {args} {
89       return $args
90     }
91
92     proc tclify {data} {
93       switch -regexp [lindex $data 0] {
94         ^true|false|null$ { uplevel 1 return [lindex $data 0] }
95         ^num|str|obj|list$ {}
96         default { error [ concat "Invalid JSON type " [lindex $data 0 0] ] }
97       }
98       eval $data
99     }
100   }
101
102   proc tclify_json {data} {
103     tclify::tclify $data
104   }
105
106   namespace eval parse {
107
108     variable json
109
110     proc eat_comma {} {
111       variable json
112       set json [string trimleft $json]
113       if {[string index $json 0] eq ","} {
114         set json [string range $json 1 end]
115       }
116     }
117
118     proc parse_list {} {
119       variable json
120       set json [ string range $json 1 end ]
121       set tcl {list}
122       while {"$json" ne ""} {
123         if {[string index $json 0] eq "]"} {
124           return $tcl
125         }
126         lappend tcl [ parse ]
127         eat_comma
128       }
129       error "Ran out of JSON. Confused now."
130     }
131
132     proc parse_str {} {
133       variable json
134 puts $json
135       # like Text::Balanced except ugly (borrowed from tcvJSON's code)
136       set reStr {(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\"))}
137       if {![regexp $reStr $json string]} {
138         error [ concat "Invalid string: " [string range $json 0 32]
139       }
140       set json [string range $json [string length $string] end]
141       # chop off outer ""s and substitute backslashes
142       # This does more than the RFC-specified backslash sequences,
143       # but it does cover them all
144       list str [subst -nocommand -novariable [string range $string 1 end-1]]
145     }
146
147     proc parse {} {
148       variable json
149       set json [string trimleft $json]
150       if {$json eq ""} {
151         return
152       }
153       switch -regexp [string index $json 0] {
154         {\{} { parse_obj }
155         {\[} { parse_list }
156         {\"} { parse_str }
157
158         {[-0-9]} { parse_number }
159
160         default { error "argh" }
161       }
162     }
163   }
164
165   proc parse_json {json} {
166     set parse::json $json
167     parse::parse
168   }
169 }
170
171 set ex_json { list {str foo} {num 0} {obj __remote_object__ {str 512}} {null} }
172
173 set jtext {
174   [
175     "foo",
176     0,
177     {
178       "__remote_object__": "512",
179     },
180     null,
181   ]
182 }
183
184 puts [ ten::json::deparse_json $ex_json 2 ]
185
186 dict for {k v} [ ten::json::tclify_json [
187   lindex [ ten::json::tclify_json $ex_json ] 2
188 ] ] { puts "$k: $v" }
189
190 #puts [ ten::json::parse_json $jtext ]
191
192 puts [ ten::json::parse_json {["foo"]} ]