在软件开发过程中,开发者经常需要处理各种数据交换格式。在某些情况下,为了满足特定的需求,开发者可能需要编写自定义的解析器。大多数商业产品中使用的复杂解析器至少有一部分是手工编写的解析过程。本文旨在提供一个小型、轻量级的解决方案,以简化手写解析器的创建过程。
手写解析器的编写和维护可能相当困难。其中一个主要问题是正确的分解(factoring),而分解的难度又因为前瞻(lookahead)的存在而增加。前瞻,简单来说,就是解析器为了选择下一个分支而需要在光标前读取的符号或字符。
许多解析器可以仅使用一个字符的前瞻来处理。例如,JSON语言的语法解析仅需要一个字符的前瞻。更复杂的语法可能在某些点需要更多的前瞻,但通常大部分情况下只需要一个字符的前瞻。
处理前瞻的一种方法是使用TextReader Peek()函数,但这会在NetworkStream上出现问题。它需要一定程度的“回溯”(seeking)才能工作,这意味着“回溯”(即多次读取同一个字符),这是不必要的。
引入ParseContext类:
ParseContext类封装了底层的TextReader或IEnumerable<char>(包括字符串),并提供了多个用于解析和捕获内容的方法。它通过保持流比光标前进一步来提供单个字符的“前瞻”。而不是从流中读取下一个字符,每次Advance()调用都会改变Current成员以反映当前输入,这样Current总是包含光标下的字符。
这使得从输入中读取和解析变得非常容易。Capture包含当前Capture缓冲区的内容,CaptureCurrent()将当前字符(如果有的话)存储到Capture缓冲区。CaptureBuffer访问用于存储捕获的底层StringBuilder。
public bool TryParseCSharpLiteral(out object result) {
result = null;
EnsureStarted();
switch (Current) {
case '@':
case '\"':
string s;
if (TryParseCSharpString(out s)) {
result = s;
return true;
}
break;
case '\'':
if (TryParseCSharpChar(out s)) {
if (1 == s.Length)
result = s[0];
else
result = s;
return true;
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
case '-':
case '+':
if (TryParseCSharpNumeric(out result))
return true;
break;
case 't':
case 'f':
bool b;
if (TryParseCSharpBool(out b)) {
result = b;
return true;
}
break;
case 'n':
if (TryReadLiteral("null"))
return true;
break;
}
return false;
}
object v;
var val = @"""\U00000022'foobar'\U00000022""";
var pc = ParseContext.Create(val);
Console.WriteLine("TryRead:");
if (pc.TryReadCSharpLiteral())
Console.WriteLine("\tCapture: {0}", pc.Capture);
else
Console.WriteLine("\tError: {0}", pc.Capture);
pc = ParseContext.Create(val);
Console.WriteLine("TryParse:");
if (pc.TryParseCSharpLiteral(out v)) {
Console.WriteLine("\tCapture: {0}", pc.Capture);
Console.WriteLine("\tValue: {0}, Type {1}", v ?? "", (null != v) ? v.GetType().Name : "");
} else
Console.WriteLine("\tError: {0}", pc.Capture);
Console.WriteLine("Parse:");
pc = ParseContext.Create(val);
v = pc.ParseCSharpLiteral();
Console.WriteLine("\tValue: {0}, Type {1}", v ?? "", (null != v) ? v.GetType().Name : "");