本文假设你会使用python。

我们会先实现一个scheme语法的简单计算器,以便了解实现一个解释器的简单步骤,接着会逐步给此计算器增加功能,使其成为一个完整的scheme解释器。

Scheme-wikipedia这里有关于scheme的更详细的介绍。

1.1 Scheme语法式的计算器

此简化的计算器包含加(+)、减(-)、乘(*)、除(/)四个表达式。

加法和乘法表达式可以接收任意数量的参数:

> (+ 1 2 3 4 )
10
> (+)
0
> (* 1 2 3 4)
24
> (*)
1

减法(-)有两种情况,当传入一个参数时,会求这个数的相反数;传入两个或两个以上的操作时,会 进行减法操作, 例如(- 10 1 2 3) 等同于10 - 1 - 2 - 3。除法(/)也有两种情况。

> (- 10 1 2 3)
4
> (- 3)
-3
> (/ 15 12)
1.25
> (/ 30 5 2)
3
> (/ 10)
0.1

一个表达式执行的时候,先会对其子表达式求值,将所得结果再执行所返回的值。

> (- 100 ( * 7 ( + 8 (/ -12 -3))))
16.0

接下来我们将会用python编写此求值器的解释器。大致步骤是读取一串字符串(例如: (+ 1 2) ),将其转换成表达式树,然后遍历表达式树,对其求值,再将求得的值输出出来。

1.2 表达式树

通过观察会发现所有的scheme表达式都是一个列表,这个列表的第一个值是函数名, 后面的值是这个函数 的参数。

>>>> s = Pair(1, Pair(2, nil))
>>>> s
Pair(1, Pair(2, nil))
>>>> print(s)
(1 2)

next

>>>> len(s)
2
>>>> s[1]
2
>>>> print(s.map(lambda x: x + 4))
(5 6)

next

>>>> expr = Pair('+', Pair('*', Pair(3, Pair(4, nil))), Pair(5, nil)))
>>>> print(expr)
(+ * 3 4 5
>>>> expr.second.first.second.first.second.first
3

1.3 解析表达式

>>>> tokenize_line('(+ 1 (* 2.3 45))')
['(', '+', 1, '(', '*', 2.3, 45, ')', ')', ')']

next

>>> lines = ['(+ 1', '   (* 2.3 45))']
>>> expression = scheme_read(Buffer(tokenize_lines(lines)))
>>> expression
Pair('+', Pair(1, Pair(Pair('*', Pair(2.3, Pair(45, nil))), nil)))
>>> print(expression)
(+ 1 (* 2.3 45))

1.4 求值

>>> def calc_eval(exp):
        """Evaluate a Calculator expression."""
        if type(exp) in (int, float):
            return simplify(exp)
        elif isinstance(exp, Pair):
            arguments = exp.second.map(calc_eval)
            return simplify(calc_apply(exp.first, arguments))
        else:
            raise TypeError(exp + ' is not a number or call expression')


next

>>> def calc_apply(operator, args):
        """Apply the named operator to a list of args."""
        if not isinstance(operator, str):
            raise TypeError(str(operator) + ' is not a symbol')
        if operator == '+':
            return reduce(add, args, 0)
        elif operator == '-':
            if len(args) == 0:
                raise TypeError(operator + ' requires at least 1 argument')
            elif len(args) == 1:
                return -args.first
            else:
                return reduce(sub, args.second, args.first)
        elif operator == '*':
            return reduce(mul, args, 1)
        elif operator == '/':
            if len(args) == 0:
                raise TypeError(operator + ' requires at least 1 argument')
            elif len(args) == 1:
                return 1/args.first
            else:
                return reduce(truediv, args.second, args.first)
        else:
            raise TypeError(operator + ' is an unknown operator')

next

>>> calc_apply('+', as_scheme_list(1, 2, 3))
6
>>> calc_apply('-', as_scheme_list(10, 1, 2, 3))
4
>>> calc_apply('*', nil)
1
>>> calc_apply('*', as_scheme_list(1, 2, 3, 4, 5))
120
>>> calc_apply('/', as_scheme_list(40, 5))
8.0

next


>>> print(exp)
(+ (* 3 4) 5)
>>> calc_eval(exp)
17

next

>>> def read_eval_print_loop():
        """Run a read-eval-print loop for calculator."""
        while True:
            src = buffer_input()
            while src.more_on_line:
                expression = scheme_read(src)
                print(calc_eval(expression))

next


> (* 1 2 3)
6
> (+)
0
> (+ 2 (/ 4 8))
2.5
> (+ 2 2) (* 3 3)
4
9
> (+ 1
     (- 23)
     (* 4 2.5))
-12

next

>>> def read_eval_print_loop():
        """Run a read-eval-print loop for calculator."""
        while True:
            try:
                src = buffer_input()
                while src.more_on_line:
                    expression = scheme_read(src)
                    print(calc_eval(expression))
            except (SyntaxError, TypeError, ValueError, ZeroDivisionError) as err:
                print(type(err).__name__ + ':', err)
            except (KeyboardInterrupt, EOFError):  # <Control>-D, etc.
                print('Calculation completed.')
                return

next


> )
SyntaxError: unexpected token: )
> 2.3.4
ValueError: invalid numeral: 2.3.4
> +
TypeError: + is not a number or call expression
> (/ 5)
TypeError: / requires exactly 2 arguments
> (/ 1 0)
ZeroDivisionError: division by zero

next

未完待续…