【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)
让我们再次深入研究解释器和编译器。
今天,我将向您展示第 1 部分中计算器的新版本,它将能够:
1、处理输入字符串中任意位置的空白字符
2、从输入中使用多位整数
3、两个整数相减(目前只能相加)
与第 1 部分的版本相比,主要的代码变化 是:
1、该get_next_token方法重构了一下。增加pos指针的逻辑被分解为一个单独的方法Advance。
2、添加了另外两种方法:skip_whitespace忽略空白字符和integer处理输入中的多位整数。
3、该EXPR方法被修改,以识别INTEGER - > MINUS - > INTEGER短语除了INTEGER - > PLUS - > INTEGER短语。该方法现在还可以在成功识别相应短语后解释加法和减法。
# -*- coding: utf-8 -*- """ # -*- coding: utf-8 -*- """ @File : calc2.py @Time : 2021/7/8 10:32 @Author : Dontla @Email : sxana@qq.com @Software: PyCharm """ # EOF (end-of-file) token is used to indicate that # there is no more input left for lexical analysis INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF' class Token(object): def __init__(self, type_, value): # token type: INTEGER, PLUS, MINUS, or EOF self.type = type_ # token value: non-negative integer value, '+', '-', or None self.value = value def __str__(self): """String representation of the class instance. Examples: Token(INTEGER, 3) Token(PLUS '+') """ return 'Token({type}, {value})'.format( type=self.type, value=repr(self.value) ) def __repr__(self): return self.__str__() class Interpreter(object): def __init__(self, text): # client string input, e.g. "3 + 5", "12 - 5", etc self.text = text # self.pos is an index into self.text self.pos = 0 # current token instance self.current_token = None self.current_char = self.text[self.pos] def error(self): raise Exception('Error parsing input') def advance(self): """Advance the 'pos' pointer and set the 'current_char' variable.""" self.pos += 1 if self.pos > len(self.text) - 1: self.current_char = None # Indicates end of input else: self.current_char = self.text[self.pos] def skip_whitespace(self): while self.current_char is not None and self.current_char.isspace(): self.advance() def integer(self): """Return a (multidigit) integer consumed from the input.""" result = '' while self.current_char is not None and self.current_char.isdigit(): result += self.current_char self.advance() return int(result) def get_next_token(self): """Lexical analyzer (also known as scanner or tokenizer) This method is responsible for breaking a sentence apart into tokens. """ while self.current_char is not None: if self.current_char.isspace(): self.skip_whitespace() continue if self.current_char.isdigit(): return Token(INTEGER, self.integer()) if self.current_char == '+': self.advance() return Token(PLUS, '+') if self.current_char == '-': self.advance() return Token(MINUS, '-') self.error() return Token(EOF, None) def eat(self, token_type): # compare the current token type with the passed token # type and if they match then "eat" the current token # and assign the next token to the self.current_token, # otherwise raise an exception. if self.current_token.type == token_type: self.current_token = self.get_next_token() else: self.error() def expr(self): """Parser / Interpreter expr -> INTEGER PLUS INTEGER expr -> INTEGER MINUS INTEGER """ # set current token to the first token taken from the input self.current_token = self.get_next_token() # we expect the current token to be an integer left = self.current_token self.eat(INTEGER) # we expect the current token to be either a '+' or '-' op = self.current_token if op.type == PLUS: self.eat(PLUS) else: self.eat(MINUS) # we expect the current token to be an integer right = self.current_token self.eat(INTEGER) # after the above call the self.current_token is set to # EOF token # at this point either the INTEGER PLUS INTEGER or # the INTEGER MINUS INTEGER sequence of tokens # has been successfully found and the method can just # return the result of adding or subtracting two integers, # thus effectively interpreting client input if op.type == PLUS: result = left.value + right.value else: result = left.value - right.value return result def main(): while True: try: # To run under Python3 replace 'raw_input' call # with 'input' # text = raw_input('calc> ') text = input('calc> ') except EOFError: break if not text: continue interpreter = Interpreter(text) result = interpreter.expr() print(result) if __name__ == '__main__': main()
运行结果:
D:\python_virtualenv\my_flask\Scripts\python.exe C:/Users/Administrator/Desktop/编译原理/python/calc2.py calc> 33234 - 324 32910
注意,跟上一课代码不同的是,eat函数在最后才调用get_next_token函数,使得最后得到的token不是当前token而是下一个token
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <string.h> #include<math.h> #define flag_digital 0 #define flag_plus 1 #define flag_minus 2 #define flag_EOF 3 struct Token { int type; int value; }; struct Interpreter { char* text; int pos; struct Token current_token; }; void error() { printf("输入非法!\n"); exit(-1); } void skip_whitespace(struct Interpreter* pipt) { while (pipt->text[pipt->pos] == ' ') { pipt->pos++; } } //判断Interpreter中当前pos是不是数字 int is_integer(char c) { if (c >= '0' && c <= '9') return 1; else return 0; } void advance(struct Interpreter* pipt) { pipt->pos++; } char current_char(struct Interpreter* pipt) { return(pipt->text[pipt->pos]); } //获取数字token的数值(把数字字符数组转换为数字) int integer(struct Interpreter* pipt) { char temp[20]; int i = 0; while (is_integer(pipt->text[pipt->pos])) { temp[i] = pipt->text[pipt->pos]; i++; advance(pipt); } int result = 0; int j = 0; int len = i; while (j < len) { result += (temp[j] - '0') * pow(10, len - j - 1); j++; } return result; } void get_next_token(struct Interpreter* pipt) { if (pipt->pos > (strlen(pipt->text) - 1)) { pipt->current_token = { flag_EOF, NULL }; return; } if (current_char(pipt) == ' ') skip_whitespace(pipt); if (is_integer(current_char(pipt))) { pipt->current_token = { flag_digital, integer(pipt) }; return; } if (current_char(pipt) == '+') { pipt->current_token = { flag_plus, NULL }; pipt->pos++; return; } if (current_char(pipt) == '-') { pipt->current_token = { flag_minus, NULL }; pipt->pos++; return; } error();//如果都不是以上的字符,则报错并退出程序 } int eat(struct Interpreter* pipt, int type) { int current_token_value = pipt->current_token.value; if (pipt->current_token.type == type) { get_next_token(pipt); return current_token_value; } else { error(); } } int expr(char* text) { struct Interpreter ipt = { text, 0 }; get_next_token(&ipt); int left = eat(&ipt, flag_digital);//断言第一个token是数字 //int left = ipt.current_token.value; int op = ipt.current_token.type; //断言第二个token是加号或减号 if (op == flag_plus) { eat(&ipt, flag_plus); } else { eat(&ipt, flag_minus); } int right = eat(&ipt, flag_digital);//断言第三个token是数字 int result = 0; if (op == flag_plus) { result = left + right; } else if (op == flag_minus) { result = left - right; } return result; } int main() { char text[50]; while (1) { printf("请输入算式:\n"); //scanf_s("%s", text, sizeof(text));//sanf没法输入空格? int i = 0; while ((text[i] = getchar()) != '\n') { //putchar(text[i]); i++; } text[i] = '\0'; int result = expr(text); printf("= %d\n\n", result); } return 0; }
运行结果:(能够自动跳过空格,那么有人会问,数字之间的空格呢,能跳过吗?通常来说,我们一般将数字间的空格视为输入非法的,空格有其另外的作用!)
请输入算式: 3+5 = 8 请输入算式: 44 + 55 = 99 请输入算式: 555 + 555 = 1110 请输入算式:
在第 1 部分中,您学习了两个重要概念,即token标记和词法分析器lexical analyzer。今天我想谈谈词素lexeme、解析parsing和解析器parsers。
您已经了解标记。但是为了让我完成对标记的讨论,我需要提及词素。什么是词素?词素是形成一个标记的字符序列。在下图中,您可以看到一些标记和示例词素的示例,希望它可以使它们之间的关系变得清晰:
现在,还记得我们的朋友expr方法吗?我之前说过,这就是算术表达式的解释实际发生的地方。但在解释一个表达式之前,您首先需要识别它是什么类型的短语,例如,是加法还是减法。这就是expr方法本质上所做的:它在从get_next_token方法获得的标记流中找到结构,然后解释已识别的短语,生成算术表达式的结果。
在标记流中找到结构的过程,或者换句话说,识别标记流中的短语的过程称为解析。执行该工作的解释器或编译器的部分称为解析器。
所以现在您知道expr方法是解析和解释发生的解释器的一部分- expr方法首先尝试识别(解析)INTEGER -> PLUS -> INTEGER或INTEGER -> MINUS -> INTEGER短语标记流,在成功识别(解析)其中一个短语后,该方法会对其进行解释,并将两个整数的加法或减法结果返回给调用者。
现在又到了锻炼的时候了。
1、扩展计算器以处理两个整数的乘法
2、扩展计算器以处理两个整数的除法
3、修改代码以解释包含任意数量的加法和减法的表达式,例如“9 - 5 + 3 + 11”
检查你的理解。
1、什么是词素lexeme?【词素是形成一个标记的字符序列】
2、在标记流中找到结构的过程的名称是什么,或者换句话说,识别该标记流中某个短语的过程的名称是什么?【解析】
3、执行解析的解释器(编译器)部分的名称是什么?【解析器】