refeat: ILexer接口适配 (#38)

Co-authored-by: Huaps <1183155719@qq.com>
Co-authored-by: duqoo <92306417+duqoo@users.noreply.github.com>
Reviewed-on: PostGuard/Canon#38
This commit is contained in:
2024-04-18 16:34:32 +08:00
parent d631a28703
commit 4b6635796c
19 changed files with 952 additions and 721 deletions

View File

@@ -0,0 +1,15 @@
namespace Canon.Tests.Utils;
public static class EnumerableExtensions
{
/// <summary>
/// 含有索引的遍历
/// </summary>
/// <param name="enumerable">可遍历的接口</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<(T, uint)> WithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable.Select((value, index) => (value, (uint)index));
}
}

View File

@@ -6,10 +6,11 @@ namespace Canon.Tests.Utils;
/// <summary>
/// 从字符串中读取源代码
/// </summary>
public sealed class StringSourceReader(string source) : ISourceReader, IDisposable
public sealed class StringSourceReader(string source) : ISourceReader
{
private readonly IEnumerator<char> _enumerator =
source.GetEnumerator();
private int _pos = -1;
private uint _lastPos;
public uint Line { get; private set; } = 1;
@@ -17,31 +18,70 @@ public sealed class StringSourceReader(string source) : ISourceReader, IDisposab
public string FileName => "string";
public bool TryReadChar([NotNullWhen(true)] out char? c)
public char Current
{
if (Pos != 0 || Line != 1)
get
{
// 不是第一次读取
if (_enumerator.Current == '\n')
if (_pos == -1)
{
Pos = 0;
Line += 1;
throw new InvalidOperationException("Reader at before the start.");
}
else
{
return source[_pos];
}
}
}
if (!_enumerator.MoveNext())
public bool Retract()
{
if (_pos <= 0)
{
return false;
}
_pos -= 1;
if (Current == '\n')
{
Line -= 1;
// TODO: 如果一直回退就完蛋了
Pos = _lastPos;
}
else
{
Pos -= 1;
}
return true;
}
public bool MoveNext()
{
if (_pos >= source.Length - 1)
{
return false;
}
if (_pos != -1 && Current == '\n')
{
Line += 1;
_lastPos = Pos;
Pos = 0;
}
_pos += 1;
Pos += 1;
return true;
}
public bool TryPeekChar([NotNullWhen(true)] out char? c)
{
if (_pos >= source.Length - 1)
{
c = null;
return false;
}
Pos += 1;
c = _enumerator.Current;
c = source[_pos + 1];
return true;
}
public void Dispose()
{
_enumerator.Dispose();
}
}

View File

@@ -8,78 +8,64 @@ public class StringSourceReaderTests
public void LineFeedTest()
{
ISourceReader reader = new StringSourceReader("program Main;\nbegin\nend.\n");
reader.MoveNext();
Assert.Equal(0u, reader.Pos);
Assert.Equal(1u, reader.Line);
// program
Assert.True(reader.TryReadChar(out char? c));
Assert.Equal('p', c);
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out c));
Assert.Equal(' ', c);
// main;
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out char? _));
Assert.True(reader.TryReadChar(out c));
Assert.Equal('\n', c);
// begin
for (uint i = 1; i <= 5; i++)
{
Assert.True(reader.TryReadChar(out char? _));
Assert.Equal(i, reader.Pos);
Assert.Equal(2u, reader.Line);
}
// \n
Assert.True(reader.TryReadChar(out c));
Assert.Equal('\n', c);
// end.
foreach (char i in "end.")
{
Assert.True(reader.TryReadChar(out c));
Assert.Equal(i, c);
}
CheckLine(reader, "program Main;", 1);
reader.MoveNext();
CheckLine(reader, "begin", 2);
reader.MoveNext();
CheckLine(reader, "end.", 3);
}
[Fact]
public void CarriageReturnLineFeedTest()
{
ISourceReader reader = new StringSourceReader("program Main;\r\nbegin\r\nend.\r\n");
reader.MoveNext();
// program Main;
foreach ((char value, uint index) in
"program Main;".Select((value, index) => (value, (uint)index)))
CheckLine(reader, "program Main;", 1);
reader.MoveNext();
reader.MoveNext();
CheckLine(reader, "begin", 2);
reader.MoveNext();
reader.MoveNext();
CheckLine(reader, "end.", 3);
}
[Fact]
public void RetractTest()
{
ISourceReader reader = new StringSourceReader("test");
reader.MoveNext();
Assert.Equal('t', reader.Current);
Assert.True(reader.MoveNext());
Assert.Equal('e', reader.Current);
Assert.True(reader.Retract());
Assert.Equal('t', reader.Current);
Assert.False(reader.Retract());
}
[Fact]
public void PeekTest()
{
ISourceReader reader = new StringSourceReader("peek");
reader.MoveNext();
Assert.Equal('p', reader.Current);
Assert.True(reader.TryPeekChar(out char? c));
Assert.Equal('e', c);
Assert.Equal('p', reader.Current);
}
private static void CheckLine(ISourceReader reader, string line, uint lineNumber)
{
foreach ((char value, uint index) in line.WithIndex())
{
Assert.True(reader.TryReadChar(out char? c));
Assert.Equal(value, c);
Assert.Equal(value, reader.Current);
Assert.Equal(lineNumber, reader.Line);
Assert.Equal(index + 1, reader.Pos);
Assert.Equal(1u, reader.Line);
}
Assert.True(reader.TryReadChar(out _));
Assert.True(reader.TryReadChar(out _));
// begin
foreach ((char value, uint index) in
"begin".Select((value, index) => (value, (uint)index)))
{
Assert.True(reader.TryReadChar(out char? c));
Assert.Equal(value, c);
Assert.Equal(index + 1, reader.Pos);
Assert.Equal(2u, reader.Line);
reader.MoveNext();
}
}
}