// vim:syntax=lpc
// $Id: JSONTokener.pike,v 1.12 2007/06/14 09:44:43 embee Exp $
//
// I really hate those comments.
//
// Copyright History:
// 1. Public Domain 2002 JSON.org
// 2. Ported to C# by Are Bjolseth, teleplan.no
// 3. Ported to pike by Bill Welliver
// "Last night, I downloaded the JSON-C# code, and converted it to pike
// (with relatively little effort, it was mostly a tedious reformatting
// job)."
// 4. Adopted by Tobias 'tobij' Josefowitz, now uses Pike datastructures,
// and can only be run in LDMud.
//
// As far as I am concerned, this still is Public Domain.
// I will probably once find out whether Are and Bill think the same wwy
// about it.
#ifdef __PIKE__
///
///
/// A JSONTokener takes a source string and extracts characters and tokens from
/// it. It is used by the JSONObject and JSONArray constructors to parse
/// JSON source strings.
///
///
/// Public Domain 2002 JSON.org
/// @author JSON.org
/// @version 0.1
///
/// Ported to C# by Are Bjolseth, teleplan.no
///
///
/// - Implement Custom exceptions
/// - Add unit testing
/// - Add log4net
///
///
///
/// The index of the next character.
int myIndex;
/// The source string being tokenized.
string mySource;
mapping(int:object) generics = ([ ]);
# define PROTECTED public
# define THROW(x) throw(Error.Generic(x))
# define SBGET(x) (x)->get()
# define int2char(x) String.int2char(x)
# define trim_whites(x) String.trim_whites(x)
program objectbuilder, arraybuilder;
#else
# define PROTECTED protected
PROTECTED int myIndex;
PROTECTED string mySource;
# define arrayp(x) pointerp(x)
# define THROW(x) raise_error(x)
# define UNDEFINED 0
# define SBGET(x) (x)
# define int2char(x) _int2char(x)
# define trim_whites(x) trim(x)
# define search strstr
PROTECTED mixed nextObject();
PROTECTED string _int2char(int c) {
string s = " ";
s[0] = c;
return s;
}
#endif
///
/// Construct a JSONTokener from a string.
///
/// A source string.
#ifdef __PIKE__
void create(string s, program|void objectb, program|void arrayb, mapping(int:object)|void builders)
#else
mixed parse_json(string s)
#endif
{
mySource = s;
myIndex = 0;
#ifdef __PIKE__
objectbuilder = objectb;
arraybuilder = arrayb;
generics = builders;
#else
return nextObject();
#endif
}
///
/// Back up one character. This provides a sort of lookahead capability,
/// so that you can test for a digit or letter before attempting to parse
/// the next number or identifier.
///
PROTECTED void back() {
if (myIndex > 0)
myIndex -= 1;
}
///
/// Get the hex value of a character (base16).
///
///
/// A character between '0' and '9' or between 'A' and 'F' or
/// between 'a' and 'f'.
///
/// An int between 0 and 15, or -1 if c was not a hex digit.
PROTECTED int dehexchar(int c) {
if (c >= '0' && c <= '9')
{
return c - '0';
}
if (c >= 'A' && c <= 'F')
{
return c + 10 - 'A';
}
if (c >= 'a' && c <= 'f')
{
return c + 10 - 'a';
}
return -1;
}
///
/// Determine if the source string still contains characters that next() can consume.
///
/// true if not yet at the end of the source.
PROTECTED int more() {
return myIndex < sizeof(mySource);
}
///
/// Get the next character in the source string.
///
/// The next character, or 0 if past the end of the source string.
#ifdef __PIKE__
PROTECTED int next(int|void x)
#else
varargs PROTECTED int next(int x)
#endif
{
if(x) {
int n = next();
if (n != x) {
THROW("Expected '" + x + "' and instead saw '" + n + "'.\n");
}
return n;
} else {
int c = more() ? mySource[myIndex] : 0;
myIndex += 1;
return c;
}
}
///
/// Get the next n characters.
///
/// The number of characters to take.
/// A string of n characters.
PROTECTED string nextn(int n) {
int i = myIndex;
int j = i + n;
if (j >= sizeof(mySource)) {
THROW("Substring bounds error\n");
}
myIndex += n;
return mySource[i..j];
}
///
/// Get the next char in the string, skipping whitespace
/// and comments (slashslash and slashstar).
///
/// A character, or 0 if there are no more characters.
PROTECTED int nextClean() {
while (1) {
int c = next();
if (c == '/') {
switch (next()) {
case '/':
do {
c = next();
} while (c != '\n' && c != '\r' && c != 0);
break;
case '*':
while (1) {
c = next();
if (c == 0) {
THROW("Unclosed comment.\n");
}
if (c == '*') {
if (next() == '/') {
break;
}
back();
}
}
break;
default:
back();
return '/';
}
}
else if (c == 0 || c > ' ') {
return c;
}
}
#ifndef __PIKE__
return 0; // will never be reached, stupid LDMud!
#endif
}
///
/// Return the characters up to the next close quote character.
/// Backslash processing is done. The formal JSON format does not
/// allow strings in single quotes, but an implementation is allowed to
/// accept them.
///
/// The quoting character, either " or '
/// A String.
PROTECTED string nextString(int quote) {
int c;
#ifdef __PIKE__
String.Buffer sb = String.Buffer();
#else
string sb = "";
#endif
while (1) {
c = next();
if ((c == 0x00) || (c == 0x0A) || (c == 0x0D)) {
THROW("Unterminated string.\n");
}
// CTRL chars
if (c == '\\') {
c = next();
switch (c) {
case 'b': //Backspace
sb+="\b";
break;
case 't': //Horizontal tab
sb+="\t";
break;
case 'n': //newline
sb+="\n";
break;
case 'f': //Form feed
sb+="\f";
break;
case 'r': // Carriage return
sb+="\r";
break;
case 'u':
#ifdef __PIKE__
int iascii;
sscanf(nextn(4), "%4x", iascii);
sb+=int2char(iascii);
#else
sb+=int2char(hex2int(nextn(2)));
sb+=int2char(hex2int(nextn(2)));
#endif
break;
default:
sb+=int2char(c);
break;
}
} else {
if (c == quote) {
#ifdef __PIKE__
return SBGET(sb);
#else
return sb;
#endif
}
sb+=int2char(c);
}
}//END-while
#ifndef __PIKE__
return ""; // will never be reached. stupid LDMud
#endif
}
#ifdef __PIKE__
PROTECTED mixed nextGeneric(int quote, object|function|program builderp) {
object builder = builderp();
for (;;) {
int c;
if (!more()) {
error("Unterminated %c%c\n", quote, quote);
}
c = next();
if (c == quote) {
return builder->finish();
} else {
builder->add(c);
}
}
}
#endif
///
/// Unescape the source text. Convert %hh sequences to single characters,
/// and convert plus to space. There are Web transport systems that insist on
/// doing unnecessary URL encoding. This provides a way to undo it.
///
///
/// Convert %hh sequences to single characters, and convert plus to space.
///
/// A string that may contain plus and %hh sequences.
/// The unescaped string.
#ifdef __PIKE__
PROTECTED string unescape(string|void s)
#else
PROTECTED varargs string unescape(string s)
#endif
{
if(!s) s = mySource;
int len = sizeof(s);
#ifdef __PIKE__
String.Buffer sb = String.Buffer();
#else
string sb = "";
#endif
for (int i=0; i < len; i++) {
int c = s[i];
if (c == '+') {
c = ' ';
} else if (c == '%' && (i + 2 < len)) {
int d = dehexchar(s[i+1]);
int e = dehexchar(s[i+2]);
if (d >= 0 && e >= 0) {
c = (d*16 + e);
i += 2;
}
}
sb+=int2char(c);
}
return SBGET(sb);
}
#ifdef __PIKE__
mapping|object jsonObject()
#else
mapping jsonObject()
#endif
{
#ifdef __PIKE__
mixed addTo = objectbuilder ? objectbuilder() : ([ ]);
#else
mapping addTo = ([ ]);
#endif
if (next() == '%') {
unescape();
}
back();
if (nextClean() != '{') {
THROW("A JSONObject must begin with '{'.\n");
}
while (1) {
int c;
string key;
mixed obj;
c = nextClean();
switch (c) {
case 0:
THROW("A JSONObject must end "
"with '}'.\n");
case '}':
#ifdef __PIKE__
return mappingp(addTo)
? addTo
: ([object]addTo)->finish();
#else
return addTo;
#endif
case '"':
back();
// TODO:: error on != " || '
key = nextObject();
break;
default:
if (has_index(generics, c)) {
back();
key = nextObject();
break;
}
THROW("Non-Quoted as "
"JSONObject-key. That "
"is invalid!\n");
}
if (nextClean() != ':') {
THROW("Expected a ':' after a key.\n");
}
obj = nextObject();
#ifdef __PIKE__
if (mappingp(addTo)) {
([mapping]addTo)[key] = obj;
} else {
([object]addTo)->add(key, obj);
}
#else
addTo[key] = obj;
#endif
switch (nextClean()) {
case ',':
if (nextClean() == '}') {
#ifdef __PIKE__
return mappingp(addTo) ? addTo : ([object]addTo)->finish();
#else
return addTo;
#endif
}
back();
break;
case '}':
#ifdef __PIKE__
return mappingp(addTo) ? addTo : ([object]addTo)->finish();
#else
return addTo;
#endif
default:
THROW("Expected a ',' or '}'");
}
}
#ifdef __PIKE__
return mappingp(addTo) ? addTo : ([object]addTo)->finish();
#else
return addTo;
#endif
}
#ifdef __PIKE__
array|object jsonArray()
#else
mixed *jsonArray()
#endif
{
#ifdef __PIKE__
mixed addTo = objectbuilder ? objectbuilder() : ({ });
#else
mixed *addTo = ({ });
#endif
if (nextClean() != '[') {
THROW("A JSONArray must start with '['.\n");
}
if (nextClean() == ']') {
#ifdef __PIKE__
return arrayp(addTo) ? addTo : ([object]addTo)->finish();
#else
return addTo;
#endif
}
back();
while (1) {
if (arrayp(addTo)) {
addTo += ({ nextObject() });
} else {
addTo->add(nextObject());
}
switch (nextClean())
{
case ',':
if (nextClean() == ']') {
#ifdef __PIKE__
return arrayp(addTo)
? addTo
: ([object]addTo)->finish();
#else
return addTo;
#endif
}
back();
break;
case ']':
#ifdef __PIKE__
return arrayp(addTo)
? addTo
: ([object]addTo)->finish();
#else
return addTo;
#endif
default:
THROW("Expected a ',' or ']'.\n");
}
}
#ifdef __PIKE__
return arrayp(addTo) ? addTo : ([object]addTo)->finish();
#else
return addTo;
#endif
}
///
/// Get the next value as object. The value can be a Boolean, Double, Integer,
/// JSONArray, JSONObject, or String, or the JSONObject.NULL object.
///
/// An object.
PROTECTED mixed nextObject() {
int c = nextClean();
string s;
if (c == '"') { // || c == '\'') {
return nextString(c);
}
// Object
if (c == '{') {
back();
return jsonObject();
}
// JSON Array
if (c == '[') {
back();
return jsonArray();
}
#ifdef __PIKE__
if (has_index(generics, c)) {
return nextGeneric(c, generics[c]);
}
#endif
#ifdef __PIKE__
String.Buffer sb = String.Buffer();
#else
string sb = "";
#endif
int b = c;
while (c >= ' ' && c != ':' && c != ',' && c != ']' && c != '}' && c != '/') {
sb+=int2char(c);
c = next();
}
back();
s = trim_whites(SBGET(sb));
if (s == "true")
return 1;
if (s == "false")
return 0;
if (s == "null")
return UNDEFINED;
if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
int a; float b_; string c_;
if (sscanf(s, "%d%s", a, c_) != 2) {
// TODO:: support "[-].[0-9]"-format
THROW("number format not supported by JSON parser.\n");
}
if(c_ && sizeof(c_)) {
#ifdef __PIKE__
sscanf(s, "%f", b_);
#else
b_ = to_float(s);
#endif
return b_;
}
else return a;
} else if (s == "") {
THROW("Missing value.\n");
} else {
THROW(sprintf("Invalid JSON:%O\n", s));
}
return s; // will never happen, but keeps lpc statisfied
}
///
/// Skip characters until the next character is the requested character.
/// If the requested character is not found, no characters are skipped.
///
/// A character to skip to.
///
/// The requested character, or zero if the requested character is not found.
///
PROTECTED int skipTo(int to) {
int c;
int i = myIndex;
do {
c = next();
if (c == 0) {
myIndex = i;
return c;
}
}while (c != to);
back();
return c;
}
///
/// Skip characters until past the requested string.
/// If it is not found, we are left at the end of the source.
///
/// A string to skip past.
PROTECTED void skipPast(string to) {
myIndex = search(mySource, to, myIndex);
if (myIndex < 0) {
myIndex = sizeof(mySource);
} else {
myIndex += sizeof(to);
}
}
// TODO implement exception SyntaxError
///
/// Make a printable string of this JSONTokener.
///
/// " at character [myIndex] of [mySource]"
PROTECTED string ToString() {
return " at character " + myIndex + " of " + mySource;
}