parent
2b86884659
commit
afe844bc95
9 changed files with 1178 additions and 2 deletions
@ -0,0 +1,26 @@ |
|||||||
|
using System.IO; |
||||||
|
using System.Text; |
||||||
|
using Google.ProtocolBuffers.Serialization; |
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace Google.ProtocolBuffers.CompatTests |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class JsonCompatibilityTests : CompatibilityTests |
||||||
|
{ |
||||||
|
protected override object SerializeMessage<TMessage, TBuilder>(TMessage message) |
||||||
|
{ |
||||||
|
StringWriter sw = new StringWriter(); |
||||||
|
new JsonFormatWriter(sw) |
||||||
|
.Formatted() |
||||||
|
.WriteMessage(message); |
||||||
|
return sw.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override TBuilder DeerializeMessage<TMessage, TBuilder>(object message, TBuilder builder, ExtensionRegistry registry) |
||||||
|
{ |
||||||
|
new JsonFormatReader((string)message).Merge(builder); |
||||||
|
return builder; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,342 @@ |
|||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using Google.ProtocolBuffers.Serialization; |
||||||
|
using NUnit.Framework; |
||||||
|
using Google.ProtocolBuffers.TestProtos; |
||||||
|
|
||||||
|
namespace Google.ProtocolBuffers |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class TestWriterFormatJson |
||||||
|
{ |
||||||
|
protected string Content; |
||||||
|
[System.Diagnostics.DebuggerNonUserCode] |
||||||
|
protected void FormatterAssert<TMessage>(TMessage message, params string[] expecting) where TMessage : IMessageLite |
||||||
|
{ |
||||||
|
StringWriter sw = new StringWriter(); |
||||||
|
new JsonFormatWriter(sw).WriteMessage(message); |
||||||
|
|
||||||
|
Content = sw.ToString(); |
||||||
|
|
||||||
|
ExtensionRegistry registry = ExtensionRegistry.CreateInstance(); |
||||||
|
UnitTestXmlSerializerTestProtoFile.RegisterAllExtensions(registry); |
||||||
|
|
||||||
|
IMessageLite copy = |
||||||
|
new JsonFormatReader(Content) |
||||||
|
.Merge(message.WeakCreateBuilderForType(), registry).WeakBuild(); |
||||||
|
|
||||||
|
Assert.AreEqual(typeof(TMessage), copy.GetType()); |
||||||
|
Assert.AreEqual(message, copy); |
||||||
|
foreach (string expect in expecting) |
||||||
|
Assert.IsTrue(Content.IndexOf(expect) >= 0, "Expected to find content '{0}' in: \r\n{1}", expect, Content); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void TestJsonFormatted() |
||||||
|
{ |
||||||
|
TestXmlMessage message = TestXmlMessage.CreateBuilder() |
||||||
|
.SetValid(true) |
||||||
|
.SetNumber(0x1010) |
||||||
|
.AddChildren(TestXmlMessage.Types.Children.CreateBuilder()) |
||||||
|
.AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.ONE)) |
||||||
|
.AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.ONE).AddOptions(EnumOptions.TWO)) |
||||||
|
.AddChildren(TestXmlMessage.Types.Children.CreateBuilder().SetBinary(ByteString.CopyFromUtf8("abc"))) |
||||||
|
.Build(); |
||||||
|
|
||||||
|
StringWriter sw = new StringWriter(); |
||||||
|
new JsonFormatWriter(sw).Formatted() |
||||||
|
.WriteMessage(message); |
||||||
|
|
||||||
|
string json = sw.ToString(); |
||||||
|
|
||||||
|
TestXmlMessage copy = new JsonFormatReader(json) |
||||||
|
.Merge(TestXmlMessage.CreateBuilder()).Build(); |
||||||
|
Assert.AreEqual(message, copy); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void TestEmptyMessage() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlChild.CreateBuilder() |
||||||
|
.Build(), |
||||||
|
@"{}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestRepeatedField() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlChild.CreateBuilder() |
||||||
|
.AddOptions(EnumOptions.ONE) |
||||||
|
.AddOptions(EnumOptions.TWO) |
||||||
|
.Build(), |
||||||
|
@"{""options"":[""ONE"",""TWO""]}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestNestedEmptyMessage() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetChild(TestXmlChild.CreateBuilder().Build()) |
||||||
|
.Build(), |
||||||
|
@"{""child"":{}}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestNestedMessage() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetChild(TestXmlChild.CreateBuilder().AddOptions(EnumOptions.TWO).Build()) |
||||||
|
.Build(), |
||||||
|
@"{""child"":{""options"":[""TWO""]}}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestBooleanTypes() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetValid(true) |
||||||
|
.Build(), |
||||||
|
@"{""valid"":true}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestFullMessage() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetValid(true) |
||||||
|
.SetText("text") |
||||||
|
.AddTextlines("a") |
||||||
|
.AddTextlines("b") |
||||||
|
.AddTextlines("c") |
||||||
|
.SetNumber(0x1010101010) |
||||||
|
.AddNumbers(1) |
||||||
|
.AddNumbers(2) |
||||||
|
.AddNumbers(3) |
||||||
|
.SetChild(TestXmlChild.CreateBuilder().AddOptions(EnumOptions.ONE).SetBinary(ByteString.CopyFrom(new byte[1]))) |
||||||
|
.AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.TWO).SetBinary(ByteString.CopyFrom(new byte[2]))) |
||||||
|
.AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.THREE).SetBinary(ByteString.CopyFrom(new byte[3]))) |
||||||
|
.Build(), |
||||||
|
@"""text"":""text""", |
||||||
|
@"[""a"",""b"",""c""]", |
||||||
|
@"[1,2,3]", |
||||||
|
@"""child"":{", |
||||||
|
@"""children"":[{", |
||||||
|
@"AA==", |
||||||
|
@"AAA=", |
||||||
|
@"AAAA", |
||||||
|
0x1010101010L.ToString() |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestMessageWithXmlText() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetText("<text></text>") |
||||||
|
.Build(), |
||||||
|
@"{""text"":""<text><\/text>""}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestWithEscapeChars() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetText(" \t <- \"leading space and trailing\" -> \\ \xef54 \x0000 \xFF \xFFFF \b \f \r \n \t ") |
||||||
|
.Build(), |
||||||
|
"{\"text\":\" \\t <- \\\"leading space and trailing\\\" -> \\\\ \\uef54 \\u0000 \\u00ff \\uffff \\b \\f \\r \\n \\t \"}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestWithExtensionText() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetValid(false) |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionText, " extension text value ! ") |
||||||
|
.Build(), |
||||||
|
@"{""valid"":false,""extension_text"":"" extension text value ! ""}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestWithExtensionNumber() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionMessage, |
||||||
|
new TestXmlExtension.Builder().SetNumber(42).Build()) |
||||||
|
.Build(), |
||||||
|
@"{""number"":42}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestWithExtensionArray() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 100) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 101) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 102) |
||||||
|
.Build(), |
||||||
|
@"{""extension_number"":[100,101,102]}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestWithExtensionEnum() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionEnum, EnumOptions.ONE) |
||||||
|
.Build(), |
||||||
|
@"{""extension_enum"":""ONE""}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestMessageWithExtensions() |
||||||
|
{ |
||||||
|
FormatterAssert( |
||||||
|
TestXmlMessage.CreateBuilder() |
||||||
|
.SetValid(true) |
||||||
|
.SetText("text") |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionText, "extension text") |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionMessage, new TestXmlExtension.Builder().SetNumber(42).Build()) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 100) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 101) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 102) |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionEnum, EnumOptions.ONE) |
||||||
|
.Build(), |
||||||
|
@"""text"":""text""", |
||||||
|
@"""valid"":true", |
||||||
|
@"""extension_enum"":""ONE""", |
||||||
|
@"""extension_text"":""extension text""", |
||||||
|
@"""extension_number"":[100,101,102]", |
||||||
|
@"""extension_message"":{""number"":42}" |
||||||
|
); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestMessageMissingExtensions() |
||||||
|
{ |
||||||
|
TestXmlMessage original = TestXmlMessage.CreateBuilder() |
||||||
|
.SetValid(true) |
||||||
|
.SetText("text") |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionText, " extension text value ! ") |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionMessage, new TestXmlExtension.Builder().SetNumber(42).Build()) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 100) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 101) |
||||||
|
.AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 102) |
||||||
|
.SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionEnum, EnumOptions.ONE) |
||||||
|
.Build(); |
||||||
|
|
||||||
|
TestXmlMessage message = original.ToBuilder() |
||||||
|
.ClearExtension(UnitTestXmlSerializerTestProtoFile.ExtensionText) |
||||||
|
.ClearExtension(UnitTestXmlSerializerTestProtoFile.ExtensionMessage) |
||||||
|
.ClearExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber) |
||||||
|
.ClearExtension(UnitTestXmlSerializerTestProtoFile.ExtensionEnum) |
||||||
|
.Build(); |
||||||
|
|
||||||
|
JsonFormatWriter writer = new JsonFormatWriter(); |
||||||
|
writer.WriteMessage(original); |
||||||
|
Content = writer.ToString(); |
||||||
|
|
||||||
|
IMessageLite copy = new JsonFormatReader(Content) |
||||||
|
.Merge(message.CreateBuilderForType()).Build(); |
||||||
|
|
||||||
|
Assert.AreNotEqual(original, message); |
||||||
|
Assert.AreNotEqual(original, copy); |
||||||
|
Assert.AreEqual(message, copy); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestMergeFields() |
||||||
|
{ |
||||||
|
TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); |
||||||
|
builder.MergeFrom(new JsonFormatReader("\"valid\": true")); |
||||||
|
builder.MergeFrom(new JsonFormatReader("\"text\": \"text\", \"number\": \"411\"")); |
||||||
|
Assert.AreEqual(true, builder.Valid); |
||||||
|
Assert.AreEqual("text", builder.Text); |
||||||
|
Assert.AreEqual(411, builder.Number); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestMessageArray() |
||||||
|
{ |
||||||
|
JsonFormatWriter writer = new JsonFormatWriter().Formatted(); |
||||||
|
using (writer.StartArray()) |
||||||
|
{ |
||||||
|
writer.WriteMessage(TestXmlMessage.CreateBuilder().SetNumber(1).AddTextlines("a").Build()); |
||||||
|
writer.WriteMessage(TestXmlMessage.CreateBuilder().SetNumber(2).AddTextlines("b").Build()); |
||||||
|
writer.WriteMessage(TestXmlMessage.CreateBuilder().SetNumber(3).AddTextlines("c").Build()); |
||||||
|
} |
||||||
|
string json = writer.ToString(); |
||||||
|
JsonFormatReader reader = new JsonFormatReader(json); |
||||||
|
|
||||||
|
TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); |
||||||
|
int ordinal = 0; |
||||||
|
|
||||||
|
foreach (JsonFormatReader r in reader.EnumerateArray()) |
||||||
|
{ |
||||||
|
r.Merge(builder); |
||||||
|
Assert.AreEqual(++ordinal, builder.Number); |
||||||
|
} |
||||||
|
Assert.AreEqual(3, ordinal); |
||||||
|
Assert.AreEqual(3, builder.TextlinesCount); |
||||||
|
} |
||||||
|
[Test] |
||||||
|
public void TestNestedMessageArray() |
||||||
|
{ |
||||||
|
JsonFormatWriter writer = new JsonFormatWriter(); |
||||||
|
using (writer.StartArray()) |
||||||
|
{ |
||||||
|
using (writer.StartArray()) |
||||||
|
{ |
||||||
|
writer.WriteMessage(TestXmlMessage.CreateBuilder().SetNumber(1).AddTextlines("a").Build()); |
||||||
|
writer.WriteMessage(TestXmlMessage.CreateBuilder().SetNumber(2).AddTextlines("b").Build()); |
||||||
|
} |
||||||
|
using (writer.StartArray()) |
||||||
|
writer.WriteMessage(TestXmlMessage.CreateBuilder().SetNumber(3).AddTextlines("c").Build()); |
||||||
|
} |
||||||
|
string json = writer.ToString(); |
||||||
|
JsonFormatReader reader = new JsonFormatReader(json); |
||||||
|
|
||||||
|
TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); |
||||||
|
int ordinal = 0; |
||||||
|
|
||||||
|
foreach (JsonFormatReader r in reader.EnumerateArray()) |
||||||
|
foreach (JsonFormatReader r2 in r.EnumerateArray()) |
||||||
|
{ |
||||||
|
r2.Merge(builder); |
||||||
|
Assert.AreEqual(++ordinal, builder.Number); |
||||||
|
} |
||||||
|
Assert.AreEqual(3, ordinal); |
||||||
|
Assert.AreEqual(3, builder.TextlinesCount); |
||||||
|
} |
||||||
|
[Test, ExpectedException(typeof(FormatException))] |
||||||
|
public void FailWithEmptyText() |
||||||
|
{ |
||||||
|
new JsonFormatReader("") |
||||||
|
.Merge(TestXmlMessage.CreateBuilder()); |
||||||
|
} |
||||||
|
[Test, ExpectedException(typeof(FormatException))] |
||||||
|
public void FailWithUnexpectedValue() |
||||||
|
{ |
||||||
|
new JsonFormatReader("{{}}") |
||||||
|
.Merge(TestXmlMessage.CreateBuilder()); |
||||||
|
} |
||||||
|
[Test, ExpectedException(typeof(FormatException))] |
||||||
|
public void FailWithUnQuotedName() |
||||||
|
{ |
||||||
|
new JsonFormatReader("{name:{}}") |
||||||
|
.Merge(TestXmlMessage.CreateBuilder()); |
||||||
|
} |
||||||
|
[Test, ExpectedException(typeof(FormatException))] |
||||||
|
public void FailWithUnexpectedType() |
||||||
|
{ |
||||||
|
new JsonFormatReader("{\"valid\":{}}") |
||||||
|
.Merge(TestXmlMessage.CreateBuilder()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,270 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Globalization; |
||||||
|
using System.IO; |
||||||
|
using System.Text; |
||||||
|
|
||||||
|
namespace Google.ProtocolBuffers.Serialization |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// JSon Tokenizer used by JsonFormatReader |
||||||
|
/// </summary> |
||||||
|
class JsonTextCursor |
||||||
|
{ |
||||||
|
public enum JsType { String, Number, Object, Array, True, False, Null } |
||||||
|
|
||||||
|
private readonly char[] _buffer; |
||||||
|
private int _bufferPos; |
||||||
|
private readonly TextReader _input; |
||||||
|
private int _lineNo, _linePos; |
||||||
|
|
||||||
|
public JsonTextCursor(char[] input) |
||||||
|
{ |
||||||
|
_input = null; |
||||||
|
_buffer = input; |
||||||
|
_bufferPos = 0; |
||||||
|
_next = Peek(); |
||||||
|
_lineNo = 1; |
||||||
|
} |
||||||
|
|
||||||
|
public JsonTextCursor(TextReader input) |
||||||
|
{ |
||||||
|
_input = input; |
||||||
|
_next = Peek(); |
||||||
|
_lineNo = 1; |
||||||
|
} |
||||||
|
|
||||||
|
private int Peek() |
||||||
|
{ |
||||||
|
if (_input != null) |
||||||
|
return _input.Peek(); |
||||||
|
else if (_bufferPos < _buffer.Length) |
||||||
|
return _buffer[_bufferPos]; |
||||||
|
else |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
private int Read() |
||||||
|
{ |
||||||
|
if (_input != null) |
||||||
|
return _input.Read(); |
||||||
|
else if (_bufferPos < _buffer.Length) |
||||||
|
return _buffer[_bufferPos++]; |
||||||
|
else |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int _next; |
||||||
|
public Char NextChar { get { SkipWhitespace(); return (char)_next; } } |
||||||
|
|
||||||
|
#region Assert(...) |
||||||
|
[System.Diagnostics.DebuggerNonUserCode] |
||||||
|
private string CharDisplay(int ch) |
||||||
|
{ |
||||||
|
return ch == -1 ? "EOF" : |
||||||
|
(ch > 32 && ch < 127) ? String.Format("'{0}'", (char)ch) : |
||||||
|
String.Format("'\\u{0:x4}'", ch); |
||||||
|
} |
||||||
|
[System.Diagnostics.DebuggerNonUserCode] |
||||||
|
private void Assert(bool cond, char expected) |
||||||
|
{ |
||||||
|
if (!cond) |
||||||
|
{ |
||||||
|
throw new FormatException( |
||||||
|
String.Format(CultureInfo.InvariantCulture, |
||||||
|
"({0}:{1}) error: Unexpected token {2}, expected: {3}.", |
||||||
|
_lineNo, _linePos, |
||||||
|
CharDisplay(_next), |
||||||
|
CharDisplay(expected) |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
[System.Diagnostics.DebuggerNonUserCode] |
||||||
|
public void Assert(bool cond, string message) |
||||||
|
{ |
||||||
|
if (!cond) |
||||||
|
{ |
||||||
|
throw new FormatException( |
||||||
|
String.Format(CultureInfo.InvariantCulture, |
||||||
|
"({0},{1}) error: {2}", _lineNo, _linePos, message)); |
||||||
|
} |
||||||
|
} |
||||||
|
[System.Diagnostics.DebuggerNonUserCode] |
||||||
|
public void Assert(bool cond, string format, params object[] args) |
||||||
|
{ |
||||||
|
if (!cond) |
||||||
|
{ |
||||||
|
if (args != null && args.Length > 0) |
||||||
|
format = String.Format(format, args); |
||||||
|
throw new FormatException( |
||||||
|
String.Format(CultureInfo.InvariantCulture, |
||||||
|
"({0},{1}) error: {2}", _lineNo, _linePos, format)); |
||||||
|
} |
||||||
|
} |
||||||
|
#endregion |
||||||
|
|
||||||
|
private char ReadChar() |
||||||
|
{ |
||||||
|
int ch = Read(); |
||||||
|
Assert(ch != -1, "Unexpected end of file."); |
||||||
|
if (ch == '\n') |
||||||
|
{ |
||||||
|
_lineNo++; |
||||||
|
_linePos = 0; |
||||||
|
} |
||||||
|
else if (ch != '\r') |
||||||
|
{ |
||||||
|
_linePos++; |
||||||
|
} |
||||||
|
_next = Peek(); |
||||||
|
return (char)ch; |
||||||
|
} |
||||||
|
|
||||||
|
public void Consume(char ch) { Assert(TryConsume(ch), ch); } |
||||||
|
public bool TryConsume(char ch) |
||||||
|
{ |
||||||
|
SkipWhitespace(); |
||||||
|
if (_next == ch) |
||||||
|
{ |
||||||
|
ReadChar(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public void Consume(string sequence) |
||||||
|
{ |
||||||
|
SkipWhitespace(); |
||||||
|
|
||||||
|
foreach (char ch in sequence) |
||||||
|
Assert(ch == ReadChar(), "Expected token '{0}'.", sequence); |
||||||
|
} |
||||||
|
|
||||||
|
public void SkipWhitespace() |
||||||
|
{ |
||||||
|
int chnext = _next; |
||||||
|
while (chnext != -1) |
||||||
|
{ |
||||||
|
if (!Char.IsWhiteSpace((char)chnext)) |
||||||
|
break; |
||||||
|
ReadChar(); |
||||||
|
chnext = _next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public string ReadString() |
||||||
|
{ |
||||||
|
SkipWhitespace(); |
||||||
|
Consume('"'); |
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
while (_next != '"') |
||||||
|
{ |
||||||
|
if (_next == '\\') |
||||||
|
{ |
||||||
|
Consume('\\');//skip the escape |
||||||
|
char ch = ReadChar(); |
||||||
|
switch (ch) |
||||||
|
{ |
||||||
|
case 'b': sb.Append('\b'); break; |
||||||
|
case 'f': sb.Append('\f'); break; |
||||||
|
case 'n': sb.Append('\n'); break; |
||||||
|
case 'r': sb.Append('\r'); break; |
||||||
|
case 't': sb.Append('\t'); break; |
||||||
|
case 'u': |
||||||
|
{ |
||||||
|
string hex = new string(new char[] { ReadChar(), ReadChar(), ReadChar(), ReadChar() }); |
||||||
|
int result; |
||||||
|
Assert(int.TryParse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out result), |
||||||
|
"Expected a 4-character hex specifier."); |
||||||
|
sb.Append((char)result); |
||||||
|
break; |
||||||
|
} |
||||||
|
default: |
||||||
|
sb.Append(ch); break; |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
Assert(_next != '\n' && _next != '\r' && _next != '\f' && _next != -1, '"'); |
||||||
|
sb.Append(ReadChar()); |
||||||
|
} |
||||||
|
} |
||||||
|
Consume('"'); |
||||||
|
return sb.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
public string ReadNumber() |
||||||
|
{ |
||||||
|
SkipWhitespace(); |
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
if (_next == '-') |
||||||
|
sb.Append(ReadChar()); |
||||||
|
Assert(_next >= '0' && _next <= '9', "Expected a numeric type."); |
||||||
|
while ((_next >= '0' && _next <= '9') || _next == '.') |
||||||
|
sb.Append(ReadChar()); |
||||||
|
if (_next == 'e' || _next == 'E') |
||||||
|
{ |
||||||
|
sb.Append(ReadChar()); |
||||||
|
if (_next == '-' || _next == '+') |
||||||
|
sb.Append(ReadChar()); |
||||||
|
Assert(_next >= '0' && _next <= '9', "Expected a numeric type."); |
||||||
|
while (_next >= '0' && _next <= '9') |
||||||
|
sb.Append(ReadChar()); |
||||||
|
} |
||||||
|
return sb.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
public JsType ReadVariant(out object value) |
||||||
|
{ |
||||||
|
SkipWhitespace(); |
||||||
|
switch (_next) |
||||||
|
{ |
||||||
|
case 'n': Consume("null"); value = null; return JsType.Null; |
||||||
|
case 't': Consume("true"); value = true; return JsType.True; |
||||||
|
case 'f': Consume("false"); value = false; return JsType.False; |
||||||
|
case '"': value = ReadString(); return JsType.String; |
||||||
|
case '{': |
||||||
|
{ |
||||||
|
Consume('{'); |
||||||
|
while (NextChar != '}') |
||||||
|
{ |
||||||
|
ReadString(); |
||||||
|
Consume(':'); |
||||||
|
object tmp; |
||||||
|
ReadVariant(out tmp); |
||||||
|
if (!TryConsume(',')) |
||||||
|
break; |
||||||
|
} |
||||||
|
Consume('}'); |
||||||
|
value = null; |
||||||
|
return JsType.Object; |
||||||
|
} |
||||||
|
case '[': |
||||||
|
{ |
||||||
|
Consume('['); |
||||||
|
List<object> values = new List<object>(); |
||||||
|
while (NextChar != ']') |
||||||
|
{ |
||||||
|
object tmp; |
||||||
|
ReadVariant(out tmp); |
||||||
|
values.Add(tmp); |
||||||
|
if (!TryConsume(',')) |
||||||
|
break; |
||||||
|
} |
||||||
|
Consume(']'); |
||||||
|
value = values.ToArray(); |
||||||
|
return JsType.Array; |
||||||
|
} |
||||||
|
default: |
||||||
|
if ((_next >= '0' && _next <= '9') || _next == '-') |
||||||
|
{ |
||||||
|
value = ReadNumber(); |
||||||
|
return JsType.Number; |
||||||
|
} |
||||||
|
Assert(false, "Expected a value."); |
||||||
|
throw new FormatException(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue