
function parseCGPObject(context) {
  for (; context.position < context.text.length; context.position++) {
    var c = context.text.charAt(context.position);
    if (c == '(') {
      return parseCGPArray(context);
    } else if (c == "\"") {
      return parseCGPString(context);
    } else if (c == "{") {
      return parseCGPDictionary(context);
    } else if (c == "#") {
      var c1 = context.text.charAt(context.position+1);
      if (c1 == 'T') {
        return parseCGPTimestamp(context);
      } else if (c1 == 'I') {
        return parseCGPInetAddr(context);
      } else if (/[\d\-]/.test(c1)) {
        return parseCGPNumber(context);
      }
    } else if (c == '[') {
        return parseCGPDatablock(context);
    } else if (/\s/.test(c)) {
      // skip a whitespace
    } else 
      return parseCGPString(context);
  }
}

function parseCGPString(context) {
  var ret = '';
  var escapeFlag = false;
  var quoted = false;
  while (context.position < context.text.length) {
    var c = context.text.charAt(context.position);
    if (!escapeFlag) {
      if (c == "\"") {
        if (quoted) {
          return ret;
        } else {
          ret = ''; // error if ret not empty
          quoted = true;
        }
      } else if (c == "\\") {
        escapeFlag = true;
      } else if (/[a-zA-Z0-9\._]/.test(c)) {
        ret += c;
      } else {
        if (quoted) {
          ret += c;
        } else if (/\S/.test(c)) {
          break; 
        }
      }
    } else {
      escapeFlag = false;
      var s1 = context.text.substring(context.position, context.position+3);
      //alert("s1 = "+s1);
      if (/^\d+$/.test(s1)) {
        ret += eval('\'\\u00'+Math.floor(s1 / 16).toString(16) +
                            (s1 % 16).toString(16)+'\'');
        context.position += 2;
      } else {
        ret += eval('\'\\'+c+'\'');
      }
    }
    context.position++;
  }
  return ret;
}

function parseCGPNumber(context) {
  context.position++;
  var ret = '';
  while (context.position < context.text.length) {
    var c = context.text.charAt(context.position);
    if (c == '-' || /\d/.test(c)) {
      ret += c;
    } else {
      return ret*1;
    }
    context.position++;
  }  
  return ret*1;
}

function parseCGPArray(context) {
  context.position++;
  var ret = new Array();
  while (context.position < context.text.length) {
    ret[ret.length] = parseCGPObject(context);
    while (context.position < context.text.length) {
      var c = context.text.charAt(context.position++);
      if (c == ")") {
        return ret;
      }
      if (c == ",") {
        break;
      }
    }  
  }
  return ret; // error
}

function parseCGPDictionary(context) {
  context.position++;
  var ret = new Object();
  while (context.position < context.text.length) {
    while (context.position < context.text.length) {
      var c = context.text.charAt(context.position++);
      if (c == "}") {
        return ret;
      }
      if (/^\S$/.test(c)) {
        context.position--;
        break;
      }
    }
    var str = parseCGPString(context);
    while (context.text.charAt(context.position++) != '=' && context.position < context.text.length);
    var val = parseCGPObject(context);
    ret[str] = val;
    while (context.text.charAt(context.position++) != ';' && context.position < context.text.length);
  }  
  return ret; // error
}

function parseCGPTimestamp(context) {
  context.position++;
  context.position++;
  var day = context.text.substring(context.position, context.position+2);
  var month = context.text.substring(context.position+3, context.position+5);
  var year = context.text.substring(context.position+6, context.position+10);
  var hour = 0;
  var minute = 0;
  var sec = 0;
  if (context.text.charAt(context.position+10) == '_') {
    hour = context.text.substring(context.position+11, context.position+13);
    minute = context.text.substring(context.position+14, context.position+16);
    sec = context.text.substring(context.position+17, context.position+19);
  }
  //alert (year+"-"+month+"-"+day+" "+hour+":"+minute+":"+sec);
  var ret = new Date(); 
  ret.setUTCFullYear(year, month, day);
  ret.setUTCHours(hour, minute, sec, 0);
  return ret;
}

function parseCGPDatablock(context) {
  return parseCGPString(context);
}

function parseCGPInetAddr(context) {
  context.position++;
  context.position++;
  var ret = '';
  for (; context.position < context.text.length; context.position++) {
    var c = context.text.charAt(context.position);
    if (/\s/.test(c)) {
      break;
    }
    ret += c;
  }
  return ret;
}

var CGPData = function () {
  var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
  s = {
    array: function (x) {
      var a = ['('], b, f, i, l = x.length, v;
        for (i = 0; i < l; i += 1) {
            v = x[i];
            f = s[typeof v];
            if (f) {
              v = f(v);
              if (typeof v == 'string') {
                if (b) {
                    a[a.length] = ',';
                }
                a[a.length] = v;
                b = true;
              }
            }
        }
        a[a.length] = ')';
        return a.join('');
    },
    'boolean': function (x) {
        return String(x);
    },
    'null': function (x) {
        return "null";
    },
    number: function (x) {
        return isFinite(x) ? "#"+String(x) : 'null';
    },
    object: function (x) {
      if (x) {
        if (x instanceof Array) {
            return s.array(x);
        }
        var a = ['{'], f, i, v;
        for (i in x) {
          if (!x.hasOwnProperty || x.hasOwnProperty(i)) {
            v = x[i];
            f = s[typeof v];
            if (f) {
              v = f(v);
              if (typeof v == 'string') {
                  a.push(s.string(i), '=', v, ';');
              }
            }
          }
        }
        a[a.length] = '}';
        return a.join('');
      }
      return 'null';
    },
    string: function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = ''+b.charCodeAt();
                        var c1 = c;
                        for (var i = 0; i < 3-c.length; i++) {
                          c1 = '0'+c1;
                        }
                        return '\\' + c1;
                    });
                }
                if (/^[A-Za-z0-9]+$/.test(x)) {
                  return x;
                } else {
                  return '"' + x + '"';
                }
    }
  };

  return {
    parse: function (text) {
      var context = { "text": text, "position": 0 };
      var ret = parseCGPObject(context);
      return ret;
    },
    stringify: function (v) {
      var f = s[typeof v];
      if (f) {
        v = f(v);
        if (typeof v == 'string') {
        return v;
        }
      }
      return;
    }
  }
}();
