json.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 if {$::tcl_version < 8.5} {
00012 package require dict
00013 }
00014
00015 package provide json 1.0
00016
00017 namespace json {}
00018
00019 ret json::getc (optional txtvar =txt) {
00020 # pop single char off the front of the text
00021 upvar 1 $txtvar txt
00022 if {$txt eq ""} {
00023 return -code error "unexpected end of text"
00024 }
00025
00026 set c [string index $txt 0]
00027 set txt [string range $txt 1 end]
00028 return $c
00029 }
00030
00031 ret json::json2dict (type txt) {
00032 return [_json2dict]
00033 }
00034
00035 ret json::_json2dict (optional txtvar =txt) {
00036 upvar 1 $txtvar txt
00037
00038 set state TOP
00039
00040 set txt [string trimleft $txt]
00041 while {$txt ne ""} {
00042 set c [string index $txt 0]
00043
00044 # skip whitespace
00045 while {[string is space $c]} {
00046 getc
00047 set c [string index $txt 0]
00048 }
00049
00050 if {$c eq "\{"} {
00051 # object
00052 switch -- $state {
00053 TOP {
00054 # we are dealing with an Object
00055 getc
00056 set state OBJECT
00057 set dictVal [dict create]
00058 }
00059 VALUE {
00060 # this object element's value is an Object
00061 dict set dictVal $name [_json2dict]
00062 set state COMMA
00063 }
00064 LIST {
00065 # next element of list is an Object
00066 lappend listVal [_json2dict]
00067 set state COMMA
00068 }
00069 default {
00070 return -code error "unexpected open brace in $state mode"
00071 }
00072 }
00073 } elseif {$c eq "\}"} {
00074 getc
00075 if {$state ne "OBJECT" && $state ne "COMMA"} {
00076 return -code error "unexpected close brace in $state mode"
00077 }
00078 return $dictVal
00079 } elseif {$c eq ":"} {
00080 # name separator
00081 getc
00082
00083 if {$state eq "COLON"} {
00084 set state VALUE
00085 } else {
00086 return -code error "unexpected colon in $state mode"
00087 }
00088 } elseif {$c eq ","} {
00089 # element separator
00090 if {$state eq "COMMA"} {
00091 getc
00092 if {[info exists listVal]} {
00093 set state LIST
00094 } elseif {[info exists dictVal]} {
00095 set state OBJECT
00096 }
00097 } else {
00098 return -code error "unexpected comma in $state mode"
00099 }
00100 } elseif {$c eq "\""} {
00101 # string
00102 # capture quoted string with backslash sequences
00103 set reStr {(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\"))}
00104 set string ""
00105 if {![regexp $reStr $txt string]} {
00106 set txt [string replace $txt 32 end ...]
00107 return -code error "invalid formatted string in $txt"
00108 }
00109 set txt [string range $txt [string length $string] end]
00110 # chop off outer ""s and substitute backslashes
00111 # This does more than the RFC-specified backslash sequences,
00112 # but it does cover them all
00113 set string [subst -nocommand -novariable \
00114 [string range $string 1 end-1]]
00115
00116 switch -- $state {
00117 TOP {
00118 return $string
00119 }
00120 OBJECT {
00121 set name $string
00122 set state COLON
00123 }
00124 LIST {
00125 lappend listVal $string
00126 set state COMMA
00127 }
00128 VALUE {
00129 dict set dictVal $name $string
00130 unset name
00131 set state COMMA
00132 }
00133 }
00134 } elseif {$c eq "\["} {
00135 # JSON array == Tcl list
00136 switch -- $state {
00137 TOP {
00138 getc
00139 set state LIST
00140 }
00141 LIST {
00142 lappend listVal [_json2dict]
00143 set state COMMA
00144 }
00145 VALUE {
00146 dict set dictVal $name [_json2dict]
00147 set state COMMA
00148 }
00149 default {
00150 return -code error "unexpected open bracket in $state mode"
00151 }
00152 }
00153 } elseif {$c eq "\]"} {
00154 # end of list
00155 getc
00156 if {![info exists listVal]} {
00157 #return -code error "unexpected close bracket in $state mode"
00158 # must be an empty list
00159 return ""
00160 }
00161
00162 return $listVal
00163 } elseif {0 && $c eq "/"} {
00164 # comment
00165 # XXX: Not in RFC 4627
00166 getc
00167 set c [getc]
00168 switch -- $c {
00169 / {
00170 #
00171 set i [string first "\n" $txt]
00172 if {$i == -1} {
00173 set txt ""
00174 } else {
00175 set txt [string range $txt [incr i] end]
00176 }
00177 }
00178 * {
00179 # form
00180 getc
00181 set i [string first "*/" $txt]
00182 if {$i == -1} {
00183 return -code error "incomplete
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275