laurent@371: json_parse = (function () {
laurent@371: 
laurent@371: // This is a function that can parse a JSON text, producing a JavaScript
laurent@371: // data structure. It is a simple, recursive descent parser. It does not use
laurent@371: // eval or regular expressions, so it can be used as a model for implementing
laurent@371: // a JSON parser in other languages.
laurent@371: 
laurent@371: // We are defining the function inside of another function to avoid creating
laurent@371: // global variables.
laurent@371: 
laurent@371:     var at,     // The index of the current character
laurent@371:         ch,     // The current character
laurent@371:         escapee = {
laurent@371:             '"':  '"',
laurent@371:             '\\': '\\',
laurent@371:             '/':  '/',
laurent@371:             b:    '\b',
laurent@371:             f:    '\f',
laurent@371:             n:    '\n',
laurent@371:             r:    '\r',
laurent@371:             t:    '\t'
laurent@371:         },
laurent@371:         text,
laurent@371: 
laurent@371:         error = function (m) {
laurent@371: 
laurent@371: // Call error when something is wrong.
laurent@371: 
laurent@371:             throw {
laurent@371:                 name:    'SyntaxError',
laurent@371:                 message: m,
laurent@371:                 at:      at,
laurent@371:                 text:    text
laurent@371:             };
laurent@371:         },
laurent@371: 
laurent@371:         next = function (c) {
laurent@371: 
laurent@371: // If a c parameter is provided, verify that it matches the current character.
laurent@371: 
laurent@371:             if (c && c !== ch) {
laurent@371:                 error("Expected '" + c + "' instead of '" + ch + "'");
laurent@371:             }
laurent@371: 
laurent@371: // Get the next character. When there are no more characters,
laurent@371: // return the empty string.
laurent@371: 
laurent@371:             ch = text.charAt(at);
laurent@371:             at += 1;
laurent@371:             return ch;
laurent@371:         },
laurent@371: 
laurent@371:         number = function () {
laurent@371: 
laurent@371: // Parse a number value.
laurent@371: 
laurent@371:             var number,
laurent@371:                 string = '';
laurent@371: 
laurent@371:             if (ch === '-') {
laurent@371:                 string = '-';
laurent@371:                 next('-');
laurent@371:             }
laurent@371:             while (ch >= '0' && ch <= '9') {
laurent@371:                 string += ch;
laurent@371:                 next();
laurent@371:             }
laurent@371:             if (ch === '.') {
laurent@371:                 string += '.';
laurent@371:                 while (next() && ch >= '0' && ch <= '9') {
laurent@371:                     string += ch;
laurent@371:                 }
laurent@371:             }
laurent@371:             if (ch === 'e' || ch === 'E') {
laurent@371:                 string += ch;
laurent@371:                 next();
laurent@371:                 if (ch === '-' || ch === '+') {
laurent@371:                     string += ch;
laurent@371:                     next();
laurent@371:                 }
laurent@371:                 while (ch >= '0' && ch <= '9') {
laurent@371:                     string += ch;
laurent@371:                     next();
laurent@371:                 }
laurent@371:             }
laurent@371:             number = +string;
laurent@371:             if (isNaN(number)) {
laurent@371:                 error("Bad number");
laurent@371:             } else {
laurent@371:                 return number;
laurent@371:             }
laurent@371:         },
laurent@371: 
laurent@371:         string = function () {
laurent@371: 
laurent@371: // Parse a string value.
laurent@371: 
laurent@371:             var hex,
laurent@371:                 i,
laurent@371:                 string = '',
laurent@371:                 uffff;
laurent@371: 
laurent@371: // When parsing for string values, we must look for " and \ characters.
laurent@371: 
laurent@371:             if (ch === '"') {
laurent@371:                 while (next()) {
laurent@371:                     if (ch === '"') {
laurent@371:                         next();
laurent@371:                         return string;
laurent@371:                     } else if (ch === '\\') {
laurent@371:                         next();
laurent@371:                         if (ch === 'u') {
laurent@371:                             uffff = 0;
laurent@371:                             for (i = 0; i < 4; i += 1) {
laurent@371:                                 hex = parseInt(next(), 16);
laurent@371:                                 if (!isFinite(hex)) {
laurent@371:                                     break;
laurent@371:                                 }
laurent@371:                                 uffff = uffff * 16 + hex;
laurent@371:                             }
laurent@371:                             string += String.fromCharCode(uffff);
laurent@371:                         } else if (typeof escapee[ch] === 'string') {
laurent@371:                             string += escapee[ch];
laurent@371:                         } else {
laurent@371:                             break;
laurent@371:                         }
laurent@371:                     } else {
laurent@371:                         string += ch;
laurent@371:                     }
laurent@371:                 }
laurent@371:             }
laurent@371:             error("Bad string");
laurent@371:         },
laurent@371: 
laurent@371:         white = function () {
laurent@371: 
laurent@371: // Skip whitespace.
laurent@371: 
laurent@371:             while (ch && ch <= ' ') {
laurent@371:                 next();
laurent@371:             }
laurent@371:         },
laurent@371: 
laurent@371:         word = function () {
laurent@371: 
laurent@371: // true, false, or null.
laurent@371: 
laurent@371:             switch (ch) {
laurent@371:             case 't':
laurent@371:                 next('t');
laurent@371:                 next('r');
laurent@371:                 next('u');
laurent@371:                 next('e');
laurent@371:                 return true;
laurent@371:             case 'f':
laurent@371:                 next('f');
laurent@371:                 next('a');
laurent@371:                 next('l');
laurent@371:                 next('s');
laurent@371:                 next('e');
laurent@371:                 return false;
laurent@371:             case 'n':
laurent@371:                 next('n');
laurent@371:                 next('u');
laurent@371:                 next('l');
laurent@371:                 next('l');
laurent@371:                 return null;
laurent@371:             }
laurent@371:             error("Unexpected '" + ch + "'");
laurent@371:         },
laurent@371: 
laurent@371:         value,  // Place holder for the value function.
laurent@371: 
laurent@371:         array = function () {
laurent@371: 
laurent@371: // Parse an array value.
laurent@371: 
laurent@371:             var array = [];
laurent@371: 
laurent@371:             if (ch === '[') {
laurent@371:                 next('[');
laurent@371:                 white();
laurent@371:                 if (ch === ']') {
laurent@371:                     next(']');
laurent@371:                     return array;   // empty array
laurent@371:                 }
laurent@371:                 while (ch) {
laurent@371:                     array.push(value());
laurent@371:                     white();
laurent@371:                     if (ch === ']') {
laurent@371:                         next(']');
laurent@371:                         return array;
laurent@371:                     }
laurent@371:                     next(',');
laurent@371:                     white();
laurent@371:                 }
laurent@371:             }
laurent@371:             error("Bad array");
laurent@371:         },
laurent@371: 
laurent@371:         object = function () {
laurent@371: 
laurent@371: // Parse an object value.
laurent@371: 
laurent@371:             var key,
laurent@371:                 object = {};
laurent@371: 
laurent@371:             if (ch === '{') {
laurent@371:                 next('{');
laurent@371:                 white();
laurent@371:                 if (ch === '}') {
laurent@371:                     next('}');
laurent@371:                     return object;   // empty object
laurent@371:                 }
laurent@371:                 while (ch) {
laurent@371:                     key = string();
laurent@371:                     white();
laurent@371:                     next(':');
laurent@371:                     if (Object.hasOwnProperty.call(object, key)) {
laurent@371:                         error('Duplicate key "' + key + '"');
laurent@371:                     }
laurent@371:                     object[key] = value();
laurent@371:                     white();
laurent@371:                     if (ch === '}') {
laurent@371:                         next('}');
laurent@371:                         return object;
laurent@371:                     }
laurent@371:                     next(',');
laurent@371:                     white();
laurent@371:                 }
laurent@371:             }
laurent@371:             error("Bad object");
laurent@371:         };
laurent@371: 
laurent@371:     value = function () {
laurent@371: 
laurent@371: // Parse a JSON value. It could be an object, an array, a string, a number,
laurent@371: // or a word.
laurent@371: 
laurent@371:         white();
laurent@371:         switch (ch) {
laurent@371:         case '{':
laurent@371:             return object();
laurent@371:         case '[':
laurent@371:             return array();
laurent@371:         case '"':
laurent@371:             return string();
laurent@371:         case '-':
laurent@371:             return number();
laurent@371:         default:
laurent@371:             return ch >= '0' && ch <= '9' ? number() : word();
laurent@371:         }
laurent@371:     };
laurent@371: 
laurent@371: // Return the json_parse function. It will have access to all of the above
laurent@371: // functions and variables.
laurent@371: 
laurent@371:     return function (source, reviver) {
laurent@371:         var result;
laurent@371: 
laurent@371:         text = source;
laurent@371:         at = 0;
laurent@371:         ch = ' ';
laurent@371:         result = value();
laurent@371:         white();
laurent@371:         if (ch) {
laurent@371:             error("Syntax error");
laurent@371:         }
laurent@371: 
laurent@371: // If there is a reviver function, we recursively walk the new structure,
laurent@371: // passing each name/value pair to the reviver function for possible
laurent@371: // transformation, starting with a temporary root object that holds the result
laurent@371: // in an empty key. If there is not a reviver function, we simply return the
laurent@371: // result.
laurent@371: 
laurent@371:         return typeof reviver === 'function' ? (function walk(holder, key) {
laurent@371:             var k, v, value = holder[key];
laurent@371:             if (value && typeof value === 'object') {
laurent@371:                 for (k in value) {
laurent@371:                     if (Object.hasOwnProperty.call(value, k)) {
laurent@371:                         v = walk(value, k);
laurent@371:                         if (v !== undefined) {
laurent@371:                             value[k] = v;
laurent@371:                         } else {
laurent@371:                             delete value[k];
laurent@371:                         }
laurent@371:                     }
laurent@371:                 }
laurent@371:             }
laurent@371:             return reviver.call(holder, key, value);
laurent@371:         }({'': result}, '')) : result;
laurent@371:     };
laurent@371: }());