Loading [MathJax]/extensions/tex2jax.js

2010年10月5日火曜日

((Pythonで) 書く (Lisp) インタプリタ)

Pythonで書くSchemeインタプリタです。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
### Env クラス
class Env(dict):
"環境: ペア{'var':val} の dict で、外部環境(outer)を持つ。"
def __init__(self, parms = (), args = (), outer = None):
self.update(zip(parms, args))
self.outer = outer
def find(self, var):
"var が現れる一番内側の Env を見つける"
return self if var in self else self.outer.find(var)
### グローバル環境
def add_globals(env):
"環境に Scheme 標準の手続きのいくつか追加する"
import math, operator as op
env.update(vars(math)) # sin, sqrt, ...
env.update(
{'+':op.add, '-':op.sub, '*':op.mul, '/':op.div, 'not':op.not_,
'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':lambda x, y: [x] + y,
'car':lambda x: x[0], 'cdr':lambda x:x[1:], 'append':op.add,
'list':lambda *x:list(x), 'list?':lambda x:isa(x, list),
'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol)})
return env
global_env = add_globals(Env())
### 実行: eval
def interpret(x, env = global_env):
"環境の中で式を評価する。"
if isa(x, Symbol): # 変数参照
return env.find(x)[x]
elif not isa(x, list): # 定数リテラル
return x
elif x[0] == 'quote': # (quote exp)
(_, exp) = x
return exp
elif x[0] == 'if': # (if test conseq alt)
(_, test, conseq, alt) = x
return interpret((conseq if interpret(test, env) else alt), env)
elif x[0] == 'define': # (define var exp)
(_, var, exp) = x
env[var] = interpret(exp, env)
elif x[0] == 'lambda': # (lambda (var*) exp)
(_, vars, exp) = x
return lambda *args: interpret(exp, Env(vars, args, env))
elif x[0] == 'begin': # (begin exp*)
for exp in x[1:]:
val = interpret(exp, env)
return val
else: # (proc exp*)
exps = [interpret(exp, env) for exp in x]
proc = exps.pop(0)
return proc(*exps)
isa = isinstance
Symbol = str
### 構文解析: read と parse
def read(s):
"文字列から Scheme 式を読み込む。"
return read_from(tokenize(s))
parse = read
def tokenize(s):
"文字列をトークンのリストに変換する。"
return s.replace('(', ' ( ').replace(')', ' ) ').split()
def read_from(tokens):
"トークンの列から式を読み込む。"
if len(tokens) == 0:
raise SyntaxError('unexpected EOF while reading')
token = tokens.pop(0)
if '(' == token:
L = []
while tokens[0] != ')':
L.append(read_from(tokens))
tokens.pop(0) # pop off ')'
return L
elif ')' == token:
raise SyntaxError('unexpected )')
else:
return atom(token)
def atom(token):
"数は数にし、それ以外のトークンはシンボルにする。"
try: return int (token)
except ValueError:
try: return float(token)
except ValueError:
return Symbol(token)
def to_string(exp):
"Python オブジェクトを Lisp の読める文字列に戻す。"
return '('+' '.join(map(to_string, exp))+')' if isa(exp, list) else str(exp)
def repl(prompt='lis.py> '):
"read-eval-print-loop のプロンプト"
while True:
val = interpret(parse(raw_input(prompt)))
if val is not None: print to_string(val)
repl()
view raw lispy.py hosted with ❤ by GitHub

実際は、Schemeとは呼べないんですけどね(笑)。仕様満たしてませんし。かつ単なる写経です(爆)。
作者はPAIPの著者として有名なPeter Norvig。過去はLisperとして有名でしたが、現在はPythonistaとして有名な人です。

「Lisp以外の言語で」Lispを実装してみる、ってのは初めての経験で、結構面白いとは思いました。
ただ、Pythonの構文の細かいトコには明るくないんで、「読解」自体は上手く行ってるとは思いませんが(笑)。んで、やっぱOOPは嫌いです(笑)。

ちなみに、Python2.6.xだとだいぶ改良されてはいますが、やっぱエンコーディングはめんどっちいと思っています。