​ 当遇到一件比较困难的事情的时候我们是如何解决的呢? 处理复杂问题需要十分强的抽丝剥茧的能力。所谓抽丝剥茧的能力,其实有另外一个名字——深度思考。而深度思考需要掌握一定的方法论才会有所效果。例如常见的有阅读杂书来培养全方位的思考能力,不停的对自己提问对某一概念的理解来测试我们的洞察深度,都是一些不错的方法。

image.png

​ 显然构建一个编译器是十分复杂的关系,普遍来讲要经过构建词法分析器,构建语法分析器,语义分析,代码生成,代码优化以及目标代码生成的过程。

  1. 词法分析器:将输入的字符流转换为特定的单词,这一步我们需要识别组合字符,识别出数字,标识符和关键字。
  2. 语法分析器:将词法分析器解析的单词转换成特定的语句,也就是组合词法分析器的结果,去匹配符合语法的语句,构建抽象语法树(AST)。
  3. 语义分析:在构建AST后我们会对其进行分析。进行类型匹配的工作,来检测代码语意的正确性。
  4. 代码生成:这一步是编译器前端后后端交互的地方,大多数语言的做法是抽象一个脱离于机器的中间语言(IR)以便后期的优化工作。这一层往往有两个选择,一种是基于栈的IR(虚拟机平台),另一种是基于寄存器的IR(LLVM)。
  5. 代码优化:这一步的目的是为了使得代码更加高效。一般分为两种优化,一是与运行机器平台相关的优化,一种是与运行机器平台无关的优化。这一部进去的是IR层,输出的也是IR层。
  6. 代码生成:代码生成基本是所有编译器做的最后的工作了,对于编译型语言会生成目标机器指令集的指令。

image.png

虽然构建一个编译器貌似是一个十分复杂的过程,但是已经有很多前辈做了很多工作比如代码优化的时候LLVM可以提供很多的pass优化,我们甚至完全可以做一个复读机来实现自己的编程语言。

回想在第四节的时候我们在计算器的feature里加入了乘法和除法功能,在普通算术和大多数编程语言中,加,减,乘和除是左关联的:

7 + 3 + 1 is equivalent to (7 + 3) + 1
7 - 3 - 1 is equivalent to (7 - 3) - 1
8 * 4 * 2 is equivalent to (8 * 4) * 2
8 / 4 / 2 is equivalent to (8 / 4) / 2

语法图如下:

img

运算优先级关系如下:

img

源代码如下:

# 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, EOF = (
    'INTEGER', 'PLUS', 'MINUS', 'MUL', 'DIV', 'EOF'
)


class Token(object):
    def __init__(self, type, value):
        # token type: INTEGER, PLUS, MINUS, 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(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. "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(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, '/')

            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"""
        token = self.current_token
        self.eat(INTEGER)
        return token.value

    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>  14 + 2 * 3 - 6 / 2
        17

        expr   : term ((PLUS | MINUS) term)*
        term   : factor ((MUL | DIV) factor)*
        factor : INTEGER
        """
        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()

运行结果会话如下:

$ python calc5.py
calc> 3
3
calc> 2 + 7 * 4
30
calc> 7 - 8 / 4
5
calc> 14 + 2 * 3 - 6 / 2
17

从本节开始代码支持python3版本了,你可以自己运行尝试一下。

一鲸落,万物生。

当一头巨鲸死去的时候,它的尸体会沉入海底,生物学家赋予这个过程为鲸落,巨鲸的尸体可以供养一套分解者为主的循环系统长达百年。

巨鲸的尸体被上万个生物吃掉,在海底诞生了独特的生态圈,甚至能够诞生新的物种,一即是全,全即是一。

​ 虽然第一台电子计算从诞生至今也不过百年,但是一鲸落而万物生,计算机俨然催生了很多兴盛的领域。如互联网,人工智能,移动互联网等等,就算是我现在能在互联网的AI公司谋求一份温饱工作也算是蹭了红利。当然事物是具有两面性的,有好的一面自然有坏的一面,越来越多的工科学生投机进入CS的热门领域,想要赚大钱,虽然初衷对个人没什么问题,但是人数的增加让就业的压力陡然增加,这点靠个人也做不了什么,希望行业以后会越来越好吧。我写这个教程其实也是在复读别人的博客,只是想找一个地方整理下脑子里的印象。最后祝愿大家早日脱单并且财富自由,找到喜欢的事情做,拜拜。


Let’s Build A Slmple Interpreter. Part 4.

Sun 21 June 2020 by kizzy

在之前的文章裏,我們學會了如何解析和翻譯加減的數學表達式,例如“7 - 3 + 2 -1”。

read more

Let’s Build A Slmple Interpreter. Part 3.

Fri 19 June 2020 by kizzy

我們是如何學習一件新的技能呢?例如游泳,我們需要花費很長的時間在水裏進行實踐。

read more

Let’s Build A Simple Interpreter. Part 2.

Wed 17 June 2020 by kizzy

如果我們只學習一些方法,我們會受困于一些方法。但是如果你學習一些理論,你就能重構自己的方法。

read more

Let’s Build A Simple Interpreter. Part 1.

Wed 17 June 2020 by kizzy

如果你不了解编译器是如何工作的,那么你应该也不会理解计算机是如何工作的。

read more