我试图在 C # 中编写一个非常简单的解析器。
我需要一个词法分析器-让我将正则表达式与令牌相关联的东西,所以它在正则表达式中读取并给我回符号。
似乎我应该能够使用 Regex 来完成实际的繁重工作,但我看不到一个简单的方法。一方面,Regex 似乎只在字符串上工作,而不是流(为什么!??)。
基本上,我想要以下接口的实现:
intece ILexer : IDisposable
{
/// <summary>
/// Return true if there are more tokens to read
/// </summary>
bool HasMoreTokens { get; }
/// <summary>
/// The actual contents that matched the token
/// </summary>
string TokenContents { get; }
/// <summary>
/// The particular token in "tokenDefinitions" that was matched (e.g. "STRING", "NUMBER", "OPEN PARENS", "CLOSE PARENS"
/// </summary>
object Token { get; }
/// <summary>
/// Move to the next token
/// </summary>
void Next();
}
intece ILexerFactory
{
/// <summary>
/// Create a Lexer for converting a stream of characters into tokens
/// </summary>
/// <param name="reader">TextReader that supplies the underlying stream</param>
/// <param name="tokenDefinitions">A dictionary from regular expressions to their "token identifers"</param>
/// <returns>The lexer</returns>
ILexer CreateLexer(TextReader reader, IDictionary<string, object> tokenDefinitions);
}
所以,pluz 发送 codz...
不,严重的是,我即将开始编写上述接口的实现,但我很难相信在.NET(2.0)中没有一些简单的方法。
所以,一个简单的方法来做上面的任何建议?(另外,我不想要任何“代码生成器”。性能对于这个东西并不重要,我不想在构建过程中引入任何复杂性。)
我在这里作为答案发布的原始版本有一个问题,因为它只有在有多个“Regex”与当前表达式匹配时才起作用。也就是说,只要只有一个 Regex 匹配,它就会返回一个令牌-而大多数人希望 Regex 是“贪婪的”。对于诸如“引用字符串”之类的事情尤其如此。
位于 Regex 之上的唯一解决方案是逐行读取输入 (这意味着你不能拥有跨越多行的令牌)。我可以忍受这一点-毕竟,这是一个可怜的人的 lexer!此外,在任何情况下从 Lexer 中获取行号信息通常都是有用的。
所以,这里有一个解决这些问题的新版本。信用也转到this
public intece IMatcher
{
/// <summary>
/// Return the number of characters that this "regex" or equivalent
/// matches.
/// </summary>
/// <param name="text">The text to be matched</param>
/// <returns>The number of characters that matched</returns>
int Match(string text);
}
sealed class RegexMatcher : IMatcher
{
private readonly Regex regex;
public RegexMatcher(string regex) => this.regex = new Regex(string.Format("^{0}", regex));
public int Match(string text)
{
var m = regex.Match(text);
return m.Success ? m.Length : 0;
}
public override string ToString() => regex.ToString();
}
public sealed class TokenDefinition
{
public readonly IMatcher Matcher;
public readonly object Token;
public TokenDefinition(string regex, object token)
{
this.Matcher = new RegexMatcher(regex);
this.Token = token;
}
}
public sealed class Lexer : IDisposable
{
private readonly TextReader reader;
private readonly TokenDefinition[] tokenDefinitions;
private string lineRemaining;
public Lexer(TextReader reader, TokenDefinition[] tokenDefinitions)
{
this.reader = reader;
this.tokenDefinitions = tokenDefinitions;
nextLine();
}
private void nextLine()
{
do
{
lineRemaining = reader.ReadLine();
++LineNumber;
Position = 0;
} while (lineRemaining != null && lineRemaining.Length == 0);
}
public bool Next()
{
if (lineRemaining == null)
return false;
foreach (var def in tokenDefinitions)
{
var matched = def.Matcher.Match(lineRemaining);
if (matched > 0)
{
Position += matched;
Token = def.Token;
TokenContents = lineRemaining.Substring(0, matched);
lineRemaining = lineRemaining.Substring(matched);
if (lineRemaining.Length == 0)
nextLine();
return true;
}
}
throw new Exception(string.Format("Unable to match against any tokens at line {0} position {1} \"{2}\"",
LineNumber, Position, lineRemaining));
}
public string TokenContents { get; private set; }
public object Token { get; private set; }
public int LineNumber { get; private set; }
public int Position { get; private set; }
public void Dispose() => reader.Dispose();
}
示例程序:
string sample = @"( one (two 456 -43.2 "" \"" quoted"" ))";
var defs = new TokenDefinition[]
{
// Thanks to [steven levithan][2] for this great quoted string
// regex
new TokenDefinition(@"([""'])(?:\\\1|.)*?\1", "QUOTED-STRING"),
// Thanks to http://www.regular-expressions.info/floatingpoint.html
new TokenDefinition(@"[-+]?\d*\.\d+([eE][-+]?\d+)?", "FLOAT"),
new TokenDefinition(@"[-+]?\d+", "INT"),
new TokenDefinition(@"#t", "TRUE"),
new TokenDefinition(@"#f", "FALSE"),
new TokenDefinition(@"[*<>\?\-+/A-Za-z->!]+", "SYMBOL"),
new TokenDefinition(@"\.", "DOT"),
new TokenDefinition(@"\(", "LEFT"),
new TokenDefinition(@"\)", "RIGHT"),
new TokenDefinition(@"\s", "SPACE")
};
TextReader r = new StringReader(sample);
Lexer l = new Lexer(r, defs);
while (l.Next())
Console.WriteLine("Token: {0} Contents: {1}", l.Token, l.TokenContents);
输出:
Token: LEFT Contents: (
Token: SPACE Contents:
Token: SYMBOL Contents: one
Token: SPACE Contents:
Token: LEFT Contents: (
Token: SYMBOL Contents: two
Token: SPACE Contents:
Token: INT Contents: 456
Token: SPACE Contents:
Token: FLOAT Contents: -43.2
Token: SPACE Contents:
Token: QUOTED-STRING Contents: " \" quoted"
Token: SPACE Contents:
Token: RIGHT Contents: )
Token: RIGHT Contents: )
这可能是过度的,但看看Irony在 CodePlex。
Irony 是一个用于在.NET 平台上实现语言的开发工具包。它使用 c # 语言和.NET Framework 3.5 的灵活性和强大功能来实现一种全新的简化的编译器构造技术。与大多数现有的 yacc / lex 风格的解决方案不同,Irony 不使用任何扫描仪或解析器代码从以专门的元语言编写的语法规范中生成代码。在 Irony 中,目标语言语法使用重载语法定义 c # 语法
除非你有一个非常非常规的语法,否则我会强烈建议不要滚动你自己的词法分析器 / 解析器。
我通常会发现 C# 的 lexer / prs 真的很缺乏。但是,F # 附带了 fslex 和 fsyacc,您可以学习如何使用in this tutorial。我已经在 F # 中编写了几个 lexer / prs,并在 C# 中使用它们,并且它非常容易做到。
我想这不是一个真正的穷人的词法分析器 / 解析器,看到你必须学习一种全新的语言才能开始,但这是一个开始。
改变我原来的答案。
看看SharpTemplate,它有不同语法类型的解析器,例如
#foreach ($product in $Products)
<tr><td>$product.Name</td>
#if ($product.Stock > 0)
<td>In stock</td>
#else
<td>Backordered</td>
#end
</tr>
#end
它为每种类型的令牌使用正则表达式:
public class Velocity : SharpTemplateConfig
{
public Velocity()
{
AddToken(TemplateTokenType.ForEach, @"#(foreach|{foreach})\s+\(\s*(?<iterator>[a-z_][a-z0-9_]*)\s+in\s+(?<expr>.*?)\s*\)", true);
AddToken(TemplateTokenType.EndBlock, @"#(end|{end})", true);
AddToken(TemplateTokenType.If, @"#(if|{if})\s+\((?<expr>.*?)\s*\)", true);
AddToken(TemplateTokenType.ElseIf, @"#(elseif|{elseif})\s+\((?<expr>.*?)\s*\)", true);
AddToken(TemplateTokenType.Else, @"#(else|{else})", true);
AddToken(TemplateTokenType.Expression, @"\${(?<expr>.*?)}", false);
AddToken(TemplateTokenType.Expression, @"\$(?<expr>[a-zA-Z_][a-zA-Z0-9_\.@]*?)(?![a-zA-Z0-9_\.@])", false);
}
}
像这样使用
foreach (Match match in regex.Matches(inputString))
{
...
switch (tokenMatch.TokenType)
{
case TemplateTokenType.Expression:
{
currentNode.Add(new ExpressionNode(tokenMatch));
}
break;
case TemplateTokenType.ForEach:
{
nodeStack.Push(currentNode);
currentNode = currentNode.Add(new ForEachNode(tokenMatch));
}
break;
....
}
....
}
它从堆栈推送并弹出以保持状态。
本站系公益性非盈利分享网址,本文来自用户投稿,不代表码文网立场,如若转载,请注明出处
评论列表(10条)