词法分析器返回其解析器的令牌的数据类型应该是什么?


21

如标题中所述,词法分析器应返回/提供解析器哪种数据类型?在阅读Wikipedia 的词法分析文章时,它指出:

在计算机科学中,词法分析是将一系列字符(例如在计算机程序或网页中)转换为一系列标记(具有已标识“含义”的字符串)的过程。

但是,与上述说法完全矛盾的是,当我在另一个站点上问过另一个问题(“代码审查”,如果您很好奇)得到回答时,回答者表示:

词法分析器通常读取字符串,并将其转换为词位流。词素只需要是一串数字

他给了这个视觉效果:

nl_output => 256
output    => 257
<string>  => 258

稍后,在他提到的文章中Flex,他已经存在一个词法分析器,并说用它编写“规则”比手工编写词法分析器更简单。他继续给我这个例子:

Space              [ \r\n\t]
QuotedString       "[^"]*"
%%
nl_output          {return 256;}
output             {return 257;}
{QuotedString}     {return 258;}
{Space}            {/* Ignore */}
.                  {error("Unmatched character");}
%%

为了进一步了解并获得更多信息,我阅读了有关Flex的Wikipedia文章。Flex文章显示,您可以通过以下方式使用令牌定义一组语法规则:

digit         [0-9]
letter        [a-zA-Z]

%%
"+"                  { return PLUS;       }
"-"                  { return MINUS;      }
"*"                  { return TIMES;      }
"/"                  { return SLASH;      }
"("                  { return LPAREN;     }
")"                  { return RPAREN;     }
";"                  { return SEMICOLON;  }
","                  { return COMMA;      }
"."                  { return PERIOD;     }
":="                 { return BECOMES;    }
"="                  { return EQL;        }
"<>"                 { return NEQ;        }
"<"                  { return LSS;        }
">"                  { return GTR;        }
"<="                 { return LEQ;        }
">="                 { return GEQ;        }
"begin"              { return BEGINSYM;   }
"call"               { return CALLSYM;    }
"const"              { return CONSTSYM;   }
"do"                 { return DOSYM;      }
"end"                { return ENDSYM;     }
"if"                 { return IFSYM;      }
"odd"                { return ODDSYM;     }
"procedure"          { return PROCSYM;    }
"then"               { return THENSYM;    }
"var"                { return VARSYM;     }
"while"              { return WHILESYM;   }

在我看来,Flex词法分析器正在返回关键字\令牌的字符串。但是它可能返回等于某些数字的常量。

如果词法分析器要返回数字,它将如何读取字符串文字?对于单个关键字,返回数字是可以的,但是如何处理字符串呢?词法分析器不必将字符串转换为二进制数字,然后解析器会将数字转换回字符串。对于词法分析器来说,返回字符串似乎更合乎逻辑(并且更容易),然后让解析器将任何数字字符串文字转换为实际数字。

还是词法分析器可能同时返回两者?我一直在尝试用c ++写一个简单的词法分析器,它使函数只有一个返回类型。因此导致我问我的问题。

将我的问题压缩为一个段落:在编写词法分析器时,假设它只能返回一种数据类型(字符串或数字),那将是更合理的选择?


词法分析器返回您告诉它返回的内容。如果您的设计要求输入数字,则它将返回数字。显然,表示字符串文字将需要的更多。另请参阅解析数字和字符串是否是Lexer的工作? 请注意,字符串文字通常不被视为“语言元素”。
罗伯特·哈维

@RobertHarvey那么您会将字符串文字转换为二进制数字吗?
克里斯汀·迪恩

据我了解,词法分析器的目的是获取语言元素(例如关键字,运算符等)并将其转换为标记。这样,带引号的字符串对词法分析器来说并不重要,因为它们不是语言元素。尽管我自己从未写过词法分析器,但我可以想象带引号的字符串只是通过不变的方式传递(包括引号)。
罗伯特·哈维

因此,您的意思是词法分析器不读取也不关心字符串文字。因此,解析器必须寻找这些字符串文字吗?这非常令人困惑。
Christian Dean

您可能需要花几分钟阅读以下内容:en.wikipedia.org/wiki/Lexical_analysis
罗伯特·哈维

Answers:


10

通常,如果您通过词法分析和语法处理语言,则可以对词法标记进行定义,例如:

NUMBER ::= [0-9]+
ID     ::= [a-Z]+, except for keywords
IF     ::= 'if'
LPAREN ::= '('
RPAREN ::= ')'
COMMA  ::= ','
LBRACE ::= '{'
RBRACE ::= '}'
SEMICOLON ::= ';'
...

并且您有语法分析器:

STATEMENT ::= IF LPAREN EXPR RPAREN STATEMENT
            | LBRACE STATEMENT BRACE
            | EXPR SEMICOLON
EXPR      ::= ID
            | NUMBER
            | ID LPAREN EXPRS RPAREN
...

您的词法分析器采用输入流并生成令牌流。令牌流由解析器消耗,以生成解析树。在某些情况下,仅知道令牌的类型就足够了(例如,LPAREN,RBRACE,FOR),但是在某些情况下,您将需要与令牌关联的实际。例如,当您遇到ID令牌时,当您试图弄清楚要引用的标识符时,将需要组成ID的实际字符。

因此,您通常会或多或少像这样:

enum TokenType {
  NUMBER, ID, IF, LPAREN, RPAREN, ...;
}

class Token {
  TokenType type;
  String value;
}

因此,当词法分析器返回令牌时,您就知道令牌的类型(解析所需的字符)以及生成令牌的字符序列(稍后需要使用它们来解释字符串和数字文字,标识符,等等。)。由于您要返回一个非常简单的聚合类型,因此可能感觉要返回两个值,但实际上确实需要两个部分。毕竟,您希望对以下程序进行不同的处理:

if (2 > 0) {
  print("2 > 0");
}
if (0 > 2) {
  print("0 > 2");
}

它们产生相同的令牌类型序列:IF,LPAREN,NUMBER,GREATER_THAN,NUMBER,RPAREN,LBRACE,ID,LPAREN,STRING,RPAREN,SEMICOLON,RBRACE。这意味着它们也解析相同的内容。但是,当您实际使用解析树进行操作时,您会注意到第一个数字的值是“ 2”(或“ 0”),而第二个数字的值是“ 0”(或“ 2” '),并且字符串的值为'2> 0'(或'0> 2')。


我得到一个什么样的说法最多,但如何String value要得到填补?它是用字符串还是数字填充?而且,我将如何定义String类型?
Christian Dean

1
@ Mr.Python在最简单的情况下,只是与词汇生成匹配的字符串。因此,如果看到foo(23,“ bar”),则会得到令牌[ID,“ foo”],[LPAREN,“(”],[NUMBER,“ 23”],[COMMA,“,” ],[STRING,“” 23“”],[RPAREN,“)”]。保留信息可能很重要。或者,您可以采用另一种方法,让值具有可以是字符串或数字等的联合类型,然后根据您所拥有的令牌类型来选择正确的值类型(例如,当令牌类型为NUMBER时) ,请使用value.num,当它为STRING时,请使用value.str)。
约书亚·泰勒

@MrPython “而且,我将如何定义String类型?” 我是用Java风格的心态写的。如果您使用的是C ++,则可以使用C ++的字符串类型;如果您使用的是C,则可以使用char *。关键是与令牌关联的,您具有相应的值或可以解释以生成该值的文本。
约书亚·泰勒

1
@ ollydbg23是一个选择,而不是一个不合理的选择,但是它使系统的内部一致性降低。例如,如果要解析的最后一个城镇的字符串值,现在必须显式检查是否为空值,然后使用反向令牌到字符串查找来查找字符串。另外,词法分析器和解析器之间的耦合更紧密。如果LPAREN可以匹配不同或多个字符串,则还有更多代码需要更新。
约书亚·泰勒

2
@ ollydbg23一种情况是简单的伪缩小符。这很容易做到parse(inputStream).forEach(token -> print(token.string); print(' '))(即,仅打印标记的字符串值,并用空格分隔)。很快。而且即使LPAREN只能来自“(”),它也可能是内存中的常量字符串,因此在令牌中包含对它的引用可能不会比包含空引用更昂贵。通常,我宁愿写不会使我成为特殊情况的代码
Joshua Taylor

6

如标题中所述,词法分析器应返回/提供解析器哪种数据类型?

显然是“令牌”。词法分析器生成令牌流,因此它应返回令牌流。

他提到了Flex,一个已经存在的词法分析器,并说用它编写“规则”比手工编写词法分析器要简单。

机器生成的词法器具有可以快速生成它们的优点,如果您认为词法语法将发生很大变化,则这特别方便。它们的缺点是您在实现选择时通常没有很大的灵活性。

也就是说,谁在乎它是否“简单”?编写词法分析器通常不是难事!

在编写词法分析器时,假设它只能返回一种数据类型(字符串或数字),那是更合理的选择?

都不行 词法分析器通常具有返回令牌的“下一个”操作,因此它应返回令牌。令牌不是字符串或数字。这是一个象征。

我写的最后一个词法分析器是“全保真”词法分析器,这意味着它返回了一个令牌,该令牌跟踪程序中所有空白和注释以及我们称之为“琐事”的位置。在我的词法分析器中,令牌定义为:

  • 一系列领先的琐事
  • 代币种类
  • 令牌宽度(以字符为单位)
  • 一系列尾随琐事

Trivia的定义为:

  • 一种琐事-空格,换行符,注释等
  • 字符的琐事宽度

所以,如果我们有类似

    foo + /* comment */
/* another comment */ bar;

这将LEX四个令牌与令牌种IdentifierPlusIdentifierSemicolon,和宽度3,1,3,1的第一个标识符具有领先琐事组成Whitespace为4的宽度和后琐事Whitespace与1宽度的Plus没有前导琐事和尾随琐事,由一个空格,一个注释和一个换行符组成。最终标识符具有注释和空格的前导琐事,依此类推。

通过这种方案,文件中的每个字符都在词法分析器的输出中得到考虑,这对于语法着色之类的东西来说是一个方便的属性。

当然,如果您不需要琐事,则只需简单地做两件事:种类和宽度。

您可能会注意到,令牌和琐事仅包含其宽度,而不包含其在源代码中的绝对位置。那是故意的 这样的方案具有以下优点:

  • 紧凑的存储器和有线格式
  • 它可以在编辑时重新词法化;如果词法分析器在IDE内运行,这将很有用。也就是说,如果您检测到令牌中的编辑,则只需在编辑之前将词法分析器备份到几个令牌,然后再次开始词法化,直到与上一个令牌流同步。当您键入一个字符时,该字符之后每个标记的位置都会更改,但是通常宽度只有一个或两个标记会更改,因此您可以重新使用所有状态。
  • 通过遍历令牌流并跟踪当前偏移量,可以轻松得出每个令牌的确切字符偏移量。一旦有了精确的字符偏移量,就可以在必要时轻松提取文本。

如果您不关心这些情况中的任何一种,那么令牌可以表示为一种和偏移量,而不是一种类型和宽度。

但是这里的关键是:编程是制作有用的抽象的艺术。您正在处理令牌,因此请对令牌进行有用的抽象,然后您可以自己选择实现基础。


3

通常,您返回一个小的结构,该结构具有一个数字,该数字表示令牌(或易于使用的枚举值)和一个可选值(字符串,或者可能是通用/模板值)。另一种方法是为需要携带额外数据的元素返回派生类型。两者都令人反感,但对于实际问题却有足够好的解决方案。


轻度令人反感是什么意思?它们是获取字符串值的低效方法吗?
基督教教务长

@ Mr.Python-在使用代码之前,它们会导致很多检查,这虽然效率低下,但还会使代码更加复杂/脆弱。
Telastyn

在C ++中设计词法分析器时,我有一个类似的问题,我可以返回a Token *或简单地返回a Token,或a TokenPtrToken类的共享指针。但我也看到一些词法分析器仅返回TokenType,并将字符串或数字值存储在其他全局或静态变量中。另一个问题是我们如何存储位置信息,我是否需要一个具有TokenType,String和Location字段的Token结构?谢谢。
ollydbg23

@ ollydbg23-这些东西都可以用。我会用一个结构。对于非学习语言,您将始终使用解析器生成器。
Telastyn

@Telastyn感谢您的答复。您的意思是Token结构可能类似于struct Token {TokenType id; std::string lexeme; int line; int column;},对吧?对于Lexer的公共函数,例如PeekToken(),该函数可以返回Token *TokenPtr。我认为有一段时间,如果函数仅返回TokenType,解析器将如何尝试获取有关Token的其他信息?因此,从此类函数返回的数据类型最好是指针。关于我的想法有何评论?谢谢
ollydbg23
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.