LoginSignup
2
2

More than 3 years have passed since last update.

簡易LISP処理系の実装例(Python版)

Last updated at Posted at 2020-09-07

【他言語版へのリンク記事】簡易LISP処理系の実装例【各言語版まとめ】

この記事は,下記拙作記事のPython版を抜粋・修正したものを利用した,原初LISP処理系("McCarthy's Original Lisp")の実装例をまとめたものです.

最低限の機能をもったLISP処理系の実装の場合,本体である評価器(eval)実装はとても簡単であり,むしろ,字句・構文解析を行うS式入出力やリスト処理実装の方が開発言語ごとの手間が多く,それが敷居になっている人向けにまとめています.

処理系の概要

実行例は次の通り.Python2ではinput()raw_input()に読み替えて下さい.

$ python3 -i jmclisp.py
>>> s_rep("(car (cdr '(10 20 30)))")
'20'
>>> s_rep(input())
((lambda (x) (car (cdr x))) '(abc def ghi))
'def'
>>> s_rep("((lambda (f x y) (f x (f y '()))) 'cons '10 '20)")
'(10 20)'
>>> s_rep("((lambda (f x y) (f x (f y '())))     \
...        '(lambda (x y) (cons x (cons y '()))) \
...        '10 '20)")
'(10 (20 ()))'
>>> s_rep("((lambda (assoc k v) (assoc k v)) \
...        '(lambda (k v)                    \
...           (cond ((eq v '()) nil)         \
...                 ((eq (car (car v)) k)    \
...                  (car v))                \
...                 ('t (assoc k (cdr v))))) \
...        'Orange \
...        '((Apple . 120) (Orange . 210) (Lemon . 180)))")
'(Orange . 210)'

実装内容は次の通り.

  • "McCarthy's Original Lisp"をベースにした評価器
  • 数字を含むアトムは全てシンボルとし,変数の値とする場合はquote')を使用
  • 構文としてquoteの他,condlambdaが使用可能
  • 組込関数:atom eq cons car cdr(内部でコンスセルを作成)
  • 真偽値はt(真)およびnil(偽)=空リスト=None
  • エラーチェックなし,モジュール化なし,ガーベジコレクションなし

"McCarthy's Original Lisp"の詳細についてはまとめ記事を参照.ダイナミックスコープということもあり,実行例ではlambda式をletrec(Scheme)やlabels(Common Lisp)などの代わりに使用しています.

実装例

ソースコード一式

jmclisp.py
#
# JMC Lisp: defined in McCarthy's 1960 paper,
# with S-expression input/output and basic list processing
#


# basic list processing: cons, car, cdr, eq, atom

def cons(x, y): return (x, y)
def car(s): return s[0]
def cdr(s): return s[1]
def eq(s1, s2): return s1 == s2
def atom(s): return isinstance(s, str) or eq(s, None) or isinstance(s, bool)


# S-expression input: s_read

def s_lex(s):
    for p in "()'": s = s.replace(p, " " + p + " ")
    return s.split()

def s_syn(s):
    def quote(x):
        if len(s) != 0 and s[-1] == "'":
            del s[-1]
            return cons("quote", cons(x, None))
        else: return x
    t = s[-1]
    del s[-1]
    if t == ")":
        r = None
        while s[-1] != "(":
            if s[-1] == ".":
                del s[-1]
                r = cons(s_syn(s), car(r))
            else: r = cons(s_syn(s), r)
        del s[-1]
        return quote(r)
    else: return quote(t)

def s_read(s): return s_syn(s_lex(s))


# S-expression output: s_string

def s_strcons(s):
    sa_r = s_string(car(s))
    sd = cdr(s)
    if eq(sd, None):
        return sa_r
    elif atom(sd):
        return sa_r + " . " + sd
    else:
        return sa_r + " " + s_strcons(sd)

def s_string(s):
    if   eq(s, None):  return "()"
    elif eq(s, True):  return "t"
    elif eq(s, False): return "nil"
    elif atom(s):
        return s
    else:
        return "(" + s_strcons(s) + ")"


# JMC Lisp evaluator: s_eval

def caar(x): return car(car(x))
def cadr(x): return car(cdr(x))
def cadar(x): return car(cdr(car(x)))
def caddr(x): return car(cdr(cdr(x)))
def caddar(x): return car(cdr(cdr(car(x))))

def s_null(x): return eq(x, None)

def s_append(x, y):
    if s_null(x): return y
    else: return cons(car(x), s_append(cdr(x), y))

def s_list(x, y): return cons(x, cons(y, None))

def s_pair(x, y):
    if s_null(x) and s_null(y): return None
    elif (not atom(x)) and (not atom(y)):
        return cons(s_list(car(x), car(y)), s_pair(cdr(x), cdr(y)))

def s_assoc(x, y):
    if eq(caar(y), x): return cadar(y)
    else: return s_assoc(x, cdr(y))

def s_eval(e, a):
    if   eq(e, "t"):   return True
    elif eq(e, "nil"): return False
    elif atom(e): return s_assoc(e, a)
    elif atom(car(e)):
        if   eq(car(e), "quote"): return cadr(e)
        elif eq(car(e), "atom"):  return atom(s_eval(cadr(e), a))
        elif eq(car(e), "eq"):    return eq(  s_eval(cadr(e), a),
                                              s_eval(caddr(e), a))
        elif eq(car(e), "car"):   return car( s_eval(cadr(e), a))
        elif eq(car(e), "cdr"):   return cdr( s_eval(cadr(e), a))
        elif eq(car(e), "cons"):  return cons(s_eval(cadr(e), a),
                                              s_eval(caddr(e), a))
        elif eq(car(e), "cond"):  return evcon(cdr(e), a)
        else: return s_eval(cons(s_assoc(car(e), a), cdr(e)), a)
    elif eq(caar(e), "lambda"):
        return s_eval(caddar(e),
                      s_append(s_pair(cadar(e), evlis(cdr(e), a)), a))
    else: print("Error")

def evcon(c, a):
    if s_eval(caar(c), a): return s_eval(cadar(c), a)
    else: return evcon(cdr(c), a)

def evlis(m, a):
    if s_null(m): return None
    else: return cons(s_eval(car(m), a), evlis(cdr(m), a))


# REP (no Loop): s_rep
def s_rep(e): return s_string(s_eval(s_read(e), s_read("()")))

解説

  • リスト処理:cons car cdr eq atom
    先の記事のタプル定義版をほぼそのまま抜粋.

  • S式入力:s_read
    先の記事から,字句解析部を()および'の識別に変更(s_lex),抽象構文木生成部をドット対とクォート記号対応としつつ,リスト処理関数でコンスセルによる構文木を生成するよう変更(s_syn),それらをまとめたS式入力関数s_readを定義.

  • S式出力:s_string
    S式出力部は新規に作成.内部ではNoneである空リストは()を,真偽値はt nilを出力するよう設定.

  • 評価器:s_eval+ユーティリティ関数
    "McCarthy's Original Lisp"をベースにs_eval関数およびユーティリティ関数を作成.

  • REP (no Loop):s_rep
    s_reads_evals_stringをまとめたs_repを定義.

備考

記事に関する補足

  • 初版のSICP 4.1ベースの評価器実装例(レキシカルスコープ/クロージャ対応)は,GitHubのこちらに移動.なお,評価器のみの場合,SICP 4.1版は約70行/2400バイトほど,現在のjmc.lisp版は約50行/1900バイトほど.

更新履歴

  • 2020-10-21:リポジトリの位置の変更に伴いリンクを修正
  • 2020-09-11:実装例をソースコード一式+解説の構成に変更
  • 2020-09-08:評価器実装を"McCarthy's Original Lisp"ベースに変更
  • 2020-09-07:実行例を修正
  • 2020-09-07:初版公開
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2