编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)(python/c/c++版)(笔记)

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)(python/c/c++版)(笔记)相关的知识,希望对你有一定的参考价值。

【编译原理】让我们来构建一个简单的解释器(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、执行解析的解释器(编译器)部分的名称是什么?【解析器】

以上是关于编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)(python/c/c++版)(笔记)的主要内容,如果未能解决你的问题,请参考以下文章

编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 6.)(python/c/c++版)(笔记)

编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 4.)(python/c/c++版)(笔记)

编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 3.)(python/c/c++版)(笔记)

编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 5.)(python/c/c++版)(笔记)Lexer词法分析程序

编译原理构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 8.)(笔记)一元运算符正负(+,-)

编译原理构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 9.)(笔记)语法分析(未完,先搁置了!)