穷人的 C#“词法分析器”

2023-12-09

我正在尝试用 C# 编写一个非常简单的解析器。

我需要一个词法分析器——它可以让我将正则表达式与标记关联起来,这样它就可以读取正则表达式并返回符号。

看来我应该能够使用正则表达式来完成实际的繁重工作,但我看不到一种简单的方法来做到这一点。一方面,正则表达式似乎只适用于字符串,而不适用于流(这是为什么!?!?)。

基本上,我想要以下接口的实现:

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);
}

所以,请发送代码...
不,说真的,我正要开始编写上述接口的实现,但我发现很难相信 .NET (2.0) 中已经没有一些简单的方法可以做到这一点。

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


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

位于正则表达式之上的唯一解决方案是逐行读取输入(这意味着您不能拥有跨越多行的标记)。我可以忍受这个——毕竟,它是一个穷人的词法分析器!此外,在任何情况下从词法分析器中获取行号信息通常都是有用的。

因此,这是解决这些问题的新版本。信用也归于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);

Output:

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: )
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

穷人的 C#“词法分析器” 的相关文章

随机推荐

  • 在 Spring 应用程序上下文文件中定义列表

    您可以在 Spring application context xml 文件中创建一个列表而不使用
  • VBA 用户表单作为输入框

    我有一个用户表单 您可以在其中编辑 Excel 工作表单元格中的文本 由于输入框没有跳过按钮 我创建了一个用户窗体 您可以在整个工作簿中搜索一个值并将其替换为另一个值条目 问题是我想逐个编辑找到的值 而不是同时编辑所有值 我有以下代码 但它
  • 重写equals方法来比较java中的多个字段

    在java中重写equals方法来比较多个字段的最佳方法是什么 例如 我的类中有 4 个对象 o1 o2 o3 o4 我想将它们与传递给 equals 方法的对象进行比较 if o1 null o2 null o3 null o4 null
  • 窗口已经聚焦 - 忽略 com.android.internal.view 的焦点增益

    我见过很多解决方案 但没有一个对我有用 我之前两次遇到过这个问题 我只需要恢复到我的备份代码就可以了 我一直不明白为什么会发生这种情况 有人说这只是一个警告 所以你可以忽略它 但我无法从一种意图切换到另一种意图 这就是确切的情况 我已经制作
  • Logcat 说 - 应用程序可能在其主线程上做了太多工作,并且错误消息显示 - StringtoReal.invalidReal(string.boolean)line:63 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心以获得指导 每当我在模拟器中运行我的应
  • 将文本从 plist 解析为 NSAttributedString

    我正在从 plist 加载文本并将其显示在我的应用程序中 理想情况下 我希望能够指定更复杂的格式 例如斜体 粗体和上标文本 并将其显示在自定义标签中 例如TTT属性标签 是否有任何可用的库可以将给定格式 最好是简单的文本格式 例如 Mark
  • ruby on Rails jquery 以 js 形式提交表单

    我认为有这段代码 br br 当我更改下拉列表中的值时 表单将提交为 Proces
  • C# 中有保证 FIFO 顺序的同步类吗?

    它是什么以及如何使用 我需要它 因为我有一个每秒插入数据库的计时器 并且我在计时器处理程序和主线程之间有一个共享资源 我想保证 如果计时器处理程序在插入过程中花费超过一秒 则应按顺序执行等待的线程 这是我的计时器处理程序的示例代码 priv
  • 使用 ankhsvn 更改颠覆服务器名称

    IT 已将 svn 服务器的位置移至另一个盒子 因此 所有源代码控制路径都需要更改 我找不到在 VS2010 AnkSvn 中执行此操作的位置 更改源代码控制对话框 文件 Subversion 更改源代码控制 列出了 SCC 绑定 URL
  • QT:QTableView 中行的内部拖放,这会更改 QTableModel 中行的顺序

    I want to perform a sort of rows in QTableView so that the underlying TableModel would have its data sorted too 如果我没有记错的
  • C Linux应用程序的带宽限制

    我可以尝试采取哪些方法来遏制send sendto 循环内的函数 我正在为我的网络创建一个端口扫描器 我尝试了两种方法 但它们似乎只在本地工作 当我在我的家用计算机上测试它们时它们工作 但当我尝试在另一台机器上测试它们时 它不想创建适当的节
  • 盗链保护

    我编写了这个简单的代码来防止从我的 php 下载文件中盗链我的文件 if strpos SERVER HTTP REFERER www domain com 0 redirect index php header Location redi
  • jQuery - 将鼠标悬停在父 div 上时播放视频

    我正在创建一个视频库 其中包含悬停时播放短视频的缩略图 我已经能够让它们在将鼠标悬停在视频本身上时播放 但我需要它们在将鼠标悬停在视频的父 div 上时播放 到目前为止 这是我的尝试 HTML div class thumbail div
  • Android:如何与另一个 Activity 进行垂直 Activity 过渡

    我的第一个 Activity 中有一个 ListView 当我点击里面的一个项目时 我启动另一个活动 好吧 但现在我想修改这两个活动之间的转换 Activity1 gt Activity2 Activity2 从下到上的垂直过渡 渐进 当我
  • 为什么调用react setState方法不会立即改变状态?

    我正在阅读Forms的部分reactjs文档并尝试使用此代码来演示onChange usage JSBIN var React require react var ControlledForm React createClass getIn
  • 脚本未在 templateurl 中运行

    这是我的角度 js 文件 测试 js 文件 var app angular module angleapp controller MainController scope function scope scope test hi all d
  • 如何使用占位符获取用户输入?

    我目前正在用 Rust 编写一个简单的记事本应用程序 在主循环期间 要求用户插入 删除或更改行 当用户更改行时 我想预先填充该行的内容 以便用户可以更轻松地编辑行 像这样 下划线代表终端光标 用户选择该行后尚未进行任何输入 Content
  • 将插件保留在项目中和配置文件中之间的区别

    谁能解释一下下面的功能吗 A 实际上下面的代码对我有用 但我不明白为什么我们需要使用下面的Maven 战争插件
  • Android 中是否可以共享音频输入(麦克风)流?

    我正在编写一个作为后台服务运行的应用程序 记录和分析从麦克风录制的音频 当我的应用程序运行时 我无法使用其他使用麦克风的应用程序 例如电话 录音等 我的问题是 有没有办法在 Android 中共享麦克风流 也许通过使用 NDK 如果没有 是
  • 穷人的 C#“词法分析器”

    我正在尝试用 C 编写一个非常简单的解析器 我需要一个词法分析器 它可以让我将正则表达式与标记关联起来 这样它就可以读取正则表达式并返回符号 看来我应该能够使用正则表达式来完成实际的繁重工作 但我看不到一种简单的方法来做到这一点 一方面 正