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

还记得Confucius说过什么吗?

“我听说了,后来我忘记了”

Hear

“我看到了,后来我记住了.”

See

“我做过了,后来我理解了.”

Do


这一章我们讲讲多个乘数法的计算,注意除法是整数的除法,比如9除以4等于2。

我还要使用一种新的表达方式,上下文无关的语法书(简称为语法书),或者叫BNF(Backus Naur样式),来表示一种编程语言的语句规则。多说一句,我用的是扩展的BNF(EBNF),不是传统的BNF。

为什么要费劲儿关心语法书这个问题呢?有几个原因:

  • 语法准确规定了语言的语句规则。不想语法图,语法书非常简洁,本系列文章以后会越来越倾向于使用语法书来表达,而不是语法图。
  • 语法书可以写出好的文档来,供码农参考。
  • 如果你想手写一个解释器,语法书是一个合适的起点。遵守一些简单的规则,你就是很容易的把语法书转换为代码。
  • 市面上有很多现成的工具,叫解析器生成器。它们可以把语法书自动转换为一个支持这个语法解释器。后文会提到这个的。

下面是描述“7 * 4 / 2 * 3”的语法。语法的写法可能有很多种,这里只展示了其中一种写法:

一条语法,它包含了一系列的规则,有时候也称他们是生成法则。我的语法有两条基本规则:

一条规则由规则头(左值)、冒号、规则体(右值)组成。

MUL, DIV和INTEGER通常被称为 终结符, expr 和factor 这种变量通常被称为非终结符。后者通常由一个或几个终结符和非终结符组合而成。

左边的非终结符称为起始符号,比如我们这儿的expr。

这条规则可以这么读:"expr就是一个因子,后面跟着一个乘法或者除法操作,后面又跟着一个因子,该因子后面可能跟着一个乘除法操作,然后后面还可能跟着一个因子。以此类推”

因子是什么?简单说就因子就是整数。

| - 竖线表示二选一,也就是“或者”。所以 (MUL | DIV)代表要么是 MUL,要么是 DIV.

  • ( … ) - 一对儿括号表示一组终结符和/或非终结符。
  • ( … )* - 星星号表示上面的这一组表示,重复多次(或者不重复)。.

这一切跟正则表达式很像。

语法定义了某种语言里的句子是如何构成的。你可以这样使用语法,来扩展算术表达式:首先你写上expr作为句子的开始,然后使用非终结符的规则体,来替换这个非终结符,重复执行这个操作,直到整个句子只包含终结符。这个过程就是语法生成语言的整个过程。

如果你发现语法没办法去扩展一个算术表达式,那解析器就报错了,因为语法不够完善,不足以支持这种表达式。

下面是个思想实验的例子:

下面展示了语法怎么去扩展表达式 3 * 7:

继续扩展除以2::

 

我刚开始读这个的时候,也觉得不太舒服。你也会这样。但是我相信有一天你会适应的。

该怎么把语法转成真正的代码呢?

下面是我总结的规则:

  1. 对于每条规则 R, 引用这个规则的方法取名为R();
  2. 二选一的(a1 | a2 | aN) 变成if-elif-else 语句;
  3. 可选的组合(…)* 变成一个 while 语句;
  4. 每一个token T 变成一个eat(T)方法。 只有匹配到当前的 lookahead token的时候,它才会吃掉这个token。接着它会从词法分析器那儿拿到一个新的token,然后把token继续交还给current_token变量。

我们的情况有两条规则,expr规则和factor(因子)规则。

拿factor规则来说,根据前面我们说的转换方法,你要先命名一个factor()方法,包含一个eat整数的方法。

def factor(self):
    self.eat(INTEGER)

这很简单,不是吗?

继续!

对于expr 规则,除了命名为expr() 外,规则体一上来就去调用factor()。  (…)* 变成 while 语句 (MUL | DIV) 变成 if-elif-else 语句。

def expr(self):
    self.factor()

    while self.current_token.type in (MUL, DIV):
        token = self.current_token
        if token.type == MUL:
            self.eat(MUL)
            self.factor()
        elif token.type == DIV:
            self.eat(DIV)
            self.factor()

运行结果如下:

$ python parser.py
calc> 3
calc> 3 * 7
calc> 3 * 7 / 2
calc> 3 *
Traceback (most recent call last):
  File "parser.py", line 155, in <module>
    main()
  File "parser.py", line 151, in main
    parser.parse()
  File "parser.py", line 136, in parse
    self.expr()
  File "parser.py", line 130, in expr
    self.factor()
  File "parser.py", line 114, in factor
    self.eat(INTEGER)
  File "parser.py", line 107, in eat
    self.error()
  File "parser.py", line 97, in error
    raise Exception('Invalid syntax')
Exception: Invalid syntax

试试吧!

想用语法图表示 expr的话,将是这样式的。

下面是代码,可以看到我重构了词法分析器的代码,把它改造成了一个Lexer类,并且更新了interpreter类用来把lexer作为输入参数。

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


class Token(object):
    def __init__(self, type, value):
        # token type: INTEGER, MUL, DIV, 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(MUL, '*')
        """
        return 'Token({type}, {value})'.format(
            type=self.type,
            value=repr(self.value)
        )

    def __repr__(self):
        return self.__str__()


class Lexer(object):
    def __init__(self, text):
        # client string input, e.g. "3 * 5", "12 / 3 * 4", etc
        self.text = text
        # self.pos is an index into self.text
        self.pos = 0
        self.current_char = self.text[self.pos]

    def error(self):
        raise Exception('Invalid character')

    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. One token at a time.
        """
        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(MUL, '*')

            if self.current_char == '/':
                self.advance()
                return Token(DIV, '/')

            self.error()

        return Token(EOF, None)


class Interpreter(object):
    def __init__(self, lexer):
        self.lexer = lexer
        # set current token to the first token taken from the input
        self.current_token = self.lexer.get_next_token()

    def error(self):
        raise Exception('Invalid syntax')

    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.lexer.get_next_token()
        else:
            self.error()

    def factor(self):
        """Return an INTEGER token value.

        factor : INTEGER
        """
        token = self.current_token
        self.eat(INTEGER)
        return token.value

    def expr(self):
        """Arithmetic expression parser / interpreter.

        expr   : factor ((MUL | DIV) factor)*
        factor : INTEGER
        """
        result = self.factor()

        while self.current_token.type in (MUL, DIV):
            token = self.current_token
            if token.type == MUL:
                self.eat(MUL)
                result = result * self.factor()
            elif token.type == DIV:
                self.eat(DIV)
                result = result / self.factor()

        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
        lexer = Lexer(text)
        interpreter = Interpreter(lexer)
        result = interpreter.expr()
        print(result)


if __name__ == '__main__':
    main()

运行一下试试:

$ python calc4.py
calc> 7 * 4 / 2
14
calc> 7 * 4 / 2 * 3
42
calc> 10 * 4  * 2 * 3 / 8
30

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值