using System;
|
using System.Collections;
|
using System.Collections.Generic;
|
using System.Globalization;
|
using System.Text;
|
|
namespace FastJSON
|
{
|
|
/// <summary>
|
/// This class encodes and decodes JSON strings.
|
/// Spec. details, see http://www.json.org/
|
/// </summary>
|
internal sealed class JsonParser
|
{
|
enum Token
|
{
|
None = -1, // Used to denote no Lookahead available
|
Curly_Open,
|
Curly_Close,
|
Squared_Open,
|
Squared_Close,
|
Colon,
|
Comma,
|
String,
|
Number,
|
True,
|
False,
|
Null
|
}
|
|
readonly string json;
|
readonly StringBuilder s = new StringBuilder();
|
Token lookAheadToken = Token.None;
|
int index;
|
|
internal JsonParser(string json)
|
{
|
this.json = json;
|
}
|
|
public object Decode()
|
{
|
return ParseValue();
|
}
|
|
private Dictionary<string, object> ParseObject()
|
{
|
Dictionary<string, object> table = new Dictionary<string, object>();
|
|
ConsumeToken(); // {
|
|
while (true)
|
{
|
switch (LookAhead())
|
{
|
|
case Token.Comma:
|
ConsumeToken();
|
break;
|
|
case Token.Curly_Close:
|
ConsumeToken();
|
return table;
|
|
default:
|
{
|
// name
|
string name = ParseString();
|
|
// :
|
if (NextToken() != Token.Colon)
|
{
|
throw new Exception("Expected colon at index " + index);
|
}
|
|
// value
|
object value = ParseValue();
|
|
table[name] = value;
|
}
|
break;
|
}
|
}
|
}
|
|
private List<object> ParseArray()
|
{
|
List<object> array = new List<object>();
|
ConsumeToken(); // [
|
|
while (true)
|
{
|
switch (LookAhead())
|
{
|
case Token.Comma:
|
ConsumeToken();
|
break;
|
|
case Token.Squared_Close:
|
ConsumeToken();
|
return array;
|
|
default:
|
array.Add(ParseValue());
|
break;
|
}
|
}
|
}
|
|
private object ParseValue()
|
{
|
switch (LookAhead())
|
{
|
case Token.Number:
|
return ParseNumber();
|
|
case Token.String:
|
return ParseString();
|
|
case Token.Curly_Open:
|
return ParseObject();
|
|
case Token.Squared_Open:
|
return ParseArray();
|
|
case Token.True:
|
ConsumeToken();
|
return true;
|
|
case Token.False:
|
ConsumeToken();
|
return false;
|
|
case Token.Null:
|
ConsumeToken();
|
return null;
|
}
|
|
throw new Exception("Unrecognized token at index" + index);
|
}
|
|
private string ParseString()
|
{
|
ConsumeToken(); // "
|
|
s.Length = 0;
|
|
int runIndex = -1;
|
|
while (index < json.Length)
|
{
|
var c = json[index++];
|
|
if (c == '"')
|
{
|
if (runIndex != -1)
|
{
|
if (s.Length == 0)
|
return json.Substring(runIndex, index - runIndex - 1);
|
|
s.Append(json, runIndex, index - runIndex - 1);
|
}
|
return s.ToString();
|
}
|
|
if (c != '\\')
|
{
|
if (runIndex == -1)
|
runIndex = index - 1;
|
|
continue;
|
}
|
|
if (index == json.Length) break;
|
|
if (runIndex != -1)
|
{
|
s.Append(json, runIndex, index - runIndex - 1);
|
runIndex = -1;
|
}
|
|
switch (json[index++])
|
{
|
case '"':
|
s.Append('"');
|
break;
|
|
case '\\':
|
s.Append('\\');
|
break;
|
|
case '/':
|
s.Append('/');
|
break;
|
|
case 'b':
|
s.Append('\b');
|
break;
|
|
case 'f':
|
s.Append('\f');
|
break;
|
|
case 'n':
|
s.Append('\n');
|
break;
|
|
case 'r':
|
s.Append('\r');
|
break;
|
|
case 't':
|
s.Append('\t');
|
break;
|
|
case 'u':
|
{
|
int remainingLength = json.Length - index;
|
if (remainingLength < 4) break;
|
|
// parse the 32 bit hex into an integer codepoint
|
uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]);
|
s.Append((char)codePoint);
|
|
// skip 4 chars
|
index += 4;
|
}
|
break;
|
}
|
}
|
|
throw new Exception("Unexpectedly reached end of string");
|
}
|
|
private uint ParseSingleChar(char c1, uint multipliyer)
|
{
|
uint p1 = 0;
|
if (c1 >= '0' && c1 <= '9')
|
p1 = (uint)(c1 - '0') * multipliyer;
|
else if (c1 >= 'A' && c1 <= 'F')
|
p1 = (uint)((c1 - 'A') + 10) * multipliyer;
|
else if (c1 >= 'a' && c1 <= 'f')
|
p1 = (uint)((c1 - 'a') + 10) * multipliyer;
|
return p1;
|
}
|
|
private uint ParseUnicode(char c1, char c2, char c3, char c4)
|
{
|
uint p1 = ParseSingleChar(c1, 0x1000);
|
uint p2 = ParseSingleChar(c2, 0x100);
|
uint p3 = ParseSingleChar(c3, 0x10);
|
uint p4 = ParseSingleChar(c4, 1);
|
|
return p1 + p2 + p3 + p4;
|
}
|
|
private long CreateLong(string s)
|
{
|
long num = 0;
|
bool neg = false;
|
foreach (char cc in s)
|
{
|
if (cc == '-')
|
neg = true;
|
else if (cc == '+')
|
neg = false;
|
else
|
{
|
num *= 10;
|
num += (int)(cc - '0');
|
}
|
}
|
|
return neg ? -num : num;
|
}
|
|
private object ParseNumber()
|
{
|
ConsumeToken();
|
|
// Need to start back one place because the first digit is also a token and would have been consumed
|
var startIndex = index - 1;
|
bool dec = false;
|
do
|
{
|
if (index == json.Length)
|
break;
|
var c = json[index];
|
|
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
|
{
|
if (c == '.' || c == 'e' || c == 'E')
|
dec = true;
|
if (++index == json.Length)
|
break;//throw new Exception("Unexpected end of string whilst parsing number");
|
continue;
|
}
|
break;
|
} while (true);
|
|
if (dec)
|
{
|
string s = json.Substring(startIndex, index - startIndex);
|
return double.Parse(s, NumberFormatInfo.InvariantInfo);
|
}
|
long num;
|
return JSON.CreateLong(out num, json, startIndex, index - startIndex);
|
}
|
|
private Token LookAhead()
|
{
|
if (lookAheadToken != Token.None) return lookAheadToken;
|
|
return lookAheadToken = NextTokenCore();
|
}
|
|
private void ConsumeToken()
|
{
|
lookAheadToken = Token.None;
|
}
|
|
private Token NextToken()
|
{
|
var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore();
|
|
lookAheadToken = Token.None;
|
|
return result;
|
}
|
|
private Token NextTokenCore()
|
{
|
char c;
|
|
// Skip past whitespace
|
do
|
{
|
c = json[index];
|
|
if (c > ' ') break;
|
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
|
|
} while (++index < json.Length);
|
|
if (index == json.Length)
|
{
|
throw new Exception("Reached end of string unexpectedly");
|
}
|
|
c = json[index];
|
|
index++;
|
|
switch (c)
|
{
|
case '{':
|
return Token.Curly_Open;
|
|
case '}':
|
return Token.Curly_Close;
|
|
case '[':
|
return Token.Squared_Open;
|
|
case ']':
|
return Token.Squared_Close;
|
|
case ',':
|
return Token.Comma;
|
|
case '"':
|
return Token.String;
|
|
case '0':
|
case '1':
|
case '2':
|
case '3':
|
case '4':
|
case '5':
|
case '6':
|
case '7':
|
case '8':
|
case '9':
|
case '-':
|
case '+':
|
case '.':
|
return Token.Number;
|
|
case ':':
|
return Token.Colon;
|
|
case 'f':
|
if (json.Length - index >= 4 &&
|
json[index + 0] == 'a' &&
|
json[index + 1] == 'l' &&
|
json[index + 2] == 's' &&
|
json[index + 3] == 'e')
|
{
|
index += 4;
|
return Token.False;
|
}
|
break;
|
|
case 't':
|
if (json.Length - index >= 3 &&
|
json[index + 0] == 'r' &&
|
json[index + 1] == 'u' &&
|
json[index + 2] == 'e')
|
{
|
index += 3;
|
return Token.True;
|
}
|
break;
|
|
case 'n':
|
if (json.Length - index >= 3 &&
|
json[index + 0] == 'u' &&
|
json[index + 1] == 'l' &&
|
json[index + 2] == 'l')
|
{
|
index += 3;
|
return Token.Null;
|
}
|
break;
|
}
|
throw new Exception("Could not find token at index " + --index);
|
}
|
}
|
}
|