我甚至不知道从哪里开始编写逐字符词法分析器。我根据我给出的规则和细节为Markdown语言(特别是HTML)编写了BNF语法规则,因此不需要添加任何语法规则。我现在必须设计并实现一个逐字符的词法分析器,它将我的Markdown语言中的源文件的词位分成标记。这是我的BNF GRAMMAR:
端子:
#DOCUMENT BEGIN,
#DOCUMENT END
#HEAD BEGIN,
#HEAD END,
#TITLE BEGIN,
#TITLE END,
#PARAGRAPH BEGIN,
#PARAGRAPH END,
#BOLD BEGIN,
#BOLD END,
#ITALICS BEGIN,
#ITALICS END,
#LIST BEGIN,
#LIST END,
#ITEM BEGIN,
#ITEM END,
#LINK BEGIN,
#TEXT,
#ADDRESS,
#LINK END,
#DEFINE BEGIN,
#NAME,
#VALUE,
#DEFINE END,
#USE BEGIN,
#USE END
请注意,这些终端不区分大小写。
非端子:
<document> ::= #DOCUMENT BEGIN <macro-‐define> <head> <body> #DOCUMENT END
<head> ::= #HEAD BEGIN <title> #HEAD END | ε
<title> ::= #TITLE BEGIN <text> #TITLE END | ε
<body> ::= <inner-‐text> <body>
| <paragraph> <body>
| <bold> <body>
| <italics> <body>
| <list> <body>
| ε
<paragraph> ::= #PARAGRAPH BEGIN <macro-‐define> <inner-‐paragraph> #PARAGRAPH END
<inner-‐paragraph> ::= <inner-‐text> <inner-‐paragraph>
| <bold> <inner-‐paragraph>
| <italics> <inner-‐paragraph>
| <list> <inner-‐paragraph>
| ε
<inner-‐text> ::= <macro-‐use> <inner-‐text>
| <text> <inner-‐text>
| ε
<macro-‐define> ::= #DEFINE BEGIN #NAME <text> #VALUE <body> #DEFINE END <macro-‐define>
| ε
<macro-‐use> ::= #USE BEGIN <text> #USE END | ε
<bold> ::= #BOLD BEGIN <macro-‐define> <inner-‐text> #BOLD END
<italics> ::= #ITALICS BEGIN <macro-‐define> <inner-‐text> #ITALICS END
<link> ::= #LINK BEGIN #TEXT <text> #ADDRESS <text> #LINK END
<list> ::= #LIST BEGIN #ITEM BEGIN <macro-‐define> <inner-‐list> #ITEM END <list-‐items> #LIST END
<list-‐items> ::= #ITEM BEGIN <macro-‐define> <inner-‐list> #ITEM END <list-‐items> | ε
<inner-‐list> ::= | <bold> <inner-‐list>
| <italics> <inner-‐list>
| <list><inner-‐list>
| <inner-‐text> <inner-‐list>
| ε
<text> ::= Any plain text | ε
我们可以假设“&lt;”,“&gt;”,“&amp;”和“/”等HTML字符不会出现在源文件的任何文本中。我们还可以假设“#”仅出现在我们的一个Markdown注释之前(例如,#DOCUMENT)。我认为最好有单独的Java类来表示令牌对象,例如: DocumentBegin,DocumentEnd,ParagraphBegin,ParagraphEnd等。遇到的任何词汇错误(例如#DOC BEGIN)都应报告为使用尽可能多的错误信息输出到控制台。遇到第一个错误后,编译器应该退出。如果遇到错误,则不应创建输出文件。
我的问题是,我知道词法分析器应该做什么,但老实说,我不知道从哪里开始编码/实现。如果您需要更多解释问题的原因,请询问,我可以尽力解释。这是我们班上应有的一个大项目的一部分。我无法完成这部分并且失去了很多分,但是现在我只需要理解它,所以一旦我们对它进行测试,我就不会迷失。
答案 0 :(得分:1)
好的,这已经有点晚了,但我们走了。
词法分析器通常与语法(和BNF表示法)相关联,但两者实际上有点不同。
词法分析器将字符转换为Tokens,它们在某种程度上处理了语法的“原子”,而解析器将令牌转换为某种中间结构(通常是树)。只关注词法分析器部分,您可以将其视为输入的低通处理,我们将字母处理为单词。
由于您已经拥有BNF语法,因此您已经知道要使用的所有令牌(结束词),因此请将它们放入列表中。这个想法是如何快速决定哪个系列的字母将映射到列表中的每个项目。例如
#, D, E, F, I, N, E, <whitespace> => #DEFINE
#, D, O, C, U, M, E, N, T, <whitespace> => #DOCUMENT
B, E, G, I, N, <whitespace> => BEGIN
E, N, D, <whitespace> => END
解析时会出现一些问题:
首先,你要做很多比较。读入的第一个字符可能是'#',如果是,那么您仍然可以匹配超过20个项目。这意味着你必须继续你的匹配到下一个角色,如果它是'D'仍然意味着有两个可能的匹配'#DEFINE'和'#DOCUMENT'。
其次,如果你在处理'#BEGIN'之后有'#BEGIN'和'#BEGINNING'这样的词,那么在你抓住下一个字符之前你无法决定两者之间的关系。抓住系统中的下一个字符,认为该字符的“消耗”使下一个令牌的处理变得复杂。可能需要窥视或预测,但这些会增加逻辑中的复杂性,以决定生成哪些令牌。
第三,你有一张外卡'文字'代币。该令牌几乎可以匹配任何内容,因此您需要针对所有其他令牌进行检查,以确保令牌生成逻辑始终知道它应生成哪个令牌。
理想情况下,令牌生成器(Lexer)不依赖于任何解析来“知道”下一个令牌;然而,有些语言非常复杂,解析器会向Lexer提供“提示”。避免使用这些类型的系统可以实现更清晰的编译器实现;不幸的是,在一些已有的语言中,并不总是可以用这种方式构建。
所以,知道你知道该怎么做(在某种意义上你可能已经有过)你是怎么做的?
嗯,您需要某种索引来跟踪已消耗的字符(已完全转换为令牌),因此您不会意外地让字符对令牌流产生双重影响。如果你要向前看,你需要一个“向前看”的第二个指针,你可能想要限制未来的数量(使逻辑变得更难)。
然后您需要未知数量的数据结构(称为令牌)。虽然并非总是需要这样做,但我建议跟踪令牌中的起始行号,起始字符索引,结束行号和结束字符索引。它使调试变得更容易。此外,最好“捕获”令牌中的子字符串。你可以这样称呼你,但有些人称之为令牌的“形象”。
当然,如果您的解析器可以区分不同类型的令牌,那么您应该通过某种方式将该令牌的类型存储在令牌中(或与令牌一起)。偶尔一个人有一个标记“价值”的概念,也可能存储它。
经过一番努力,你应该能够将一串字符推入Lexer,然后出现一系列令牌。祝你好运。
答案 1 :(得分:0)
我在Java中发现的最好的(也就是我所知道的)词法分析器叫做JFlex。我们在大学使用它来标记语言,我已经商业化地使用它来为应用程序中的特定领域语言创建语法高亮。
JFlex词法分析器
Cup Parser
http://www2.cs.tum.edu/projects/cup/
关于LALR(1)Parsers
的一点点http://en.wikipedia.org/wiki/LALR_parser
如果您需要示例(例如示例代码)给我留言,我会给您发送一些说明。虽然我确信某些大学网站(即普林斯顿大学)可能有什么东西,但快速谷歌并没有显示出任何有用的东西。
干杯,
约翰