【Interpreter】构建简单的解释器(第6部分)

【Interpreter】构建简单的解释器(第6部分)

简单翻译了下,方便查看,水平有限,喜欢的朋友去看 原文

今天是特别的一天 ? “为什么呢?” 你可能会问。 因为今天我们会通过在语法中添加带括号的表达式并实现一个能够用任意深度嵌套来计算带括号的表达式的解释器来完成对算术表达式的讨论(好吧,差不多就这样了),比如表达式 7 + 3 * (10 / ( 12 / ( 3 + 1 ) - 1 ) )

让我们开始吧。

首先,让我们修改语法来支持括号内的表达式。 正如在 第5部分 中那样,factor 规则用于处理表达式中的基本单元。 在那篇文章中,我们唯一的基本单元是整数。 今天我们要添加另一个基本单元 —— 带括号的表达式。 我们开始吧。

下面是我们最新修改的语法:
lsbasi_part6_grammar
exprterm第5部分 里面保持一致。这里唯一改变的地方是 factor,这里的 LPAREN 代表左括号 ‘(’,RPAREN 代表右括号 ‘(’,两个括号中间的 非终结符 expr 遵循第一条的 expr 规则。

下面是 factor 修改后的语法图,里面包含了可选项。
lsbasi_part6_factor_diagram

因为 exprterm 的语法规则并没有变,所以它们的语法图和 第5部分 保持一致:
lsbasi_part6_expr_term_diagram

在新的语法里有个很有趣的特点——递归。如果你想解析表达式 2 * ( 7 + 3 ),你需要从 expr 的起始符号开始,最终在你遇到表达式 ( 7 + 3 ) 的时候,你需要再次根据 expr 的规则来解析。

根据语法分解 表达式 2 * ( 7 + 3 ),得到下图:
lsbasi_part6_decomposition

一点题外话:如果你需要复习一下关于递归的知识的话,可以看看 Daniel P. Friedman 和 Matthias Felleisen 合著的 The Little Schemer 这本书,讲的非常好。

好了,接下来让我们把新修改的语法翻译成代码。

下面是根据前文对代码做的主要修改:

  1. Lexer 修改后可以多返回两个 tokenLPAREN 代表左括号,RPAREN 代表右括号。
  2. 除了直接解析整数之外,解释器的 factor 方法已经略微改进以解析带括号的表达式。

这是一份完整的可以处理 任意深度括号嵌套 和 任意位数整数 的加、减、乘、除四则运算的计算器的源代码:

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


class Token(object):
    def __init__(self, type, value):
        self.type = type
        self.value = value

    def __str__(self):
        """String representation of the class instance.

        Examples:
            Token(INTEGER, 3)
            Token(PLUS, '+')
            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. "4 + 2 * 3 - 6 / 2"
        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(PLUS, '+')

            if self.current_char == '-':
                self.advance()
                return Token(MINUS, '-')

            if self.current_char == '*':
                self.advance()
                return Token(MUL, '*')

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

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

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

            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):
        """factor : INTEGER | LPAREN expr RPAREN"""
        token = self.current_token
        if token.type == INTEGER:
            self.eat(INTEGER)
            return token.value
        elif token.type == LPAREN:
            self.eat(LPAREN)
            result = self.expr()
            self.eat(RPAREN)
            return result

    def term(self):
        """term : factor ((MUL | DIV) factor)*"""
        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 expr(self):
        """Arithmetic expression parser / interpreter.

        calc> 7 + 3 * (10 / (12 / (3 + 1) - 1))
        22

        expr   : term ((PLUS | MINUS) term)*
        term   : factor ((MUL | DIV) factor)*
        factor : INTEGER | LPAREN expr RPAREN
        """
        result = self.term()

        while self.current_token.type in (PLUS, MINUS):
            token = self.current_token
            if token.type == PLUS:
                self.eat(PLUS)
                result = result + self.term()
            elif token.type == MINUS:
                self.eat(MINUS)
                result = result - self.term()

        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()

保存上面的代码到 calc6.py 文件中,测试一下,看看新版解释器能否正确处理包含不同操作符和任意深度嵌套的算术表达式。

下面是一个简单的运行示例:

$ python calc6.py
calc> 3
3
calc> 2 + 7 * 4
30
calc> 7 - 8 / 4
5
calc> 14 + 2 * 3 - 6 / 2
17
calc> 7 + 3 * (10 / (12 / (3 + 1) - 1))
22
calc> 7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)
10
calc> 7 + (((3 + 2)))
12

下面是今天的新练习题:
lsbasi_part6_exercises

  • 按照本文讲解的方法编写一个自己版本的算数表达式解析器。记住:重复练习是所有学习方法之母。

嘿,你已经读到最后了!恭喜你,你已经学会了如何创建(如果你做了所有练习——并且真的亲手编写过代码)一个可以执行很复杂算术表达式的递归文法分析器/解释器。

下一篇文章中我将讲解更多关于递归文法分析器的细节。我也会介绍一个整个系列都会用到,而且在解析器和编译器中都非常重要且运用广泛的数据结构。

敬请期待。在那之前请你继续练习编写解析器。重要的是:保持乐趣享受过程!

以下是我推荐的书籍清单,可以帮助你学习解释器和编译器:

  1. Language Implementation Patterns: Create Your Own Domain-Specific and General Programming Languages (Pragmatic Programmers)
  2. Writing Compilers and Interpreters: A Software Engineering Approach
  3. Modern Compiler Implementation in Java
  4. Modern Compiler Design
  5. Compilers: Principles, Techniques, and Tools (2nd Edition)

原文链接:Let’s Build A Simple Interpreter. Part 6.

作者博客:Ruslan’s Blog


——2019-01-24——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值