如何写一个简单的解释器(Interpreter)-1

 原创声明:这一个系列是翻译自https://ruslanspivak.com。插图是原文中自带的,同时我删除了一些没必要的解释。

如果你不知道compiler是怎么工作的,那么你就不会清楚计算机是怎么工作的。如果你不是100%确定地知道‘你知道compiler是怎么工作的’,那么你肯定不知道compiler是怎么工作的。 — Steve Yegge

别着急,试着跟我读完整个教程,最终你会学会怎么写interpreter和compiler的。并且你一定会变得自信,至少我希望如此。为什么要学习这些呢?我给三个理由你听听。

  1. 为了写成一个interpreter或者compiler,你必须学习相当多的技术和技巧,而且会综合运用。这些工作会让你更善于使用这些技术,变成一个更好的码农。同时,你得到的是如何开发好一个软件,而不仅仅是interpreter和compiler。
  2. 你会真切地想知道计算机内部是怎么工作的。通常人们都认为interpreter和compiler像是魔法,并且你一定不习惯那个魔法。你会想解开魔法的帘子,去弄明白帘子里面是什么,它们是怎么控制这一切的。
  3. 或者你希望创造一个你自己的编程语言或者领域语言。如果你想这么做,那你就要为这个语言创建一个interpreter或者compiler。干这个事儿最近很流行,很多新语言层出不穷,比如Elixir、Go、Rust,还有好多。

OK,那什么是interpreter和compiler呢?

Interpreter 或者 compiler 的目标是吧源程序翻译成某些高层语言的形式。听起来好像没有说,是吗?相信我,看完这些文章你会弄明白源程序到底被转化成了什么东西。

看到这里,你可能心生疑问:interpreter和compiler区别在哪儿?看下图吧。compiler是源程序转换成了机器语言,而interpreter不转换。

来,我们动手,写一个Pascal语言的interpreter。为了简单,我们选择用python来写。

下面是一个经典的阶乘运算的Pascal程序。

program factorial;

function factorial(n: integer): longint;
begin
    if n = 0 then
        factorial := 1
    else
        factorial := n * factorial(n - 1);
end;

var
    n: integer;

begin
    for n := 0 to 16 do
        writeln(n, '! = ', factorial(n));
end.

直接写好一个interpreter不太现实。下面我们用python先写一个开头,加法计算器。够简单吧?

# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
INTEGER, PLUS, EOF = 'INTEGER', 'PLUS', 'EOF'

class Token(object):
    def __init__(self, type, value):
        # token type: INTEGER, PLUS, or EOF
        self.type = type
        # token value: 0, 1, 2. 3, 4, 5, 6, 7, 8, 9, '+', 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"
        self.text = text
        # self.pos is an index into self.text
        self.pos = 0
        # current token instance
        self.current_token = None
    def error(self):
        raise Exception('Error parsing input')
    def get_next_token(self):
        """Lexical analyzer (also known as scanner or tokenizer)
        This method is responsible for breaking a sentence
        apart into tokens. One token at a time.
        """
        text = self.text
        # is self.pos index past the end of the self.text ?
        # if so, then return EOF token because there is no more
        # input left to convert into tokens
        if self.pos > len(text) - 1:
            return Token(EOF, None)
        # get a character at the position self.pos and decide
        # what token to create based on the single character
        current_char = text[self.pos]
        # if the character is a digit then convert it to
        # integer, create an INTEGER token, increment self.pos
        # index to point to the next character after the digit,
        # and return the INTEGER token
        if current_char.isdigit():
            token = Token(INTEGER, int(current_char))
            self.pos += 1
            return token
        if current_char == '+':
            token = Token(PLUS, current_char)
            self.pos += 1
            return token
        self.error()
    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):
        """expr -> INTEGER PLUS 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 a single-digit integer
        left = self.current_token
        self.eat(INTEGER)
        # we expect the current token to be a '+' token
        op = self.current_token
        self.eat(PLUS)
        # we expect the current token to be a single-digit integer
        right = self.current_token
        self.eat(INTEGER)
        # after the above call the self.current_token is set to
        # EOF token
        # at this point INTEGER PLUS INTEGER sequence of tokens
        # has been successfully found and the method can just
        # return the result of adding two integers, thus
        # effectively interpreting client input
        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> ')
        except EOFError:
            break
        if not text:
            continue
        interpreter = Interpreter(text)
        result = interpreter.expr()
        print(result)
if __name__ == '__main__':
    main()


运行一下:

$ python calc1.py
calc> 3+4
7
calc> 3+5
8
calc> 3+9
12
calc>

为了让这个简单到不能再简单的interpreter正常工作,不出现异常。你必须保证:

  • 只输入简单的整数数字
  • 只使用加法操作
  • 没有空白字符

当你输入3+5的时候,你的interpreter得到了一个字符串“3+5”。为了让interpreter真真的理解要干什么,它首先就要分解这个“3+5”串,编程一系列的tokensToken是一个物体,它包含类型和数值。比如,“3”这个字符串,它对应的token的类型就是INTEGER,数值是整数3。

把串拆分为tokens的过程叫词法分析,完成这个过程的工具叫词法分析器,或者扫描器。它把你的一串输入数字转换成了一个长长的token串。

get_next_token 是 Interpreter python类的一个方法,他就是词法分析器。每次调用它的时候,你就能得到输入串的下一个token。 仔细看看,输入的字符串是放在变量text 中的,text中除了有输入串,还有一个 pos代表输入串的索引。pos 初始值为0,指向“3”。这个方法首先检查第一个字符是不是数字。如果是数字,方法就增加pos并返回一个整形3的token。 

现在,pos指向了text中的 ‘+’ 这个字符。下次再调用这个方法,它会检测到当前的符号不是数字,而是加号,于是返回一个PLUS+的token。

类似3的处理,5也会处理好。方法返回整形5这个token。

pos现在来到了EOF,代表输入串处理完毕,于是方法退出。

你可以用下面的交互式python执行方式,来验证一下整个过程:

>>>
>>> interpreter = Interpreter('3+5')
>>> interpreter.get_next_token()
Token(INTEGER, 3)
>>>
>>> interpreter.get_next_token()
Token(PLUS, '+')
>>>
>>> interpreter.get_next_token()
Token(INTEGER, 5)
>>>
>>> interpreter.get_next_token()
Token(EOF, None)
>>>

结果是:token的序列为 INTEGER -> PLUS -> INTEGER。interpreter会知道,它要找的序列是:一个整数加上另一个整数。负责找这个序列的方法是 expr。它会验证一下序列是否合法,该找的数字有没有错位。一切正常的情况下,它会成功执行出结果。

expr 方法使用 helper 方法来 eat token,从而来来验证一个个的current_token指向的字符。出现异常的话就抛出来。

祝贺你,完成了你的第一个简单的interpreter!

猜你喜欢

转载自blog.csdn.net/weixin_38151747/article/details/85726276