我正在尝试用C#编写一个非常简单的解析器.
我需要一个词法分析器 - 让我将正则表达式与标记相关联的东西,所以它读取正则表达式并给我回符号.
看起来我应该能够使用正则表达式进行实际繁重的工作,但我看不出一个简单的方法.首先,Regex似乎只能处理字符串,而不是流(为什么会这样!?!?).
基本上,我想要一个以下接口的实现:
interface ILexer : IDisposable { ////// Return true if there are more tokens to read /// bool HasMoreTokens { get; } ////// The actual contents that matched the token /// string TokenContents { get; } ////// The particular token in "tokenDefinitions" that was matched (e.g. "STRING", "NUMBER", "OPEN PARENS", "CLOSE PARENS" /// object Token { get; } ////// Move to the next token /// void Next(); } interface ILexerFactory { ////// Create a Lexer for converting a stream of characters into tokens /// /// TextReader that supplies the underlying stream /// A dictionary from regular expressions to their "token identifers" ///The lexer ILexer CreateLexer(TextReader reader, IDictionarytokenDefinitions); }
所以,pluz发送codz ...
不,说真的,我即将开始编写上述接口的实现但我发现很难相信在.NET(2.0)中没有一些简单的方法可以做到这一点.
那么,有什么建议可以通过简单的方法来完成上述操作吗?(另外,我不想要任何"代码生成器".性能对于这件事并不重要,我不想在构建过程中引入任何复杂性.)
我在这里作为答案发布的原始版本有一个问题,它只有在有多个"正则表达式"与当前表达式匹配时才有效.也就是说,只要一个正则表达式匹配,它就会返回一个令牌 - 而大多数人都希望正则表达式"贪婪".对于诸如"引用字符串"之类的内容尤其如此.
位于Regex之上的唯一解决方案是逐行读取输入(这意味着您不能拥有跨越多行的令牌).我可以忍受这个 - 毕竟,这是一个穷人的词法分子!此外,在任何情况下从Lexer获取行号信息通常很有用.
所以,这是一个解决这些问题的新版本.信用也归于此
public interface IMatcher { ////// Return the number of characters that this "regex" or equivalent /// matches. /// /// The text to be matched ///The number of characters that matched 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: )
这可能有点矫枉过正,但看看CodePlex 上的反讽.
Irony是一个用于在.NET平台上实现语言的开发工具包.它利用c#语言和.NET Framework 3.5的灵活性和强大功能来实现全新的简化编译器构造技术.与大多数现有的yacc/lex风格解决方案不同,Irony不会使用以专门的元语言编写的语法规范生成任何扫描程序或解析器代码.在Irony中,目标语言语法使用运算符重载直接在c#中编码,以表达语法结构.Irony的扫描程序和解析器模块使用编码为c#类的语法来控制解析过程.有关c#类中语法定义的示例,请参阅表达式语法示例,并在工作解析器中使用它.
除非你有一个非常规的语法,否则我强烈建议你不要使用自己的词法分析器/解析器.
我经常发现C#的词法分析器/解析器确实缺乏.但是,F#附带了fslex和fsyacc,您可以在本教程中学习如何使用它.我在F#中编写了几个词法分析器/解析器并在C#中使用它们,它很容易做到.
我认为它不是一个穷人的词法分析器/解析器,看到你必须学习一种全新的语言来开始,但它是一个开始.