779 lines
20 KiB
C#
779 lines
20 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
// ReSharper disable once CheckNamespace
|
|
namespace GameDevWare.Serialization
|
|
{
|
|
public abstract class JsonReader : IJsonReader
|
|
{
|
|
protected const int DEFAULT_BUFFER_SIZE = 1024;
|
|
|
|
private sealed class Buffer : IList<char>
|
|
{
|
|
private const float SHIFT_THRESHOLD = 0.5f; // position over 50% of buffer
|
|
private const float GROW_THRESHOLD = 0.1f; // when less that 10% of space is free
|
|
|
|
private readonly JsonReader reader;
|
|
private char[] buffer;
|
|
private int? lazyFixation;
|
|
private int end;
|
|
private readonly int initialSize;
|
|
private bool isLast;
|
|
private int lineNumber;
|
|
private long lineStartIndex;
|
|
private long charsReaded;
|
|
|
|
private int Capacity
|
|
{
|
|
get { return this.buffer.Length; }
|
|
set
|
|
{
|
|
if (value <= 0)
|
|
throw new ArgumentOutOfRangeException("value");
|
|
|
|
var newBuffer = new char[value];
|
|
BlockCopy(this.buffer, 0, newBuffer, 0, Math.Min(newBuffer.Length, this.buffer.Length));
|
|
this.buffer = newBuffer;
|
|
}
|
|
}
|
|
|
|
public int Offset { get; private set; }
|
|
public long CharactersReaded { get { return this.charsReaded + this.Offset; } }
|
|
public int LineNumber { get { return this.lineNumber + 1; } }
|
|
public int ColumnNumber { get { return (int)(this.CharactersReaded - this.lineStartIndex + 1); } }
|
|
public char this[int index]
|
|
{
|
|
get
|
|
{
|
|
if (index < 0)
|
|
throw new ArgumentOutOfRangeException("index");
|
|
|
|
if (this.IsBeyondOfStream(index))
|
|
return (char)0;
|
|
|
|
return this.buffer[this.Offset + index];
|
|
}
|
|
}
|
|
|
|
public Buffer(JsonReader reader, char[] buffer)
|
|
{
|
|
if (reader == null) new ArgumentNullException("reader");
|
|
|
|
this.reader = reader;
|
|
this.buffer = buffer ?? new char[DEFAULT_BUFFER_SIZE];
|
|
this.initialSize = this.buffer.Length;
|
|
this.end = 0;
|
|
this.Offset = 0;
|
|
}
|
|
|
|
public void FixateNow()
|
|
{
|
|
if (this.lazyFixation == null) return;
|
|
|
|
this.Fixate(this.lazyFixation.Value);
|
|
this.lazyFixation = null;
|
|
}
|
|
public void Fixate(int atIndex)
|
|
{
|
|
if (atIndex < 0) throw new ArgumentOutOfRangeException("atIndex");
|
|
|
|
for (var i = 0; i < atIndex; i++)
|
|
{
|
|
if (this[i] != '\n') continue;
|
|
|
|
this.lineNumber++;
|
|
this.lineStartIndex = this.CharactersReaded + i;
|
|
}
|
|
|
|
// ensure that fixation point in loaded range
|
|
this.IsBeyondOfStream(atIndex);
|
|
|
|
this.Offset += atIndex;
|
|
|
|
// when we are at second half of buffer - we need to shift back
|
|
if ((this.Offset / (float)this.buffer.Length) > SHIFT_THRESHOLD)
|
|
this.ShiftToZero();
|
|
}
|
|
public void FixateLater(int atIndex)
|
|
{
|
|
if (atIndex < 0) throw new ArgumentOutOfRangeException("atIndex");
|
|
|
|
if (this.lazyFixation != null)
|
|
this.lazyFixation += atIndex;
|
|
else
|
|
this.lazyFixation = atIndex;
|
|
}
|
|
|
|
public bool IsBeyondOfStream(int index)
|
|
{
|
|
if (!this.isLast && this.Offset + index >= this.end)
|
|
this.ReadNextBlock();
|
|
|
|
if (this.isLast && this.Offset + index >= this.end)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public char[] GetChars()
|
|
{
|
|
return this.buffer;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
this.FixateNow();
|
|
this.ShiftToZero();
|
|
this.charsReaded = 0;
|
|
this.lineNumber = 0;
|
|
this.lineStartIndex = 0;
|
|
}
|
|
|
|
private void ReadNextBlock()
|
|
{
|
|
// when we are at second half of buffer - we need to shift back
|
|
if ((this.Offset / (float)this.buffer.Length) > SHIFT_THRESHOLD)
|
|
this.ShiftToZero();
|
|
|
|
// check for free space
|
|
var free = this.buffer.Length - this.end;
|
|
if ((free / (float)this.initialSize) < GROW_THRESHOLD)
|
|
this.Capacity += this.initialSize;
|
|
|
|
var newEnd = this.reader.FillBuffer(this.buffer, this.end);
|
|
this.isLast = newEnd == this.end;
|
|
this.end = newEnd;
|
|
}
|
|
|
|
private void ShiftToZero()
|
|
{
|
|
this.charsReaded += this.Offset;
|
|
|
|
var block = Math.Min(this.Offset, this.end - this.Offset);
|
|
var start = this.Offset;
|
|
var lastBlock = 0;
|
|
while (start < this.end)
|
|
{
|
|
BlockCopy(this.buffer, start, this.buffer, lastBlock, Math.Min(block, this.end - start));
|
|
lastBlock += block;
|
|
start += block;
|
|
}
|
|
|
|
this.end = this.end - this.Offset;
|
|
this.Offset = 0;
|
|
#if DEBUG
|
|
if (this.end < this.buffer.Length) // zero unused space(just for debug)
|
|
Array.Clear(this.buffer, this.end, this.buffer.Length - this.end);
|
|
#endif
|
|
}
|
|
|
|
private static void BlockCopy(char[] from, int fromIdx, char[] to, int toIdx, int len)
|
|
{
|
|
const int CHAR_SIZE = sizeof(char);
|
|
|
|
System.Buffer.BlockCopy(from, fromIdx * CHAR_SIZE, to, toIdx * CHAR_SIZE, len * CHAR_SIZE);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return new string(this.buffer, this.Offset, this.end - this.Offset);
|
|
}
|
|
|
|
#region IList<char> Members
|
|
|
|
int IList<char>.IndexOf(char item)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
void IList<char>.Insert(int index, char item)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
void IList<char>.RemoveAt(int index)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
char IList<char>.this[int index]
|
|
{
|
|
get { return this[index]; }
|
|
set { throw new NotImplementedException(); }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ICollection<char> Members
|
|
|
|
void ICollection<char>.Add(char item)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
void ICollection<char>.Clear()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
bool ICollection<char>.Contains(char item)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
void ICollection<char>.CopyTo(char[] array, int arrayIndex)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
int ICollection<char>.Count
|
|
{
|
|
get { return this.buffer.Length; }
|
|
}
|
|
|
|
bool ICollection<char>.IsReadOnly
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
bool ICollection<char>.Remove(char item)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable<char> Members
|
|
|
|
IEnumerator<char> IEnumerable<char>.GetEnumerator()
|
|
{
|
|
return (this.buffer as IList<char>).GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable Members
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return this.buffer.GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
private sealed class LazyValueInfo : IValueInfo
|
|
{
|
|
private enum Kind : byte
|
|
{
|
|
Explicit = 0,
|
|
QuotedString,
|
|
String
|
|
};
|
|
|
|
private readonly JsonReader reader;
|
|
private int jsonStart;
|
|
private int jsonLen;
|
|
private object value;
|
|
private Kind valueKind;
|
|
|
|
public bool HasValue { get; private set; }
|
|
public object Raw
|
|
{
|
|
get
|
|
{
|
|
// eval lazy value
|
|
if (this.valueKind == Kind.String)
|
|
this.Raw = new string(this.reader.buffer.GetChars(), this.reader.buffer.Offset + this.jsonStart, this.jsonLen);
|
|
else if (this.valueKind == Kind.QuotedString)
|
|
this.Raw = JsonUtils.UnescapeBuffer(this.reader.buffer.GetChars(), this.reader.buffer.Offset + this.jsonStart, this.jsonLen);
|
|
|
|
return this.value;
|
|
}
|
|
set
|
|
{
|
|
this.valueKind = Kind.Explicit;
|
|
this.value = value;
|
|
this.HasValue = true;
|
|
}
|
|
}
|
|
public Type Type
|
|
{
|
|
get
|
|
{
|
|
if (this.valueKind != Kind.Explicit)
|
|
{
|
|
switch (this.reader.token)
|
|
{
|
|
case JsonToken.BeginArray:
|
|
return typeof(List<object>);
|
|
case JsonToken.Number:
|
|
return typeof(double);
|
|
case JsonToken.Member:
|
|
case JsonToken.StringLiteral:
|
|
return typeof(string);
|
|
case JsonToken.DateTime:
|
|
return typeof(DateTime);
|
|
case JsonToken.Boolean:
|
|
return typeof(bool);
|
|
}
|
|
}
|
|
|
|
if (this.value != null)
|
|
return this.value.GetType();
|
|
else
|
|
return typeof(object);
|
|
}
|
|
}
|
|
public int LineNumber { get; private set; }
|
|
public int ColumnNumber { get; private set; }
|
|
public bool AsBoolean { get { return Convert.ToBoolean(this.Raw, this.reader.Context.Format); } }
|
|
public byte AsByte { get { return Convert.ToByte(this.Raw, this.reader.Context.Format); } }
|
|
public short AsInt16 { get { return Convert.ToInt16(this.Raw, this.reader.Context.Format); } }
|
|
public int AsInt32 { get { return Convert.ToInt32(this.Raw, this.reader.Context.Format); } }
|
|
public long AsInt64 { get { return Convert.ToInt64(this.Raw, this.reader.Context.Format); } }
|
|
public sbyte AsSByte { get { return Convert.ToSByte(this.Raw, this.reader.Context.Format); } }
|
|
public ushort AsUInt16 { get { return Convert.ToUInt16(this.Raw, this.reader.Context.Format); } }
|
|
public uint AsUInt32 { get { return Convert.ToUInt32(this.Raw, this.reader.Context.Format); } }
|
|
public ulong AsUInt64 { get { return Convert.ToUInt64(this.Raw, this.reader.Context.Format); } }
|
|
public float AsSingle { get { return Convert.ToSingle(this.Raw, this.reader.Context.Format); } }
|
|
public double AsDouble { get { return Convert.ToDouble(this.Raw, this.reader.Context.Format); } }
|
|
public decimal AsDecimal { get { return Convert.ToDecimal(this.Raw, this.reader.Context.Format); } }
|
|
public DateTime AsDateTime
|
|
{
|
|
get
|
|
{
|
|
if (this.Raw is DateTime) return (DateTime)this.Raw;
|
|
else
|
|
return DateTime.ParseExact(this.AsString, this.reader.Context.DateTimeFormats, this.reader.Context.Format,
|
|
DateTimeStyles.AssumeUniversal);
|
|
}
|
|
}
|
|
public string AsString
|
|
{
|
|
get
|
|
{
|
|
var raw = this.Raw;
|
|
if (raw is string)
|
|
return (string)raw;
|
|
else if (raw is byte[])
|
|
return Convert.ToBase64String((byte[])raw);
|
|
else
|
|
return Convert.ToString(raw, this.reader.Context.Format);
|
|
}
|
|
}
|
|
|
|
public LazyValueInfo(JsonReader reader)
|
|
{
|
|
if (reader == null)
|
|
throw new ArgumentNullException("reader");
|
|
|
|
this.reader = reader;
|
|
}
|
|
|
|
public void ClearValue()
|
|
{
|
|
this.value = null;
|
|
this.HasValue = false;
|
|
this.valueKind = Kind.String;
|
|
}
|
|
|
|
public void SetBufferBounds(int start, int len)
|
|
{
|
|
if (start < 0)
|
|
throw new ArgumentOutOfRangeException("start");
|
|
if (len < 0)
|
|
throw new ArgumentOutOfRangeException("len");
|
|
|
|
this.LineNumber = this.reader.buffer.LineNumber;
|
|
this.ColumnNumber = this.reader.buffer.ColumnNumber + start;
|
|
this.jsonStart = start;
|
|
this.jsonLen = len;
|
|
}
|
|
public void SetAsLazyString(bool quoted)
|
|
{
|
|
this.valueKind = quoted ? Kind.QuotedString : Kind.String;
|
|
this.HasValue = true;
|
|
}
|
|
}
|
|
|
|
private const char INSIGNIFICANT_TAB = '\t';
|
|
private const char INSIGNIFICANT_SPACE = ' ';
|
|
private const char INSIGNIFICANT_NEWLINE = '\n';
|
|
private const char INSIGNIFICANT_RETURN = '\r';
|
|
private const char INSIGNIFICANT_NAME_SEPARATOR = ':';
|
|
private const char INSIGNIFICANT_VALUE_SEPARATOR = ',';
|
|
private const char SIGNIFICANT_BEGIN_ARRAY = '[';
|
|
private const char SIGNIFICANT_END_ARRAY = ']';
|
|
private const char SIGNIFICANT_BEGIN_OBJECT = '{';
|
|
private const char SIGNIFICANT_END_OBJECT = '}';
|
|
private const char SIGNIFICANT_QUOTE = '\"';
|
|
private const char SIGNIFICANT_QUOTE_ALT = '\'';
|
|
|
|
private LazyValueInfo lazyValue;
|
|
private JsonToken token;
|
|
private readonly Buffer buffer;
|
|
|
|
protected JsonReader(SerializationContext context, char[] buffer = null)
|
|
{
|
|
if (context == null) throw new ArgumentNullException("context");
|
|
if (buffer != null && buffer.Length < 1024) throw new ArgumentOutOfRangeException("buffer", "Buffer should be at least 1024 chars long.");
|
|
|
|
this.Context = context;
|
|
this.lazyValue = new LazyValueInfo(this);
|
|
this.buffer = new Buffer(this, buffer);
|
|
}
|
|
|
|
#region IJsonReader Members
|
|
|
|
public SerializationContext Context { get; private set; }
|
|
|
|
public IValueInfo Value
|
|
{
|
|
get
|
|
{
|
|
if (this.Token == JsonToken.None)
|
|
this.NextToken();
|
|
return this.lazyValue;
|
|
}
|
|
}
|
|
|
|
public JsonToken Token
|
|
{
|
|
get
|
|
{
|
|
if (this.token == JsonToken.None)
|
|
this.NextToken();
|
|
|
|
return this.token;
|
|
}
|
|
}
|
|
|
|
public object RawValue
|
|
{
|
|
get
|
|
{
|
|
if (this.token == JsonToken.None)
|
|
this.NextToken();
|
|
|
|
return this.Value.Raw;
|
|
}
|
|
}
|
|
|
|
public long CharactersReaded
|
|
{
|
|
get { return this.buffer.CharactersReaded; }
|
|
}
|
|
|
|
public bool NextToken()
|
|
{
|
|
var start = 0;
|
|
var len = 0;
|
|
var quoted = false;
|
|
var isMember = false;
|
|
|
|
if (!this.NextLexeme(ref start, ref len, ref quoted, ref isMember)) // end of stream
|
|
{
|
|
this.token = JsonToken.EndOfStream;
|
|
this.lazyValue.Raw = null;
|
|
return false;
|
|
}
|
|
|
|
this.lazyValue.ClearValue();
|
|
this.lazyValue.SetBufferBounds(start, len);
|
|
if (len == 1 && !quoted && this.buffer[start] == SIGNIFICANT_BEGIN_ARRAY)
|
|
{
|
|
this.token = JsonToken.BeginArray;
|
|
}
|
|
else if (len == 1 && !quoted && this.buffer[start] == SIGNIFICANT_BEGIN_OBJECT)
|
|
{
|
|
this.token = JsonToken.BeginObject;
|
|
}
|
|
else if (len == 1 && !quoted && this.buffer[start] == SIGNIFICANT_END_ARRAY)
|
|
{
|
|
this.token = JsonToken.EndOfArray;
|
|
}
|
|
else if (len == 1 && !quoted && this.buffer[start] == SIGNIFICANT_END_OBJECT)
|
|
{
|
|
this.token = JsonToken.EndOfObject;
|
|
}
|
|
else if (len == 4 && !quoted && this.LookupAt(this.buffer, start, len, "null"))
|
|
{
|
|
this.token = JsonToken.Null;
|
|
}
|
|
else if (len == 4 && !quoted && this.LookupAt(this.buffer, start, len, "true"))
|
|
{
|
|
this.token = JsonToken.Boolean;
|
|
this.lazyValue.Raw = true;
|
|
}
|
|
else if (len == 5 && !quoted && this.LookupAt(this.buffer, start, len, "false"))
|
|
{
|
|
this.token = JsonToken.Boolean;
|
|
this.lazyValue.Raw = false;
|
|
}
|
|
else if (quoted && this.LookupAt(this.buffer, start, 6, "/Date(") && this.LookupAt(this.buffer, start + len - 2, 2, ")/"))
|
|
{
|
|
var ticks = JsonUtils.StringToInt64(this.buffer.GetChars(), this.buffer.Offset + start + 6, len - 8);
|
|
|
|
this.token = JsonToken.DateTime;
|
|
var dateTime = new DateTime(ticks * 0x2710L + JsonUtils.UnixEpochTicks, DateTimeKind.Utc);
|
|
this.lazyValue.Raw = dateTime;
|
|
}
|
|
else if (!quoted && IsNumber(this.buffer, start, len))
|
|
{
|
|
this.token = JsonToken.Number;
|
|
this.lazyValue.SetAsLazyString(false);
|
|
}
|
|
else
|
|
{
|
|
this.token = isMember ? JsonToken.Member : JsonToken.StringLiteral;
|
|
this.lazyValue.SetAsLazyString(quoted);
|
|
}
|
|
|
|
this.buffer.FixateLater(start + len + (quoted ? 1 : 0));
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool IsEndOfStream()
|
|
{
|
|
return this.token == JsonToken.EndOfStream;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
this.buffer.Reset();
|
|
if (this.token != JsonToken.EndOfStream)
|
|
{
|
|
this.lazyValue = new LazyValueInfo(this);
|
|
this.token = JsonToken.None;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private static bool IsNumber(Buffer buffer, int start, int len)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException("buffer");
|
|
if (start < 0)
|
|
throw new ArgumentOutOfRangeException("start");
|
|
if (len < 0)
|
|
throw new ArgumentOutOfRangeException("len");
|
|
|
|
|
|
const int INT_PART = 0;
|
|
const int FRAC_PART = 1;
|
|
const int EXP_PART = 2;
|
|
const char POINT = '.';
|
|
const char EXP = 'E';
|
|
const char PLUS = '+';
|
|
const char MINUS = '-';
|
|
|
|
len = start + len;
|
|
|
|
var part = INT_PART;
|
|
|
|
for (var i = start; i < len; i++)
|
|
{
|
|
var ch = buffer[i];
|
|
|
|
switch (part)
|
|
{
|
|
case INT_PART:
|
|
if (ch == MINUS)
|
|
{
|
|
if (i != start)
|
|
return false;
|
|
}
|
|
#if !STRICT
|
|
else if (ch == PLUS)
|
|
{
|
|
if (i != start)
|
|
return false;
|
|
}
|
|
#endif
|
|
else if (ch == POINT)
|
|
{
|
|
if (i == start)
|
|
return false; // decimal point as first character
|
|
else
|
|
part = FRAC_PART;
|
|
}
|
|
else if (char.ToUpper(ch) == EXP)
|
|
{
|
|
if (i == start)
|
|
return false; // exp at first character
|
|
else
|
|
part = EXP_PART;
|
|
}
|
|
else if (!char.IsDigit(ch))
|
|
return false; // non digit character in int part
|
|
break;
|
|
case FRAC_PART:
|
|
if (char.ToUpper(ch) == EXP)
|
|
{
|
|
if (i == start)
|
|
return false; // exp at first character
|
|
else
|
|
part = EXP_PART;
|
|
}
|
|
else if (!char.IsDigit(ch))
|
|
return false; // non digit character in frac part
|
|
break;
|
|
case EXP_PART:
|
|
if ((ch == PLUS || ch == MINUS))
|
|
{
|
|
if (char.ToUpper(buffer[i - 1]) != EXP)
|
|
return false; // sign not at start of exp part
|
|
}
|
|
else if (!char.IsDigit(ch))
|
|
return false; // non digit character in exp part
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
private static bool IsInsignificantWhitespace(char symbol)
|
|
{
|
|
return symbol == INSIGNIFICANT_NEWLINE || symbol == INSIGNIFICANT_RETURN || symbol == INSIGNIFICANT_SPACE ||
|
|
symbol == INSIGNIFICANT_TAB;
|
|
}
|
|
private static bool IsInsignificant(char symbol)
|
|
{
|
|
return symbol == INSIGNIFICANT_NEWLINE || symbol == INSIGNIFICANT_RETURN || symbol == INSIGNIFICANT_SPACE ||
|
|
symbol == INSIGNIFICANT_TAB || symbol == INSIGNIFICANT_NAME_SEPARATOR || symbol == INSIGNIFICANT_VALUE_SEPARATOR;
|
|
}
|
|
private static bool IsLiteralTerminator(char ch, bool quoted, char quoteCh, bool escaped, bool eos, IJsonReader reader)
|
|
{
|
|
if (!escaped && quoted && ch == quoteCh)
|
|
return true;
|
|
else if (quoted && (ch == INSIGNIFICANT_NEWLINE || ch == INSIGNIFICANT_RETURN))
|
|
throw JsonSerializationException.UnterminatedStringLiteral(reader);
|
|
else if (eos)
|
|
{
|
|
if (quoted)
|
|
throw JsonSerializationException.UnexpectedEndOfStream(reader);
|
|
else
|
|
return true;
|
|
}
|
|
else if (!quoted &&
|
|
(ch == SIGNIFICANT_BEGIN_ARRAY || ch == SIGNIFICANT_BEGIN_OBJECT || ch == SIGNIFICANT_END_ARRAY ||
|
|
ch == SIGNIFICANT_END_OBJECT || ch == INSIGNIFICANT_VALUE_SEPARATOR || ch == INSIGNIFICANT_NAME_SEPARATOR))
|
|
return true;
|
|
else if (!quoted && IsInsignificantWhitespace(ch))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
private bool LookupAt(Buffer buffer, int start, int len, string matchString)
|
|
{
|
|
for (var i = 0; i < len; i++)
|
|
{
|
|
if (buffer[start + i] != matchString[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
private bool LookupAtSkipWhitespace(Buffer buffer, int start, int len, string matchString)
|
|
{
|
|
while (IsInsignificantWhitespace(buffer[start])) start++;
|
|
|
|
for (var i = 0; i < len; i++)
|
|
{
|
|
if (buffer[start + i] != matchString[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get next lexeme from current buffer
|
|
/// </summary>
|
|
/// <param name="start">return position of returned lexeme in buffer</param>
|
|
/// <param name="len">return size of returned lexeme</param>
|
|
/// <param name="quoted">return true when string literal was quoted</param>
|
|
/// <param name="isMember">is lexeme is object's member</param>
|
|
/// <returns>Null in case of "end of stream", or character buffer with result</returns>
|
|
private bool NextLexeme(ref int start, ref int len, ref bool quoted, ref bool isMember)
|
|
{
|
|
// apply 'lazy' fixation
|
|
this.buffer.FixateNow();
|
|
|
|
if (this.buffer.IsBeyondOfStream(0))
|
|
return false;
|
|
|
|
var position = 0;
|
|
var ch = this.buffer[position];
|
|
|
|
// skip insignificant characters
|
|
while (!this.buffer.IsBeyondOfStream(position) && IsInsignificant(this.buffer[position])) position++;
|
|
|
|
// we reached end of stream
|
|
if (this.buffer.IsBeyondOfStream(position))
|
|
return false;
|
|
|
|
|
|
// tell buffer that significant characters starts here
|
|
// this prevents buffer overgrow
|
|
this.buffer.Fixate(position);
|
|
position = 0;
|
|
var literalStart = position;
|
|
//
|
|
|
|
ch = this.buffer[position];
|
|
//
|
|
// check for quote character
|
|
var quoteCh = '\0';
|
|
if (ch == SIGNIFICANT_QUOTE)
|
|
{
|
|
quoteCh = ch;
|
|
quoted = true;
|
|
position++;
|
|
literalStart++;
|
|
}
|
|
#if !STRICT
|
|
else if (ch == SIGNIFICANT_QUOTE_ALT)
|
|
{
|
|
quoteCh = ch;
|
|
quoted = true;
|
|
position++;
|
|
literalStart++;
|
|
}
|
|
#endif
|
|
var escaped = false; // is character is escaped
|
|
var eos = false; // is end of stream
|
|
do
|
|
{
|
|
eos = this.buffer.IsBeyondOfStream(position);
|
|
escaped = ch == '\\';
|
|
ch = this.buffer[position];
|
|
position++;
|
|
} while (!IsLiteralTerminator(ch, quoted, quoteCh, escaped, eos, this));
|
|
|
|
var literalEnd = position - 1; // minus terminator character
|
|
|
|
start = literalStart;
|
|
len = literalEnd - literalStart;
|
|
|
|
// special case - self terminated lexeme
|
|
if (literalStart == literalEnd && !quoted)
|
|
len = 1;
|
|
|
|
isMember = this.LookupAtSkipWhitespace(this.buffer, literalEnd + (quoted ? 1 : 0), 1, ":");
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills buffer with new characters, staring from <paramref name="index" />
|
|
/// </summary>
|
|
/// <param name="buffer">Character buffer to fill</param>
|
|
/// <param name="index">index from which to start</param>
|
|
/// <returns>new buffer size</returns>
|
|
protected abstract int FillBuffer(char[] buffer, int index);
|
|
}
|
|
}
|