提出词法分析器标记


14

我正在为我创建的标记语言编写一个解析器(用python编写,但这与这个问题并不相关-实际上,如果这似乎是一个坏主意,我希望有一个更好的建议) 。

我在这里了解有关解析器的信息:http : //www.ferg.org/parsing/index.html,并且我正在编写词法分析器,如果我理解正确的话,应将内容拆分为标记。我无法理解的是我应该使用哪种令牌类型或如何创建它们。例如,我链接到的示例中的令牌类型为:

  • 识别码
  • 白空间
  • 评论
  • 紧急行动
  • 许多符号,例如{和(算作自己的标记类型

我遇到的问题是,更通用的令牌类型对我来说似乎有些武断。例如,为什么要STRING自己使用单独的令牌类型,而不要使用IDENTIFIER。字符串可以表示为STRING_START +(IDENTIFIER | WHITESPACE)+ STRING_START。

这也可能与我的语言困难有关。例如,变量声明编写为,{var-name var value}并使用进行部署{var-name}。看起来'{'并且'}'应该是它们自己的令牌,但是VAR_NAME和VAR_VALUE是否符合令牌类型,或者这两者都属于IDENTIFIER吗?而且,VAR_VALUE实际上可以包含空格。after后面的空格var-name用于表示声明中值的开始。其他任何空格都是该值的一部分。这个空格会成为自己的令牌吗?在这种情况下,空白仅具有该含义。而且,{可能不是变量声明的开始..它取决于上下文(再次有该词!)。 {:开始一个名称声明,然后{ 甚至可以用作某些价值的一部分。

我的语言与Python类似,因为代码块是使用缩进创建的。我正在阅读有关Python如何使用词法分析器创建INDENT和DEDENT令牌的信息(它们或多或少的作用{以及}在许多其他语言中的作用)。Python声称是无上下文的,这对我来说意味着,至少词法分析器在创建令牌时不必关心它在流中的位置。Python的词法分析器如何在不知道先前字符的情况下就知道要构建特定长度的INDENT令牌(例如,上一行是换行符,因此开始为INDENT创建空间)?我问,因为我也需要知道这一点。

我的最后一个问题是最愚蠢的问题:为什么一个词法分析器甚至是必要的?在我看来,解析器可以逐个字符地找出它在哪里以及它期望什么。词法分析器是否增加了简单性的好处?


2
努力尝试编写无扫描程序的解析器。如果它完全有效(我想对于某些解析算法来说结果可能太含糊了),那么您很可能看不到“也允许在此处空白”和“等等,在我解析一个标识符还是数字?”。我从经验上讲。

为什么要重塑自定义轮子?您是否已考虑使用已经内置的词法分析器附带的现有语言(例如LISP或FORTH),而不是设计需要定制词法分析器的语言?
约翰·斯特罗姆

2
@ JohnR.Strohm用于学术目的。无论如何,语言本身可能几乎没有用。
爆炸药

Answers:


11

您的问题(如您的最后一段所暗示)实际上不是关于词法分析器的,而是关于词法分析器与解析器之间接口的正确设计的。您可能会想到有很多关于词法分析器和解析器设计的书。我碰巧喜欢Dick Grune解析器书,但它可能不是一本很好的入门书。我碰巧强烈不喜欢Appel的基于C的书,因为代码无法有效地扩展到您自己的编译器中(因为假装C的决策中固有的内存管理问题就像ML)。我自己的介绍是PJ Brown的书,但这不是很好的一般介绍(尽管特别适合口译员)。但是回到您的问题。

答案是,在词法分析器中可以做尽可能多的事情,而无需使用前向或后向约束。

这意味着(当然,这取决于语言的详细信息)您应该将字符串识别为“字符,后跟一系列not-”,然后再识别另一个“字符。将其作为一个单元返回到解析器。有多个原因,但重要的是

  1. 这减少了解析器需要维护的状态量,从而限制了其内存消耗。
  2. 这使词法分析器的实现方式可以专注于识别基本的构建块,并释放解析器来描述如何使用各个语法元素来构建程序。

解析器通常可以立即采取行动,从词法分析器接收令牌。例如,一旦收到IDENTIFIER,解析器就可以执行符号表查找,以找出符号是否已知。如果您的解析器还将字符串常量解析为QUOTE(IDENTIFIER SPACES)* QUOTE,您将执行许多不相关的符号表查找,或者最终将符号表查找提升到语法分析器的语法元素树的更高位置现在您确定您没有在看字符串。

为了重述我想说的内容,但换句话说,词法分析器应关注事物的拼写,而解析器应关注事物的结构。

您可能会注意到,我对字符串的描述很像一个正则表达式。这不是巧合。词法分析器通常以使用正则表达式的小语言(在Jon Bentley的著作《Programming Pearls》中有意义)实现。识别文本时,我只是习惯于使用正则表达式。

关于空白,请在词法分析器中识别。如果您的语言是自由格式,请不要将WHITESPACE令牌返回解析器,因为它只需要丢弃它们,因此解析器的生产规则实际上会充满噪音-要识别的东西只是抛出他们走了。

至于在语法上很重要的情况下如何处理空格,这意味着什么,我不确定是否可以在不了解您的语言的情况下为您做出判断,该判断确实会很好地起作用。我的明智判断是避免出现空格有时很重要而有时并不重要的情况,并使用某种分隔符(例如引号)。但是,如果您无法以自己喜欢的方式设计语言,则可能无法使用此选项。

还有其他方法可以设计语言解析系统。当然,有一些编译器构造系统允许您指定组合的词法分析器和解析器系统(我认为ANTLR的Java版本可以做到这一点),但我从未使用过。

最后一个历史笔记。几十年前,对于词法分析器来说,在移交给解析器之前要尽可能多地做是很重要的,因为这两个程序不能同时放入内存中。在词法分析器中执行更多操作可留下更多内存,以使解析器更智能。我曾经使用Whitesmiths C编译器很多年了,如果我理解正确的话,它只能在64KB的RAM中运行(这是一个小型MS-DOS程序),因此即使它翻译了C的变体,非常接近ANSIC。


关于内存大小的良好历史记录是将作业首先分解为词法分析器和解析器的原因之一。
stevegt

3

我要回答你的最后一个问题,实际上这不是愚蠢的。解析器可以并且确实在逐个字符的基础上构建复杂的构造。如果我还记得,Harbison和Steele的语法(“ C-参考手册”)中的产品使用单个字符作为终端,并根据单个字符建立标识符,字符串,数字等作为非终端。

从形式语言的角度来看,基于正则表达式的词法分析器可以识别并归类为“字符串文字”,“标识符”,“数字”,“关键字”等的任何内容,甚至LL(1)解析器也可以识别。因此,使用解析器生成器识别所有内容在理论上没有问题。

从算法的角度来看,正则表达式识别器的运行速度比任何解析器都快。从认知的角度来看,对于程序员来说,在正则表达式词法分析器和语法分析器生成器编写的语法分析器之间分解工作可能更容易。

我要说的是,出于实际考虑,人们决定使用单独的词法分析器和解析器。


是的-C标准本身做同样的事情,就像我没记错的那样,Kernighan和Ritchie的两个版本都做过。
James Youngman

3

看来您是在尝试编写词法分析器/语法分析器,而没有真正理解语法。通常,当人们在编写词法分析器和解析器时,他们正在编写它们以符合某种语法。 词法分析器应在语法中返回标记,而解析器使用这些标记来匹配rule / non-terminals。如果您可以轻松地逐字节解析输入内容,那么词法分析器和解析器可能会显得过大。

Lexers使事情变得简单。

语法概述:语法是一组有关某些语法或输入的外观的规则。例如,这是一个玩具语法(simple_command是开始符号):

simple_command:
 WORD DIGIT AND_SYMBOL
simple_command:
     addition_expression

addition_expression:
    NUM '+' NUM

该语法的意思是-
一个simple_command由
A)WORD,DIGIT和AND_SYMBOL(这些是我定义的“令牌”)组成;
B)一个“ addition_expression”(这是一条规则或“非终结符”)

一个additional_expression由以下组成:
NUM后跟一个“ +”,后跟一个NUM(NUM是我定义的“令牌”,“ +”是文字加号)。

因此,由于simple_command是“开始符号”(我开始的位置),因此当我收到令牌时,请检查其是否适合simple_command。如果输入中的第一个标记是WORD,下一个标记是DIGIT,下一个标记是AND_SYMBOL,那么我已经匹配了一些simple_command并可以采取一些措施。否则,我将尝试将其与simple_command的另一个规则(即additional_expression)匹配。因此,如果第一个标记是NUM,然后是“ +”,然后是NUM,则我匹配了simple_command并采取了一些措施。如果这些都不是,那么我有语法错误。

这是语法的非常非常基础的介绍。要获得更全面的了解,请查看此Wiki文章,并在网上搜索免费的无上下文语法教程。

使用词法分析器/解析器安排,以下是解析器外观的示例:

bool simple_command(){
   if (peek_next_token() == WORD){
       get_next_token();
       if (get_next_token() == DIGIT){
           if (get_next_token() == AND_SYMBOL){
               return true;
           } 
       }
   }
   else if (addition_expression()){
       return true;
   }

   return false;
}

bool addition_expression(){
    if (get_next_token() == NUM){
        if (get_next_token() == '+'){
             if (get_next_token() == NUM){
                  return true;
             }
        }
    }
    return false;
}

好的,这样的代码有点丑陋,我绝不建议三重嵌套if语句。但是重点是,想象一下尝试逐个字符地执行此操作,而不是使用漂亮的模块化“ get_next_token”和“ peek_next_token”函数。认真地试一试。您不会喜欢结果。现在请记住,上面的语法比几乎所有有用的语法都低30倍。您看到使用词法分析器的好处了吗?

老实说,词法分析器和解析器不是世界上最基本的主题。我建议您先阅读和理解语法,然后再阅读一些有关词法分析器/语法分析器的知识,然后再深入研究。


您对学习语法有什么建议吗?
爆炸药

我刚刚编辑了答案,包括一个非常基本的语法介绍和一些进一步学习的建议。语法是计算机科学中非常重要的主题,因此值得学习。
Casey Patton

1

我的最后一个问题是最愚蠢的问题:为什么一个词法分析器甚至是必要的?在我看来,解析器可以逐个字符地找出它在哪里以及它期望什么。

这不是愚蠢的,只是事实。

但是实用性在某种程度上取决于您的工具和目标。例如,如果您使用不带词法分析器的yacc,并且要允许在标识符中使用unicode字母,则必须编写一个大而丑陋的规则,以明确枚举所有有效字符。而在一个词法分析器中,您可能会问一个库例程,如果一个字符是字母类别的成员。

使用或不使用词法分析器是在您的语言和字符级别之间具有一定抽象级别的问题。请注意,如今的字符级别是字节级别之上的另一种抽象,即位级别之上的一种抽象。

因此,最后,您甚至可以在位级别上进行解析。


0
STRING_START + (IDENTIFIER | WHITESPACE) + STRING_START.

不,不能。那"("呢 根据您的说法,这不是有效的字符串。和逃脱?

通常,处理空格的最佳方法是忽略空格,而不是分隔标记。许多人都喜欢完全不同的空白,而执行空白规则充其量是有争议的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.