diff --git a/build/Json.shade b/build/Json.shade new file mode 100644 index 0000000000..c16cbacd17 --- /dev/null +++ b/build/Json.shade @@ -0,0 +1,883 @@ +use namespace='System' +use namespace='System.Collections.Generic' +use namespace='System.Globalization' +use namespace='System.IO' +use namespace='System.Text' + +functions @{ + public class JsonArray : JsonValue + { + private readonly JsonValue[] _array; + + public JsonArray(JsonValue[] array, int line, int column) + : base(line, column) + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + + _array = array; + } + + public int Length { get { return _array.Length; } } + public IEnumerable Values { get { return _array; }} + public JsonValue this[int index] { get { return _array[index]; }} + } + + public class JsonBoolean : JsonValue + { + public JsonBoolean(JsonToken token) + : base(token.Line, token.Column) + { + if (token.Type == JsonTokenType.True) + { + Value = true; + } + else if (token.Type == JsonTokenType.False) + { + Value = false; + } + else + { + throw new ArgumentException("Token value should be either True or False.", "token"); + } + } + + public bool Value { get; private set; } + + public static implicit operator bool (JsonBoolean jsonBoolean) + { + return jsonBoolean.Value; + } + } + + public class JsonString : JsonValue + { + private readonly string _value; + + public JsonString(string value, int line, int column) + : base(line, column) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _value = value; + } + + public string Value + { + get { return _value; } + } + + public override string ToString() + { + return _value; + } + + public static implicit operator string (JsonString instance) + { + if (instance == null) + { + return null; + } + else + { + return instance.Value; + } + } + } + + public class JsonNull : JsonValue + { + public JsonNull(int line, int column) + : base(line, column) + { + } + } + + public class JsonValue + { + public JsonValue(int line, int column) + { + Line = line; + Column = column; + } + + public int Line { get; private set; } + + public int Column { get; private set; } + } + + public class JsonObject : JsonValue + { + private readonly IDictionary _data; + + public JsonObject(IDictionary data, int line, int column) + : base(line, column) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + _data = data; + } + + public ICollection Keys + { + get { return _data.Keys; } + } + + public JsonValue Value(string key) + { + JsonValue result; + if (!_data.TryGetValue(key, out result)) + { + result = null; + } + + return result; + } + + public JsonObject ValueAsJsonObject(string key) + { + return Value(key) as JsonObject; + } + + public JsonString ValueAsString(string key) + { + return Value(key) as JsonString; + } + + public int ValueAsInt(string key) + { + var number = Value(key) as JsonNumber; + if (number == null) + { + throw new FormatException(); + } + return Convert.ToInt32(number.Raw); + } + + public bool ValueAsBoolean(string key, bool defaultValue = false) + { + var boolVal = Value(key) as JsonBoolean; + if (boolVal != null) + { + return boolVal.Value; + } + + return defaultValue; + } + + public bool? ValueAsNullableBoolean(string key) + { + var boolVal = Value(key) as JsonBoolean; + if (boolVal != null) + { + return boolVal.Value; + } + + return null; + } + + public string[] ValueAsStringArray(string key) + { + var list = Value(key) as JsonArray; + if (list == null) + { + return null; + } + + var result = new string[list.Length]; + + for (int i = 0; i < list.Length; ++i) + { + var jsonString = list[i] as JsonString; + if (jsonString != null) + { + result[i] = jsonString.ToString(); + } + } + + return result; + } + + internal object ValueAsJsonObject(object packIncludePropertyName) + { + throw new NotImplementedException(); + } + } + + public class JsonNumber : JsonValue + { + private readonly string _raw; + private readonly double _double; + + public JsonNumber(JsonToken token) + : base(token.Line, token.Column) + { + try + { + _raw = token.Value; + _double = double.Parse(_raw, NumberStyles.Float); + } + catch (FormatException ex) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidFloatNumberFormat(_raw), + ex, + token.Line, + token.Column); + } + catch (OverflowException ex) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_FloatNumberOverflow(_raw), + ex, + token.Line, + token.Column); + } + } + + public double Double + { + get { return _double; } + } + + public string Raw + { + get { return _raw; } + } + } + + public static class Json + { + public static JsonValue Deserialize(string content) + { + using (var reader = new StringReader(content)) + { + return Deserialize(reader); + } + } + + public static JsonValue Deserialize(TextReader reader) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + + var buffer = new JsonBuffer(reader); + + var result = DeserializeInternal(buffer.Read(), buffer); + + // There are still unprocessed char. The parsing is not finished. Error happened. + var nextToken = buffer.Read(); + if (nextToken.Type != JsonTokenType.EOF) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_UnfinishedJSON(nextToken.Value), + nextToken); + } + + return result; + } + + private static JsonValue DeserializeInternal(JsonToken next, JsonBuffer buffer) + { + if (next.Type == JsonTokenType.EOF) + { + return null; + } + + if (next.Type == JsonTokenType.LeftSquareBracket) + { + return DeserializeArray(next, buffer); + } + + if (next.Type == JsonTokenType.LeftCurlyBracket) + { + return DeserializeObject(next, buffer); + } + + if (next.Type == JsonTokenType.String) + { + return new JsonString(next.Value, next.Line, next.Column); + } + + if (next.Type == JsonTokenType.True || next.Type == JsonTokenType.False) + { + return new JsonBoolean(next); + } + + if (next.Type == JsonTokenType.Null) + { + return new JsonNull(next.Line, next.Column); + } + + if (next.Type == JsonTokenType.Number) + { + return new JsonNumber(next); + } + + throw new JsonDeserializerException(JsonDeserializerResource.Format_InvalidTokenExpectation( + next.Value, "'{', (char)'[', true, false, null, JSON string, JSON number, or the end of the file"), + next); + } + + private static JsonArray DeserializeArray(JsonToken head, JsonBuffer buffer) + { + var list = new List(); + while (true) + { + var next = buffer.Read(); + if (next.Type == JsonTokenType.RightSquareBracket) + { + break; + } + + list.Add(DeserializeInternal(next, buffer)); + + next = buffer.Read(); + if (next.Type == JsonTokenType.EOF) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", (char)']', (char)','), + next); + } + else if (next.Type == JsonTokenType.RightSquareBracket) + { + break; + } + else if (next.Type != JsonTokenType.Comma) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", (char)','), + next); + } + } + + return new JsonArray(list.ToArray(), head.Line, head.Column); + } + + private static JsonObject DeserializeObject(JsonToken head, JsonBuffer buffer) + { + var dictionary = new Dictionary(); + + // Loop through each JSON entry in the input object + while (true) + { + var next = buffer.Read(); + if (next.Type == JsonTokenType.EOF) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", (char)'}'), + next); + } + + if (next.Type == JsonTokenType.Colon) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxNotExpected("JSON object", (char)':'), + next); + } + else if (next.Type == JsonTokenType.RightCurlyBracket) + { + break; + } + else + { + if (next.Type != JsonTokenType.String) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object member name", "JSON string"), + next); + } + + var memberName = next.Value; + if (dictionary.ContainsKey(memberName)) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_DuplicateObjectMemberName(memberName), + next); + } + + next = buffer.Read(); + if (next.Type != JsonTokenType.Colon) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", (char)':'), + next); + } + + dictionary[memberName] = DeserializeInternal(buffer.Read(), buffer); + + next = buffer.Read(); + if (next.Type == JsonTokenType.RightCurlyBracket) + { + break; + } + else if (next.Type != JsonTokenType.Comma) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", (char)',', (char)'}'), + next); + } + } + } + + return new JsonObject(dictionary, head.Line, head.Column); + } + } + + internal class JsonBuffer + { + public const string ValueNull = "null"; + public const string ValueTrue = "true"; + public const string ValueFalse = "false"; + + private readonly StringBuilder _buffer = new StringBuilder(); + private readonly StringBuilder _codePointBuffer = new StringBuilder(4); + private readonly TextReader _reader; + private JsonToken _token; + private int _line; + private int _column; + + public JsonBuffer(TextReader reader) + { + _reader = reader; + _line = 1; + } + + public JsonToken Read() + { + int first; + while (true) + { + first = ReadNextChar(); + + if (first == -1) + { + _token.Type = JsonTokenType.EOF; + return _token; + } + else if (!IsWhitespace(first)) + { + break; + } + } + + _token.Value = ((char)first).ToString(); + _token.Line = _line; + _token.Column = _column; + + if (first == (char)'{') + { + _token.Type = JsonTokenType.LeftCurlyBracket; + } + else if (first == (char)'}') + { + _token.Type = JsonTokenType.RightCurlyBracket; + } + else if (first == (char)'[') + { + _token.Type = JsonTokenType.LeftSquareBracket; + } + else if (first == (char)']') + { + _token.Type = JsonTokenType.RightSquareBracket; + } + else if (first == (char)':') + { + _token.Type = JsonTokenType.Colon; + } + else if (first == (char)',') + { + _token.Type = JsonTokenType.Comma; + } + else if (first == (char)'"') + { + _token.Type = JsonTokenType.String; + _token.Value = ReadString(); + } + else if (first == (char)'t') + { + ReadLiteral(ValueTrue); + _token.Type = JsonTokenType.True; + } + else if (first == (char)'f') + { + ReadLiteral(ValueFalse); + _token.Type = JsonTokenType.False; + } + else if (first == (char)'n') + { + ReadLiteral(ValueNull); + _token.Type = JsonTokenType.Null; + } + else if ((first >= (char)'0' && first <= (char)'9') || first == (char)'-') + { + _token.Type = JsonTokenType.Number; + _token.Value = ReadNumber(first); + } + else + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_IllegalCharacter(first), + _token); + } + + // JsonToken is a value type + return _token; + } + + private int ReadNextChar() + { + while (true) + { + var value = _reader.Read(); + _column++; + + if (value == -1) + { + // This is the end of file + return -1; + } + else if (value == (char)'\n') + { + // This is a new line. Let the next loop read the first charactor of the following line. + // Set position ahead of next line + _column = 0; + _line++; + + continue; + } + else if (value == (char)'\r') + { + // Skip the carriage return. + // Let the next loop read the following char + } + else + { + // Returns the normal value + return value; + } + } + } + + private string ReadNumber(int firstRead) + { + _buffer.Clear(); + _buffer.Append((char)firstRead); + + while (true) + { + var next = _reader.Peek(); + + if ((next >= (char)'0' && next <= (char)'9') || + next == (char)'.' || + next == (char)'e' || + next == (char)'E') + { + _buffer.Append((char)ReadNextChar()); + } + else + { + break; + } + } + + return _buffer.ToString(); + } + + private void ReadLiteral(string literal) + { + for (int i = 1; i < literal.Length; ++i) + { + var next = _reader.Peek(); + if (next != literal[i]) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_UnrecognizedLiteral(literal), + _line, _column); + } + else + { + ReadNextChar(); + } + } + + var tail = _reader.Peek(); + if (tail != (char)'}' && + tail != (char)']' && + tail != (char)',' && + tail != (char)'\n' && + tail != -1 && + !IsWhitespace(tail)) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_IllegalTrailingCharacterAfterLiteral(tail, literal), + _line, _column); + } + } + + private string ReadString() + { + _buffer.Clear(); + var escaped = false; + + while (true) + { + var next = ReadNextChar(); + + if (next == -1 || next == (char)'\n') + { + throw new JsonDeserializerException( + JsonDeserializerResource.JSON_OpenString, + _line, _column); + } + else if (escaped) + { + if ((next == (char)'"') || (next == (char)'\\') || (next == (char)'/')) + { + _buffer.Append((char)next); + } + else if (next == (char)'b') + { + // (char)'\b' backspace + _buffer.Append('\b'); + } + else if (next == (char)'f') + { + // (char)'\f' form feed + _buffer.Append('\f'); + } + else if (next == (char)'n') + { + // (char)'\n' line feed + _buffer.Append('\n'); + } + else if (next == (char)'r') + { + // (char)'\r' carriage return + _buffer.Append('\r'); + } + else if (next == (char)'t') + { + // (char)'\t' tab + _buffer.Append('\t'); + } + else if (next == (char)'u') + { + // (char)'\uXXXX' unicode + var unicodeLine = _line; + var unicodeColumn = _column; + + _codePointBuffer.Clear(); + for (int i = 0; i < 4; ++i) + { + next = ReadNextChar(); + if (next == -1) + { + throw new JsonDeserializerException( + JsonDeserializerResource.JSON_InvalidEnd, + unicodeLine, + unicodeColumn); + } + else + { + _codePointBuffer[i] = (char)next; + } + } + + try + { + var unicodeValue = int.Parse(_codePointBuffer.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + _buffer.Append((char)unicodeValue); + } + catch (FormatException ex) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidUnicode(_codePointBuffer.ToString()), + ex, + unicodeLine, + unicodeColumn); + } + } + else + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxNotExpected("charactor escape", "\\" + next), + _line, + _column); + } + + escaped = false; + } + else if (next == (char)'\\') + { + escaped = true; + } + else if (next == (char)'"') + { + break; + } + else + { + _buffer.Append((char)next); + } + } + + return _buffer.ToString(); + } + + private static bool IsWhitespace(int value) + { + return value == (char)' ' || value == (char)'\t' || value == (char)'\r'; + } + } + + public enum JsonTokenType + { + LeftCurlyBracket, // [ + LeftSquareBracket, // { + RightCurlyBracket, // ] + RightSquareBracket, // } + Colon, // : + Comma, // , + Null, + True, + False, + Number, + String, + EOF + } + + public struct JsonToken + { + public JsonTokenType Type; + public string Value; + public int Line; + public int Column; + } + + public class JsonDeserializerException : Exception + { + public JsonDeserializerException(string message, Exception innerException, int line, int column) + : base(message, innerException) + { + Line = line; + Column = column; + } + + public JsonDeserializerException(string message, int line, int column) + : base(message) + { + Line = line; + Column = column; + } + + public JsonDeserializerException(string message, JsonToken nextToken) + : base(message) + { + Line = nextToken.Line; + Column = nextToken.Column; + } + + public int Line { get; private set; } + + public int Column { get; private set; } + } + + internal class JsonDeserializerResource + { + internal static string Format_IllegalCharacter(int value) + { + return string.Format("Illegal character (char)'{0}' (Unicode hexadecimal {0:X4}).", value); + } + + internal static string Format_IllegalTrailingCharacterAfterLiteral(int value, string literal) + { + return string.Format("Illegal character(char)'{0}'(Unicode hexadecimal { 0:X4}) after the literal name (char)'{1}'.", value, literal); + } + + internal static string Format_UnrecognizedLiteral(string literal) + { + return string.Format("Invalid JSON literal.Expected literal(char)'{0}'.", literal); + } + + internal static string Format_DuplicateObjectMemberName(string memberName) + { + return Format_InvalidSyntax("JSON object", string.Format("Duplicate member name(char)'{0}'", memberName)); + } + + internal static string Format_InvalidFloatNumberFormat(string raw) + { + return string.Format("Invalid float number format: {0}", raw); + } + + internal static string Format_FloatNumberOverflow(string raw) + { + return string.Format("Float number overflow: {0}", raw); + } + + internal static string Format_InvalidSyntax(string syntaxName, string issue) + { + return string.Format("Invalid {0}syntax. {1}.", syntaxName, issue); + } + + internal static string Format_InvalidSyntaxNotExpected(string syntaxName, char unexpected) + { + return string.Format("Invalid {0} syntax.Unexpected(char)'{1}'.", syntaxName, unexpected); + } + + internal static string Format_InvalidSyntaxNotExpected(string syntaxName, string unexpected) + { + return string.Format("Invalid {0} syntax.Unexpected { 1}.", syntaxName, unexpected); + } + + internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation) + { + return string.Format("Invalid {0} syntax.Expected(char)'{1}'.", syntaxName, expectation); + } + + internal static string Format_InvalidSyntaxExpectation(string syntaxName, string expectation) + { + return string.Format("Invalid {0} syntax.Expected {1}.", syntaxName, expectation); + } + + internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation1, char expectation2) + { + return string.Format("Invalid {0} syntax.Expected(char)'{1}' or(char)'{2}'.", syntaxName, expectation1, expectation2); + } + + internal static string Format_InvalidTokenExpectation(string tokenValue, string expectation) + { + return string.Format("Unexpected token(char)'{0}'.Expected {1}.", tokenValue, expectation); + } + + internal static string Format_InvalidUnicode(string unicode) + { + return string.Format("Invalid Unicode[{0}]", unicode); + } + + internal static string Format_UnfinishedJSON(string nextTokenValue) + { + return string.Format("Invalid JSON end.Unprocessed token {0}.", nextTokenValue); + } + + internal static string JSON_OpenString + { + get { return Format_InvalidSyntaxExpectation("JSON string", (char)'\"'); } + } + + internal static string JSON_InvalidEnd + { + get { return "Invalid JSON. Unexpected end of file."; } + } + } +} \ No newline at end of file diff --git a/build/_k-test.shade b/build/_k-test.shade index 431bf4a817..765dd13d02 100644 --- a/build/_k-test.shade +++ b/build/_k-test.shade @@ -1,5 +1,4 @@ -use namespace='System.Web.Script.Serialization' -use assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" +use import="Json" use import="Environment" default NO_PARALLEL_TEST_PROJECTS='${E("NO_PARALLEL_TEST_PROJECTS")}' @@ -15,16 +14,12 @@ projectFile='' */} @{ - var serializer = new JavaScriptSerializer(); var projectText = File.ReadAllText(projectFile); - var project = (Dictionary)serializer.DeserializeObject(projectText); + var project = (JsonObject)Json.Deserialize(projectText); - object commandsObject; - var commands = project.TryGetValue("commands", out commandsObject) - ? (Dictionary)commandsObject - : new Dictionary(); + var commands = project.ValueAsJsonObject("commands"); - if (commands.Keys.Contains("test")) + if (commands != null && commands.Keys.Contains("test")) { var projectFolder = Path.GetDirectoryName(projectFile); var projectName = Path.GetFileName(projectFolder); @@ -35,16 +30,20 @@ projectFile='' noParallelTestProjects.UnionWith(NO_PARALLEL_TEST_PROJECTS.Split((char)',')); } - object configsObject; - var configs = project.TryGetValue("frameworks", out configsObject) - ? (Dictionary)configsObject - : new Dictionary - { - { "dnx451", new Dictionary() } // Assume dnx451 only if none specified - }; + var configs = project.ValueAsJsonObject("frameworks"); + IEnumerable targetFrameworks; + if (configs == null) + { + // Assume dnx451 only if none specified + targetFrameworks = new[] { "dnx451" }; + } + else + { + targetFrameworks = configs.Keys; + } // Currently only dnx* targets are supported. See aspnet/Universe#53 - var targetFrameworks = configs.Keys.Where(k => k.StartsWith("dnx", StringComparison.OrdinalIgnoreCase)); + targetFrameworks = targetFrameworks.Where(k => k.StartsWith("dnx", StringComparison.OrdinalIgnoreCase)); foreach (var framework in targetFrameworks) {