浏览代码

spine-csharp now uses SharpJson

pharan 9 年之前
父节点
当前提交
777fb110e8
共有 2 个文件被更改,包括 502 次插入523 次删除
  1. 7 523
      spine-csharp/src/Json.cs
  2. 495 0
      spine-csharp/src/SharpJson.cs

+ 7 - 523
spine-csharp/src/Json.cs

@@ -1,533 +1,17 @@
-/*
- * Copyright (c) 2012 Calvin Rien
- *
- * Based on the JSON parser by Patrick van Bergen
- * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
- *
- * Simplified it so that it doesn't throw exceptions
- * and can be used in Unity iPhone with maximum code stripping.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-using System;
-using System.Collections;
-using System.Collections.Generic;
 using System.IO;
-using System.Text;
-using System.Globalization;
 
 namespace Spine {
-	// Example usage:
-	//
-	//  using UnityEngine;
-	//  using System.Collections;
-	//  using System.Collections.Generic;
-	//  using MiniJSON;
-	//
-	//  public class MiniJSONTest : MonoBehaviour {
-	//      void Start () {
-	//          var jsonString = "{ \"array\": [1.44,2,3], " +
-	//                          "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
-	//                          "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
-	//                          "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
-	//                          "\"int\": 65536, " +
-	//                          "\"float\": 3.1415926, " +
-	//                          "\"bool\": true, " +
-	//                          "\"null\": null }";
-	//
-	//          var dict = Json.Deserialize(jsonString) as Dictionary<string,object>;
-	//
-	//          Debug.Log("deserialized: " + dict.GetType());
-	//          Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]);
-	//          Debug.Log("dict['string']: " + (string) dict["string"]);
-	//          Debug.Log("dict['float']: " + (float) dict["float"]);
-	//          Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs
-	//          Debug.Log("dict['unicode']: " + (string) dict["unicode"]);
-	//
-	//          var str = Json.Serialize(dict);
-	//
-	//          Debug.Log("serialized: " + str);
-	//      }
-	//  }
-
-	/// <summary>
-	/// This class encodes and decodes JSON strings.
-	/// Spec. details, see http://www.json.org/
-	///
-	/// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary.
-	/// All numbers are parsed to floats.
-	/// </summary>
 	public static class Json {
-		/// <summary>
-		/// Parses the string json into a value
-		/// </summary>
-		/// <param name="json">A JSON string.</param>
-		/// <returns>An List&lt;object&gt;, a Dictionary&lt;string, object&gt;, a float, an integer,a string, null, true, or false</returns>
-		public static object Deserialize (TextReader json) {
-			if (json == null) {
-				return null;
-			}
-			return Parser.Parse(json);
-		}
-
-		sealed class Parser : IDisposable {
-			const string WHITE_SPACE = " \t\n\r";
-			const string WORD_BREAK = " \t\n\r{}[],:\"";
-
-			enum TOKEN {
-				NONE,
-				CURLY_OPEN,
-				CURLY_CLOSE,
-				SQUARED_OPEN,
-				SQUARED_CLOSE,
-				COLON,
-				COMMA,
-				STRING,
-				NUMBER,
-				TRUE,
-				FALSE,
-				NULL
-			};
-
-			TextReader json;
-
-			Parser (TextReader reader) {
-				json = reader;
-			}
-
-			public static object Parse (TextReader reader) {
-				using (var instance = new Parser(reader)) {
-					return instance.ParseValue();
-				}
-			}
-
-			public void Dispose () {
-				json.Dispose();
-				json = null;
-			}
-
-			Dictionary<string, object> ParseObject () {
-				Dictionary<string, object> table = new Dictionary<string, object>();
-
-				// ditch opening brace
-				json.Read();
-
-				// {
-				while (true) {
-					switch (NextToken) {
-					case TOKEN.NONE:
-						return null;
-					case TOKEN.COMMA:
-						continue;
-					case TOKEN.CURLY_CLOSE:
-						return table;
-					default:
-						// name
-						string name = ParseString();
-						if (name == null) {
-							return null;
-						}
-
-						// :
-						if (NextToken != TOKEN.COLON) {
-							return null;
-						}
-						// ditch the colon
-						json.Read();
-
-						// value
-						table[name] = ParseValue();
-						break;
-					}
-				}
-			}
-
-			List<object> ParseArray () {
-				List<object> array = new List<object>();
-
-				// ditch opening bracket
-				json.Read();
-
-				// [
-				var parsing = true;
-				while (parsing) {
-					TOKEN nextToken = NextToken;
-
-					switch (nextToken) {
-					case TOKEN.NONE:
-						return null;
-					case TOKEN.COMMA:
-						continue;
-					case TOKEN.SQUARED_CLOSE:
-						parsing = false;
-						break;
-					default:
-						object value = ParseByToken(nextToken);
-
-						array.Add(value);
-						break;
-					}
-				}
-
-				return array;
-			}
-
-			object ParseValue () {
-				TOKEN nextToken = NextToken;
-				return ParseByToken(nextToken);
-			}
-
-			object ParseByToken (TOKEN token) {
-				switch (token) {
-				case TOKEN.STRING:
-					return ParseString();
-				case TOKEN.NUMBER:
-					return ParseNumber();
-				case TOKEN.CURLY_OPEN:
-					return ParseObject();
-				case TOKEN.SQUARED_OPEN:
-					return ParseArray();
-				case TOKEN.TRUE:
-					return true;
-				case TOKEN.FALSE:
-					return false;
-				case TOKEN.NULL:
-					return null;
-				default:
-					return null;
-				}
-			}
-
-			string ParseString () {
-				StringBuilder s = new StringBuilder();
-				char c;
-
-				// ditch opening quote
-				json.Read();
-
-				bool parsing = true;
-				while (parsing) {
-
-					if (json.Peek() == -1) {
-						parsing = false;
-						break;
-					}
-
-					c = NextChar;
-					switch (c) {
-					case '"':
-						parsing = false;
-						break;
-					case '\\':
-						if (json.Peek() == -1) {
-							parsing = false;
-							break;
-						}
-
-						c = NextChar;
-						switch (c) {
-						case '"':
-						case '\\':
-						case '/':
-							s.Append(c);
-							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':
-							var hex = new StringBuilder();
-
-							for (int i = 0; i < 4; i++) {
-								hex.Append(NextChar);
-							}
-
-							s.Append((char)Convert.ToInt32(hex.ToString(), 16));
-							break;
-						}
-						break;
-					default:
-						s.Append(c);
-						break;
-					}
-				}
-
-				return s.ToString();
-			}
-
-			object ParseNumber () {
-				string number = NextWord;
-				float parsedFloat;
-				float.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedFloat);
-				return parsedFloat;
-			}
-
-			void EatWhitespace () {
-				while (WHITE_SPACE.IndexOf(PeekChar) != -1) {
-					json.Read();
-
-					if (json.Peek() == -1) {
-						break;
-					}
-				}
-			}
-
-			char PeekChar {
-				get {
-					return Convert.ToChar(json.Peek());
-				}
-			}
-
-			char NextChar {
-				get {
-					return Convert.ToChar(json.Read());
-				}
-			}
+		
+		static readonly SharpJson.JsonDecoder parser;
 
-			string NextWord {
-				get {
-					StringBuilder word = new StringBuilder();
-
-					while (WORD_BREAK.IndexOf(PeekChar) == -1) {
-						word.Append(NextChar);
-
-						if (json.Peek() == -1) {
-							break;
-						}
-					}
-
-					return word.ToString();
-				}
-			}
-
-			TOKEN NextToken {
-				get {
-					EatWhitespace();
-
-					if (json.Peek() == -1) {
-						return TOKEN.NONE;
-					}
-
-					char c = PeekChar;
-					switch (c) {
-					case '{':
-						return TOKEN.CURLY_OPEN;
-					case '}':
-						json.Read();
-						return TOKEN.CURLY_CLOSE;
-					case '[':
-						return TOKEN.SQUARED_OPEN;
-					case ']':
-						json.Read();
-						return TOKEN.SQUARED_CLOSE;
-					case ',':
-						json.Read();
-						return TOKEN.COMMA;
-					case '"':
-						return TOKEN.STRING;
-					case ':':
-						return TOKEN.COLON;
-					case '0':
-					case '1':
-					case '2':
-					case '3':
-					case '4':
-					case '5':
-					case '6':
-					case '7':
-					case '8':
-					case '9':
-					case '-':
-						return TOKEN.NUMBER;
-					}
-
-					string word = NextWord;
-
-					switch (word) {
-					case "false":
-						return TOKEN.FALSE;
-					case "true":
-						return TOKEN.TRUE;
-					case "null":
-						return TOKEN.NULL;
-					}
-
-					return TOKEN.NONE;
-				}
-			}
+		static Json () {
+			parser = new SharpJson.JsonDecoder();
+			parser.parseNumbersAsFloat = true;
 		}
 
-		/// <summary>
-		/// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
-		/// </summary>
-		/// <param name="json">A Dictionary&lt;string, object&gt; / List&lt;object&gt;</param>
-		/// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
-		public static string Serialize (object obj) {
-			return Serializer.Serialize(obj);
-		}
-
-		sealed class Serializer {
-			StringBuilder builder;
-
-			Serializer () {
-				builder = new StringBuilder();
-			}
-
-			public static string Serialize (object obj) {
-				var instance = new Serializer();
-
-				instance.SerializeValue(obj);
-
-				return instance.builder.ToString();
-			}
-
-			void SerializeValue (object value) {
-				IList asList;
-				IDictionary asDict;
-				string asStr;
-
-				if (value == null) {
-					builder.Append("null");
-				} else if ((asStr = value as string) != null) {
-					SerializeString(asStr);
-				} else if (value is bool) {
-					builder.Append(value.ToString().ToLower());
-				} else if ((asList = value as IList) != null) {
-					SerializeArray(asList);
-				} else if ((asDict = value as IDictionary) != null) {
-					SerializeObject(asDict);
-				} else if (value is char) {
-					SerializeString(value.ToString());
-				} else {
-					SerializeOther(value);
-				}
-			}
-
-			void SerializeObject (IDictionary obj) {
-				bool first = true;
-
-				builder.Append('{');
-
-				foreach (object e in obj.Keys) {
-					if (!first) {
-						builder.Append(',');
-					}
-
-					SerializeString(e.ToString());
-					builder.Append(':');
-
-					SerializeValue(obj[e]);
-
-					first = false;
-				}
-
-				builder.Append('}');
-			}
-
-			void SerializeArray (IList anArray) {
-				builder.Append('[');
-
-				bool first = true;
-
-				foreach (object obj in anArray) {
-					if (!first) {
-						builder.Append(',');
-					}
-
-					SerializeValue(obj);
-
-					first = false;
-				}
-
-				builder.Append(']');
-			}
-
-			void SerializeString (string str) {
-				builder.Append('\"');
-
-				char[] charArray = str.ToCharArray();
-				foreach (var c in charArray) {
-					switch (c) {
-					case '"':
-						builder.Append("\\\"");
-						break;
-					case '\\':
-						builder.Append("\\\\");
-						break;
-					case '\b':
-						builder.Append("\\b");
-						break;
-					case '\f':
-						builder.Append("\\f");
-						break;
-					case '\n':
-						builder.Append("\\n");
-						break;
-					case '\r':
-						builder.Append("\\r");
-						break;
-					case '\t':
-						builder.Append("\\t");
-						break;
-					default:
-						int codepoint = Convert.ToInt32(c);
-						if ((codepoint >= 32) && (codepoint <= 126)) {
-							builder.Append(c);
-						} else {
-							builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0'));
-						}
-						break;
-					}
-				}
-
-				builder.Append('\"');
-			}
-
-			void SerializeOther (object value) {
-				if (value is float
-					 || value is int
-					 || value is uint
-					 || value is long
-					 || value is float
-					 || value is sbyte
-					 || value is byte
-					 || value is short
-					 || value is ushort
-					 || value is ulong
-					 || value is decimal) {
-					builder.Append(value.ToString());
-				} else {
-					SerializeString(value.ToString());
-				}
-			}
+		public static object Deserialize (TextReader text) {
+			return parser.Decode(text.ReadToEnd());
 		}
 	}
 }

+ 495 - 0
spine-csharp/src/SharpJson.cs

@@ -0,0 +1,495 @@
+/**
+ *
+ * Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende
+ * 
+ * Based on the JSON parser by Patrick van Bergen
+ * http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html
+ *
+ * Changes made:
+ * 
+ * 	- Optimized parser speed (deserialize roughly near 3x faster than original)
+ *  - Added support to handle lexer/parser error messages with line numbers
+ *  - Added more fine grained control over type conversions during the parsing
+ *  - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
+ * portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+ * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * 
+ */
+
+using System;
+using System.Text;
+using System.Collections;
+using System.Globalization;
+using System.Collections.Generic;
+
+namespace SharpJson
+{
+	class Lexer
+	{
+		public enum Token {
+			None,
+			Null,
+			True,
+			False,
+			Colon,
+			Comma,
+			String,
+			Number,
+			CurlyOpen,
+			CurlyClose,
+			SquaredOpen,
+			SquaredClose,
+		};
+
+		public bool hasError {
+			get {
+				return !success;
+			}
+		}
+
+		public int lineNumber {
+			get;
+			private set;
+		}
+
+		public bool parseNumbersAsFloat {
+			get;
+			set;
+		}
+
+		char[] json;
+		int index = 0;
+		bool success = true;
+		char[] stringBuffer = new char[4096];
+
+		public Lexer(string text)
+		{
+			Reset();
+
+			json = text.ToCharArray();
+			parseNumbersAsFloat = false;
+		}
+
+		public void Reset()
+		{
+			index = 0;
+			lineNumber = 1;
+			success = true;
+		}
+
+		public string ParseString()
+		{
+			int idx = 0;
+			StringBuilder builder = null;
+			
+			SkipWhiteSpaces();
+			
+			// "
+			char c = json[index++];
+			
+			bool failed = false;
+			bool complete = false;
+			
+			while (!complete && !failed) {
+				if (index == json.Length)
+					break;
+				
+				c = json[index++];
+				if (c == '"') {
+					complete = true;
+					break;
+				} else if (c == '\\') {
+					if (index == json.Length)
+						break;
+					
+					c = json[index++];
+					
+					switch (c) {
+					case '"':
+						stringBuffer[idx++] = '"';
+						break;
+					case '\\':
+						stringBuffer[idx++] = '\\';
+						break;
+					case '/':
+						stringBuffer[idx++] = '/';
+						break;
+					case 'b':
+						stringBuffer[idx++] = '\b';
+						break;
+					case'f':
+							stringBuffer[idx++] = '\f';
+						break;
+					case 'n':
+						stringBuffer[idx++] = '\n';
+						break;
+					case 'r':
+						stringBuffer[idx++] = '\r';
+						break;
+					case 't':
+						stringBuffer[idx++] = '\t';
+						break;
+					case 'u':
+						int remainingLength = json.Length - index;
+						if (remainingLength >= 4) {
+							var hex = new string(json, index, 4);
+							
+							// XXX: handle UTF
+							stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16);
+							
+							// skip 4 chars
+							index += 4;
+						} else {
+							failed = true;
+						}
+						break;
+					}
+				} else {
+					stringBuffer[idx++] = c;
+				}
+				
+				if (idx >= stringBuffer.Length) {
+					if (builder == null)
+						builder = new StringBuilder();
+					
+					builder.Append(stringBuffer, 0, idx);
+					idx = 0;
+				}
+			}
+			
+			if (!complete) {
+				success = false;
+				return null;
+			}
+			
+			if (builder != null)
+				return builder.ToString ();
+			else
+				return new string (stringBuffer, 0, idx);
+		}
+		
+		string GetNumberString()
+		{
+			SkipWhiteSpaces();
+
+			int lastIndex = GetLastIndexOfNumber(index);
+			int charLength = (lastIndex - index) + 1;
+			
+			var result = new string (json, index, charLength);
+			
+			index = lastIndex + 1;
+			
+			return result;
+		}
+
+		public float ParseFloatNumber()
+		{
+			float number;
+			var str = GetNumberString ();
+			
+			if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
+				return 0;
+			
+			return number;
+		}
+
+		public double ParseDoubleNumber()
+		{
+			double number;
+			var str = GetNumberString ();
+			
+			if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
+				return 0;
+			
+			return number;
+		}
+		
+		int GetLastIndexOfNumber(int index)
+		{
+			int lastIndex;
+			
+			for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
+				char ch = json[lastIndex];
+				
+				if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
+				    && ch != '.' && ch != 'e' && ch != 'E')
+					break;
+			}
+			
+			return lastIndex - 1;
+		}
+
+		void SkipWhiteSpaces()
+		{
+			for (; index < json.Length; index++) {
+				char ch = json[index];
+
+				if (ch == '\n')
+					lineNumber++;
+
+				if (!char.IsWhiteSpace(json[index]))
+					break;
+			}
+		}
+
+		public Token LookAhead()
+		{
+			SkipWhiteSpaces();
+
+			int savedIndex = index;
+			return NextToken(json, ref savedIndex);
+		}
+
+		public Token NextToken()
+		{
+			SkipWhiteSpaces();
+			return NextToken(json, ref index);
+		}
+
+		static Token NextToken(char[] json, ref int index)
+		{
+			if (index == json.Length)
+				return Token.None;
+			
+			char c = json[index++];
+			
+			switch (c) {
+			case '{':
+				return Token.CurlyOpen;
+			case '}':
+				return Token.CurlyClose;
+			case '[':
+				return Token.SquaredOpen;
+			case ']':
+				return Token.SquaredClose;
+			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 '-':
+				return Token.Number;
+			case ':':
+				return Token.Colon;
+			}
+
+			index--;
+			
+			int remainingLength = json.Length - index;
+			
+			// false
+			if (remainingLength >= 5) {
+				if (json[index] == 'f' &&
+				    json[index + 1] == 'a' &&
+				    json[index + 2] == 'l' &&
+				    json[index + 3] == 's' &&
+				    json[index + 4] == 'e') {
+					index += 5;
+					return Token.False;
+				}
+			}
+			
+			// true
+			if (remainingLength >= 4) {
+				if (json[index] == 't' &&
+				    json[index + 1] == 'r' &&
+				    json[index + 2] == 'u' &&
+				    json[index + 3] == 'e') {
+					index += 4;
+					return Token.True;
+				}
+			}
+			
+			// null
+			if (remainingLength >= 4) {
+				if (json[index] == 'n' &&
+				    json[index + 1] == 'u' &&
+				    json[index + 2] == 'l' &&
+				    json[index + 3] == 'l') {
+					index += 4;
+					return Token.Null;
+				}
+			}
+
+			return Token.None;
+		}
+	}
+
+	public class JsonDecoder
+	{
+		public string errorMessage {
+			get;
+			private set;
+		}
+
+		public bool parseNumbersAsFloat {
+			get;
+			set;
+		}
+
+		Lexer lexer;
+
+		public JsonDecoder()
+		{
+			errorMessage = null;
+			parseNumbersAsFloat = false;
+		}
+
+		public object Decode(string text)
+		{
+			errorMessage = null;
+
+			lexer = new Lexer(text);
+			lexer.parseNumbersAsFloat = parseNumbersAsFloat;
+
+			return ParseValue();
+		}
+
+		public static object DecodeText(string text)
+		{
+			var builder = new JsonDecoder();
+			return builder.Decode(text);
+		}
+
+		IDictionary<string, object> ParseObject()
+		{
+			var table = new Dictionary<string, object>();
+
+			// {
+			lexer.NextToken();
+
+			while (true) {
+				var token = lexer.LookAhead();
+
+				switch (token) {
+				case Lexer.Token.None:
+					TriggerError("Invalid token");
+					return null;
+				case Lexer.Token.Comma:
+					lexer.NextToken();
+					break;
+				case Lexer.Token.CurlyClose:
+					lexer.NextToken();
+					return table;
+				default:
+					// name
+					string name = EvalLexer(lexer.ParseString());
+
+					if (errorMessage != null)
+						return null;
+
+					// :
+					token = lexer.NextToken();
+
+					if (token != Lexer.Token.Colon) {
+						TriggerError("Invalid token; expected ':'");
+						return null;
+					}
+					
+					// value
+					object value = ParseValue();
+
+					if (errorMessage != null)
+						return null;
+					
+					table[name] = value;
+					break;
+				}
+			}
+			
+			//return null; // Unreachable code
+		}
+
+		IList<object> ParseArray()
+		{
+			var array = new List<object>();
+			
+			// [
+			lexer.NextToken();
+
+			while (true) {
+				var token = lexer.LookAhead();
+
+				switch (token) {
+				case Lexer.Token.None:
+					TriggerError("Invalid token");
+					return null;
+				case Lexer.Token.Comma:
+					lexer.NextToken();
+					break;
+				case Lexer.Token.SquaredClose:
+					lexer.NextToken();
+					return array;
+				default:
+					object value = ParseValue();
+
+					if (errorMessage != null)
+						return null;
+
+					array.Add(value);
+					break;
+				}
+			}
+			
+			//return null; // Unreachable code
+		}
+
+		object ParseValue()
+		{
+			switch (lexer.LookAhead()) {
+			case Lexer.Token.String:
+				return EvalLexer(lexer.ParseString());
+			case Lexer.Token.Number:
+				if (parseNumbersAsFloat)
+					return EvalLexer(lexer.ParseFloatNumber());
+				else
+					return EvalLexer(lexer.ParseDoubleNumber());
+			case Lexer.Token.CurlyOpen:
+				return ParseObject();
+			case Lexer.Token.SquaredOpen:
+				return ParseArray();
+			case Lexer.Token.True:
+				lexer.NextToken();
+				return true;
+			case Lexer.Token.False:
+				lexer.NextToken();
+				return false;
+			case Lexer.Token.Null:
+				lexer.NextToken();
+				return null;
+			case Lexer.Token.None:
+				break;
+			}
+
+			TriggerError("Unable to parse value");
+			return null;
+		}
+
+		void TriggerError(string message)
+		{
+			errorMessage = string.Format("Error: '{0}' at line {1}",
+			                             message, lexer.lineNumber);
+		}
+
+		T EvalLexer<T>(T value)
+		{
+			if (lexer.hasError)
+				TriggerError("Lexical error ocurred");
+
+			return value;
+		}
+	}
+}