changeset 0:e985e6d762b9 default tip

Origination.
author Atul Varma <varmaa@toolness.com>
date Wed, 24 Sep 2008 16:47:18 -0700
parents
children
files json.py json2.js post.js py.js test.py
diffstat 5 files changed, 995 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json.py	Wed Sep 24 16:47:18 2008 -0700
@@ -0,0 +1,310 @@
+import string
+import types
+
+##    json.py implements a JSON (http://json.org) reader and writer.
+##    Copyright (C) 2005  Patrick D. Logan
+##    Contact mailto:patrickdlogan@stardecisions.com
+##
+##    This library is free software; you can redistribute it and/or
+##    modify it under the terms of the GNU Lesser General Public
+##    License as published by the Free Software Foundation; either
+##    version 2.1 of the License, or (at your option) any later version.
+##
+##    This library is distributed in the hope that it will be useful,
+##    but WITHOUT ANY WARRANTY; without even the implied warranty of
+##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+##    Lesser General Public License for more details.
+##
+##    You should have received a copy of the GNU Lesser General Public
+##    License along with this library; if not, write to the Free Software
+##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+class _StringGenerator(object):
+	def __init__(self, string):
+		self.string = string
+		self.index = -1
+	def peek(self):
+		i = self.index + 1
+		if i < len(self.string):
+			return self.string[i]
+		else:
+			return None
+	def next(self):
+		self.index += 1
+		if self.index < len(self.string):
+			return self.string[self.index]
+		else:
+			raise StopIteration
+	def all(self):
+		return self.string
+
+class WriteException(Exception):
+    pass
+
+class ReadException(Exception):
+    pass
+
+class JsonReader(object):
+    hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
+    escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}
+
+    def read(self, s):
+        self._generator = _StringGenerator(s)
+        result = self._read()
+        return result
+
+    def _read(self):
+        self._eatWhitespace()
+        peek = self._peek()
+        if peek is None:
+            raise ReadException, "Nothing to read: '%s'" % self._generator.all()
+        if peek == '{':
+            return self._readObject()
+        elif peek == '[':
+            return self._readArray()            
+        elif peek == '"':
+            return self._readString()
+        elif peek == '-' or peek.isdigit():
+            return self._readNumber()
+        elif peek == 't':
+            return self._readTrue()
+        elif peek == 'f':
+            return self._readFalse()
+        elif peek == 'n':
+            return self._readNull()
+        elif peek == '/':
+            self._readComment()
+            return self._read()
+        else:
+            raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
+
+    def _readTrue(self):
+        self._assertNext('t', "true")
+        self._assertNext('r', "true")
+        self._assertNext('u', "true")
+        self._assertNext('e', "true")
+        return True
+
+    def _readFalse(self):
+        self._assertNext('f', "false")
+        self._assertNext('a', "false")
+        self._assertNext('l', "false")
+        self._assertNext('s', "false")
+        self._assertNext('e', "false")
+        return False
+
+    def _readNull(self):
+        self._assertNext('n', "null")
+        self._assertNext('u', "null")
+        self._assertNext('l', "null")
+        self._assertNext('l', "null")
+        return None
+
+    def _assertNext(self, ch, target):
+        if self._next() != ch:
+            raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
+
+    def _readNumber(self):
+        isfloat = False
+        result = self._next()
+        peek = self._peek()
+        while peek is not None and (peek.isdigit() or peek == "."):
+            isfloat = isfloat or peek == "."
+            result = result + self._next()
+            peek = self._peek()
+        try:
+            if isfloat:
+                return float(result)
+            else:
+                return int(result)
+        except ValueError:
+            raise ReadException, "Not a valid JSON number: '%s'" % result
+
+    def _readString(self):
+        result = ""
+        assert self._next() == '"'
+        try:
+            while self._peek() != '"':
+                ch = self._next()
+                if ch == "\\":
+                    ch = self._next()
+                    if ch in 'brnft':
+                        ch = self.escapes[ch]
+                    elif ch == "u":
+		        ch4096 = self._next()
+			ch256  = self._next()
+			ch16   = self._next()
+			ch1    = self._next()
+			n = 4096 * self._hexDigitToInt(ch4096)
+			n += 256 * self._hexDigitToInt(ch256)
+			n += 16  * self._hexDigitToInt(ch16)
+			n += self._hexDigitToInt(ch1)
+			ch = unichr(n)
+                    elif ch not in '"/\\':
+                        raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
+                result = result + ch
+        except StopIteration:
+            raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
+        assert self._next() == '"'
+        return result
+
+    def _hexDigitToInt(self, ch):
+        try:
+            result = self.hex_digits[ch.upper()]
+        except KeyError:
+            try:
+                result = int(ch)
+	    except ValueError:
+	         raise ReadException, "The character %s is not a hex digit." % ch
+        return result
+
+    def _readComment(self):
+        assert self._next() == "/"
+        second = self._next()
+        if second == "/":
+            self._readDoubleSolidusComment()
+        elif second == '*':
+            self._readCStyleComment()
+        else:
+            raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
+
+    def _readCStyleComment(self):
+        try:
+            done = False
+            while not done:
+                ch = self._next()
+                done = (ch == "*" and self._peek() == "/")
+                if not done and ch == "/" and self._peek() == "*":
+                    raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
+            self._next()
+        except StopIteration:
+            raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
+
+    def _readDoubleSolidusComment(self):
+        try:
+            ch = self._next()
+            while ch != "\r" and ch != "\n":
+                ch = self._next()
+        except StopIteration:
+            pass
+
+    def _readArray(self):
+        result = []
+        assert self._next() == '['
+        done = self._peek() == ']'
+        while not done:
+            item = self._read()
+            result.append(item)
+            self._eatWhitespace()
+            done = self._peek() == ']'
+            if not done:
+                ch = self._next()
+                if ch != ",":
+                    raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
+        assert ']' == self._next()
+        return result
+
+    def _readObject(self):
+        result = {}
+        assert self._next() == '{'
+        done = self._peek() == '}'
+        while not done:
+            key = self._read()
+            if type(key) is not types.StringType:
+                raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
+            self._eatWhitespace()
+            ch = self._next()
+            if ch != ":":
+                raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
+            self._eatWhitespace()
+            val = self._read()
+            result[key] = val
+            self._eatWhitespace()
+            done = self._peek() == '}'
+            if not done:
+                ch = self._next()
+                if ch != ",":
+                    raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
+	assert self._next() == "}"
+        return result
+
+    def _eatWhitespace(self):
+        p = self._peek()
+        while p is not None and p in string.whitespace or p == '/':
+            if p == '/':
+                self._readComment()
+            else:
+                self._next()
+            p = self._peek()
+
+    def _peek(self):
+        return self._generator.peek()
+
+    def _next(self):
+        return self._generator.next()
+
+class JsonWriter(object):
+        
+    def _append(self, s):
+        self._results.append(s)
+
+    def write(self, obj, escaped_forward_slash=False):
+        self._escaped_forward_slash = escaped_forward_slash
+        self._results = []
+        self._write(obj)
+        return "".join(self._results)
+
+    def _write(self, obj):
+        ty = type(obj)
+        if ty is types.DictType:
+            n = len(obj)
+            self._append("{")
+            for k, v in obj.items():
+                self._write(k)
+                self._append(":")
+                self._write(v)
+                n = n - 1
+                if n > 0:
+                    self._append(",")
+            self._append("}")
+        elif ty is types.ListType or ty is types.TupleType:
+            n = len(obj)
+            self._append("[")
+            for item in obj:
+                self._write(item)
+                n = n - 1
+                if n > 0:
+                    self._append(",")
+            self._append("]")
+        elif ty is types.StringType or ty is types.UnicodeType:
+            self._append('"')
+	    obj = obj.replace('\\', r'\\')
+            if self._escaped_forward_slash:
+                obj = obj.replace('/', r'\/')
+	    obj = obj.replace('"', r'\"')
+	    obj = obj.replace('\b', r'\b')
+	    obj = obj.replace('\f', r'\f')
+	    obj = obj.replace('\n', r'\n')
+	    obj = obj.replace('\r', r'\r')
+	    obj = obj.replace('\t', r'\t')
+            self._append(obj)
+            self._append('"')
+        elif ty is types.IntType or ty is types.LongType:
+            self._append(str(obj))
+        elif ty is types.FloatType:
+            self._append("%f" % obj)
+        elif obj is True:
+            self._append("true")
+        elif obj is False:
+            self._append("false")
+        elif obj is None:
+            self._append("null")
+        else:
+            raise WriteException, "Cannot write in JSON: %s" % repr(obj)
+
+def write(obj, escaped_forward_slash=False):
+    return JsonWriter().write(obj, escaped_forward_slash)
+
+def read(s):
+    return JsonReader().read(s)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json2.js	Wed Sep 24 16:47:18 2008 -0700
@@ -0,0 +1,481 @@
+/*
+    http://www.JSON.org/json2.js
+    2008-09-01
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the object holding the key.
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array, then it will be used to
+            select the members to be serialized. It filters the results such
+            that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*global JSON */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
+    charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
+    getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
+    parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+    JSON = {};
+}
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z';
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapeable.lastIndex = 0;
+        return escapeable.test(string) ?
+            '"' + string.replace(escapeable, function (a) {
+                var c = meta[a];
+                if (typeof c === 'string') {
+                    return c;
+                }
+                return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// If the object has a dontEnum length property, we'll treat it as an array.
+
+            if (typeof value.length === 'number' &&
+                    !value.propertyIsEnumerable('length')) {
+
+// The object is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/post.js	Wed Sep 24 16:47:18 2008 -0700
@@ -0,0 +1,1 @@
+print(JSON.stringify(new Py(jsoncode)));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/py.js	Wed Sep 24 16:47:18 2008 -0700
@@ -0,0 +1,50 @@
+function Py(code) {
+  if ('Py' in this)
+    throw new Error("Py() must be called with 'new' keyword");
+
+  var self = this;
+
+  var co_code = code.co_code;
+  var co_consts = code.co_consts;
+  var co_names = code.co_names;
+
+  var ops = {};
+  var stack = [];
+  var globals = {};
+  var locals = {};
+
+  ops.LOAD_CONST = function LOAD_CONST(index) {
+    stack.push(co_consts[index]);
+  };
+
+  ops.LOAD_NAME = function LOAD_NAME(index) {
+    stack.push(locals[co_names[index]]);
+  };
+
+  ops.BINARY_ADD = function BINARY_ADD() {
+    stack.push(stack.pop() + stack.pop());
+  };
+
+  ops.STORE_NAME = function STORE_NAME(index) {
+    locals[co_names[index]] = stack.pop();
+  };
+
+  ops.STORE_GLOBAL = function STORE_GLOBAL(index) {
+    globals[co_names[index]] = stack.pop();
+  };
+
+  ops.RETURN_VALUE = function RETURN_VALUE() {
+    self.returnValue = stack.pop();
+  };
+
+  for (var i = 0; i < co_code.ops.length; i++) {
+    var op = co_code.ops[i].op;
+    var arg = co_code.ops[i].arg;
+    if (!(op in ops))
+      throw new Error('No implementation for op ' + op);
+    ops[op](arg);
+  }
+
+  self.locals = locals;
+  self.globals = globals;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test.py	Wed Sep 24 16:47:18 2008 -0700
@@ -0,0 +1,153 @@
+import sys
+import os
+import tempfile
+import subprocess
+import dis
+
+import json
+
+def disassemble(co, lasti=-1):
+    """Disassemble a code object and return a JSON-able dictionary
+    of its representation.  This function is a slightly modified version
+    of dis.disassemble()."""
+
+    code = co.co_code
+    labels = dis.findlabels(code)
+    linestarts = dict(dis.findlinestarts(co))
+    n = len(code)
+    i = 0
+    extended_arg = 0
+    free = None
+
+    op_infos = []
+    bytes_to_op_infos = {}
+
+    while i < n:
+        op_info = {}
+        op_infos.append(op_info)
+        bytes_to_op_infos[i] = len(op_infos) - 1
+
+        c = code[i]
+        op = ord(c)
+        if i in linestarts:
+            op_info['line'] = linestarts[i]
+
+        op_info['op'] = dis.opname[op]
+        i = i+1
+        if op >= dis.HAVE_ARGUMENT:
+            oparg = ord(code[i]) + ord(code[i+1])*256 + extended_arg
+            extended_arg = 0
+            i = i+2
+            if op == dis.EXTENDED_ARG:
+                extended_arg = oparg*65536L
+            op_info['arg'] = oparg
+
+    return {'ops': op_infos,
+            'bytes_to_ops': bytes_to_op_infos}
+
+def exec_js(code, extra_output=None):
+    if extra_output is None:
+        extra_output = {}
+
+    fd, temppath = tempfile.mkstemp('.js')
+    os.close(fd)
+    temp = open(temppath, 'w')
+    temp.write(code.encode('utf-8'))
+    temp.close()
+
+    extra_output['errors'] = []
+
+    BRANCH_LIMIT = '500'
+    OUTPUT_FILENAME = '__temp_output.txt'
+    ERROR_FILENAME = '__temp_input.txt'
+
+    out_file = open(OUTPUT_FILENAME, 'w')
+    err_file = open(ERROR_FILENAME, 'w')
+
+    args = ['js',
+            '-b', BRANCH_LIMIT,
+            '-f', 'json2.js',
+            '-f', 'py.js',
+            '-f', temppath,
+            '-f', 'post.js']
+
+    popen = subprocess.Popen(args,
+                             stdout=out_file,
+                             stderr=err_file)
+
+    popen.wait()
+
+    os.remove(temppath)
+
+    out_file = open(OUTPUT_FILENAME, 'r')
+    output = out_file.read()
+
+    err_file = open(ERROR_FILENAME, 'r')
+    error = err_file.read()
+
+    out_file.close()
+    err_file.close()
+
+    os.remove(OUTPUT_FILENAME)
+    os.remove(ERROR_FILENAME)
+
+    output = output.strip()
+
+    if error:
+        extra_output['errors'].append(error)
+
+    try:
+        obj = json.read(output)
+    except Exception, e:
+        extra_output['errors'].append(str(e))
+        obj = None
+
+    return obj
+
+def run(pycode):
+    bytecode = compile(pycode, "<string>", "exec")
+    jsoncode = {'co_consts': bytecode.co_consts,
+                'co_names': bytecode.co_names,
+                'co_code': disassemble(bytecode)}
+    extra = {}
+    retval = exec_js('jsoncode = %s' % json.write(jsoncode), extra)
+    if extra['errors']:
+        print "Errors occurred: "
+        print
+        for error in extra['errors']:
+            print "  * %s" % error,
+        print
+        print
+        print "Disassembly of code:"
+        print
+        dis.dis(bytecode)
+        raise Exception()
+    return retval
+
+def test_assignment_works():
+    """
+    >>> run('a = 1')['locals']['a']
+    1
+    """
+
+    pass
+
+def test_binary_addition_works():
+    """
+    >>> run('a = 1; a = 5 + a')['locals']['a']
+    6
+    """
+
+    pass
+
+def test_global_works():
+    """
+    >>> run('global a; a = 1')['globals']['a']
+    1
+    """
+
+    pass
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()