代码之家  ›  专栏  ›  技术社区  ›  Paul Hollingsworth

可怜的男人的“lexer”为C#

  •  31
  • Paul Hollingsworth  · 技术社区  · 16 年前

    似乎我应该能够使用正则表达式来完成实际的繁重工作,但我看不到一个简单的方法来完成。首先,正则表达式似乎只在字符串上工作,而不在流上工作(为什么呢!?)。

    interface 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();
    }
    
    interface 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。。。

    那么,有什么简单的方法来实现上述目标的建议吗?(另外,我不想要任何“代码生成器”。性能对这件事并不重要,我也不想在构建过程中引入任何复杂性。)

    7 回复  |  直到 16 年前
        1
  •  27
  •   AustinWBryan ravenspoint    7 年前

    我在这里发布的作为答案的原始版本有一个问题,它只在有多个“Regex”匹配当前表达式时才起作用。也就是说,只要一个正则表达式匹配,它就会返回一个令牌——而大多数人希望正则表达式是“贪婪的”。对于“带引号的字符串”之类的东西,情况尤其如此。

    位于正则表达式顶部的唯一解决方案是逐行读取输入(这意味着您不能拥有跨越多行的令牌)。我可以接受这一点——毕竟,这是一个穷人的雷克瑟!此外,在任何情况下,从Lexer中获取行号信息通常都很有用。

    this

    public interface 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: )
    
        2
  •  6
  •   Andy Dent    16 年前

    Irony 在CodePlex上。

    Irony是一个用于在.NET平台上实现语言的开发工具包。它利用c#语言和.NET Framework 3.5的灵活性和强大功能,实现了一种全新的、精简的编译器构造技术。 与大多数现有的yacc/lex风格的解决方案不同,反讽不使用任何扫描程序或解析器代码生成,这些代码是从用专门的元语言编写的语法规范生成的。讽刺的是,目标语言语法直接用c#编码,使用运算符重载来表示语法结构。Irony的扫描器和解析器模块使用编码为c#类的语法来控制解析过程。有关c#类中的语法定义示例以及在工作解析器中使用它的示例,请参见表达式语法示例。

        3
  •  5
  •   Juliet    16 年前

    除非你有一个非常非传统的语法,否则我会 建议不要使用自己的lexer/parser。

    我通常发现C#的lexer/parser非常缺乏。然而,F#附带了fslex和fsyacc,您可以学习如何使用它们 in this tutorial

    我想这并不是穷人的词法分析器,因为你必须学习一门全新的语言才能开始,但这是一个开始。

        4
  •  2
  •   Chris S    16 年前

    改变我原来的答案。

    看看 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;
            ....
        }
    
        ....
    }
    

    它从堆栈中推动和弹出以保持状态。

        5
  •  2
  •   Kieron    15 年前

    here . 通过为LEX创建正则表达式来工作。。。

    Direct download

        6
  •  0
  •   erik    16 年前

    爱尔兰大学的研究人员开发了一个部分实现,可以在以下链接中找到: Flex/Bison for C#

    这绝对可以被认为是一个“可怜的男人词法分析器”,因为他的实现似乎仍然存在一些问题,例如没有预处理器,存在“悬而未决的其他”情况等问题。

        7
  •  0
  •   Tim Cooper    13 年前

    如果你看一下我书中的ExpressionConverter WPF Converters library ,它具有C#表达式的基本词法分析和语法分析。从内存中看,不涉及正则表达式。