summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/json/json.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/json/json.tcl')
-rw-r--r--tcllib/modules/json/json.tcl282
1 files changed, 282 insertions, 0 deletions
diff --git a/tcllib/modules/json/json.tcl b/tcllib/modules/json/json.tcl
new file mode 100644
index 0000000..90308ac
--- /dev/null
+++ b/tcllib/modules/json/json.tcl
@@ -0,0 +1,282 @@
+# json.tcl --
+#
+# JSON parser for Tcl. Management code, Tcl/C detection and selection.
+#
+# Copyright (c) 2013 by Andreas Kupries
+
+# @mdgen EXCLUDE: jsonc.tcl
+
+package require Tcl 8.4
+namespace eval ::json {}
+
+# ### ### ### ######### ######### #########
+## Management of json implementations.
+
+# ::json::LoadAccelerator --
+#
+# Loads a named implementation, if possible.
+#
+# Arguments:
+# key Name of the implementation to load.
+#
+# Results:
+# A boolean flag. True if the implementation
+# was successfully loaded; and False otherwise.
+
+proc ::json::LoadAccelerator {key} {
+ variable accel
+ set r 0
+ switch -exact -- $key {
+ critcl {
+ # Critcl implementation of json requires Tcl 8.4.
+ if {![package vsatisfies [package provide Tcl] 8.4]} {return 0}
+ if {[catch {package require tcllibc}]} {return 0}
+ # Check for the jsonc 1.1.1 API we are fixing later.
+ set r [llength [info commands ::json::many_json2dict_critcl]]
+ }
+ tcl {
+ variable selfdir
+ source [file join $selfdir json_tcl.tcl]
+ set r 1
+ }
+ default {
+ return -code error "invalid accelerator/impl. package $key:\
+ must be one of [join [KnownImplementations] {, }]"
+ }
+ }
+ set accel($key) $r
+ return $r
+}
+
+# ::json::SwitchTo --
+#
+# Activates a loaded named implementation.
+#
+# Arguments:
+# key Name of the implementation to activate.
+#
+# Results:
+# None.
+
+proc ::json::SwitchTo {key} {
+ variable accel
+ variable loaded
+ variable apicmds
+
+ if {[string equal $key $loaded]} {
+ # No change, nothing to do.
+ return
+ } elseif {![string equal $key ""]} {
+ # Validate the target implementation of the switch.
+
+ if {![info exists accel($key)]} {
+ return -code error "Unable to activate unknown implementation \"$key\""
+ } elseif {![info exists accel($key)] || !$accel($key)} {
+ return -code error "Unable to activate missing implementation \"$key\""
+ }
+ }
+
+ # Deactivate the previous implementation, if there was any.
+
+ if {![string equal $loaded ""]} {
+ foreach c $apicmds {
+ rename ::json::${c} ::json::${c}_$loaded
+ }
+ }
+
+ # Activate the new implementation, if there is any.
+
+ if {![string equal $key ""]} {
+ foreach c $apicmds {
+ rename ::json::${c}_$key ::json::${c}
+ }
+ }
+
+ # Remember the active implementation, for deactivation by future
+ # switches.
+
+ set loaded $key
+ return
+}
+
+# ::json::Implementations --
+#
+# Determines which implementations are
+# present, i.e. loaded.
+#
+# Arguments:
+# None.
+#
+# Results:
+# A list of implementation keys.
+
+proc ::json::Implementations {} {
+ variable accel
+ set res {}
+ foreach n [array names accel] {
+ if {!$accel($n)} continue
+ lappend res $n
+ }
+ return $res
+}
+
+# ::json::KnownImplementations --
+#
+# Determines which implementations are known
+# as possible implementations.
+#
+# Arguments:
+# None.
+#
+# Results:
+# A list of implementation keys. In the order
+# of preference, most prefered first.
+
+proc ::json::KnownImplementations {} {
+ return {critcl tcl}
+}
+
+proc ::json::Names {} {
+ return {
+ critcl {tcllibc based}
+ tcl {pure Tcl}
+ }
+}
+
+# ### ### ### ######### ######### #########
+## Initialization: Data structures.
+
+namespace eval ::json {
+ variable selfdir [file dirname [info script]]
+ variable accel
+ array set accel {tcl 0 critcl 0}
+ variable loaded {}
+
+ variable apicmds {
+ json2dict
+ many-json2dict
+ }
+}
+
+# ### ### ### ######### ######### #########
+## Wrapper fix for the jsonc package to match APIs.
+
+proc ::json::many-json2dict_critcl {args} {
+ eval [linsert $args 0 ::json::many_json2dict_critcl]
+}
+
+# ### ### ### ######### ######### #########
+## Initialization: Choose an implementation,
+## most prefered first. Loads only one of the
+## possible implementations. And activates it.
+
+namespace eval ::json {
+ variable e
+ foreach e [KnownImplementations] {
+ if {[LoadAccelerator $e]} {
+ SwitchTo $e
+ break
+ }
+ }
+ unset e
+}
+
+# ### ### ### ######### ######### #########
+## Tcl implementation of validation, shared for Tcl and C implementation.
+##
+## The regexp based validation is consistently faster than json-c.
+## Suspected reasons: Tcl REs are mainly in C as well, and json-c has
+## overhead in constructing its own data structures. While irrelevant
+## to validation json-c still builds them, it has no mode doing pure
+## syntax checking.
+
+namespace eval ::json {
+ # Regular expression for tokenizing a JSON text (cf. http://json.org/)
+
+ # tokens consisting of a single character
+ variable singleCharTokens { "{" "}" ":" "\\[" "\\]" "," }
+ variable singleCharTokenRE "\[[join $singleCharTokens {}]\]"
+
+ # quoted string tokens
+ variable escapableREs { "[\\\"\\\\/bfnrt]" "u[[:xdigit:]]{4}" "." }
+ variable escapedCharRE "\\\\(?:[join $escapableREs |])"
+ variable unescapedCharRE {[^\\\"]}
+ variable stringRE "\"(?:$escapedCharRE|$unescapedCharRE)*\""
+
+ # as above, for validation
+ variable escapableREsv { "[\\\"\\\\/bfnrt]" "u[[:xdigit:]]{4}" }
+ variable escapedCharREv "\\\\(?:[join $escapableREsv |])"
+ variable stringREv "\"(?:$escapedCharREv|$unescapedCharRE)*\""
+
+ # (unquoted) words
+ variable wordTokens { "true" "false" "null" }
+ variable wordTokenRE [join $wordTokens "|"]
+
+ # number tokens
+ # negative lookahead (?!0)[[:digit:]]+ might be more elegant, but
+ # would slow down tokenizing by a factor of up to 3!
+ variable positiveRE {[1-9][[:digit:]]*}
+ variable cardinalRE "-?(?:$positiveRE|0)"
+ variable fractionRE {[.][[:digit:]]+}
+ variable exponentialRE {[eE][+-]?[[:digit:]]+}
+ variable numberRE "${cardinalRE}(?:$fractionRE)?(?:$exponentialRE)?"
+
+ # JSON token, and validation
+ variable tokenRE "$singleCharTokenRE|$stringRE|$wordTokenRE|$numberRE"
+ variable tokenREv "$singleCharTokenRE|$stringREv|$wordTokenRE|$numberRE"
+
+
+ # 0..n white space characters
+ set whiteSpaceRE {[[:space:]]*}
+
+ # Regular expression for validating a JSON text
+ variable validJsonRE "^(?:${whiteSpaceRE}(?:$tokenREv))*${whiteSpaceRE}$"
+}
+
+
+# Validate JSON text
+# @param jsonText JSON text
+# @return 1 iff $jsonText conforms to the JSON grammar
+# (@see http://json.org/)
+proc ::json::validate {jsonText} {
+ variable validJsonRE
+
+ return [regexp -- $validJsonRE $jsonText]
+}
+
+# ### ### ### ######### ######### #########
+## These three procedures shared between Tcl and Critcl implementations.
+## See also package "json::write".
+
+proc ::json::dict2json {dictVal} {
+ # XXX: Currently this API isn't symmetrical, as to create proper
+ # XXX: JSON text requires type knowledge of the input data
+ set json ""
+ set prefix ""
+
+ foreach {key val} $dictVal {
+ # key must always be a string, val may be a number, string or
+ # bare word (true|false|null)
+ if {0 && ![string is double -strict $val]
+ && ![regexp {^(?:true|false|null)$} $val]} {
+ set val "\"$val\""
+ }
+ append json "$prefix\"$key\": $val" \n
+ set prefix ,
+ }
+
+ return "\{${json}\}"
+}
+
+proc ::json::list2json {listVal} {
+ return "\[[join $listVal ,]\]"
+}
+
+proc ::json::string2json {str} {
+ return "\"$str\""
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide json 1.3.3