【问题标题】:Writing an extremely simple parser编写一个极其简单的解析器
【发布时间】:2012-10-29 22:44:16
【问题描述】:

我正在编写一个非常基本的 Web 服务器,它必须支持极其有限的特殊服务器端脚本语言。基本上我需要支持的是“回显”,只有 2 个操作数的加法/减法/乘法(无除法),一个简单的“date()”函数,输出日期和使用“&”运算符连接字符串。

一个例子可以是:

echo "Here is the date: " & date();
echo "9 x 15 = : & 9*15;

我已经完成并创建了生成令牌所需的代码,但我不确定我是否使用了正确的令牌。

我为以下内容创建了令牌:

ECHO - The echo command
WHITESPACE - Any whitespace
STRING - A string inside quotations
DATE - The date() function
CONCAT - the & operator for concatenation
MATH - Any instance of binary operation (5+4, 9*2, 8-2, etc)
TERM - The terminal character (;)

我特别不确定的数学题。通常我看到人们专门为整数创建一个标记,然后也为每个运算符创建一个标记,但由于我只想允许二进制操作,我认为将它分组为一个标记是有意义的。如果我要分开做所有事情,我将不得不做一些额外的工作,以确保我永远不会接受“5+4+1”。

所以问题 1 是我在使用哪些令牌的正确轨道上?

我的下一个问题是接下来如何处理这些标记以确保语法正确?我想到的方法基本上是说,“好吧,我知道我有这个令牌,这是一个基于当前令牌允许下一个令牌的列表。列表中的下一个令牌吗?”

基于此,我列出了我的所有标记以及哪些标记可以直接出现在它们之后(为简单起见,不包括空格)。

ECHO        ->      STRING|MATH|DATE
STRING      ->      TERM|CONCAT
MATH        ->      TERM|CONCAT
DATE        ->      TERM|CONCAT
CONCAT      ->      STRING|MATH|DATE

问题是我完全不确定如何最好地实现这一点。真的,我还需要跟踪空格以确保标记之间有空格。但这意味着我必须一次向前看两个令牌,这变得更加令人生畏。我也不确定如何管理“有效的下一个令牌”而不只是 if 块的一些令人作呕的部分。我应该在尝试实际执行脚本之前检查有效的语法,还是应该一次完成所有操作并在遇到意外令牌时抛出错误?在这个简单的示例中,从左到右解析一切都会正常工作,没有真正的优先规则(除了 MATH 的东西,但这就是为什么我将它组合成一个标记的部分原因,即使感觉不对。)即使这样,我也不会不介意设计一个更具可扩展性和优雅的解决方案。

在我关于编写解析器的研究中,我看到很多关于创建“accept()”和“expect()”函数的参考资料,但我找不到任何关于它们应该做什么或它们是如何做的明确描述应该可以工作。

我想我只是不确定如何实现这一点,然后在一天结束时如何实际得出结果字符串。

我是否朝着正确的方向前进,是否有人知道可以帮助我了解如何最好地实现这样简单的事情的资源?我需要手动完成,不能使用 ANTLR 之类的工具。

提前感谢您的帮助。

【问题讨论】:

  • 你很幸运,我的朋友,有人已经完成了困难的部分。 irony.codeplex.com
  • 你也可以使用Javascript stackoverflow.com/questions/12118077/…
  • @asawyer,我想你错过了 “我需要手工完成,不能使用像 ANTLR 这样的工具”,所以很可能也不允许讽刺...
  • @BartKiers 也许,但由于它生成一个普通的 c# 程序集,我认为它可能是可行的。
  • 这是作业吗?如果是也可以,但你应该使用homework 标签。

标签: c# parsing compiler-construction scripting


【解决方案1】:

expect 是您的解析器为获取下一个令牌所做的操作,如果该令牌不是正确的后续令牌,则会失败。首先,您的解析器 expects ECHOWHITESPACE。这些是唯一有效的起始条款。看到“ECHO”后,您的解析器 expectsWHITESPACE|STRING|MATH|DATE 之一;其他任何事情都是错误。以此类推。

accept 是您的解析器看到一个完整的“语句” - ECHO,然后是有效的标记序列,然后是 TERM。您的解析器现在有足够的信息来处理您的 ECHO 命令。

哦,手写解析器(尤其是简单的解析器)通常是令人厌恶的 if 块集合(或类似 switch 语句的道德等价物):) 进一步提高优雅性将是某种状态机,再往上是语法生成器,如 yacc 或 GOLD Parser Generator(反过来又会为你生成丑陋的ifswitch 和状态机)。

编辑以提供更多详细信息。

为了帮助理清职责,创建一个“词法分析器”,其工作是读取输入并生成标记。这涉及确定令牌的外观。一个简单的标记是“回声”这个词。一个不太容易的记号是数学运算;令牌将由一个或多个数字、一个运算符和一个或多个数字组成,中间没有空格。词法分析器会处理跳过空格,以及理解带引号的字符串和构成 date() 函数的字符。词法分析器将返回两件事 - 读取的令牌类型和令牌的值(例如,“MATH”和“9*15”)。

有了词法分析器来读取您的输入,解析器会使用标记并确保它们的顺序正确。首先,您必须看到 ECHO 令牌。如果不是,则失败并显示错误消息。之后,您必须查看 STRINGDATEMATH。如果不是,则失败并显示错误消息。之后,您循环,观察 TERMCONCAT 后跟另一个 STRINGDATE数学。如果您看到 TERM,请中断循环。如果您既没有看到 TERM 也没有看到 CONCAT,则失败并显示错误消息。

您可以在解析时处理 ECHO 命令,因为它是一个简单的语法。每次找到 STRINGDATEMATH 时,评估它并将其连接到您已有的内容。找到TERM后,退出函数并返回构建好的字符串。

问题?注释?煎蛋卷? :)

【讨论】:

  • 谢谢大卫,这绝对是有帮助的。我仍在苦苦挣扎的是这个遍历令牌的循环是如何工作的。例如,“期望”的实际实现会是什么样子?在 WHITESPACE 之后,我所期待的实际上取决于 WHITESPACE 之前的令牌是什么,这就是令人困惑的地方。 “期望”是如何实际使用的,它是一个将可能的标记列表作为参数并返回真/假的函数吗?它是否还会将我的“当前”令牌更改为刚刚读取的“预期”,然后我再次调用预期?只是对实现感到困惑:/
【解决方案2】:

您需要做的第一件事是丢弃所有空格(字符串中的空格除外)。这样,当您将令牌添加到令牌列表时,您可以确定该列表仅包含有效令牌。例如,考虑以下语句:

echo "Here is the date: " & date();

我将开始标记化并首先根据空格分隔 echo(是的,此处需要空格来分隔它,但在那之后就没有用了)。然后分词器遇到 双引号 并继续读取所有内容,直到找到 右双引号。同样,我为 &date() 创建单独的标记。

我的令牌列表现在包含以下令牌:

echo
“这是日期:”
&
日期
()

现在,在解析阶段,我们读取了这些标记。解析器循环遍历令牌列表中的每个令牌。它读取 echo 并检查它是否有效(基于您对该语言的规则/功能)。它前进到下一个标记并查看它是 datestring 还是 ma​​th。同样,它检查其余的令牌。如果在任何时候,令牌不应该在那里,你可以抛出一个错误,指示语法错误或其他东西。

对于数学语句标记化,仅将括号中包含的表达式与其余操作数和运算符分开组合。例如:9/3 + (7-3+1) 将具有标记 9、/、3、+ 和 (7-3+1)。由于每个令牌都有自己的优先级(您在令牌结构中定义),您可以从最高优先级令牌开始评估到最低令牌优先级。这样你就可以有优先的表达式。如果您仍然有困惑,请告诉我。我给你写一些示例代码。

【讨论】:

  • 非常感谢,这绝对有帮助。今天下午我打算试一试,如果我仍然遇到问题,我会带你去提供示例代码。再次感谢!
猜你喜欢
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-08
  • 2010-09-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多