Posted to tcl by DS at Tue Nov 23 03:22:43 GMT 2010view raw
- #------------------------------------------------------------------------------
- # json.ij
- # "JavaScript Object Notation"
- #------------------------------------------------------------------------------
- namespace eval json {
- array unset json_command
- # the rule table. each entry is a list with three
- # elements. {expr vars code}
- # "expr" - the regular expression (expanded) to match this rule
- # "vars" - the match variables to assign from the regexp
- # "code" - code to evaluate when this rule is matched
- variable _rule
- array unset _rule
- # [process _indices _var _code]
- # "called by [eat] to evaluate code when a rule is matched"
- # arguments:
- # "_indices" - list of indices
- # "_vars" - name of variables to assign strings using the ranges in "_indices"
- # "_code" - code to evaluate
- # returns:
- # list with two elements. first is status, second is result from evaluation.
- proc process {_indices _vars _code} {
- # collect variables from parent call frame
- upvar _next _next
- upvar _size _size
- upvar _prev _prev
- upvar _source _source
- # assign the vars
- foreach _var $_vars _index $_indices {
- set $_var [eval [list string range $_source] $_index]
- }
- # evaluate the code and return the result
- set _status [catch $_code _result]
- list $_status $_result
- }
- # [eat ?rule? ?rule...?]
- # "attempt to eat rules"
- # arguments:
- # "rule" - the name of the rule to match
- # returns:
- # parsed result
- # notes:
- # attempts to match rules from first to last. If a rule generates an
- # error, [eat] attempts to match the next rule, and so on.
- # if a rule generates a "continue" [eat] starts again from the top.
- #
- # this proc carries the variables (via upvar):
- # _size (source size)
- # _next (current read pointer)
- # _ns_table (namespace table)
- # _source (source to parse)
- proc eat args {
- # the rule table
- variable _rule
- # the size of the source stream and the previous
- # read pointer
- upvar _size _size
- upvar _next _prev
- # have we run out of shit to parse?
- #if {$_prev >= $_size} {error "out of data"}
- # the source code
- upvar _source _source
- # foreach named rool
- foreach name $args {
- # fetch and decode the rule, try to match the expr
- set rule $_rule($name)
- foreach {expr vars code} $rule {}
- set indices [eval [list regexp -inline -indices -expanded -start $_prev -- $expr $_source]]
- # did the expr match?
- if {[llength $indices] > 0} {
- # set the _next pointer to point past the end of the match
- set _next [expr {1 + [lindex [lindex $indices 0] 1]}]
- # process the code associated with this rule
- foreach {status result} [process $indices $vars $code] {}
- switch -- $status {
- 0 {
- # shouldn't happen!
- }
- 1 {}
- 2 {
- # continue
- uplevel [list set _next $_next]
- return $result
- }
- 4 {
- # return
- # return a parsed item
- set ret [eval [concat eat $args]]
- uplevel [list set _next $_next]
- return $ret
- }
- default {
- error "shouldn't happen"
- }
- }
- }
- }
- # no rule matched
- error "syntax error"
- }
- # rule to match nothing
- set _rule(nothing) {{\A} match {return {}}}
- # rule to ignore whitespace
- set _rule(ignore.whitespace) {{\A\s+} match continue}
- # rule to match number
- # (needs work)
- set _rule(number) {
- {\A-?[0-9]+(\.[0-9]+)?}
- {match}
- {return [list number $match]}
- }
- # rule to match boolean
- set _rule(boolean) {{\Atrue|\Afalse} match {return [list boolean $match]}}
- # rule to match null
- set _rule(undefined) {{\Anull} match {return undefined}}
- # rule to match string
- set _rule(string) {
- {
- \A"((?:[^\\"]*(?:\\.)?)*){1,1}"
- |
- \A'((?:[^\\']*(?:\\.)?)*){1,1}'
- }
- {match str0 str1}
- {return [list string [subst -nocommands -novariables $str0$str1]]}
- }
- #" (stupid syntax highlighting!)
- # rule to match array end "]"
- set _rule(endarray) {{\A\]} {match} {return endarray}}
- # rule to match comma
- set _rule(comma) {{\A\,} {match} {return comma}}
- # rule to match colon
- set _rule(colon) {{\A\:} {match} {return colon}}
- # rule to match object end
- set _rule(endobject) {{\A\}} {match} {return endobject}}
- # rule to match object
- set _rule(object) {
- {\A\{}
- {match}
- {
- set tok [eat endobject ignore.whitespace nothing]
- if {$tok eq {endobject}} {return {object {}}}
- set ret {}
- while true {
- set key [eat string ignore.whitespace]
- eat colon whitespace.ignore
- set value [eat object array string undefined boolean number ignore.whitespace]
- lappend ret [lindex $key 1] $value
- set tok [eat comma endobject ignore.whitespace]
- if {$tok eq {endobject}} break
- }
- return [list object $ret]
- }
- }
- # rule to match array
- set _rule(array) {
- {\A\[}
- {match}
- {
- set tok [eat endarray ignore.whitespace nothing]
- if {$tok eq {endarray}} {return {array {}}}
- set ret {}
- while true {
- lappend ret [eat object array string undefined boolean number ignore.whitespace]
- set tok [eat comma endarray ignore.whitespace]
- if {$tok eq {endarray}} break
- }
- return [list array $ret]
- }
- }
- # rule to match end
- set _rule(end) {{\A\Z} {match} {return end}}
- # [json_decode slave _source]
- # "decode JSON string"
- set json_command(decode) json_decode
- proc json_decode {slave _source} {
- # initialize size and current read pointer
- set _size [string length $_source]
- set _next 0
- # eat a JSON object
- set ret [eat object array string undefined boolean number ignore.whitespace]
- # eat remaining whitespace
- eat end ignore.whitespace
- return $ret
- }
- # [json_set slave jsonvar ?key...? jsonvalue]
- # "kludgy! works like dict set, but for typed json objects"
- # arguments:
- # "jsonvar" - name of variable containing JSON object
- # "key" - name of key to set
- # "jsonvalue" - json value to set
- set json_command(set) json_set
- proc json_set_rec {json args} {
- set arglen [llength $args]
- if {$arglen == 0} {return $json}
- # TODO: need to validate json object here
- if {$arglen == 1} {return [lindex $args 0]}
- # modify a sub object
- set key [lindex $args 0]
- set args [lrange $args 1 end]
- set type [lindex $json 0]
- # does the object in question actually have properies?
- if {[lsearch {undefined string number boolean} $type] != -1} {
- error {object has no properties}
- }
- # modify a sub object
- switch -- $type {
- object {
- array set object [lindex $json 1]
- if {[llength $args] == 1} {
- # set element
- set object($key) [lindex $args 0]
- } else {
- # modify sub element
- set object($key) [eval [list json_set_rec $object($key)] $args]
- }
- return [list object [array get object]]
- }
- array {
- # set the implicit "length" property of the array?
- if {$key eq {length}} {
- # length has no properties
- if {[llength $args] == 1} {
- # TODO: not sure whether to round the index, or force it to be an
- # integer ... need to check up on what javascript does ...
- set newlenjson [lindex $args 0]
- if {[lindex $newlenjson 0] ne {number}} {error {invalid length}}
- # make sure length is a positive integer
- set newlen [expr {round([lindex $newlenjson 1])}]
- if {$newlen < 0} {error {invalid length}}
- # get the current array length
- set elements [lindex $json 1]
- set curlen [llength $elements]
- # resize array?
- if {$newlen == 0} {
- # empty the array
- set elements {}
- } elseif {$newlen > $curlen} {
- # new array entries are undefined
- while {[llength $elements] < $newlen} {
- lappend elements {undefined}
- }
- } elseif {$newlen < $curlen} {
- # shrink the array
- set elements [lrange $elements 0 [expr {$newlen - 1}]]
- }
- # return the new array
- return [list array $elements]
- }
- # can't set sub object of "length"!
- error {object has no properties}
- }
- # set an array index
- set elements [lindex $json 1]
- set element [lindex $elements $key]
- # make sure the index is valid
- if {$element eq {}} {error invalid index}
- set element [eval [list json_set_rec $element] $args]
- return [list array [lreplace $elements $key $key $element]]
- }
- }
- # oops!
- error {invalid JSON object}
- }
- proc json_set {slave jsonvar args} {
- set arglen [llength $args]
- # get the value
- if {$arglen == 0} {
- if {$slave eq {}} {
- return [uplevel 2 [list set $jsonvar]]
- } else {
- return [interp eval $slave [list set $jsonvar]]
- }
- }
- # directly set the value
- if {$arglen == 1} {
- if {$slave eq {}} {
- return [uplevel 2 [list set $jsonvar [lindex $args 0]]]
- } else {
- return [interp eval $slave [list set $jsonvar [lindex $args 0]]]
- }
- }
- # set a sub element
- if {$slave eq {}} {
- # call is from master interp, use uplevel
- set json [uplevel 2 [list set $jsonvar]]
- uplevel 2 [list set $jsonvar [eval [list json_set_rec $json] $args]]
- } else {
- # call is from slave, use interp eval
- set json [interp eval $slave [list set $jsonvar]]
- interp eval $slave [list set $jsonvar [eval [list json_set_rec $json] $args]]
- }
- }
- # [json_get slave json ?key...?]
- # "get an item from a JSON object"
- set json_command(get) json_get
- proc json_get {slave json args} {
- # return the whole object?
- if {$args eq {}} {return $json}
- # return a sub object!
- set key [lindex $args 0]
- set args [lrange $args 1 end]
- set type [lindex $json 0]
- # does the object in question actually have properies?
- if {[lsearch {undefined string number boolean} $type] != -1} {
- error {object has no properties}
- }
- # fetch the sub object
- switch -- $type {
- object {
- # return an object element
- array set object [lindex $json 1]
- return [eval [list json_get $slave $object($key)] $args]
- }
- array {
- # length property
- if {$key eq {length}} {
- if {[llength $args] != 0} {error {object has no properties}}
- return [list number [llength [lindex $json 1]]]
- }
- # return an array element, throw an error if the array index
- # is out of bounds
- set ret [eval [list json_get $slave [lindex [lindex $json 1] $key]] $args]
- if {$ret eq {}} {error "invalid index"}
- return $ret
- }
- }
- # oops!
- error {invalid JSON object}
- }
- # [json_encode slave json]
- # "encode a JSON object"
- # arguments:
- # "json" - type tagged data to encode
- # returns:
- # JSON encoded string
- set json_command(encode) json_encode
- proc json_encode {slave json} {
- switch -- [lindex $json 0] {
- undefined {return null}
- number {
- set value [lindex $json 1]
- if {[string is double $value]} {return $value}
- }
- string {return [escape jstr [lindex $json 1]]}
- boolean {
- if {[lindex $json 1]} {return true}
- return false
- }
- object {
- set items {}
- foreach {item value} [lindex $json 1] {
- lappend items [escape jstr $item]:[json_encode $slave $value]
- }
- return "{[join $items ,]}"
- }
- array {
- set items {}
- foreach value [lindex $json 1] {
- lappend items [json_encode $slave $value]
- }
- return "\[[join $items ,]\]"
- }
- }
- error {invalid JSON object}
- }
- # [json_typeof slave json]
- # "return the type of a JSON object"
- set json_command(typeof) json_typeof
- proc json_typeof {slave json} {
- set type [lindex $json 0]
- if {[lsearch {undefined string number array object boolean} $type] != -1} {return $type}
- error {invalid JSON object}
- }
- # [json_undefined slave]
- # "return an undefined JSON object"
- set json_command(undefined) json_undefined
- proc json_undefined {slave} {return undefined}
- # [json_number slave number]
- # "return a JSON number object"
- set json_command(number) json_number
- proc json_number {slave number} {
- if {[string is double $number]} {return [list number $number]}
- error {not a number}
- }
- # [json_object slave ?key value? ?key value...?]
- # "return a JSON object"
- set json_command(object) json_object
- proc json_object {slave args} {
- return [list object $args]
- }
- # [json_string slave string]
- # "return a JSON string"
- set json_command(string) json_string
- proc json_string {slave string} {return [list string $string]}
- # [json_boolean slave boolean]
- # "return a JSON boolean"
- set json_command(boolean) json_boolean
- proc json_boolean {slave boolean} {
- if {$boolean} {return {boolean true}}
- return {boolean false}
- }
- # [json_array slave ?item...?]
- # "return a JSON array"
- set json_command(array) json_array
- proc json_array {slave args} {return [list array $args]}
- # [json command args]
- # "json command ensemble"
- namespace export json
- proc json {command args} {
- variable json_command
- eval $json_command($command) [list {}] $args
- }
- proc protected_json {slave command args} {
- variable json_command
- eval $json_command($command) [list $slave] $args
- }
-
- proc on_interp_create {interp} {
- interp alias $interp json {} [namespace current]::protected_json $interp
- }
- }
- namespace import json::*
-