这是一个很大的话题,但是与其花大笔的“读书,孩子”来代替你,不如说我会很高兴地为您提供指示,以帮助您将头缠在脑子里。
大多数编译器和/或解释器的工作方式如下:
令牌化:扫描代码文本,并将其打入标记列表。
这一步可能很棘手,因为您不能只将字符串分割成空格,而是必须识别出这if (bar) foo += "a string";
是8个标记的列表:WORD,OPEN_PAREN,WORD,CLOSE_PAREN,WORD,ASIGNMENT_ADD,STRING_LITERAL,TERMINATOR。如您所见,简单地将源代码分割成空格是行不通的,您必须按顺序读取每个字符,因此,如果遇到字母数字字符,您将继续读取字符,直到遇到非字母数字字符,并且该字符串刚刚读到的是一个WORD,稍后再进行分类。您可以自己决定令牌生成器的粒度:它是"a string"
作为一个称为STRING_LITERAL的令牌吞入以待稍后解析,还是会看到"a string"
像OPEN_QUOTE,UNPARSED_TEXT,CLOSE_QUOTE或其他内容,这只是编码时必须自行决定的众多选择之一。
Lex:所以现在您有了令牌列表。您可能用诸如WORD之类的歧义标记了一些标记,因为在第一遍过程中,您无需花费太多精力来尝试找出每个字符串的上下文。因此,现在再次阅读源标记列表,并根据您的语言中的关键字,使用更具体的标记类型对每个歧义标记进行重新分类。因此,您有一个诸如“ if”之类的单词,并且“ if”在特殊符号列表中称为“符号IF”,因此您将该令牌的符号类型从WORD更改为IF,而不在特殊关键字列表中的任何WORD (例如WORD foo)是IDENTIFIER。
解析:因此,现在您打开if (bar) foo += "a string";
了一个清单如下的词汇标记:IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR。该步骤是将标记序列识别为语句。这是解析。您可以使用如下语法:
STATEMENT:= ASIGN_EXPRESSION | IF_STATEMENT
IF_STATEMENT:= IF,PAREN_EXPRESSION,STATEMENT
ASIGN_EXPRESSION:= IDENTIFIER,ASIGN_OP,VALUE
PAREN_EXPRESSSION:= OPEN_PAREN,VALUE,CLOSE_PAREN
值:=标识符| STRING_LITERAL | PAREN_EXPRESSION
ASIGN_OP:=等于| ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT
使用“ |”的作品 术语之间的意思是“匹配任何这些术语”,如果术语之间有逗号,则表示“匹配此术语序列”
你怎么使用这个?从第一个令牌开始,尝试将令牌序列与这些产品匹配。因此,首先您尝试将令牌列表与STATEMENT匹配,因此您阅读了STATEMENT的规则,并说“ STATEMENT是ASIGN_EXPRESSION或IF_STATEMENT”,因此您首先尝试匹配ASIGN_EXPRESSION,因此您查找了ASIGN_EXPRESSION的语法规则并显示“ ASIGN_EXPRESSION是IDENTIFIER,后跟一个ASIGN_OP,后跟一个VALUE,因此您查找IDENTIFIER的语法规则,就会发现IDENTIFIER没有语法ruke,这意味着IDENTIFIER是一个“终端”,这意味着不需要进行解析以匹配它,因此您可以尝试直接将其与令牌匹配。但是您的第一个源令牌是IF,并且IF与IDENTIFIER不同,因此匹配失败。现在怎么办?您回到STATEMENT规则并尝试匹配下一项:IF_STATEMENT。您查找IF_STATEMENT,以IF开头,查找IF,如果IF是一个终端,将终端与您的第一个令牌进行比较,如果令牌匹配,真棒,继续下去,下一个是PAREN_EXPRESSION,查找PAREN_EXPRESSION,它不是终端,它是第一个术语, PAREN_EXPRESSION以OPEN_PAREN开始,查找OPEN_PAREN,这是一个终端,将OPEN_PAREN与您的下一个令牌匹配,它与....依此类推。
实现此步骤的最简单方法是,您拥有一个名为parse()的函数,该函数将要与之匹配的源代码令牌以及与之匹配的语法术语传递给该函数。如果语法术语不是终结符,则递归:再次调用parse(),将相同的源令牌和该语法规则的第一项传递给parse()。这就是为什么它被称为“递归下降解析器”的原因parse()函数返回(或修改)您在读取源令牌中的当前位置,它实际上将返回匹配序列中的最后一个令牌,然后您继续对从那里解析。
每次parse()与ASIGN_EXPRESSION之类的生产匹配时,您都会创建一个代表该代码段的结构。此结构包含对原始源令牌的引用。您开始构建这些结构的列表。我们将整个结构称为抽象语法树(AST)
编译和/或执行:对于语法中的某些生成,您已经创建了处理函数,如果给定AST结构,该处理函数将编译或执行该AST块。
因此,让我们看看您的AST的类型为ASIGN_ADD。因此,作为解释器,您具有ASIGN_ADD_execute()函数。该函数作为与的解析树相对应的AST的一部分传递foo += "a string"
,因此此函数查看该结构,并且知道该结构中的第一项必须是IDENTIFIER,第二项是VALUE,因此ASIGN_ADD_execute()将VALUE术语传递给VALUE_eval()函数,该函数返回表示内存中已评估值的对象,然后ASIGN_ADD_execute()在变量表中查找“ foo”,并存储对eval_value()返回值的引用功能。
那是翻译。相反,编译器将具有处理函数,而不是将AST转换为字节代码或机器代码。
使用Flex和Bison之类的工具可以使第1步到第3步,甚至第4步更容易。(又名Lex和Yacc),但从头开始编写解释器可能是任何程序员都可以完成的最有力量的工作。在提出这一挑战之后,所有其他编程挑战似乎都是微不足道的。
我的建议是从小开始:一门小语言,一门小语法,然后尝试解析并执行一些简单的语句,然后从那里开始发展。
阅读这些,祝您好运!
http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c
http://en.wikipedia.org/wiki/Recursive_descent_parser
lex
,yacc
和bison
。