// Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.NRefactory.Editor;
namespace ICSharpCode.NRefactory.Xml
{
class TokenReader
{
protected readonly ITextSource input;
protected readonly int inputLength;
int currentLocation;
// CurrentLocation is assumed to be touched and the fact does not
// have to be recorded in this variable.
// This stores any value bigger than that if applicable.
// Actual value is max(currentLocation, maxTouchedLocation).
int maxTouchedLocation;
public int InputLength {
get { return inputLength; }
}
public int CurrentLocation {
get { return currentLocation; }
}
public int MaxTouchedLocation {
// add 1 to currentLocation because single-char-peek does not increment maxTouchedLocation
get { return Math.Max(currentLocation + 1, maxTouchedLocation); }
}
public TokenReader(ITextSource input)
{
this.input = input;
this.inputLength = input.TextLength;
}
protected bool IsEndOfFile()
{
return currentLocation == inputLength;
}
protected bool HasMoreData()
{
return currentLocation < inputLength;
}
protected void AssertHasMoreData()
{
Log.Assert(HasMoreData(), "Unexpected end of file");
}
protected bool TryMoveNext()
{
if (currentLocation == inputLength) return false;
currentLocation++;
return true;
}
protected void Skip(int count)
{
Log.Assert(currentLocation + count <= inputLength, "Skipping after the end of file");
currentLocation += count;
}
protected void GoBack(int oldLocation)
{
Log.Assert(oldLocation <= currentLocation, "Trying to move forward");
// add 1 because single-char-peek does not increment maxTouchedLocation
maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + 1);
currentLocation = oldLocation;
}
protected bool TryRead(char c)
{
if (currentLocation == inputLength) return false;
if (input.GetCharAt(currentLocation) == c) {
currentLocation++;
return true;
} else {
return false;
}
}
protected bool TryReadAnyOf(params char[] c)
{
if (currentLocation == inputLength) return false;
if (c.Contains(input.GetCharAt(currentLocation))) {
currentLocation++;
return true;
} else {
return false;
}
}
protected bool TryRead(string text)
{
if (TryPeek(text)) {
currentLocation += text.Length;
return true;
} else {
return false;
}
}
protected bool TryPeekPrevious(char c, int back)
{
if (currentLocation - back == inputLength) return false;
if (currentLocation - back < 0 ) return false;
return input.GetCharAt(currentLocation - back) == c;
}
protected bool TryPeek(char c)
{
if (currentLocation == inputLength) return false;
return input.GetCharAt(currentLocation) == c;
}
protected bool TryPeekAnyOf(params char[] chars)
{
if (currentLocation == inputLength) return false;
return chars.Contains(input.GetCharAt(currentLocation));
}
protected bool TryPeek(string text)
{
if (!TryPeek(text[0])) return false; // Early exit
maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + text.Length);
// The following comparison 'touches' the end of file - it does depend on the end being there
if (currentLocation + text.Length > inputLength) return false;
return input.GetText(currentLocation, text.Length) == text;
}
protected bool TryPeekWhiteSpace()
{
if (currentLocation == inputLength) return false;
char c = input.GetCharAt(currentLocation);
return ((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r');
}
// The move functions do not have to move if already at target
// The move functions allow 'overriding' of the document length
protected bool TryMoveTo(char c)
{
return TryMoveTo(c, inputLength);
}
protected bool TryMoveTo(char c, int inputLength)
{
if (currentLocation == inputLength) return false;
int index = input.IndexOf(c, currentLocation, inputLength - currentLocation);
if (index != -1) {
currentLocation = index;
return true;
} else {
currentLocation = inputLength;
return false;
}
}
protected bool TryMoveToAnyOf(params char[] c)
{
return TryMoveToAnyOf(c, inputLength);
}
protected bool TryMoveToAnyOf(char[] c, int inputLength)
{
if (currentLocation == inputLength) return false;
int index = input.IndexOfAny(c, currentLocation, inputLength - currentLocation);
if (index != -1) {
currentLocation = index;
return true;
} else {
currentLocation = inputLength;
return false;
}
}
protected bool TryMoveTo(string text)
{
return TryMoveTo(text, inputLength);
}
protected bool TryMoveTo(string text, int inputLength)
{
if (currentLocation == inputLength) return false;
int index = input.IndexOf(text, currentLocation, inputLength - currentLocation, StringComparison.Ordinal);
if (index != -1) {
maxTouchedLocation = index + text.Length;
currentLocation = index;
return true;
} else {
currentLocation = inputLength;
return false;
}
}
protected bool TryMoveToNonWhiteSpace()
{
return TryMoveToNonWhiteSpace(inputLength);
}
protected bool TryMoveToNonWhiteSpace(int inputLength)
{
while(true) {
if (currentLocation == inputLength) return false; // Reject end of file
char c = input.GetCharAt(currentLocation);
if (((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
currentLocation++; // Accept white-space
continue;
} else {
return true; // Found non-white-space
}
}
}
///
/// Read a name token.
/// The following characters are not allowed:
/// "" End of file
/// " \n\r\t" Whitesapce
/// "=\'\"" Attribute value
/// "<>/?" Tags
///
/// Returns the length of the name
protected bool TryReadName()
{
int start = currentLocation;
// Keep reading up to invalid character
while (HasMoreData()) {
char c = input.GetCharAt(currentLocation);
if (0x41 <= (int)c) { // Accpet from 'A' onwards
currentLocation++;
continue;
}
if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || // Reject whitesapce
c == '=' || c == '\'' || c == '"' || // Reject attributes
c == '<' || c == '>' || c == '/' || c == '?') { // Reject tags
break;
} else {
currentLocation++;
continue; // Accept other character
}
}
return currentLocation > start;
}
protected bool TryReadName(out string name)
{
int start = currentLocation;
if (TryReadName()) {
name = GetCachedString(GetText(start, currentLocation));
return true;
} else {
name = string.Empty;
return false;
}
}
protected string GetText(int start, int end)
{
Log.Assert(end <= currentLocation, "Reading ahead of current location");
return input.GetText(start, end - start);
}
Dictionary stringCache = new Dictionary();
#if DEBUG
int stringCacheRequestedCount;
int stringCacheRequestedSize;
int stringCacheStoredCount;
int stringCacheStoredSize;
#endif
internal void PrintStringCacheStats()
{
#if DEBUG
Log.WriteLine("String cache: Requested {0} ({1} bytes); Actaully stored {2} ({3} bytes); {4}% stored", stringCacheRequestedCount, stringCacheRequestedSize, stringCacheStoredCount, stringCacheStoredSize, stringCacheRequestedSize == 0 ? 0 : stringCacheStoredSize * 100 / stringCacheRequestedSize);
#endif
}
[Conditional("DEBUG")]
void AddToRequestedSize(string text)
{
#if DEBUG
stringCacheRequestedCount += 1;
stringCacheRequestedSize += 8 + 2 * text.Length;
#endif
}
[Conditional("DEBUG")]
void AddToStoredSize(string text)
{
#if DEBUG
stringCacheStoredCount += 1;
stringCacheStoredSize += 8 + 2 * text.Length;
#endif
}
protected string GetCachedString(string cached)
{
AddToRequestedSize(cached);
// Do not bother with long strings
if (cached.Length > 32) {
AddToStoredSize(cached);
return cached;
}
string result;
if (stringCache.TryGetValue(cached, out result)) {
// Get the instance from the cache instead
return result;
} else {
// Add to cache
AddToStoredSize(cached);
stringCache.Add(cached, cached);
return cached;
}
}
}
}