194 lines
6.0 KiB
C#
194 lines
6.0 KiB
C#
// <copyright>
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
|
|
namespace Microsoft.Activities.Presentation.Xaml
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
// NOTE: (x, y) denotes line x column y, where x and y are 0 based,
|
|
// while the line/column in SourceLocation and LineNumberPair is 1 based.
|
|
internal class SourceTextScanner
|
|
{
|
|
private const char NewLine = '\n';
|
|
|
|
private const char Return = '\r';
|
|
|
|
private string source;
|
|
|
|
// <start index, length>
|
|
// say a line is "abc\n"
|
|
// the length is 4, with '\n' included.
|
|
private List<Tuple<int, int>> indexCache;
|
|
|
|
internal SourceTextScanner(string source)
|
|
{
|
|
SharedFx.Assert(source != null, "source != null");
|
|
this.source = source;
|
|
this.indexCache = new List<Tuple<int, int>>();
|
|
}
|
|
|
|
internal Tuple<LineColumnPair, char> SearchCharAfter(LineColumnPair startPoint, params char[] charsToSearch)
|
|
{
|
|
SharedFx.Assert(startPoint != null, "startPoint != null");
|
|
|
|
int line = startPoint.LineNumber - 1;
|
|
int column = startPoint.ColumnNumber - 1;
|
|
|
|
HashSet<char> charsToSearchSet = new HashSet<char>(charsToSearch);
|
|
int index = this.GetIndex(line, column);
|
|
if (index < 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
bool firstLoop = true;
|
|
foreach (Tuple<char, int> currentPair in this.Scan(index))
|
|
{
|
|
if (firstLoop)
|
|
{
|
|
firstLoop = false;
|
|
}
|
|
else
|
|
{
|
|
if (charsToSearchSet.Contains(currentPair.Item1))
|
|
{
|
|
LineColumnPair location = this.GetLocation(currentPair.Item2);
|
|
SharedFx.Assert(location != null, "invalid location");
|
|
return Tuple.Create(location, currentPair.Item1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private LineColumnPair GetLocation(int index)
|
|
{
|
|
SharedFx.Assert(index >= 0 && index < this.source.Length, "index out of range");
|
|
|
|
while (!this.IsIndexInScannedLine(index))
|
|
{
|
|
this.TryScanNextLine();
|
|
}
|
|
|
|
int line = this.indexCache.Count - 1;
|
|
for (; line >= 0; --line)
|
|
{
|
|
if (index >= this.indexCache[line].Item1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
SharedFx.Assert(line >= 0, "line < this.indexCache.Count");
|
|
int column = index - this.indexCache[line].Item1;
|
|
SharedFx.Assert(column < this.indexCache[line].Item2, "Should Not Happen");
|
|
|
|
return new LineColumnPair(line + 1, column + 1);
|
|
}
|
|
|
|
private int GetIndex(int line, int column)
|
|
{
|
|
while (this.indexCache.Count <= line)
|
|
{
|
|
if (!this.TryScanNextLine())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this.indexCache.Count <= line)
|
|
{
|
|
SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "line out of range:({0},{1})", line + 1, column + 1));
|
|
return -1;
|
|
}
|
|
|
|
if (column >= this.indexCache[line].Item2)
|
|
{
|
|
SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "column out of range:({0},{1})", line + 1, column + 1));
|
|
return -1;
|
|
}
|
|
|
|
return this.indexCache[line].Item1 + column;
|
|
}
|
|
|
|
private bool IsIndexInScannedLine(int index)
|
|
{
|
|
SharedFx.Assert(index >= 0 && index < this.source.Length, "invalid index");
|
|
|
|
int last = this.indexCache.Count - 1;
|
|
return last >= 0 && index < this.indexCache[last].Item1 + this.indexCache[last].Item2;
|
|
}
|
|
|
|
// return created
|
|
private bool TryScanNextLine()
|
|
{
|
|
int startIndex = 0;
|
|
if (this.indexCache.Count > 0)
|
|
{
|
|
int tail = this.indexCache.Count - 1;
|
|
startIndex = this.indexCache[tail].Item1 + this.indexCache[tail].Item2;
|
|
}
|
|
|
|
if (startIndex >= this.source.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int lastIndex = -1;
|
|
foreach (Tuple<char, int> currentPair in this.Scan(startIndex))
|
|
{
|
|
lastIndex = currentPair.Item2;
|
|
if (currentPair.Item1 == NewLine)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lastIndex < 0)
|
|
{
|
|
SharedFx.Assert("lastIndex < 0");
|
|
return false;
|
|
}
|
|
|
|
int lineLength = lastIndex - startIndex + 1;
|
|
this.indexCache.Add(Tuple.Create(startIndex, lineLength));
|
|
return true;
|
|
}
|
|
|
|
// Tuple<current charactor, charactor index>
|
|
// this Scan will replace \r\n=>\n \r=>\n
|
|
// \r\n return: <\n, \n's index>
|
|
// \r return: <\n, \r's index>
|
|
private IEnumerable<Tuple<char, int>> Scan(int index)
|
|
{
|
|
if (index < 0 || index >= this.source.Length)
|
|
{
|
|
SharedFx.Assert("index < 0 || index >= this.source.Length");
|
|
yield break;
|
|
}
|
|
|
|
while (index < this.source.Length)
|
|
{
|
|
char currentChar = this.source[index];
|
|
|
|
if (currentChar == Return)
|
|
{
|
|
if (index + 1 < this.source.Length && this.source[index + 1] == NewLine)
|
|
{
|
|
++index;
|
|
}
|
|
|
|
currentChar = NewLine;
|
|
}
|
|
|
|
yield return Tuple.Create(currentChar, index);
|
|
++index;
|
|
}
|
|
}
|
|
}
|
|
}
|