From d006bfb7c33addf08b319793376c33fb001b17fe Mon Sep 17 00:00:00 2001 From: CYJB Date: Wed, 27 Mar 2024 04:11:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20SourceReader=20?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/Lexers/LexerRunner`1.cs | 2 +- Runtime/Lexers/LexerTokenizer`1.cs | 11 +- Runtime/Text/ISourceBuffer.cs | 66 ---- Runtime/Text/SourceCompleteBuffer.cs | 209 ------------ .../PartialSourceReader.cs} | 225 ++++++------- .../Text/{ => SourceReader}/SourceReader.cs | 317 ++++++++++-------- .../Text/SourceReader/StringSourceReader.cs | 166 +++++++++ .../SourceReader/StringViewSourceReader.cs | 180 ++++++++++ TestCompilers/Text/UnitTestSourceReader.cs | 43 +-- 9 files changed, 648 insertions(+), 571 deletions(-) delete mode 100644 Runtime/Text/ISourceBuffer.cs delete mode 100644 Runtime/Text/SourceCompleteBuffer.cs rename Runtime/Text/{SourcePartialBuffer.cs => SourceReader/PartialSourceReader.cs} (68%) rename Runtime/Text/{ => SourceReader}/SourceReader.cs (66%) create mode 100644 Runtime/Text/SourceReader/StringSourceReader.cs create mode 100644 Runtime/Text/SourceReader/StringViewSourceReader.cs diff --git a/Runtime/Lexers/LexerRunner`1.cs b/Runtime/Lexers/LexerRunner`1.cs index 9a2b969..e9a4927 100644 --- a/Runtime/Lexers/LexerRunner`1.cs +++ b/Runtime/Lexers/LexerRunner`1.cs @@ -55,7 +55,7 @@ internal LexerRunner(LexerData lexerData, LexerController controller) public void Parse(string source) { ArgumentNullException.ThrowIfNull(source); - Parse(new SourceReader(new StringReader(source))); + Parse(SourceReader.Create(source)); } /// diff --git a/Runtime/Lexers/LexerTokenizer`1.cs b/Runtime/Lexers/LexerTokenizer`1.cs index 37b3a46..4cb2418 100644 --- a/Runtime/Lexers/LexerTokenizer`1.cs +++ b/Runtime/Lexers/LexerTokenizer`1.cs @@ -59,16 +59,7 @@ internal LexerTokenizer(LexerData data, LexerController controller) public void Load(string source) { ArgumentNullException.ThrowIfNull(source); - Load(new SourceReader(source)); - } - - /// - /// 加载指定的源码。 - /// - /// 要加载的源码。 - public void Load(StringView source) - { - Load(new SourceReader(source)); + Load(SourceReader.Create(source)); } /// diff --git a/Runtime/Text/ISourceBuffer.cs b/Runtime/Text/ISourceBuffer.cs deleted file mode 100644 index 866ed44..0000000 --- a/Runtime/Text/ISourceBuffer.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace Cyjb.Text; - -/// -/// 表示 的字符缓冲区。 -/// -internal interface ISourceBuffer : IDisposable -{ - /// - /// 获取或设置读取的起始位置。 - /// - int StartIndex { get; set; } - /// - /// 获取或设置当前读取的位置。 - /// - /// 可以设置为大于 的值,此时会尝试将更多字符读取到缓冲区中。 - /// 如果没有更多可以读取的字符,那么 会被设置为 - int Index { get; set; } - /// - /// 获取当前位置是否位于行首。 - /// - bool IsLineStart { get; } - /// - /// 获取字符缓冲区的总字符长度。 - /// - /// 仅包含已读取到缓冲区的字符,可能仍有字符尚未被读取到缓冲区。 - int Length { get; } - - /// - /// 获取指定索引的字符。 - /// - /// 要检查的字符索引。 - /// 指定索引的字符。 - /// 调用方确保 之间。 - char this[int index] { get; } - - /// - /// 设置关联到的行列定位器。 - /// - void SetLocator(LineLocator locator); - /// - /// 返回 之后 偏移的字符,并提升索引。 - /// - /// 要读取的位置偏移。 - /// 之后 偏移的字符。 - char Read(int offset); - /// - /// 返回 之后 偏移的字符,但不提升索引。 - /// - /// 要读取的位置偏移。 - /// 之后 偏移的字符。 - char Peek(int offset); - /// - /// 读取指定范围的文本。 - /// - /// 起始索引。 - /// 要读取的文本长度。 - /// 读取到的文本。 - /// 调用方确保 在有效范围之内。 - StringView ReadBlock(int start, int count); - - /// - /// 释放指定索引之前的内存,释放后的字符无法再被读取。 - /// - /// 要释放的字符起始索引。 - void Free(int index); -} diff --git a/Runtime/Text/SourceCompleteBuffer.cs b/Runtime/Text/SourceCompleteBuffer.cs deleted file mode 100644 index 463e1fa..0000000 --- a/Runtime/Text/SourceCompleteBuffer.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System.Diagnostics; - -namespace Cyjb.Text; - -/// -/// 一次性读取完整字符串的缓冲区。 -/// -/// 的包装, -/// 会返回原始的完整字符串,不需要再额外复制到缓冲区。 -internal sealed class SourceCompleteBuffer : ISourceBuffer -{ - /// - /// 文本内容。 - /// - private readonly string text; - /// - /// 当前读取的位置。 - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int index = 0; - /// - /// 可读取的起始位置。 - /// - private readonly int offset = 0; - /// - /// 可读取的文本长度。 - /// - private readonly int length; - /// - /// 关联到的行列定位器。 - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private LineLocator? locator; - - /// - /// 使用指定的文本读取器初始化 类的新实例。 - /// - /// 文本读取器。 - public SourceCompleteBuffer(TextReader reader) - { - text = reader.ReadToEnd(); - length = text.Length; - } - - /// - /// 使用指定的文本内容初始化 类的新实例。 - /// - /// 文本内容。 - public SourceCompleteBuffer(string text) - { - this.text = text; - length = text.Length; - } - - /// - /// 使用指定的字符串视图初始化 类的新实例。 - /// - /// 字符串视图。 - public SourceCompleteBuffer(StringView view) - { - view.GetOrigin(out text, out offset, out length); - } - - /// - /// 获取或设置读取的起始位置。 - /// - public int StartIndex { get; set; } - /// - /// 获取或设置当前读取的位置。 - /// - /// 可以设置为大于 的值,此时会尝试将更多字符读取到缓冲区中。 - /// 如果没有更多可以读取的字符,那么 会被设置为 - public int Index - { - get => index; - set - { - if (value >= length) - { - index = length; - } - else - { - index = value; - } - } - } - /// - /// 获取当前位置是否位于行首。 - /// - public bool IsLineStart - { - get - { - if (index <= 0) - { - return true; - } - char ch = text[index + offset - 1]; - if (!Utils.IsLineBreak(ch)) - { - return false; - } - // 兼容 \r\n 的场景。 - if (ch == '\r') - { - if (index == length) - { - return true; - } - else - { - ch = text[index + offset]; - } - if (ch == '\n') - { - return false; - } - } - return true; - } - } - /// - /// 获取字符缓冲区的总字符长度。 - /// - /// 仅包含已读取到缓冲区的字符,可能仍有字符尚未被读取到缓冲区。 - public int Length => length; - - /// - /// 获取指定索引的字符。 - /// - /// 要检查的字符索引。 - /// 指定索引的字符。 - /// 调用方确保 之间。 - public char this[int index] => text[index + offset]; - - /// - /// 设置关联到的行列定位器。 - /// - public void SetLocator(LineLocator locator) - { - this.locator = locator; - // 总是在关联的时候一次性完成读取。 - locator.Read(text.AsSpan(offset, length)); - } - - /// - /// 返回 之后 偏移的字符。 - /// - /// 要读取的位置偏移。 - /// 之后 偏移的字符。 - public char Read(int offset) - { - index += offset; - if (index >= length) - { - index = length; - return SourceReader.InvalidCharacter; - } - char ch = text[index++ + this.offset]; - return ch; - } - - /// - /// 返回 之后 偏移的字符,但不提升索引。 - /// - /// 要读取的位置偏移。 - /// 之后 偏移的字符。 - public char Peek(int offset) - { - int idx = index + offset; - if (idx < length) - { - return text[idx + this.offset]; - } - else - { - return SourceReader.InvalidCharacter; - } - } - - /// - /// 读取指定范围的文本。 - /// - /// 起始索引。 - /// 要读取的文本长度。 - /// 读取到的文本。 - /// 调用方确保 在有效范围之内。 - public StringView ReadBlock(int start, int count) - { - return text.AsView(start + offset, count); - } - - /// - /// 释放指定索引之前的字符,释放后的字符无法再被读取。 - /// - /// 要释放的字符起始索引。 - public void Free(int index) { } - - #region IDisposable 成员 - - /// - /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 - /// - public void Dispose() { } - - #endregion - -} diff --git a/Runtime/Text/SourcePartialBuffer.cs b/Runtime/Text/SourceReader/PartialSourceReader.cs similarity index 68% rename from Runtime/Text/SourcePartialBuffer.cs rename to Runtime/Text/SourceReader/PartialSourceReader.cs index eb77423..c6479c3 100644 --- a/Runtime/Text/SourcePartialBuffer.cs +++ b/Runtime/Text/SourceReader/PartialSourceReader.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Cyjb.Text; @@ -10,32 +9,21 @@ namespace Cyjb.Text; /// 而是每次创建新的缓冲区,并在相关 Token 均被释放后自动回收。 /// /// -internal sealed class SourcePartialBuffer : ISourceBuffer +internal sealed class PartialSourceReader : SourceReader { /// /// 文本的读取器。 /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private TextReader? reader; /// /// 缓冲区的大小。 /// private readonly int bufferSize; /// - /// 当前读取的位置。 - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int index = 0; - /// /// 字符缓冲区的总字符长度。 /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private int length = 0; /// - /// 关联到的行列定位器。 - /// - private LineLocator? locator; - /// /// 是否已经全部读取完毕。 /// private bool readFinished = false; @@ -71,11 +59,11 @@ internal sealed class SourcePartialBuffer : ISourceBuffer private int readedStart; /// - /// 使用指定的文本读取器初始化 类的新实例。 + /// 使用指定的文本读取器初始化 类的新实例。 /// /// 文本读取器。 /// 缓冲区的大小。 - public SourcePartialBuffer(TextReader reader, int bufferSize) + public PartialSourceReader(TextReader reader, int bufferSize) { this.reader = reader; this.bufferSize = bufferSize; @@ -83,45 +71,10 @@ public SourcePartialBuffer(TextReader reader, int bufferSize) first = last = current; } - /// - /// 获取或设置读取的起始位置。 - /// - public int StartIndex { get; set; } - /// - /// 获取或设置当前读取的位置。 - /// - /// 可以设置为大于 的值,此时会尝试将更多字符读取到缓冲区中。 - /// 如果没有更多可以读取的字符,那么 会被设置为 - public int Index - { - get => index; - set - { - if (index == value) - { - return; - } - EnsureBuffer(ref value); - // 之前已确保 value 不会超出有效范围。 - currentIndex += value - index; - index = value; - // 调整当前缓冲区的位置。 - while (currentIndex < 0) - { - current = current.Prev; - currentIndex += bufferSize; - } - while (currentIndex >= bufferSize) - { - current = current.Next; - currentIndex -= bufferSize; - } - } - } /// /// 获取当前位置是否位于行首。 /// - public bool IsLineStart + public override bool IsLineStart { get { @@ -137,7 +90,7 @@ public bool IsLineStart // 兼容 \r\n 的场景。 if (ch == '\r') { - if (index == length) + if (curIndex == length) { // 达到当前缓冲区结尾,尝试加载下一缓冲区。 if (current == last && !PrepareBuffer()) @@ -148,7 +101,7 @@ public bool IsLineStart } else { - ch = current.Text[index]; + ch = current.Text[curIndex]; } if (ch == '\n') { @@ -158,54 +111,100 @@ public bool IsLineStart return true; } } + /// - /// 获取字符缓冲区的总字符长度。 + /// 返回下一个可用的字符,但不使用它。 /// - /// 仅包含已读取到缓冲区的字符,可能仍有字符尚未被读取到缓冲区。 - public int Length => length; + /// 表示下一个要读取的字符的整数,或者如果没有要读取的字符,则为 + /// + /// + /// + /// 返回之后可用的字符,但不使用它。 + /// + /// + public override char Peek() + { + if (!EnsureBuffer(ref curIndex)) + { + // idx 已经超出有效范围。 + return InvalidCharacter; + } + // 之前已确保 value 不会超出有效范围。 + if (currentIndex >= bufferSize) + { + return current.Next.Text[0]; + } + else + { + return current.Text[currentIndex]; + } + } /// - /// 获取指定索引的字符。 + /// 返回文本读取器中之后的 偏移的字符,但不使用它。 + /// Peek(0) 等价于 。 /// - /// 要检查的字符索引。 - /// 指定索引的字符。 - /// 调用方确保 之间。 - public char this[int index] + /// 要读取的偏移。 + /// 文本读取器中之后的 偏移的字符, + /// 或为 (如果没有更多的可用字符)。 + public override char Peek(int offset) { - get + int idx = curIndex + offset; + if (!EnsureBuffer(ref idx)) { - // 从 first 开始查找。 - Buffer buffer = first; - index -= buffer.StartIndex; - while (index >= bufferSize) - { - buffer = buffer.Next; - index -= bufferSize; - } - return buffer.Text[index]; + // idx 已经超出有效范围。 + return InvalidCharacter; + } + // 之前已确保 value 不会超出有效范围。 + idx += currentIndex - curIndex; + Buffer buffer = current; + while (idx >= bufferSize) + { + buffer = buffer.Next; + idx -= bufferSize; } + return buffer.Text[idx]; } /// - /// 设置关联到的行列定位器。 + /// 读取文本读取器中的下一个字符并使该字符的位置提升一个字符。 + /// + /// 文本读取器中的下一个字符,或为 (如果没有更多的可用字符)。 + /// + /// + /// 返回之后可用的字符,并使该字符的位置提升。 /// - public void SetLocator(LineLocator locator) + /// + public override char Read() { - this.locator = locator; + if (!EnsureBuffer(ref curIndex)) + { + // index 已经超出有效范围。 + return InvalidCharacter; + } + curIndex++; + if (currentIndex >= bufferSize) + { + current = current.Next; + currentIndex -= bufferSize; + } + return current.Text[currentIndex++]; } /// - /// 返回 之后 偏移的字符。 + /// 读取文本读取器中之后的 偏移的字符,并使该字符的位置提升。 + /// Read(0) 等价于 。 /// - /// 要读取的位置偏移。 - /// 之后 偏移的字符。 - public char Read(int offset) + /// 要读取的偏移。 + /// 文本读取器中之后的 偏移的字符, + /// 或为 (如果没有更多的可用字符)。 + public override char Read(int offset) { - index += offset; - if (!EnsureBuffer(ref index)) + curIndex += offset; + if (!EnsureBuffer(ref curIndex)) { // index 已经超出有效范围。 - return SourceReader.InvalidCharacter; + return InvalidCharacter; } currentIndex += offset; while (currentIndex >= bufferSize) @@ -213,32 +212,44 @@ public char Read(int offset) current = current.Next; currentIndex -= bufferSize; } - index++; + curIndex++; return current.Text[currentIndex++]; } /// - /// 返回 之后 偏移的字符,但不提升索引。 + /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 /// - /// 要读取的位置偏移。 - /// 之后 偏移的字符。 - public char Peek(int offset) + /// 是否释放托管资源。 + protected override void Dispose(bool disposing) { - int idx = index + offset; - if (!EnsureBuffer(ref idx)) + if (disposing && reader != null) { - // idx 已经超出有效范围。 - return SourceReader.InvalidCharacter; + reader.Dispose(); + reader = null; } + } + + /// + /// 设置当前的字符索引。 + /// + /// 当前的字符索引,该索引从零开始。设置索引时,不能达到被丢弃的字符,或者超出文件结尾。 + protected override void SetIndex(int value) + { + EnsureBuffer(ref value); // 之前已确保 value 不会超出有效范围。 - idx += currentIndex - index; - Buffer buffer = current; - while (idx >= bufferSize) + currentIndex += value - curIndex; + curIndex = value; + // 调整当前缓冲区的位置。 + while (currentIndex < 0) { - buffer = buffer.Next; - idx -= bufferSize; + current = current.Prev; + currentIndex += bufferSize; + } + while (currentIndex >= bufferSize) + { + current = current.Next; + currentIndex -= bufferSize; } - return buffer.Text[idx]; } /// @@ -248,7 +259,7 @@ public char Peek(int offset) /// 要读取的文本长度。 /// 读取到的文本。 /// 调用方确保 在有效范围之内。 - public StringView ReadBlock(int start, int count) + protected override StringView ReadBlockInternal(int start, int count) { if (readedText != null && readedStart == start && readedText.Length == count) { @@ -297,7 +308,7 @@ public StringView ReadBlock(int start, int count) /// 释放指定索引之前的字符,释放后的字符无法再被读取。 /// /// 要释放的字符起始索引。 - public void Free(int index) + protected override void Free(int index) { freeIndex = index; while (index >= first.StartIndex + bufferSize) @@ -370,27 +381,11 @@ private bool PrepareBuffer() Buffer buffer = new(reader, bufferSize, last); last = buffer; } - locator?.Read(last.Text.AsSpan(0, last.Length)); + sourceLocator?.Read(last.Text.AsSpan(0, last.Length)); length += last.Length; return true; } - #region IDisposable 成员 - - /// - /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 - /// - public void Dispose() - { - if (reader != null) - { - reader.Dispose(); - reader = null; - } - } - - #endregion - /// /// 表示 的字符缓冲区。 /// diff --git a/Runtime/Text/SourceReader.cs b/Runtime/Text/SourceReader/SourceReader.cs similarity index 66% rename from Runtime/Text/SourceReader.cs rename to Runtime/Text/SourceReader/SourceReader.cs index 88a1da2..df0f9af 100644 --- a/Runtime/Text/SourceReader.cs +++ b/Runtime/Text/SourceReader/SourceReader.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Cyjb.Compilers; namespace Cyjb.Text; @@ -11,7 +12,7 @@ namespace Cyjb.Text; /// 《C# 词法分析器(二)输入缓冲和代码定位》。 /// /// 《C# 词法分析器(二)输入缓冲和代码定位》 -public sealed class SourceReader : IDisposable +public abstract class SourceReader : IDisposable { /// /// 用于表示到达流结尾的字符。 @@ -21,27 +22,77 @@ public sealed class SourceReader : IDisposable /// /// 空的源文件读取器。 /// - internal static readonly SourceReader Empty = new(new StringReader(string.Empty)); + internal static readonly SourceReader Empty = new StringSourceReader(string.Empty); /// - /// 字符缓冲区。 + /// 使用指定的文本内容创建 类的新实例。 /// - private readonly ISourceBuffer buffer; - /// - /// 行列定位器。 - /// - private LineLocator? locator; + /// 文本内容。 + /// 要读取的起始位置。 + /// null + /// 小于 0 + public static SourceReader Create(string source, int start = 0) + { + ArgumentNullException.ThrowIfNull(source); + if (start < 0) + { + throw CommonExceptions.ArgumentNegative(start); + } + if (start == 0) + { + return new StringSourceReader(source); + } + else + { + return new StringViewSourceReader(source, start, source.Length - start); + } + } + /// - /// 标记列表。 + /// 使用指定的文本内容创建 类的新实例。 /// - private List? marks; + /// 文本内容。 + /// 要读取的起始位置。 + /// 要读取的文本长度。 + /// null + /// 小于 0 + /// + + /// 表示的位置不在字符串范围内。 + public static SourceReader Create(string source, int start, int length) + { + ArgumentNullException.ThrowIfNull(source); + if (start < 0) + { + throw CommonExceptions.ArgumentNegative(start); + } + if (start + length < 0 || start + length >= source.Length) + { + throw CommonExceptions.ArgumentNegative(length); + } + if (start == 0 && length == source.Length) + { + return new StringSourceReader(source); + } + else + { + return new StringViewSourceReader(source, start, length); + } + } + /// - /// 标记的起始索引。 + /// 使用指定的文本视图内容创建 类的新实例。 /// - private int markIndex = int.MaxValue; + /// 文本视图内容。 + /// null + public static SourceReader Create(StringView source) + { + ArgumentNullException.ThrowIfNull(source); + source.GetOrigin(out string text, out int start, out int length); + return new StringViewSourceReader(text, start, length); + } /// - /// 使用指定的字符读取器初始化 类的新实例。 + /// 使用指定的字符读取器创建 类的新实例。 /// /// 用于读取源文件的字符读取器。 /// 读取文本的缓冲区尺寸。 @@ -49,50 +100,50 @@ public sealed class SourceReader : IDisposable /// 其他值会限制每块缓冲区的大小,当不在使用相关 后可以释放缓冲区节约内容, /// 缓冲区不宜设置太小,否则容易影响性能,可以考虑设置为 0x1000 或更高。 /// - /// null - public SourceReader(TextReader reader, int bufferSize = 0) + public static SourceReader Create(TextReader reader, int bufferSize = 0) { ArgumentNullException.ThrowIfNull(reader); if (reader is StringReader || bufferSize <= 0 || bufferSize == int.MaxValue) { // StringReader 已经包含了完整的字符串,没有分块读取的意义。 - buffer = new SourceCompleteBuffer(reader); + return new StringSourceReader(reader.ReadToEnd()); } else { - buffer = new SourcePartialBuffer(reader, bufferSize); + return new PartialSourceReader(reader, bufferSize); } } /// - /// 使用指定的文本内容初始化 类的新实例。 + /// 当前读取的位置。 /// - /// 文本内容。 - /// null - public SourceReader(string text) - { - ArgumentNullException.ThrowIfNull(text); - buffer = new SourceCompleteBuffer(text); - } - + protected int curIndex = 0; /// - /// 使用指定的字符串视图初始化 类的新实例。 + /// 读取的起始位置。 /// - /// 字符串视图。 - public SourceReader(StringView view) - { - buffer = new SourceCompleteBuffer(view); - } + protected int startIndex = 0; + /// + /// 行列定位器。 + /// + protected LineLocator? sourceLocator; + /// + /// 标记列表。 + /// + private List? marks; + /// + /// 标记的起始索引。 + /// + private int markIndex = int.MaxValue; /// - /// 获取关联到的行列定位器。 + /// 初始化 类的新实例。 /// - public LineLocator? Locator => locator; + protected SourceReader() { } + /// - /// 获取或设置结束读取的位置。 + /// 获取关联到的行列定位器。 /// - /// 会为超出 的读取返回 - public int End { get; set; } = int.MaxValue; + public LineLocator? Locator => sourceLocator; /// /// 获取或设置当前的字符索引。 @@ -100,24 +151,11 @@ public SourceReader(StringView view) /// 当前的字符索引,该索引从零开始。设置索引时,不能达到被丢弃的字符,或者超出文件结尾。 public int Index { - get => buffer.Index; - set - { - if (value < buffer.Index) - { - if (value < buffer.StartIndex) - { - value = buffer.StartIndex; - } - buffer.Index = value; - } - else if (value > buffer.Index) + get => curIndex; + set { + if (curIndex != value) { - if (value > End) - { - value = End; - } - buffer.Index = value; + SetIndex(value); } } } @@ -125,7 +163,7 @@ public int Index /// /// 返回当前是否位于行首。 /// - public bool IsLineStart => buffer.IsLineStart; + public abstract bool IsLineStart { get; } /// /// 开启行列定位功能,允许通过 获取指定索引的行列位置。 @@ -133,13 +171,10 @@ public int Index /// /// Tab 的宽度。 /// 当前源读取器。 - public SourceReader UseLineLocator(int tabSize = 4) + [MemberNotNull(nameof(sourceLocator))] + public virtual SourceReader UseLineLocator(int tabSize = 4) { - if (locator == null) - { - locator = new LineLocator(tabSize); - buffer.SetLocator(locator); - } + sourceLocator ??= new LineLocator(tabSize); return this; } @@ -151,11 +186,11 @@ public SourceReader UseLineLocator(int tabSize = 4) /// 未提前调用 public LinePosition GetPosition(int index) { - if (locator == null) + if (sourceLocator == null) { throw new InvalidOperationException(Resources.GetPositionBeforeUse); } - return locator.GetPosition(index); + return sourceLocator.GetPosition(index); } /// @@ -166,11 +201,11 @@ public LinePosition GetPosition(int index) /// 未提前调用 public LinePositionSpan GetLinePositionSpan(TextSpan span) { - if (locator == null) + if (sourceLocator == null) { throw new InvalidOperationException(Resources.GetPositionBeforeUse); } - return locator.GetSpan(span); + return sourceLocator.GetSpan(span); } /// @@ -181,18 +216,6 @@ public void Close() Dispose(); } - #region IDisposable 成员 - - /// - /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 - /// - public void Dispose() - { - buffer.Dispose(); - } - - #endregion - #region 读取字符 /// @@ -204,14 +227,7 @@ public void Dispose() /// 返回之后可用的字符,但不使用它。 /// /// - public char Peek() - { - if (buffer.Index >= End) - { - return InvalidCharacter; - } - return buffer.Peek(0); - } + public abstract char Peek(); /// /// 返回文本读取器中之后的 偏移的字符,但不使用它。 @@ -222,18 +238,7 @@ public char Peek() /// 或为 (如果没有更多的可用字符)。 /// 当前 已关闭。 /// 小于 0 - public char Peek(int offset) - { - if (offset < 0) - { - throw CommonExceptions.ArgumentNegative(offset); - } - if (buffer.Index + offset >= End) - { - return InvalidCharacter; - } - return buffer.Peek(offset); - } + public abstract char Peek(int offset); /// /// 读取文本读取器中的下一个字符并使该字符的位置提升一个字符。 @@ -244,14 +249,7 @@ public char Peek(int offset) /// 返回之后可用的字符,并使该字符的位置提升。 /// /// - public char Read() - { - if (buffer.Index >= End) - { - return InvalidCharacter; - } - return buffer.Read(0); - } + public abstract char Read(); /// /// 读取文本读取器中之后的 偏移的字符,并使该字符的位置提升。 @@ -262,18 +260,7 @@ public char Read() /// 或为 (如果没有更多的可用字符)。 /// 当前 已关闭。 /// 小于 0 - public char Read(int offset) - { - if (offset < 0) - { - throw CommonExceptions.ArgumentNegative(offset); - } - if (buffer.Index + offset >= End) - { - offset = End - buffer.Index; - } - return buffer.Read(offset); - } + public abstract char Read(int offset); /// /// 回退最后被读取的字符,只有之前的数据未被丢弃时才可以进行回退。 @@ -286,15 +273,12 @@ public char Read(int offset) /// public bool Unget() { - if (buffer.Index > buffer.StartIndex) + if (curIndex > startIndex) { - buffer.Index--; + SetIndex(curIndex - 1); return true; } - else - { - return false; - } + return false; } /// @@ -315,8 +299,8 @@ public int Unget(int count) { return 0; } - count = Math.Min(count, buffer.Index - buffer.StartIndex); - buffer.Index -= count; + count = Math.Min(count, curIndex - startIndex); + SetIndex(curIndex - count); return count; } @@ -326,7 +310,7 @@ public int Unget(int count) /// 当前位置之前的数据。 public StringView GetReadedText() { - return buffer.ReadBlock(buffer.StartIndex, buffer.Index - buffer.StartIndex); + return ReadBlockInternal(startIndex, curIndex - startIndex); } /// @@ -334,8 +318,8 @@ public StringView GetReadedText() /// public void Drop() { - buffer.StartIndex = buffer.Index; - buffer.Free(Math.Min(buffer.StartIndex, markIndex)); + startIndex = curIndex; + Free(Math.Min(startIndex, markIndex)); } /// @@ -344,7 +328,7 @@ public void Drop() /// 当前位置之前的数据。 public StringView Accept() { - StringView text = buffer.ReadBlock(buffer.StartIndex, buffer.Index - buffer.StartIndex); + StringView text = ReadBlockInternal(startIndex, curIndex - startIndex); Drop(); return text; } @@ -368,7 +352,7 @@ public Token AcceptToken(T kind, TextSpan? span = null, object? value = nu } else { - tokenSpan = new TextSpan(buffer.StartIndex, buffer.Index); + tokenSpan = new TextSpan(startIndex, curIndex); } return new Token(kind, Accept(), tokenSpan, value); } @@ -384,7 +368,7 @@ public Token AcceptToken(T kind, TextSpan? span = null, object? value = nu /// 被标记的位置及其之后的字符可以确保能够通过 ReadBlock 方法读取。 public SourceMark Mark() { - SourceMark mark = new(buffer.Index); + SourceMark mark = new(curIndex); marks ??= new List(); int index = marks.BinarySearch(mark); if (index < 0) @@ -418,11 +402,11 @@ public void Release(SourceMark? mark) if (marks.Count == 0) { markIndex = int.MaxValue; - buffer.Free(buffer.StartIndex); + Free(startIndex); } - else if (marks[0].Index < buffer.StartIndex) + else if (marks[0].Index < startIndex) { - buffer.Free(marks[0].Index); + Free(marks[0].Index); } } } @@ -487,7 +471,7 @@ public StringView ReadBlock(int index, int count) { throw CommonExceptions.ArgumentOutOfRange(index); } - if (index + count > buffer.Index) + if (index + count > curIndex) { throw CommonExceptions.ArgumentOutOfRange(count); } @@ -495,7 +479,7 @@ public StringView ReadBlock(int index, int count) { return StringView.Empty; } - return buffer.ReadBlock(index, count); + return ReadBlockInternal(index, count); } /// @@ -527,10 +511,69 @@ public StringView ReadBlock(SourceMark start, SourceMark end) } else { - return buffer.ReadBlock(start.Index, count); + return ReadBlockInternal(start.Index, count); } } #endregion // 标记 + #region IDisposable 成员 + + /// + /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 + /// + /// + /// + /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 + /// + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 执行与释放或重置非托管资源相关的应用程序定义的任务。 + /// + /// 是否释放托管资源。 + protected virtual void Dispose(bool disposing) { } + + #endregion // IDisposable 成员 + + /// + /// 设置当前的字符索引。 + /// + /// 当前的字符索引,该索引从零开始。设置索引时,不能达到被丢弃的字符,或者超出文件结尾。 + protected virtual void SetIndex(int value) + { + if (value < curIndex) + { + if (value < startIndex) + { + value = startIndex; + } + curIndex = value; + } + else + { + curIndex = value; + } + } + + /// + /// 读取指定范围的文本。 + /// + /// 起始索引。 + /// 要读取的文本长度。 + /// 读取到的文本。 + /// 调用方确保 在有效范围之内。 + protected abstract StringView ReadBlockInternal(int start, int count); + + /// + /// 释放指定索引之前的字符,释放后的字符无法再被读取。 + /// + /// 要释放的字符起始索引。 + protected virtual void Free(int index) { } + } diff --git a/Runtime/Text/SourceReader/StringSourceReader.cs b/Runtime/Text/SourceReader/StringSourceReader.cs new file mode 100644 index 0000000..dc9bc57 --- /dev/null +++ b/Runtime/Text/SourceReader/StringSourceReader.cs @@ -0,0 +1,166 @@ +namespace Cyjb.Text; + +/// +/// 表示字符串的源文件读取器。 +/// +internal sealed class StringSourceReader : SourceReader +{ + /// + /// 文本内容。 + /// + private readonly string source; + /// + /// 可读取的文本长度。 + /// + private readonly int length; + + /// + /// 使用指定的文本内容初始化 类的新实例。 + /// + /// 文本内容。 + public StringSourceReader(string source) + { + this.source = source; + length = source.Length; + } + + /// + /// 获取当前位置是否位于行首。 + /// + public override bool IsLineStart + { + get + { + if (curIndex <= 0) + { + return true; + } + char ch = source[curIndex - 1]; + if (!Utils.IsLineBreak(ch)) + { + return false; + } + // 兼容 \r\n 的场景。 + if (ch == '\r') + { + if (curIndex == length) + { + return true; + } + else + { + ch = source[curIndex]; + } + if (ch == '\n') + { + return false; + } + } + return true; + } + } + + /// + /// 开启行列定位功能,允许通过 获取指定索引的行列位置。 + /// 需要在读取字符之前设置。 + /// + /// Tab 的宽度。 + /// 当前源读取器。 + public override SourceReader UseLineLocator(int tabSize = 4) + { + base.UseLineLocator(tabSize); + // 总是在关联的时候一次性完成读取。 + sourceLocator.Read(source); + return this; + } + + /// + /// 返回下一个可用的字符,但不使用它。 + /// + /// 表示下一个要读取的字符的整数,或者如果没有要读取的字符,则为 + /// + /// + /// + /// 返回之后可用的字符,但不使用它。 + /// + /// + public override char Peek() + { + if (curIndex < length) + { + return source[curIndex]; + } + else + { + return InvalidCharacter; + } + } + + /// + /// 返回文本读取器中之后的 偏移的字符,但不使用它。 + /// Peek(0) 等价于 。 + /// + /// 要读取的偏移。 + /// 文本读取器中之后的 偏移的字符, + /// 或为 (如果没有更多的可用字符)。 + public override char Peek(int offset) + { + int idx = curIndex + offset; + if (idx < length) + { + return source[idx]; + } + else + { + return InvalidCharacter; + } + } + + /// + /// 读取文本读取器中的下一个字符并使该字符的位置提升一个字符。 + /// + /// 文本读取器中的下一个字符,或为 (如果没有更多的可用字符)。 + /// + /// + /// 返回之后可用的字符,并使该字符的位置提升。 + /// + /// + public override char Read() + { + if (curIndex >= length) + { + return InvalidCharacter; + } + return source[curIndex++]; + } + + /// + /// 读取文本读取器中之后的 偏移的字符,并使该字符的位置提升。 + /// Read(0) 等价于 。 + /// + /// 要读取的偏移。 + /// 文本读取器中之后的 偏移的字符, + /// 或为 (如果没有更多的可用字符)。 + public override char Read(int offset) + { + curIndex += offset; + if (curIndex >= length) + { + curIndex = length; + return InvalidCharacter; + } + return source[curIndex++]; + } + + /// + /// 读取指定范围的文本。 + /// + /// 起始索引。 + /// 要读取的文本长度。 + /// 读取到的文本。 + /// 调用方确保 在有效范围之内。 + protected override StringView ReadBlockInternal(int start, int count) + { + return source.AsView(start, count); + } +} diff --git a/Runtime/Text/SourceReader/StringViewSourceReader.cs b/Runtime/Text/SourceReader/StringViewSourceReader.cs new file mode 100644 index 0000000..6ef6510 --- /dev/null +++ b/Runtime/Text/SourceReader/StringViewSourceReader.cs @@ -0,0 +1,180 @@ +namespace Cyjb.Text; + +/// +/// 表示字符串视图的源文件读取器。 +/// +internal sealed class StringViewSourceReader : SourceReader +{ + /// + /// 文本内容。 + /// + private readonly string source; + /// + /// 要读取的起始索引。 + /// + private readonly int start; + /// + /// 可读取的文本长度。 + /// + private readonly int length; + + /// + /// 使用指定的文本内容初始化 类的新实例。 + /// + /// 文本内容。 + /// 要读取的起始索引。 + /// 要读取的字符长度。 + public StringViewSourceReader(string source, int start, int length) + { + this.source = source; + this.start = start; + this.length = length; + } + + /// + /// 获取当前位置是否位于行首。 + /// + public override bool IsLineStart + { + get + { + if (curIndex <= 0) + { + return true; + } + char ch = source[curIndex + start - 1]; + if (!Utils.IsLineBreak(ch)) + { + return false; + } + // 兼容 \r\n 的场景。 + if (ch == '\r') + { + if (curIndex == length) + { + return true; + } + else + { + ch = source[curIndex + start]; + } + if (ch == '\n') + { + return false; + } + } + return true; + } + } + + /// + /// 开启行列定位功能,允许通过 获取指定索引的行列位置。 + /// 需要在读取字符之前设置。 + /// + /// Tab 的宽度。 + /// 当前源读取器。 + public override SourceReader UseLineLocator(int tabSize = 4) + { + base.UseLineLocator(tabSize); + // 总是在关联的时候一次性完成读取。 + sourceLocator.Read(source.AsSpan(start, length)); + return this; + } + + /// + /// 返回下一个可用的字符,但不使用它。 + /// + /// 表示下一个要读取的字符的整数,或者如果没有要读取的字符,则为 + /// + /// + /// 返回之后可用的字符,但不使用它。 + /// + /// + public override char Peek() + { + if (curIndex < length) + { + return source[curIndex + start]; + } + else + { + return InvalidCharacter; + } + } + + /// + /// 返回文本读取器中之后的 偏移的字符,但不使用它。 + /// Peek(0) 等价于 。 + /// + /// 要读取的偏移。 + /// 文本读取器中之后的 偏移的字符, + /// 或为 (如果没有更多的可用字符)。 + /// 当前 已关闭。 + /// 小于 0 + public override char Peek(int offset) + { + if (offset < 0) + { + throw CommonExceptions.ArgumentNegative(offset); + } + int idx = curIndex + offset; + if (idx < length) + { + return source[idx + start]; + } + else + { + return InvalidCharacter; + } + } + + /// + /// 读取文本读取器中的下一个字符并使该字符的位置提升一个字符。 + /// + /// 文本读取器中的下一个字符,或为 (如果没有更多的可用字符)。 + /// + /// + /// 返回之后可用的字符,并使该字符的位置提升。 + /// + /// + public override char Read() + { + if (curIndex >= length) + { + return InvalidCharacter; + } + return source[curIndex++ + start]; + } + + /// + /// 读取文本读取器中之后的 偏移的字符,并使该字符的位置提升。 + /// Read(0) 等价于 。 + /// + /// 要读取的偏移。 + /// 文本读取器中之后的 偏移的字符, + /// 或为 (如果没有更多的可用字符)。 + /// 当前 已关闭。 + /// 小于 0 + public override char Read(int offset) + { + curIndex += offset; + if (curIndex >= length) + { + curIndex = length; + return InvalidCharacter; + } + return source[curIndex++ + start]; + } + + /// + /// 读取指定范围的文本。 + /// + /// 起始索引。 + /// 要读取的文本长度。 + /// 读取到的文本。 + /// 调用方确保 在有效范围之内。 + protected override StringView ReadBlockInternal(int start, int count) + { + return source.AsView(start + this.start, count); + } +} diff --git a/TestCompilers/Text/UnitTestSourceReader.cs b/TestCompilers/Text/UnitTestSourceReader.cs index 518b907..2543f8a 100644 --- a/TestCompilers/Text/UnitTestSourceReader.cs +++ b/TestCompilers/Text/UnitTestSourceReader.cs @@ -19,9 +19,9 @@ public class UnitTestSourceReader /// 对 的字符串构造函数进行测试。 /// [TestMethod] - public void TestCtorString() + public void TestCreateString() { - SourceReader reader = new("1234567890"); + SourceReader reader = SourceReader.Create("1234567890"); Assert.AreEqual('1', reader.Peek()); Assert.AreEqual('1', reader.Peek()); Assert.AreEqual('1', reader.Read()); @@ -62,9 +62,9 @@ public void TestCtorString() /// 对 的字符串视图构造函数进行测试。 /// [TestMethod] - public void TestCtorStringView() + public void TestCreateStringView() { - SourceReader reader = new("a1234567890b".AsView(1, 10)); + SourceReader reader = SourceReader.Create("a1234567890b".AsView(1, 10)); Assert.AreEqual('1', reader.Peek()); Assert.AreEqual('1', reader.Peek()); Assert.AreEqual('1', reader.Read()); @@ -109,7 +109,7 @@ public void TestCtorStringView() [DataRow("TextReader")] public void TestShortText(string type) { - SourceReader reader = new(CreateReader(type, "1234567890"), BufferLength); + SourceReader reader = SourceReader.Create(CreateReader(type, "1234567890"), BufferLength); Assert.AreEqual('1', reader.Peek()); Assert.AreEqual('1', reader.Peek()); Assert.AreEqual('1', reader.Read()); @@ -166,7 +166,7 @@ public void TestLongText(string type) builder.Append((char)Random.Shared.Next(char.MaxValue)); } string text = builder.ToString(); - SourceReader reader = new(CreateReader(type, text), BufferLength); + SourceReader reader = SourceReader.Create(CreateReader(type, text), BufferLength); Assert.AreEqual(text[0], reader.Peek()); Assert.AreEqual(text[0], reader.Peek()); @@ -220,7 +220,7 @@ public void TestIsLineStart(string type) builder.Append("\r\n123"); builder.Append('1', BufferLength * 2 - builder.Length - 1); builder.Append("\n123"); - SourceReader reader = new(CreateReader(type, builder.ToString()), BufferLength); + SourceReader reader = SourceReader.Create(CreateReader(type, builder.ToString()), BufferLength); Assert.IsTrue(reader.IsLineStart); Assert.AreEqual('3', reader.Read(2)); @@ -296,7 +296,7 @@ public void TestLocator(string type) { string text = "12\r34\n56\r\n78"; // 一次性读入。 - SourceReader reader = new(CreateReader(type, text), BufferLength); + SourceReader reader = SourceReader.Create(CreateReader(type, text), BufferLength); reader.UseLineLocator(); Assert.AreEqual('8', reader.Read(11)); @@ -314,7 +314,7 @@ public void TestLocator(string type) Assert.AreEqual(new LinePosition(4, 1, 2), reader.GetPosition(11)); // 分批读入。 - reader = new(CreateReader(type, text), BufferLength); + reader = SourceReader.Create(CreateReader(type, text), BufferLength); reader.UseLineLocator(); Assert.AreEqual('2', reader.Read(1)); @@ -351,29 +351,6 @@ public void TestLocator(string type) Assert.AreEqual(new LinePosition(4, 1, 2), reader.GetPosition(11)); } - /// - /// 对 进行测试。 - /// - [DataTestMethod] - [DataRow("StringReader")] - [DataRow("TextReader")] - public void TestEnd(string type) - { - SourceReader reader = new(CreateReader(type, "1234567890"), BufferLength); - Assert.AreEqual('1', reader.Peek()); - Assert.AreEqual('1', reader.Read()); - reader.End = 2; - Assert.AreEqual('2', reader.Peek()); - Assert.AreEqual('2', reader.Read()); - Assert.AreEqual(SourceReader.InvalidCharacter, reader.Peek()); - Assert.AreEqual(SourceReader.InvalidCharacter, reader.Read()); - reader.End = 3; - Assert.AreEqual('3', reader.Peek()); - Assert.AreEqual('3', reader.Read()); - Assert.AreEqual(SourceReader.InvalidCharacter, reader.Peek()); - Assert.AreEqual(SourceReader.InvalidCharacter, reader.Read()); - } - /// /// 对 进行测试。 /// @@ -387,7 +364,7 @@ public void TestMark(string type) { builder.Append("0123456789"); } - SourceReader reader = new(CreateReader(type, builder.ToString()), BufferLength); + SourceReader reader = SourceReader.Create(CreateReader(type, builder.ToString()), BufferLength); Assert.AreEqual('0', reader.Read()); Assert.AreEqual("0", reader.ReadBlock(0, 1)); Assert.AreEqual("", reader.ReadBlock(0, 0));