【编译原理】让我们来构建一个简单的解释器(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短语。该方法现在还可以在成功识别相应短语后解释加法和减法。
python代码
# -*- 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
c代码
注意,跟上一课代码不同的是,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、执行解析的解释器(编译器)部分的名称是什么?【解析器】