{"id":18862630,"url":"https://github.com/zk-phi/philisp","last_synced_at":"2025-04-14T13:05:37.796Z","repository":{"id":25077425,"uuid":"28497858","full_name":"zk-phi/philisp","owner":"zk-phi","description":"WIP zk_phi's experiment towards a simple, happy-hacking language","archived":false,"fork":false,"pushed_at":"2017-11-27T01:34:01.000Z","size":97,"stargazers_count":4,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T02:11:08.676Z","etag":null,"topics":["interpreter"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zk-phi.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-26T03:06:07.000Z","updated_at":"2021-02-13T04:47:32.000Z","dependencies_parsed_at":"2022-08-23T15:40:42.905Z","dependency_job_id":null,"html_url":"https://github.com/zk-phi/philisp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zk-phi%2Fphilisp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zk-phi%2Fphilisp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zk-phi%2Fphilisp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zk-phi%2Fphilisp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zk-phi","download_url":"https://codeload.github.com/zk-phi/philisp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248886310,"owners_count":21177643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["interpreter"],"created_at":"2024-11-08T04:35:19.988Z","updated_at":"2025-04-14T13:05:37.764Z","avatar_url":"https://github.com/zk-phi.png","language":"C","readme":"# φLISP\n\nオレオレ LISP を作ってみたかった\n\n## 目標\n\nシンプルでいて高い表現力、(多少安全性を犠牲にしてでも) 書いていて楽しい\n高い自由度\n\n## 特徴\n\n* LISP 系\n\n* 1-lisp (関数と変数の名前空間は共通)\n\n* 動的束縛が基本、関数閉包は明示的に作る\n\n* 引数を評価するかを関数側で指定できる\n\n* すべてのオブジェクトは適用可能\n  * 後置記法 (メソッドチェイン)、中置記法が書ける\n\n* 関数を作らずに部分適用ができる\n\n* 末尾呼び最適化\n\n* 第一級継続\n\n* Pure C 実装\n\n* ...\n\n## コンパイル・実行\n\n```text\n\u003e\u003e make\n\u003e\u003e ./bin/philisp\n```\n\n## 文字\n\n文字は `?` で表現します。C と同様のエスケープシーケンスを書くことができ\nます。\n\n```text\n\u003e\u003e ?a\n?a\n\n\u003e\u003e ?\\n\n?\\n\n```\n\n## 数値\n\n整数、浮動小数点数の表現は C とだいたい同じです。\n\n```text\n\u003e\u003e 10\n10\n\n\u003e\u003e .1\n1.000000\n\n\u003e\u003e 10.\n10.000000\n\n\u003e\u003e 1e5\n100000\n```\n\n## 配列・文字列\n\n配列は `[]` で表現します。任意の要素へのアクセスが O(1) です。\n\n```text\n\u003e\u003e [1 2 3]\n[1 2 3]\n```\n\n文字列は (ユーザーから見ると) 要素がすべて文字の配列です。\n\n```text\n\u003e\u003e \"hoge\"\n\"hoge\"\n\n\u003e\u003e [?h ?o ?g ?e]\n\"hoge\"\n```\n\n-----\n\n内部的には、文字列と配列は区別して扱われています。なので、例えば文字列\n(文字の配列) の第１要素に整数の 1 を代入する、のような文字列と配列を行\nき来するような操作を激しく行うプログラムは、実行はできますが書かない方\nがいいです。\n\n## コメント\n\n`;` から行末まではコメントとして扱われ、スペースと区別しません。\n\n```text\n\u003e\u003e 1;hogehoge\n1\n```\n\n## 関数呼び出しとリスト\n\n関数呼び出しは、一般的な LISP と同様に、関数と引数を並べて括弧でくくる\nことで表現します。\n\n```text\n(関数 引数 引数 ...)\n```\n\nたとえば、式 `(mod 5 3)` を評価すると `2` が得られます。\n\n```text\n\u003e\u003e (mod 5 3) ;; 5 を 3 で割った余り\n2\n```\n\n一方、 φLISP のもっとも基本的なデータ構造は、これも一般的な LISP と同\n様にリストです。リストは、要素を括弧でくくることで表現します。\n\n```text\n(要素 要素 ...)\n```\n\nこれでは関数呼び出しとリストの見分けがつかなくなってしまうのですが、こ\nれが LISP 系言語の強みでもあります。関数呼び出しを表す式 `(mod 5 3)` は、\n実は `mod` と `5` と `3` のリストです。このリストを「評価すると」、値\n2 が得られます。評価せずにたんにリストを作りたい場合には、リストの前に\nクォートを付けます。\n\n```text\n\u003e\u003e '(mod 5 3)\n(mod 5 3)\n```\n\nたとえばこのリストを、リストの先頭要素を返す関数 `car` に渡すと `mod`\nが得られます。\n\n```text\n\u003e\u003e (car '(mod 5 3))\nmod\n```\n\n## cons と ()\n\nφLISP では、２つのもののペア (ドット対) を次のようにドットで表現します。\n\n```text\n\u003e\u003e '(1 . 2)\n(1 . 2)\n```\n\nあるいは、２つのものをペアにする関数 `cons` を使って表現してもかまいま\nせん。\n\n```text\n\u003e\u003e (cons 1 2)\n(1 . 2)\n```\n\n一方、 `()` は見た目通り、空リストを表すオブジェクトです。\n\n一般的な LISP と同様に、すべての (空でない) リストは、実は `()` と\n`cons` から作られています。たとえば、リスト `(1 2 3)` は、実は `(1\n. (2 . (3 . ())))` の略記です。\n\n```text\n\u003e\u003e '(1 . (2 . (3 . ())))\n(1 2 3)\n```\n\nしたがって、 `cons` はリストに要素を１つ追加する関数にもなっています。\n\n```text\n\u003e\u003e (cons 1 '(2 3))\n(1 2 3)\n```\n\nドット対の第一要素を CAR 部、第二要素を CDR 部といい、ドット対からこれ\nらを取り出す関数 `car`, `cdr` が用意されています。\n\n```text\n\u003e\u003e (car '(1 . 2))\n1\n\n\u003e\u003e (cdr '(1 . 2))\n2\n```\n\nリストはただのドット対だったので、 `car`, `cdr` はリストに対しても使え\nます。このとき、ちょうど `car` はリストの先頭要素を取り出す関数、\n`cdr` はリストから先頭要素を除いた残りを取り出す関数として機能します。\n\n```text\n\u003e\u003e (car '(1 2 3))\n1\n\n\u003e\u003e (cdr '(1 2 3))\n(2 3)\n```\n\n末尾が `()` で終端されていないリストというのを考えることもできます (非\n真性リストとかいうことがあります)。このようなリストを書くために、次の\nような略記法が用意されています。\n\n```text\n\u003e\u003e '(1 . (2 . (3 . 4)))\n(1 2 3 . 4)\n\n\u003e\u003e '(1 2 3 . 4)\n(1 2 3 . 4)\n```\n\n-----\n\nペアから要素を取り出す関数の名前 `car`, `cdr` は歴史的にそうなっている\nのだそうで、 `head`, `tail` のほうがいいという人もよくいます。が、やっ\nぱり `car`, `cdr` が僕にはしっくりきました。\n\n## シンボル\n\n一般的な言語で \"変数名\" や \"関数名\" に相当するのがシンボルです。たとえ\nばリストの例で出てきた `mod` はシンボルです。\n\n```text\n\u003e\u003e (car '(mod 5 3))\nmod\n```\n\nシンボルは、評価するとそのシンボルの指す値が得られるオブジェクトです。\n試しに `mod` を評価してみると、組込み関数 mod のオブジェクトが得られま\nす。\n\n```text\n\u003e\u003e mod\n#\u003csubr:2 subr_mod\u003e\n```\n\nシンボルもリストと同様に、クォートを付けることで \"シンボル自体\" を表現\nすることができます。\n\n```text\n\u003e\u003e 'mod\nmod\n```\n\nシンボルを値に束縛する (≒変数に値を代入する) ために、関数 `bind!` を用\nいます。たとえばシンボル `hoge` を値 `1` に束縛するには、 `(bind!\n'hoge 1)` を評価します。\n\n```text\n\u003e\u003e (bind! 'hoge 1)\n1\n```\n\nいま、 `hoge` を評価すれば `1` が得られ、 `(+ hoge 2)` を評価すれば\n`3` が得られます。\n\n```text\n\u003e\u003e hoge\n1\n\n\u003e\u003e (+ hoge 2)\n3\n```\n\nシンボルもただのデータなので、シンボルに束縛することができます。\n\n```text\n\u003e\u003e (bind! 'hoge 'fuga)\nfuga\n\n\u003e\u003e hoge\nfuga\n```\n\n見かけによらず、 φLISP のシンボルは名前を持っていません。代わりに、シ\nンボルテーブルと呼ばれる、名前からシンボルへの単射があります (すべての\n名前には対応するシンボルがあるが、すべてのシンボルに対応する名前がある\nとは限らない)。一般的な LISP にもシンボルテーブルはありますが、ふつう、\nシンボルのオブジェクトもまた名前を保持しています。\n\n名前からシンボルを取得するには関数 `intern` を、どの名前にも割り当てら\nれていないシンボルを１つ取得するには関数 `gensym` を使います。\n\n```text\n\u003e\u003e (intern \"hoge\")\nhoge\n\n\u003e\u003e (gensym)\n#\u003csymbol 0x600083fc0\u003e\n```\n\n古い LISP とは異なり、 `(intern \"hoge\")` と `(intern \"HOGE\")` は別のシ\nンボルを返します。\n\n-----\n\n変数を束縛する関数の名前は `set!` と `bind!` で迷っています。どうしよう\nかな。\n\n## eval\n\n(クォートされた) データを評価したくなったら、データの前にカンマを付けま\nす。\n\n```text\n\u003e\u003e '(mod 5 3)\n(mod 5 3)\n\n\u003e\u003e ,'(mod 5 3)\n2\n```\n\n```text\n\u003e\u003e (bind! 'foo 1)\n1\n\n\u003e\u003e (bind! 'bar 'foo)\nfoo\n\n\u003e\u003e (bind! 'baz 'bar)\nbar\n\n\u003e\u003e 'baz\nbaz\n\n\u003e\u003e baz\nbar\n\n\u003e\u003e ,baz\nfoo\n\n\u003e\u003e ,,baz\n1\n```\n\nリストやシンボル以外は、何度評価しても自分自身に評価されます\n(self-evaluating な値とかいいます)。\n\n```text\n\u003e\u003e ,1\n1\n```\n\n## 関数\n\n一般的な LISP と同様に、 φLISP では関数は第一級のオブジェクトです。関\n数を作るために、組み込み関数 `fn` を使います。\n\n```text\n\u003e\u003e (fn (x y) (+ x y))\n#\u003cfunc:2 (+ ...)\u003e\n```\n\nこの関数は、２つの引数 `x`, `y` を受け取って、その和を返す関数です。ためし\nに `2`, `3` を渡してみます。\n\n```text\n\u003e\u003e ((fn (x y) (+ x y)) 2 3)\n5\n```\n\n関数も他の値と同じようにシンボルに束縛できます。\n\n```text\n\u003e\u003e (bind! 'add (fn (x y) (+ x y)))\n#\u003cfunc:2 (+ ...)\u003e\n\n\u003e\u003e (add 1 2)\n3\n```\n\n不特定多数の引数を受け取る関数を、関数の引数リストに非真性リストを書く\nことで表現できます。\n\n```text\n\u003e\u003e (fn (x . y) x)\n#\u003cfunc:1+ 0x600081ee0\u003e\n\n\u003e\u003e ((fn (x . y) x) 1 2 3 4) ;; 先頭が x\n1\n\n\u003e\u003e ((fn (x . y) y) 1 2 3 4) ;; 残りが y\n(2 3 4)\n```\n\n関数の中では、シンボル `self` がその関数自身に束縛されます。これを利用\nして再帰関数を簡単に書くことができます。\n\n```text\n\u003e\u003e (bind! 'fact (fn (n) (if (= n 0) 1 (* n (self (- n 1))))))\n#\u003cfunc:1 (if ...)\u003e\n\n\u003e\u003e (fact 10)\n3628800\n```\n\n-----\n\n関数を作るマクロ (φLISP では関数) の名前 `fn` は UnCL を参考にしました\n(どうでもいいけど、Scheme も元々は \"an uncommon lisp\" を名乗っていまし\nた)。 `lambda` と迷いましたが、よく書くのでやっぱり短い方がいいですね。\n\n可変長引数の構文は Scheme から借りています。とてもきれいな構文で気に入っ\nています (が、オプショナル引数が書けないのは少し不便かも)。\n\n## 名前受け取りをする関数\n\nφLISP では、引数を評価するか否かを関数側から指定することができます。\n引数リストの中で、評価せずに受け取りたい引数にカンマを付けます。\n\n```text\n\u003e\u003e ((fn (x) x) car)  ;; car に束縛されている関数が渡される\n#\u003csubr:1 subr_car\u003e\n\n\u003e\u003e ((fn (,x) x) car) ;; シンボル car 自身が渡される\ncar\n```\n\nこれを使って、たとえば次のような関数を定義することができます。\n\n```text\n\u003e\u003e (bind! 'define (fn (,var val) (bind! var val)))\n#\u003cfunc:2 (bind! ...)\u003e\n\n\u003e\u003e (define fuga 1)\n1\n\n\u003e\u003e fuga\n1\n```\n\n`(define a b)` はちょうど `(bind! 'a b)` と等価です。\n\n-----\n\nφLISP には、名前受け取り関数がある代わりに、マクロはありません。古い\nLISP の fexpr に近いかな？\n\n名前受け取り関数はマクロではないので、一般的な LISP とは異なり、\n`apply` できたりします。\n\n```text\n(apply define '(a 1))\n```\n\n関数の中で名前受け取り関数が呼ばれたときの挙動が、わかりにくいし使いづ\nらいので、現在の挙動は暫定です。\n\n```text\n\u003e\u003e (bind! 'map (fn (f l) (if l (cons (f (car l)) (map f (cdr l))) ())))\n#\u003cfunc:2 (if ...)\u003e\n\n\u003e\u003e (map quote '(1 2 3))\n((car l) (car l) (car l))\n```\n\n## 条件分岐\n\n組込み関数 `if` は３つの引数を取り、そのうち後ろの２つは名前受け取りを\nします。\n\n```text\n(fn (a ,b ,c) ...)\n```\n\nもし第一引数の評価値が `()` であれば式 `c` を、そうでなければ式 `b` を\n評価し、得られた値を返します。\n\n```text\n\u003e\u003e (if () 1 2)\n2\n\n\u003e\u003e (if 'a 1 2)\n1\n```\n\n-----\n\n`#f` (false を表す特別な値) 以外はすべて真 (`()` も真) 、とする LISP も\nありますが、リスト処理などでなんだかんだ便利なので `()` を偽にしました。\n\n## 変数束縛の戦略\n\nφLISP の変数はデフォルトで動的に束縛されます。たとえば\n\n```text\n\u003e\u003e (bind! 'x 1)                ;; x = 1\n1\n\n\u003e\u003e (bind! 'f (fn (y) (+ x y))) ;; f(y) = x + y\n#\u003cfunc:1 (+ ...)\u003e\n```\n\nという関数を定義したとき、\n\n```text\n\u003e\u003e ((fn (x) (f 1)) 10) ;; x を 10 として f(10) を計算\n11\n```\n\nになります (束縛が静的であれば、この式の評価値は `2` です)。\n\n動的束縛なので、名前のある再帰関数は実は `self` を用いなくても書けます。\n\n```text\n\u003e\u003e (bind! 'fact (fn (n) (if (= n 0) 1 (* n (fact (- n 1))))))\n#\u003cfunc:1 (if ...)\u003e\n\n\u003e\u003e (fact 5)\n120\n```\n\nデフォルトで動的束縛であることによって、すでに定義されている関数の挙動\nを後から変えたり、あるいは再定義したりすることが簡単にできます。もちろ\nんうかつに使うとバグのもとになる危険な機能ですが、ここでは安全性よりも\n自由度、楽しさを優先しました。\n\nたとえば、式を評価する関数 `eval` を、何もしない関数で上書きすると、\n\n```text\n\u003e\u003e (bind! 'eval (fn (x) x))\n#\u003cfunc:1 0x600082120\u003e\n\n\u003e\u003e a\na\n\n\u003e\u003e (+ 1 2)\n(+ 1 2)\n```\n\nもはや式は評価されなくなります。\n\n同様に、たとえばもし `()` が偽になるのが気に食わなければ `if` を再定義\nするなど、言語自体を目的や好みに合わせて変化させることができます。\n\n## クロージャー\n\n関数 `closure` は１つのオブジェクトを受け取り、 `closure` が呼ばれた時\n点での環境 (変数束縛の一覧) と与えられたオブジェクトをペアにした新しい\nオブジェクトを作ります (一般的な言語と異なり、関数以外のクロージャーを\n作ることもできます)。\n\n`closure` のもっとも基本的な使い方は、関数を `closure` で囲むことで静的\n束縛な関数 (関数閉包) を作ることです。\n\n```text\n\u003e\u003e (bind! 'x 1)\n1\n\n\u003e\u003e (bind! 'f (closure (fn (y) (+ x y))))\n#\u003cclosure:1 0x600081930\u003e\n\n\u003e\u003e ((fn (x) (f 1)) 10) ;; f には x が 1 である、という環境が保存されている\n2\n```\n\n関数閉包を利用すると、たとえば状態を持った関数を作ることができます。\n\n```text\n\u003e\u003e ((fn (x) (bind! 'count (closure (fn () (bind! 'x (+ x 1)))))) 0)\n#\u003cclosure:0 0x600087b70\u003e\n\n\u003e\u003e (count)\n1\n\n\u003e\u003e (count)\n2\n\n\u003e\u003e (count)\n3\n\n\u003e\u003e (bind! 'x 10)\n10\n\n\u003e\u003e (count)\n4\n```\n\n再帰的な関数閉包を作るためには、その関数閉包に名前が付いていても\n`self` を用いる必要があることに注意します。なぜならば、クロージャーに保\n存される環境からは、これから付けられるところの名前は見えないからです。\n\n```text\n\u003e\u003e (bind! 'fact (closure (fn (n) (if (= n 0) 1 (* n (fact (- n 1)))))))\n#\u003cclosure #\u003cfunc:1 (if ...)\u003e\u003e\n\n\u003e\u003e (fact 10) ;; クロージャーの中からは fact が見えない\nERROR: reference to unbound symbol.\n```\n\n一般的な言語と異なり、関数以外にリストやシンボルのクロージャーを作るこ\nともできます。シンボルやリストのクロージャーを評価すると、保存された環\n境の中でそのシンボルやリストを評価したときの評価値が得られます。\n\n```text\n\u003e\u003e (bind! 'x 1)\n1\n\n\u003e\u003e (bind! 'expr '(+ x 1))\n(+ x 1)\n\n\u003e\u003e ,expr\n2\n\n\u003e\u003e (bind! 'expr2 (closure expr))\n#\u003cclosure (+ x 1)\u003e\n\n\u003e\u003e ((fn (x) ,expr) 10)\n11\n\n\u003e\u003e ((fn (x) ,expr2) 10)\n2\n```\n\nリストのクロージャーも、通常のリストと同様に `car` や `cdr` を取ること\nができます。\n\n```text\n\u003e\u003e (car (closure '(1 2 3)))\n1\n```\n\n## 制御構造 / 第一級継続\n\n`if` 以外の制御構造は `call-cc` だけです。だけですが、こいつは大域脱出\nからコルーチンまでこなすすごい奴です。 C の `longjmp` の上位互換といえ\nばすごさが伝わるかもしれません。僕にはうまく説明できないので、詳しくは\n他の資料を探してください。\n\n```text\n\u003e\u003e (call-cc (fn (cc) (+ 1 (cc 2))))\n2\n```\n\n-----\n\n`unwind-protect` は未実装ですが、いずれ実装します。\n\n動的に束縛された変数があるときの CPS 変換がちょっと怖かったので、CPS変\n換ではなくコールスタックを自前で管理する方法で実装しました。C の関数呼\nび出しのしくみを使えないので、 `eval` が `goto` まみれになって大変でし\nた。\n\n## ファイル IO\n\n省略。 Scheme のポートっぽい感じのことができ〼。\n\n## 共有オブジェクトのロード\n\n`dlsubr` 関数によって DLL を動的にロードし、 φLISP の関数として呼び出\nすことができます。\n\n```text\n\u003e\u003e (bind! 'sin (dlsubr \"./lib/libmath.a\" \"math_sin\"))\n#\u003csubr:1 math_sin\u003e\n\n\u003e\u003e (sin (div 3.14 2))\n1.000000\n```\n\nこれによって、 φLISP だけで記述できないような処理や速度の要求される処\n理をあらかじめ他言語で実装・コンパイルしておき、これを φLISP から利用\nすることができます。\n\n## 例外の扱い\n\n制御構造は `call-cc` だけなので、例外処理のしくみは原則ありません。かわ\nりに、失敗する可能性のある関数はエラー時に呼び出されるオブジェクトを\n(任意に) 受け取ります。\n\nたとえば、シンボルの束縛されている値を返す関数 `bound-value` は、シン\nボルが未束縛であればエラーになります。\n\n```text\n\u003e\u003e (bound-value 'hoge)\nERROR: reference to unbound symbol.\n```\n\nが、 `bound-value` は２つ目の引数として、エラー時に呼び出される関数を与\nえることができます。シンボルが未束縛のとき、失敗時の関数が与えられてい\nるならば、この関数をエラーメッセージを引数として呼び出した結果が全体の\n結果になります (引数の仕様は暫定)。\n\n```text\n\u003e\u003e (bound-value 'hoge (fn (x) x))\n\"reference to unbound symbol\"\n```\n\nたとえばエラー時の関数として `(fn (x) ())` を与えれば、「失敗時には\n`()` を返す」という挙動を、\n\n```text\n\u003e\u003e (bound-value 'hoge (fn (x) ()))\n()\n```\n\nあるいは継続を与えれば例外処理のような大域脱出を表現できます。\n\n```text\n\u003e\u003e (call-cc (fn (cc) (+ 1 (bound-value 'hoge cc))))\n\"reference to unbound symbol.\"\n```\n\n## 末尾呼びの最適化\n\nφLISP の処理系は真性に末尾再帰的です。すなわち、関数呼び出しを行うとき、\n本当に必要がある場合にだけスタックを消費します。\n\nたとえば次のコードは (GC の実装が終われば) 、スタックオーバーフローせず\nに無限ループします (してほしい)。\n\n```text\n\u003e\u003e (bind! 'loop (fn () (loop)))\n#\u003cfunc:0 (loop ...)\u003e\n\n\u003e\u003e (loop)\n```\n\n加えて、eval(`,`) 内の式の末尾位置での呼び出しも最適化されます。これは\nインタプリタならではな気がします。たぶん。\n\n```text\n\u003e\u003e (bind! 'loop (fn () ,'(loop)))\n#\u003cfunc:0 (eval ...)\u003e\n```\n\n-----\n\n動的束縛では末尾呼びの最適化は難しいんじゃないかと一瞬思ったのですが、\nどうやらできるらしいことが LISP の古文書に載っていました\n( http://ci.nii.ac.jp/naid/110002720392 )。\n\n## Codez comme vous voulez\n\n楽しい、自由度の高い言語にするために、 φLISP ではほかの LISP では認め\nていないような書き方をいくつか認めています。\n\n### 関数オブジェクトを作らない部分適用\n\n関数適用において、引数の数が足りないとき、一般的な LISP ではエラーにな\nります。 φLISP では、これを部分適用として扱います。\n\nたとえば、 `(cons 1)` は `(fn (x) (cons 1 x))` と等価です。\n\n```text\n\u003e\u003e (cons 1)\n#\u003cfunc:(pa/#\u003csubr:2 subr_cons\u003e)\u003e\n\n\u003e\u003e ((cons 1) 2)\n(1 . 2)\n```\n\n-----\n\n内部的には、部分適用は関数オブジェクトとは別の特別なオブジェクトで管理\nされているので、 `(cons 1)` はむしろ `(fn (x) (cons 1 x))` よりも効率が\nいいです。\n\n### 中置記法\n\nたとえば `(f 1)` をうっかり `(1 f)` と書いてしまうことはまずありません。\nそれならば、 `(1 f)` をエラーとするのではなく、この記法にもなにか意味を\n与えたらどうか、というのが基本的なアイデアです。\n\nφLISP では、整数・浮動小数点数に対する関数呼び出しを、次のルールで評価\nします。\n\n```text\n(1 f x ...) = ((f 1 x) ...)\n(1 f)       = (fn (y) (f 1 y))\n(1)         = 1\n```\n\nたとえば、\n\n```text\n(1 + 2 + 3) = ((+ 1 2) + 3) = (3 + 3) = ((+ 3 3)) = (6) = 6\n```\n\nという具合です。これによって、 LISP の苦手な (?) 数式を、中置記法で書く\nことができます (そうするかどうかは自由です)。ただし、すべての演算子の優\n先度が同じかつ左結合であることに注意してください。\n\n２番目のルールは、数値の後に関数が１つだけ続いた場合には、これを後置記\n法ではなく中置記法の部分適用として扱うことを表しています。この性質を使\nうと、たとえば次のようなコード\n\n```text\n(filter (fn (x) (\u003c 0 x)) '(-1 0 1 2)) =\u003e '(1 2)\n```\n\nを\n\n```text\n(filter (0 \u003c) '(-1 0 1 2))\n```\n\nと書くことができます。\n\n### 後置記法\n\nφLISP では、関数でも整数・浮動小数点数でもないオブジェクトの関数呼び出\nしを、次のようなルールで評価します。\n\n```text\n('(1 2 3) f ...) = ((f '(1 2 3)) ...)\n('(1 2 3))       = '(1 2 3)\n```\n\nたとえば、\n\n```text\n('(1 2 3) cdr car) = ((cdr '(1 2 3)) car) = ('(2 3) car) = ((car '(2 3))) = (2) = 2\n```\n\nとなります。これによって、メソッドチェインのような書き方ができます。\n\n```text\n(\"hoge fuga piyo\" (split-string ?\\s) car) =\u003e \"hoge\"\n```\n\n### 述語はなるべく意味のある値を返す\n\n引数を受け取って true または false (LISP 的には、 `()` かそれ以外) を返\nす組込み関数は、なるべく意味の値を返すようになっています。たとえば、\n`(\u003c 1 2)` (中置記法を使えば、 `(1 \u003c 2)`) の評価値は `2` です。この性質\nは次のように利用できます。\n\n```text\n\u003e\u003e (1 \u003c 2 \u003c 3)\n3\n\n\u003e\u003e (1 \u003c 2 \u003c 2)\n()\n```\n\n### シンボル以外のオブジェクトの束縛\n\n一般的な LISP と異なり、 φLISP ではシンボル以外のオブジェクトも値に束\n縛することができます。束縛された値は関数 `bound-value` で取り出すことが\nできます。\n\n```text\n\u003e\u003e (bind! 'a 1)\n1\n\n\u003e\u003e (bind! a 2)\n2\n\n\u003e\u003e (bound-value 'a)\n1\n\n\u003e\u003e (bound-value a)\n2\n```\n\n何に使うかはまだよく考えていませんが、いつか何かの役に立つかもしれませ\nん。\n\n## 組込み関数の全一覧\n\nソースコードからの抽出です。読みにくくてごめんなさい。\n\n(nil? O) =\u003e an unspecified non-() value if O is (), or () otherwise.\n\n(symbol? O) =\u003e O if O is a symbol, or () otherwise.\n\n(gensym) =\u003e an uninterned symbol.\n\n(intern NAME) =\u003e a symbol associated with NAME.\n\n(bind! O1 [O2]) =\u003e bind O1 to object O2 in the innermost scope and\nreturn O2. if O2 is omitted, bind O1 to ().\n\n(bound-value O [ERRORBACK]) =\u003e object which O is bound to. if O is\nunbound, call ERRORBACK with error message, or error if ERRORBACK is\nomitted.\n\n(character? O) =\u003e O if O is a character, or () otherwise.\n\n(char-\u003eint CHAR) =\u003e ASCII encode CHAR.\n\n(int-\u003echar N) =\u003e ASCII decode N.\n\n(integer? O) =\u003e O if O is an integer, or () otherwise.\n\n(float? O) =\u003e O if O is a float, or () otherwise.\n\n(mod INT1 INT2) =\u003e return (INT1 % INT2).\n\n(/ INT1 INT2 ...) =\u003e return (INT1 / INT2 / ...).\n\n(round NUM) =\u003e the largest integer no greater than NUM.\n\n(+ NUM1 ...) =\u003e sum of NUM1, NUM2, ... . result is an integer iff\nNUM1, NUM2, ... are all integer.\n\n(* NUM1 ...) =\u003e product of NUM1, NUM2, ... . result is an integer iff\nNUM1, NUM2, ... are all integer.\n\n(- NUM1 NUM2 ...) =\u003e negate NUM1 or subtract NUM2 ... from\nNUM1. result is an integer iff NUM1, NUM2 ... are all integers.\n\n(div NUM1 NUM2 ...) =\u003e invert NUM1 or divide NUM1 with NUM2\n... . result is always a float.\n\n(\u003c= NUM1 ...) =\u003e last number if NUM1 ... is weakly increasing, or ()\notherwise. if no numbers are given, return an unspecified non-()\nvalue.\n\n(\u003c NUM1 ...) =\u003e last number if NUM1 ... is strongly increasing, or ()\notherwise. if no numbers are given, return an unspecified non-()\nvalue.\n\n(\u003e= NUM1 ...) =\u003e last number if NUM1 ... is weakly decreasing, or ()\notherwise. if no numbers are given, return an unspecified non-()\nvalue.\n\n(\u003e NUM1 ...) =\u003e last number if NUM1 ... is strongly decreasing, or ()\notherwise. if no numbers are given, return an unspecified non-()\nvalue.\n\n(stream? O) =\u003e O if O is a stream, or () otherwise.\n\n(input-port) =\u003e current input port, which defaults to stdin.\n\n(output-port) =\u003e current output port, which defaults to stdout.\n\n(error-port) =\u003e current error port, which defaults to stderr.\n\n(set-ports [ISTREAM OSTREAM ESTREAM]) =\u003e change input port to ISTREAM\n(resp. output port, error port). some of arguments can be omitted or\n(), which represents \"no-change\". (return value is unspecified)\n\n(getc [ERRORBACK]) =\u003e get a character from input port. on failure,\nERRORBACK is called with error message, or error if ERRORBACK is\nomitted.\n\n(putc CHAR [ERRORBACK]) =\u003e write CHAR to output port and return\nCHAR. on failure, ERRORBACK is called with error message, or error if\nERRORBACK is omitted.\n\n(puts STRING [ERRORBACK]) =\u003e write STRING to output port and return\nSTRING. on failure, ERRORBACK is called with error message, or error\nif ERRORBACK is omitted.\n\n(ungetc CHAR [ERRORBACK]) =\u003e unget CHAR from input stream and return\nCHAR. when this subr is called multiple times without re-getting the\nungot char, behavior is not guaranteed. on failure, ERRORBACK is\ncalled with error message, or error if ERRORBACK is omitted.\n\n(open FILE [WRITABLE APPEND BINARY ERRORBACK]) =\u003e open a stream for\nFILE. if WRITABLE is omitted or (), open FILE in read-only\nmode. (resp. APPEND, BINARY)\n\n(close! STREAM [ERRORBACK]) =\u003e close STREAM (return value is\nunspecified). on failure, ERRORBACK is called with error message, or\nerror if ERRORBACK is omitted.\n\n(cons? O) =\u003e O if O is a pair, or () otherwise.\n\n(cons O1 O2) =\u003e pair of O1 and O2.\n\n(car PAIR) =\u003e CAR part of PAIR. if PAIR is (), return ().\n\n(cdr PAIR) =\u003e CDR part of PAIR. if PAIR is (), return ().\n\n(setcar! PAIR NEWCAR) =\u003e set CAR part of PAIR to NEWCAR. return NEWCAR.\n\n(setcdr! PAIR NEWCDR) =\u003e set CDR part of PAIR to NEWCDR. return NEWCDR.\n\n(array? O) =\u003e O if O is an array, or () otherwise.\n\n(make-array LENGTH [INIT]) =\u003e make an array of LENGTH slots which\ndefaults to INIT. if INIT is omitted, initialize with () instead.\n\n(aref ARRAY N) =\u003e N-th element of ARRAY. error if N is negative or\ngreater than the length of ARRAY.\n\n(aset! ARRAY N O) =\u003e set N-th element of ARRAY to O and return\nO. error if O is negative or greater than the length of ARRAY.\n\n(string? O) =\u003e O if O is a char-array, or () otherwise.\n\n(function? O) =\u003e O iff O is a function, or () otherwise.\n\n(fn ,FORMALS ,EXPR) =\u003e a function.\n\n(closure? O) =\u003e O iff O is a function, or () otherwise.\n\n(closure FN) =\u003e make a closure of function FN.\n\n(subr? O) =\u003e O iff O is a subr (a compiled function), or () otherwise.\n\n(dlsubr FILENAME SUBRNAME [ERRORBACK]) =\u003e load SUBRNAME from\nFILENAME. on failure, ERRORBACK is called with error message, or error\nif ERRORBACK is omitted.\n\n(continuation? O) =\u003e O iff O is a continuation object, or ()\notherwise.\n\n(eq O1 ...) =\u003e an unspecified non-() value if O1 ... are all the same\nobject, or () otherwise.\n\n(char= CH1 ...) =\u003e last char if CH1 ... are all equal as chars, or ()\notherwise. if no characters are given, return an unspecified non-()\nvalue.\n\n(= NUM1 ...) =\u003e last number if NUM1 ... are all equal as numbers, or\n() otherwise. if no numbers are given, return an unspecified non-()\nvalue.\n\n(print O) =\u003e print string representation of object O to output port\nand return O.\n\n(read [ERRORBACK]) =\u003e read an S-expression from input port. on\nfailure, ERRORBACK is called with error message, or error if ERRORBACK\nis omitted.\n\n(if COND ,THEN [,ELSE]) =\u003e if COND is non-(), evaluate THEN, else\nevaluate ELSE. if ELSE is omitted, return ().\n\n(evlis PROC EXPRS) =\u003e evaluate list of expressions in accordance with\nevaluation rule of PROC.\n\n(apply PROC ARGS) =\u003e apply ARGS to PROC.\n\n(unwind-protect ,BODY ,AFTER) =\u003e evaluate BODY and then AFTER. when a\ncontinuation is called in BODY, evaluate AFTER before winding the\ncontinuation.\n\n(call-cc FUNC) =\u003e evaluate BODY and then AFTER. when a continuation is\ncalled in BODY, evaluate AFTER before winding the continuation.\n\n(eval O [ERRORBACK]) =\u003e evaluate O. on failure, call ERRORBACK with\nerror message, or error if ERRORBACK is omitted.\n\n(quote ,O) =\u003e O.\n\n(error MSG) =\u003e print MSG to error port and quit.\n\n## 今後やること\n\n今の時点では完全にオモチャ処理系です。いろいろ直さねば…。\n\n### バグ\n\n* 保存した継続を起動すると引数が多すぎると言われる\n  * 戻りがけで継続を破壊的に変更してるから\n\n```text\n(print (call-cc (fn (cc) (bind! 'print2 cc))))\n(print2 'hoge)\n```\n\n* ERRORBACK を受け取る関数群で ERRORBACK が呼ばれると継続が吹き飛ぶ\n* \"eval\" をリエントラントにになっていないせい\n\n### 要検討\n\n* 名前呼びのセマンティクス\n  * Y コンビネータを名前呼びすると無限ループ？\n    * 再帰呼ばれた Y から見た f が Y になってる\n    * 名前渡された式は名前渡した側の環境でエバらないと変数捕捉が起きる\n  * Scala の名前呼びは実はサンク (クロージャー) を渡してる\n    * けどそれでは同図像性が生かせない\n  * 環境オブジェクトを扱えるようにするべき (そうするとクロージャーはいらない)\n    * fexpr はそうなってる\n\n```text\n(bind! 'Y (closure (fn (f) (f (Y f)))))\n(bind! 'fact (Y (closure (fn (,f n) (if (n = 0) 1 (n * (,f (n - 1))))))))\n(fact 1)\n```\n\n* int や float もすべてオブジェクトなので効率が悪い\n\n* シンプルさをもとめるなら、配列とか文字列は実はいらない？\n  * PicoLisp は配列を捨てた\n    * 文字列はシンボル、操作するときには文字のリストに変換\n    * 全てのオブジェクトはアトムかコンス\n\n* `(1 1)` が無限ループ\n\n### 未実装\n\n* evlis, unwind-protect\n\n* GC\n\n* エラー処理\n\n## 影響を受けた言語\n\n* Emacs Lisp\n* Scheme\n* TAO\n* CommonLisp\n* Smalltalk\n* Lisp 1.5\n* NewLisp\n* UnCL\n* Haskell\n* Scala\n\n* Picolisp?\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzk-phi%2Fphilisp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzk-phi%2Fphilisp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzk-phi%2Fphilisp/lists"}