出品 | Python大本营(ID: pythonnews)
本文有两个目的:一是展示如何实现一个计算机语言的解释器,二是演示如何使用 Python 3 构造 Lisp 的一种方言 Schema,我把我的语言解释器称作 Lispy。几年前,作者曾展示过如何用 Java 和 Common Lisp 写 Schema 解释器。而本次的目的很纯粹,作者会尽可能简明扼要,就像 Alan Kay 所谓的 “软件中的麦克斯韦方程组”。
语言1:Lispy Calculator
(define r 10)
(* pi (* r r))
下面是一张有关全部表达式的表格:
Expression(表达式) | Syntax(语法) | Semantics and Example(语义和例子) |
variable reference | symbol | 一个标识符被解释为变量名;它的值是变量的值。例子:r ⇒ 10 (假设 r 已被定义为10) |
constant literal | number | 计算结果为数字本身。例子:12 ⇒ 12 or -3.45e+6 ⇒ -3.45e+6 |
conditional | (if test conseq alt) | 执行 test;如果结果为 true,计算返回 conseq;否则返回 alt。例子:(if (> 10 20) (+ 1 1) (+ 3 3)) ⇒ 6 |
definition | (define symbol exp) | 定义一个新变量,并计算表达式 exp 赋值给它。例子:(define r 10) |
procedure call | (proc arg...) | 如果表达式不是这些标识符 if, define 或 quote,那它就是一个过程。执行表达式及全部参数,那么该过程就会被调用,而参数值列表也被调用。例子:(sqrt (* 2 8)) ⇒ 4.0 |
>> program = "(begin (define r 10) (* pi (* r r)))"
>>> parse(program)
['begin', ['define', 'r', 10], ['*', 'pi', ['*', 'r', 'r']]]
>>> eval(parse(program))
314.1592653589793
类型定义
Symbol = str # A Scheme Symbol is implemented as a Python str
Number = (int, float) # A Scheme Number is implemented as a Python int or float
Atom = (Symbol, Number) # A Scheme Atom is a Symbol or Number
List = list # A Scheme List is implemented as a Python list
Exp = (Atom, List) # A Scheme expression is an Atom or List
Env = dict # A Scheme environment (defined below)
# is a mapping of {variable: value
def tokenize(chars: str) -> list:
"Convert a string of characters into a list of tokens."
return chars.replace('(', ' ( ').replace(')', ' ) ').split()
>>> program = "(begin (define r 10) (* pi (* r r)))"
>>> tokenize(program)
['(', 'begin', '(', 'define', 'r', '10', ')', '(', '*', 'pi', '(', '*', 'r', 'r', ')', ')', ')']
def parse(program: str) -> Exp:
"Read a Scheme expression from a string."
return read_from_tokens(tokenize(program))
def read_from_tokens(tokens: list) -> Exp:
"Read an expression from a sequence of tokens."
if len(tokens) == 0:
raise SyntaxError('unexpected EOF')
token = tokens.pop(0)
if token == '(':
L = []
while tokens[0] != ')':
L.append(read_from_tokens(tokens))
tokens.pop(0) # pop off ')'
return L
elif token == ')':
raise SyntaxError('unexpected )')
else:
return atom(token)
def atom(token: str) -> Atom:
"Numbers become numbers; every other token is a symbol."
try: return int(token)
except ValueError:
try: return float(token)
except ValueError:
return Symbol(token)
>>> program = "(begin (define r 10) (* pi (* r r)))"
>>> parse(program)
['begin', ['define', 'r', 10], ['*', 'pi', ['*', 'r', 'r']]]
import math
import operator as op
def standard_env() -> Env:
"An environment with some Scheme standard procedures."
env = Env()
env.update(vars(math)) # sin, cos, sqrt, pi, ...
env.update({
'+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv,
'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
'abs': abs,
'append': op.add,
'apply': lambda proc, args: proc(*args),
'begin': lambda *x: x[-1],
'car': lambda x: x[0],
'cdr': lambda x: x[1:],
'cons': lambda x,y: [x] + y,
'eq?': op.is_,
'expt': pow,
'equal?': op.eq,
'length': len,
'list': lambda *x: List(x),
'list?': lambda x: isinstance(x, List),
'map': map,
'max': max,
'min': min,
'not': op.not_,
'null?': lambda x: x == [],
'number?': lambda x: isinstance(x, Number),
'print': print,
'procedure?': callable,
'round': round,
'symbol?': lambda x: isinstance(x, Symbol),
})
return env
global_env = standard_env()
Evaluation: eval
我们已经做好实现 eval 的准备了。作为初学者,来回顾一下之前的 Lispy Calculator 表:
Expression(表达式) |
Syntax(语法) |
Semantics and Example (语义和例子) |
variable reference |
symbol |
一个标识符被解释为变量名;它的值是变量的值。 例子:r ⇒ 10 (假设 r 已被定义为10) |
constant literal |
number |
计算结果为数字本身。 例子:12 ⇒ 12 or -3.45e+6 ⇒ -3.45e+6 |
conditional |
(if test conseq alt) |
执行 test;如果结果为 true,计算返回 conseq;否则返回 alt。 例子:(if (> 10 20) (+ 1 1) (+ 3 3)) ⇒ 6 |
definition |
(define symbol exp) |
定义一个新变量,并计算表达式 exp 赋值给它。 例子:(define r 10) |
procedure call |
(proc arg...) |
如果表达式不是这些标识符 if, define 或 quote,那它就是一个过程。执行表达式及全部参数,那么该过程就会被调用,而参数值列表也被调用。 例子:(sqrt (* 2 8)) ⇒ 4.0 |
下面是实现 eval 的代码,完全遵循上面的表格:
def eval(x: Exp, env=global_env) -> Exp:
"Evaluate an expression in an environment."
if isinstance(x, Symbol): # variable reference
return env[x]
elif not isinstance(x, Number): # constant number
return x
elif x[0] == 'if': # conditional
(_, test, conseq, alt) = x
exp = (conseq if eval(test, env) else alt)
return eval(exp, env)
elif x[0] == 'define': # definition
(_, symbol, exp) = x
env[symbol] = eval(exp, env)
else: # procedure call
proc = eval(x[0], env)
args = [eval(arg, env) for arg in x[1:]]
return proc(*args)
>>> eval(parse("(begin (define r 10) (* pi (* r r)))"))
314.1592653589793
def repl(prompt='lis.py> '):
"A prompt-read-eval-print loop."
while True:
val = eval(parse(raw_input(prompt)))
if val is not None:
print(schemestr(val))
def schemestr(exp):
"Convert a Python object back into a Scheme-readable string."
if isinstance(exp, List):
return '(' + ' '.join(map(schemestr, exp)) + ')'
else:
return str(exp)
>>> repl()
lis.py> (define r 10)
lis.py> (* pi (* r r))
314.159265359
lis.py> (if (> (* 11 11) 120) (* 7 6) oops)
42
lis.py> (list (+ 1 1) (+ 2 2) (* 2 3) (expt 2 3))
lis.py>
Expression(表达式) | Syntax(语法) | Semantics and Example(语义和例子) |
quotation | (quote exp) | 返回表达式 exp 的值,但不进行计算。例子:(quote (+ 1 2)) ⇒ (+ 1 2) |
assignment | (set! symbol exp) | 执行 exp 并把值赋给 symbol,symbol 必须被预先定义好。例子:(set! r2 (* r r)) |
procedure | (lambda (symbol...)exp) | 创造一个带参数 (symbol...) 的过程,exp 为其主体。例子:(lambda (r) (* pi (* r r))) |
lis.py> (define circle-area (lambda (r) (* pi (* r r)))
lis.py> (circle-area (+ 5 5))
314.159265359
class Env(dict):
"An environment: a dict of {'var': val} pairs, with an outer Env."
def __init__(self, parms=(), args=(), outer=None):
self.update(zip(parms, args))
self.outer = outer
def find(self, var):
"Find the innermost Env where var appears."
return self if (var in self) else self.outer.find(var)
class Procedure(object):
"A user-defined Scheme procedure."
def __init__(self, parms, body, env):
self.parms, self.body, self.env = parms, body, env
def __call__(self, *args):
return eval(self.body, Env(self.parms, args, self.env))
global_env = standard_env()
def eval(x, env=global_env):
"Evaluate an expression in an environment."
if isinstance(x, Symbol): # variable reference
return env.find(x)[x]
elif not isinstance(x, List):# constant
return x
op, *args = x
if op == 'quote': # quotation
return args[0]
elif op == 'if': # conditional
(test, conseq, alt) = args
exp = (conseq if eval(test, env) else alt)
return eval(exp, env)
elif op == 'define': # definition
(symbol, exp) = args
env[symbol] = eval(exp, env)
elif op == 'set!': # assignment
(symbol, exp) = args
env.find(symbol)[symbol] = eval(exp, env)
elif op == 'lambda': # procedure
(parms, body) = args
return Procedure(parms, body, env)
else: # procedure call
proc = eval(op, env)
vals = [eval(arg, env) for arg in args]
return proc(*vals)
>>> repl()
lis.py> (define circle-area (lambda (r) (* pi (* r r))))
lis.py> (circle-area 3)
28.274333877
lis.py> (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))
lis.py> (fact 10)
3628800
lis.py> (fact 100)
9332621544394415268169923885626670049071596826438162146859296389521759999322991
5608941463976156518286253697920827223758251185210916864000000000000000000000000
lis.py> (circle-area (fact 10))
4.1369087198e+13
lis.py> (define first car)
lis.py> (define rest cdr)
lis.py> (define count (lambda (item L) (if L (+ (equal? item (first L)) (count item (rest L))) 0)))
lis.py> (count 0 (list 0 1 2 3 0 0))
3
lis.py> (count (quote the) (quote (the more the merrier the bigger the better)))
4
lis.py> (define twice (lambda (x) (* 2 x)))
lis.py> (twice 5)
10
lis.py> (define repeat (lambda (f) (lambda (x) (f (f x)))))
lis.py> ((repeat twice) 10)
40
lis.py> ((repeat (repeat twice)) 10)
160
lis.py> ((repeat (repeat (repeat twice))) 10)
2560
lis.py> ((repeat (repeat (repeat (repeat twice)))) 10)
655360
lis.py> (pow 2 16)
65536.0
lis.py> (define fib (lambda (n) (if (< n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))))
lis.py> (define range (lambda (a b) (if (= a b) (quote ()) (cons a (range (+ a 1) b)))))
lis.py> (range 0 10)
(0 1 2 3 4 5 6 7 8 9)
lis.py> (map fib (range 0 10))
(1 1 2 3 5 8 13 21 34 55)
lis.py> (map fib (range 0 20))
(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)
社群福利
扫码添加小助手,回复:大会,加入2019 AI开发者大会福利群,每周一、三、五更新技术福利,还有不定期的抽奖活动~
◆
精彩推荐
◆
60+技术大咖与你相约 2019 AI ProCon!3.5折优惠票,倒计时4天速抢进行中......2019 AI开发者大会将于9月6日-7日在北京举行,这一届AI开发者大会有哪些亮点?一线公司的大牛们都在关注什么?AI行业的风向是什么?2019 AI开发者大会,倾听大牛分享,聚焦技术实践,和万千开发者共成长。
谁偷偷删了你的微信?别慌!Python帮你都揪出来了
干货 | 20个Python教程,掌握时间序列的特征分析(附代码)
吐血整理!140种Python标准库、第三方库和外部工具都有了
Python传奇:30年崛起之路
干货 | Python后台开发的高并发场景优化解决方案