Tiny Lisp、小さな通訳


33

Lispプログラマーは、Lispは非常に小さなプリミティブ操作のセットから構築できる強力な言語であることを自慢しています。と呼ばれる方言の通訳をゴルフで練習して、そのアイデアを実践しましょうtinylisp

言語仕様

この仕様では、結果が「未定義」と記述されている条件は、インタープリターで何でもできます:クラッシュ、サイレントフェール、ランダムgobbldegookの生成、または期待どおりの動作。Python 3のリファレンス実装はこちらから入手できます

構文

tinylispでのトークンは()、または括弧やスペースを除く一個の以上の印刷可能なASCII文字の任意の文字列。(つまり、次の正規表現:[()]|[^() ]+。)数字のみで構成されるトークンは、整数リテラルです。(先頭のゼロは大丈夫です。)非数字を含むすべてのトークンは、のようにも、数字に見える例、シンボルである123abc3.14-10。トークンを区切る場合を除き、すべての空白(少なくとも、ASCII文字32および10を含む)は無視されます。

tinylispプログラムは一連の式で構成されています。各式は、整数、記号、またはs式(リスト)のいずれかです。リストは、括弧で囲まれた0個以上の式で構成されます。アイテム間にセパレータは使用されません。式の例を次に示します。

4
tinylisp!!
()
(c b a)
(q ((1 2)(3 4)))

整形式でない式(特に、括弧が一致しない式)は、未定義の動作をもたらします。(参照実装は、開いているかっこを自動的に閉じ、一致しない閉じたかっこで解析を停止します。)

データ型

tinylispのデータ型は、整数、記号、およびリストです。組み込み関数とマクロも型と見なすことができますが、出力形式は未定義です。リストには、任意のタイプの値をいくつでも含めることができ、任意の深さにネストできます。整数は、少なくとも-2 ^ 31〜2 ^ 31-1でサポートされる必要があります。

空のリスト(()nilとも呼ばれる)と整数0は、論理的に偽と見なされる唯一の値です。他のすべての整数、空でないリスト、ビルトイン、およびすべてのシンボルは論理的に真です。

評価

プログラム内の式は順番に評価され、それぞれの結果はstdoutに送信されます(出力のフォーマットについては後で説明します)。

  • 整数リテラルはそれ自体に評価されます。
  • 空のリスト()はそれ自体に評価されます。
  • 1つ以上の項目のリストは、最初の項目を評価し、それを関数またはマクロとして扱い、残りの項目を引数として呼び出します。アイテムが関数/マクロでない場合、動作は未定義です。
  • シンボルは名前として評価され、現在の関数でその名前にバインドされた値を提供します。名前が現在の関数で定義されていない場合、グローバルスコープでバインドされた値に評価されます。名前が現在のスコープまたはグローバルスコープで定義されていない場合、結果は未定義です(参照実装ではエラーメッセージが表示され、nilが返されます)。

組み込み関数とマクロ

tinylispには7つの組み込み関数があります。関数は、いくつかの操作を適用して結果を返す前に、各引数を評価します。

  • c-cons [truct list]。値とリストの2つの引数を取り、リストの先頭に値を追加して取得した新しいリストを返します。
  • h-頭(、Lisp用語では)。リストを取得し、リストの最初の項目を返します。nilが指定されている場合はnilを返します。
  • t-テール(Lisp用語ではcdr)。リストを取得し、最初のアイテム以外のすべてを含む新しいリストを返します。nilが指定されている場合はnilを返します。
  • s-引く。2つの整数を取り、最初のマイナス2番目を返します。
  • l- 未満。2つの整数を取ります。最初の値が2番目の値より小さい場合は1を返し、それ以外の場合は0を返します。
  • e-等しい。同じタイプの2つの値(整数、両方のリスト、または両方のシンボル)を取ります。2つが等しい(またはすべての要素で同一)場合は1を返し、そうでない場合は0を返します。組み込みの等価性のテストは未定義です(参照実装は期待どおりに機能します)。
  • v-評価。式を表す1つのリスト、整数、またはシンボルを取り、それを評価します。たとえば、行うこと(v (q (c a b)))は行うことと同じ(c a b)です。(v 1)与える1

ここでの「値」には、特に指定がない限り、リスト、整数、シンボル、または組み込みが含まれます。関数が特定の型を取るようにリストされている場合、異なる型を渡すことは、間違った数の引数を渡すことと同様に未定義の動作です(参照実装は通常クラッシュします)。

tinylispには3つの組み込みマクロがあります。マクロは、関数とは異なり、操作を適用する前に引数を評価しません。

  • q- 見積もり。1つの式を受け取り、評価せずに返します。たとえば、関数またはマクロとして(1 2 3)呼び出そうとしますが、listを返すため、評価はエラーになります。評価すると、name にバインドされた値が得られますが、名前自体が得られます。1(q (1 2 3))(1 2 3)aa(q a)
  • i-場合。条件、iftrue式、iffalse式の3つの式を取ります。最初に条件を評価します。結果が偽(0またはnil)の場合、iffalse式を評価して返します。それ以外の場合、iftrue式を評価して返します。返されない式は評価されないことに注意してください。
  • d-def。シンボルと式を取ります。式を評価し、グローバルスコープで名前として扱われる特定のシンボルにバインドし、そのシンボルを返します。名前を再定義しようとすると失敗します(黙って、メッセージが表示されるか、クラッシュします。参照実装はエラーメッセージを表示します)。注:を渡す前に名前を引用符で囲む必要はありdませんが、評価したくないリストまたはシンボルの場合は式を引用符で囲む必要があります(d x (q (1 2 3)))

間違った数の引数をマクロに渡すと、未定義の動作になります(参照実装がクラッシュします)。の最初の引数としてシンボルではないものを渡すことdは、未定義の動作です(参照実装ではエラーは発生しませんが、その後値を参照することはできません)。

ユーザー定義の関数とマクロ

これらの10個のビルトインから開始して、新しい関数とマクロを作成することで言語を拡張できます。これらには専用のデータ型はありません。それらは特定の構造を持つ単なるリストです。

  • 関数は、2つのアイテムのリストです。1つ目は、1つ以上のパラメーター名のリスト、または関数に渡される引数のリストを受け取る単一の名前のいずれかです(したがって、可変アリティ関数が許可されます)。2番目は、関数の本体である式です。
  • マクロは関数と同じですが、パラメータ名の前にnilが含まれているため、3項目リストになっています。(nilで始まらない3項目リストの呼び出しは未定義の動作です。参照実装は最初の引数を無視し、マクロとしても扱います。)

たとえば、次の式は2つの整数を追加する関数です。

(q               List must be quoted to prevent evaluation
 (
  (x y)          Parameter names
  (s x (s 0 y))  Expression (in infix, x - (0 - y))
 )   
)

そして、任意の数の引数を取り、最初の引数を評価して返すマクロ:

(q
 (
  ()
  args
  (v (h args))
 )
)

関数とマクロは、直接呼び出して、を使用して名前にバインドdし、他の関数またはマクロに渡すことができます。

関数本体は定義時に実行されないため、再帰関数は簡単に定義できます。

(d len
 (q (
  (list)
  (i list                      If list is nonempty
   (s 1 (s 0 (len (t list))))  1 - (0 - len(tail(list)))
   0                           else 0
  )
 ))
)

ただし、上記は長さ関数を使用しないため、長さ関数を定義する良い方法ではないことに注意してください...

末尾呼び出し再帰

末尾呼び出し再帰は、Lispの重要な概念です。特定の種類の再帰をループとして実装し、呼び出しスタックを小さく保ちます。tinylispインタープリターは、適切な末尾呼び出し再帰を実装する必要があります!

  • ユーザー定義関数またはマクロの戻り式が別のユーザー定義関数またはマクロの呼び出しである場合、インタープリターは再帰を使用してその呼び出しを評価してはなりません。代わりに、現在の関数と引数を新しい関数と引数で置き換え、呼び出しのチェーンが解決されるまでループする必要があります。
  • ユーザー定義関数またはマクロの戻り式がの呼び出しであるi場合、選択されたブランチをすぐに評価しないでください。代わりに、別のユーザー定義関数またはマクロの呼び出しかどうかを確認してください。その場合、上記のように関数と引数を交換します。これは、の任意の深くネストされたオカレンスに適用されiます。

末尾再帰は、直接再帰(関数がそれ自体を呼び出す)と間接再帰(関数aがfunction bを呼び出す[etc]を呼び出す関数を呼び出すa)の両方で機能する必要があります。

末尾再帰の長さ関数(ヘルパー関数付きlen*):

(d len*
 (q (
  (list accum)
  (i list
   (len*
    (t list)
    (s 1 (s 0 accum))
   )
   accum
  )
 ))
)
(d len
 (q (
  (list)
  (len* list 0)
 ))
)

この実装は、任意の大きなリストに対して機能し、最大整数サイズによってのみ制限されます。

範囲

関数パラメーターはローカル変数です(変更できないため、実際には定数です)。それらは、その関数のその呼び出しの本体が実行されている間はスコープ内にあり、より深い呼び出し中および関数が戻った後はスコープ外になります。グローバルに定義された名前を「シャドウ」することができ、それによりグローバル名を一時的に使用できなくします。たとえば、次のコードは41ではなく5を返します。

(d x 42)
(d f
 (q (
  (x)
  (s x 1)
 ))
)
(f 6)

ただし、x呼び出しレベル1では呼び出しレベル2からアクセスできないため、次のコードは41を返します。

(d x 42)
(d f
 (q (
  (x)
  (g 15)
 ))
)
(d g
 (q (
  (y)
  (s x 1)
 ))
)
(f 6)

常にスコープ内の名前は、1)現在実行中の関数のローカル名(存在する場合)、2)グローバル名のみです。

提出要件

入出力

インタープリターは、stdinまたはstdinまたはコマンドライン引数で指定されたファイルからプログラムを読み取ることができます。各式を評価した後、その式の結果を、後続の改行で標準出力に出力する必要があります。

  • 整数は、実装言語の最も自然な表現で出力する必要があります。負の整数は、先頭にマイナス記号を付けて出力できます。
  • シンボルは、引用符やエスケープを囲むことなく、文字列として出力する必要があります。
  • リストは、すべての項目をスペースで区切って出力し、括弧で囲む必要があります。括弧内の空間は任意であり:(1 2 3)そして( 1 2 3 )両方の可能な形式です。
  • 組み込み関数とマクロの出力は未定義の動作です。(参照解釈では、それらがとして表示され<built-in function>ます。)

その他

参照インタープリターには、REPL環境と、tinylispモジュールを他のファイルからロードする機能が含まれています。これらは便宜上提供されており、このチャレンジには必要ありません。

テストケース

テストケースはいくつかのグループに分けられているため、より複雑なものに取り組む前に、より簡単なものをテストできます。ただし、それらをすべて1つのファイルにまとめてダンプした場合も正常に機能します。実行する前に、見出しと予想される出力を削除することを忘れないでください。

末尾呼び出し再帰を適切に実装すると、スタックオーバーフローを引き起こすことなく、最終的な(マルチパート)テストケースが返されます。リファレンス実装では、ラップトップで約6秒で計算されます。


「完全に数字で構成されるトークンは整数リテラルです。(先行ゼロは問題ありません。)数字以外のトークンを含むトークンは、123abc、3.14、-10などの数値に見える例でも記号です。」「少なくとも-2 ^ 31から2 ^ 31-1までの整数をサポートする必要がある」と矛盾しているようです。
msh210

3
@ msh210前者はトークンについて、後者はについて話しているため、実際にはそうではありません。直接入力する方法はありませんが-1、を実行することで値-1を生成でき(s 0 1)ます。
DLosc

1
@coredump 関連するWikipediaの記事を読んだ後、実装は実際には動的に近いが、スコープのネストはないという結論に達しました。functionの変数はF、関数GifのF呼び出しG(動的スコープの場合など)では使用できませんが、内部で定義されたネストされた関数のH場合(lexicalスコープの場合)、関数では使用できません。テストケース5を参照してください。 「誤解を招く可能性があります。HF
DLosc

1
別の言い方をすれば、スコープのネストがないため、実装では動的スコープまたは語彙のスコープ戦略を使用して同じ結果を出すことができます。常にスコープ内の名前は、1)現在実行中の関数のローカル名(存在する場合)、2)グローバル名のみです。閉鎖はサポートされていません。(参照実装は、呼び出しスタックに対応する名前バインディングのスタックを保持します。動的スタイルのアプローチで、実装が最も簡単だと思います。)
DLosc

1
必須のxkcd
mınxomaτ

回答:


11

Python 2、 685 675 660 657 646 642 640バイト

import sys,re
E=[]
G=zip("chtsle",[eval("lambda x,y=0:"+f)for f
in"[x]+y (x+[E])[0] x[1:] x-y +(x<y) +(x==y)".split()])
def V(e,L=E):
 while 1:
    try:return e and int("0%s"%e)
    except:A=e[1:]
    if""<e:return dict(G+L).get(e,e)
    f=V(e[0],L)
    if""<f:
     if f in"iv":t=V(A[0],L);e=(e[~bool(t)],t)[f>"u"];continue
     if"e">f:G[:]+=(A[0],V(A[1],L)),
     return A[0]
    if[]>f or f[0]:A=[V(a,L)for a in A]
    if[]>f:return f(*A)
    P,e=f[-2:];L=([(P,A)],zip(P,A))[P<""]
F=lambda x:V<x<""and"(%s)"%" ".join(map(F,x))or"%s"%x
for t in re.sub("([()])"," \\1 ",sys.stdin.read()).split():
 if")"==t:t=E.pop()
 if"("==t:E+=[],
 elif E:E[-1]+=t,
 else:print F(V(t))

STDINから入力を読み取り、STDOUTに出力を書き込みます。

厳密に必須ではありませんが、インタープリターはヌル関数とマクロをサポートし、を通じて実行される末尾呼び出しを最適化しvます。

説明

解析

入力を解析するには、最初に(との各出現)をスペースで囲み、結果の文字列を単語に分割します。これにより、トークンのリストが得られます。E最初は空の式スタックを維持します。次の順序でトークンをスキャンします。

  • に遭遇した場合(、式スタックの一番上に空のリストをプッシュします。
  • に遭遇した場合)、式スタックの一番上に値をポップし、それを以前スタックの下にあったリ​​ストに追加します。
  • それ以外の場合は、現在のトークンを文字列として式スタックの最上部のリストに追加します(この段階では整数を文字列として保持し、評価中に解析します)。

通常トークンをprocesssingとき場合、または起因して、スタックからの発現をポップした後)、式スタックが空である、我々はトップレベルの式でいる、と我々は我々がそうでない場合は使用して、追加されていると思い価値を評価V()し、を使用して適切にフォーマットされた結果を出力しF()ます。

評価

Gキーと値のペアのリストとして、グローバルスコープを維持します。最初は、vラムダとして実装されている組み込み関数のみが含まれます(ただし、マクロではなく、マクロとして扱われます)。

評価は、内部に発生したV()、評価する式をとる、eとローカルスコープ、L(トップレベルの式を評価するときに、ローカルスコープは空である。)の根性、キー/値ペアのリストもあり、V()ライブ無限ループ内。これは、後で説明するように、末尾呼び出し最適化(TCO)を実行する方法です。

eタイプに応じて処理します。

  • 空のリスト、またはintに変換可能な文字列の場合、すぐに(おそらくintへの変換後に)返します。さもないと、

  • 文字列の場合、グローバルスコープとローカルスコープの連結から構築された辞書で検索します。関連する値が見つかった場合、それを返します。それ以外の場合は、e(つまり、組み込みマクロの名前でなければなりませんqidまたはv)、と私たちは変わらず、それを返します。それ以外の場合、eが文字列でない場合、

  • e(空ではない)リスト、つまり関数呼び出しです。リストの最初の要素、つまり関数式を、V()再帰的に呼び出して(現在のローカルスコープを使用して)評価します。結果を呼び出しますf。リストの残りは、A引数のリストです。 f文字列のみにすることができ、その場合は組み込みマクロ(または関数v)、ラムダ、その場合は組み込み関数、またはリスト(この場合はユーザー定義関数またはマクロ)です。

    場合はfAAの文字列、すなわち、組み込みマクロで、私たちはその場でそれを処理します。マクロiまたはの場合v、第1オペランドを評価し、の場合は第2オペランドまたは第3オペランドを選択するかiv; の場合は第1オペランドの結果を使用します。選択した式を再帰的に評価してTCOを無効にする代わりeに、上記の式に置き換えて、ループの先頭にジャンプします。fがマクロの場合d、最初の要素が最初のオペランドであり、2番目の要素が2番目のオペランドの評価結果であるペアをグローバルスコープに追加し、最初のオペランドGを返します。それ以外の場合fは、マクロqです。この場合、単にオペランドを直接返します。

    そうでなければf、がlambdaであるか、最初の要素がでないリストである場合()、それは非ヌル関数であり、マクロではありません。その場合、引数、つまりの要素を評価し、結果でA置き換えAます。

    fがlambdaである場合、それを呼び出して、にアンパックされた引数を渡しA、結果を返します。

    それ以外の場合、fリスト、つまりユーザー定義の関数またはマクロです。パラメータリストは最後から2番目の要素であり、本体は最後の要素です。マクロiおよびの場合と同様に、vTCOを実行するために、本文を再帰的に評価するのではなく、本文に置き換えeて次の反復に進みます。ただし、iおよびとは異なり、ローカルスコープを関数の新しいローカルスコープにv置き換えLます。パラメータリスト場合は、P実際には、リストは、新しいローカルスコープは、パラメータリストをビュンによって構築されているP、引数リストで、A。それ以外の場合は、可変引数関数を処理します。その場合、新しいローカルスコープには要素が1つだけあり(P, A)ます。

REPL

試してみたい場合は、REPLバージョンのインタープリターをご覧ください。シンボルの再定義、およびコマンドライン引数または(import <filename>)マクロによるファイルのインポートをサポートしています。インタープリターを終了するには、入力を終了します(通常、Ctrl + DまたはCtrl + Z)。

次に、マージソートを実装するセッションの例を示します。


あなたは、あなたのコードは、バイト単位で変換:)のzlibを使用して圧縮し、これまでより短く何かを得る、とに置き換えることができます:import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
ラボ

あなたは割り当てることで2つのバイトを救うことができるA[0]だけでブロックを除いた後、いくつかのいずれか-char型の変数に
ハンネスKarppila

@HannesKarppilaその通りですが、これは無効な機能を壊します(Aこの場合は空なので)。
エル

4

C(GNU)、1095バイト

アクションの多くは、巨大なv機能で行われます。代わりに、明示的に末尾再帰を実装するので、vからの呼び出しの多くのように構成されているvのは、vGCCの末尾再帰の最適化によって処理されます。ガベージコレクションはありません。

これはGCC拡張機能を多用するため、gccでのみコンパイルできます(コマンドを使用gcc -w -Os tl.c)。またscanf、Windowsで利用できなかったいくつかの拡張機能も使用しますが、これは通常使用します。標準scanfでパーサーを作成する見込みは非常に悪かったため、代わりにLinux VMを使用してプログラムをテストしました。scanf文字クラスを使用しない解析では、おそらく100バイト以上が追加されていたでしょう。

#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})
#define P printf
#define F(I,B)({for(I;x->c;x=x->l)B;})
#define Z return
typedef struct o{struct o*n,*l,*c;int i,t;}o;E(o a,o b){Z
a.n?!strcmp(a.n,b.n):a.c?b.c&&E(*a.c,*b.c)&E(*a.l,*b.l):!b.c&a.i==b.i;}p(o*x){x->t?P("%d ",x->i):x->n?P("%s ",x->n):F(P("("),p(x->c);P(")"));}o*x,G,N;*C(o*h,o*t){Z
O(c:h,l:t);}o*v(o*x,o*e){o*W(o*l,o*e){Z
l->c?C(v(l->c,e),W(l->l,e)):&N;}o*y,*a,*f;int t;Z
x->c?y=v(x->c,e),x=x->l,t=y->i,t?9/t?a=v(x->c,e),t>7?(t>8?a->c:a->l)?:a:t>6?v(a,e):t<6?x=v(x->l->c,e),t>4?C(a,x):O(t:1,i:t>3?E(*a,*x):t>2?a->i<x->i:a->i-x->i):v((a-&N&&!a->t|a->i?x:x->l)->l->c,e):(t&1&&d(x->c->n,v(x->l->c,e)),x->c):(y->l->l->l?y=y->l:(x=W(x,e)),a=y->c,v(y->l->c,a->n?O(n:a->n,c:x,l:&G):F(f=&G,(f=O(n:a->c->n,c:x->c,l:f),a=a->l);f))):x->n?e->n?strcmp(x->n,e->n)?v(x,e->l):e->c:e:x;}d(o*n,o*x){*v(O(n:""),&G)=(o){n:n,c:x,l:O()};}*R(h){char*z,*q;Z
scanf(" %m[^ \n()]",&q)>0?h=strtoul(q,&z,10),C(*z?O(n:q):O(t:1,i:h),R()):~getchar()&1?q=R(),C(q,R()):&N;}main(i){for(;++i<12;)d(strndup("slecivthqd"+i-2,1),O(i:i));F(x=R(),p(v(x->c,&G)));}

セミウナギ

typedef struct o o;
struct o {
    char* n;
    o* l, //next in this list
     * c; 
    int i,
        t;
} ;



#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})

E(o a, o b) { //tests equality 
    return
        a.n ? !strcmp(a.n,b.n) :
        a.t ? a.i==b.i :
        a.c ? b.c && E(*a.c,*b.c)&E(*a.l,*b.l) :
        !b.c
    ;
}

#define P printf


p(o*x){
    x->t?P("%d ",x->i):x->n?P("%s ",x->n):({for(P("(");x->c;x=x->l)p(x->c);P(")");});
}


o*_,G,N; //N = nil



o*C(o*h,o*t){return O(c:h,l:t);}


/*
        2 3 4 5 6 7 8 9 10 11
        s l e c i v t h d  q
    */


o* v(o* x, o* e) { //takes list, int, or name
    o*W(o* l, o* e) { //eval each item in list
        return l->c ? C(v(l->c ,e), W(l->l, e)) : &N;
    }

    o*y,*a,*f;int t;
    return x->c ? //nonempty list = function/macro call
        y = v(x->c,e), //evals to function/macro
        x = x->l,   //list position of first arg (if it exists)
        (t=y->t)?   //builtin no., if any
             t>9 ?
              t&1 ? x->c // 11 = q
                : //10 = d
                (d(x->c,v(x->l->c,e)),x->c)
           : (a = v(x->c,e), //eval'd first arg
             t)>7 ? // t/h
                (t > 8 ? a->c : a->l) ?: a
           : t>6 ? //v
                v(a,e)
           : (x = x->l, //position of 2nd arg in list
             t)>5 ? //i
                v( (a->n||a->l||a->i|a->t>1 ? x : x->l)->c, e)
           : (x = v(x->c,e), //evaluated 2nd arg
             t)>4 ? // c
                C(a,x)
           : O(t:1,i:
                t>3 ? E(*a,*x) :  //e
                t>2 ? a->i<x->i : //l
                      a->i-x->i   //s
              )
        :
        (
            y->l->l->l ? //whether this is macro
                y = y->l :
                (x = W(x,e)),  //eval args
            a = y->c,  //a = arg list
            //a = a->n ? x=C(x, &N), C(a, &N) : a, //transform variadic style to normal
            v(y->l->c,
               a->n ? //variadic
                O(n:a->n,c:x,l:&G)
              : ({
                   for(f=&G; a->c; a=a->l,x=x->l)
                      f=O(n:a->c->n, c: x->c, l:f);
                   f;
                })
            )
        )
    :
    x->n ? // name
        e->n ?
            strcmp(x->n,e->n) ?
                v(x,e->l)
            : e->c
        : e
     : x; //int or nil
}

d(o*n,o*x){
    * v(O(n:""),&G) =
        (o){n:n->n,c:x,l:O()};
}


;
o*R(){
    char*z,*q;int h;
return scanf(" %m[^ \n()]",&q)>0?
    h=strtoul(q,&z,10),
    C(*z ? O(n:q) : O(t:1,i:h), R())
: getchar()&1?&N:(q=R(),C(q,R()));
}
main(i) {

    for(;++i<12;) d(O(n:strndup("slecivthdq"+i-2,1)),O(t:i));

    o *q;
    for(q=R(); q->c; q=q->l) p(v(q->c,&G));

}

コンパイルされた実行可能ファイルの使用法は何ですか?REPLですか?入力としてファイル名を取りますか?
ckjbgames

@ckjbgames標準入力からプログラムを読み取ります。
-feersum

はい。あなたの答えを編集し、それに注意すべきだと思います。
ckjbgames

1

セイロン、2422バイト

(これはこれまでで一番長いゴルフプログラムだと思う。)

import ceylon.language{sh=shared,va=variable,fo=formal,O=Object}import ceylon.language.meta.model{F=Function}interface X{sh fo V v(S t);sh fo G g;}class G(va Map<S,V>m)satisfies X{v(S t)=>m[t]else nV;g=>this;sh S d(S s,V v){assert(!s in m);m=map{s->v,*m};return s;}}V nV=>nothing;class LC(G c,Map<S,V>m)satisfies X{g=>c;v(S t)=>m[t]else g.v(t);}alias C=>V|Co;interface Co{sh fo C st();}interface V{sh fo C l(X c,V[]a);sh default Boolean b=>0<1;sh fo C vO(X c);sh default V vF(X c){va C v=vO(c);while(is Co n=v){v=n.st();}assert(is V r=v);return r;}}class L(sh V*i)satisfies V{vO(X c)=>if(nonempty i)then i[0].vF(c).l(c,i.rest)else this;equals(O o)=>if(is L o)then i==o.i else 1<0;b=>!i.empty;string=>"(``" ".join(i)``)";hash=>i.hash;sh actual C l(X c,V[]p){value[h,ns,x]=i.size<3then[f,i[0],i[1]]else[m,i[1],i[2]];value n=if(is L ns)then[*ns.i.narrow<S>()]else ns;assert(is S|S[]n,is V x);V[]a=h(c,p);LC lC=if(is S n)then LC(c.g,map{n->L(*a)})else LC(c.g,map(zipEntries(n,a)));return object satisfies Co{st()=>x.vO(lC);};}}class S(String n)satisfies V{vO(X c)=>c.v(this);l(X c,V[]a)=>nV;equals(O o)=>if(is S o)then n==o.n else 1<0;hash=>n.hash;string=>n;}class I(sh Integer i)satisfies V{vO(X c)=>this;l(X c,V[]a)=>nV;equals(O o)=>if(is I o)then i==o.i else 1<0;hash=>i;b=>!i.zero;string=>i.string;}V[]f(X c,V[]a)=>[for(v in a)v.vF(c)];V[]m(X c,V[]a)=>a;L c(X c,V h,L t)=>L(h,*t.i);V h(X c,L l)=>l.i[0]else L();V t(X c,L l)=>L(*l.i.rest);I s(X c,I f,I s)=>I(f.i-s.i);I l(X c,I f,I s)=>I(f.i<s.i then 1else 0);I e(X c,V v1,V v2)=>I(v1==v2then 1else 0);C v(X c,V v)=>v.vO(c);V q(X c,V a)=>a;C i(X c,V d,V t,V f)=>d.vF(c).b then t.vO(c)else f.vO(c);S d(X c,S s,V x)=>c.g.d(s,x.vF(c));class B<A>(F<C,A>nat,V[](X,V[])h=f)satisfies V given A satisfies[X,V+]{vO(X c)=>nV;string=>nat.declaration.name;l(X c,V[]a)=>nat.apply(c,*h(c,a));}{<S->V>*}b=>{S("c")->B(`c`),S("h")->B(`h`),S("t")->B(`t`),S("s")->B(`s`),S("l")->B(`l`),S("e")->B(`e`),S("v")->B(`v`),S("q")->B(`q`,m),S("i")->B(`i`,m),S("d")->B(`d`,m)};[V*]p(String inp){value ts=inp.split(" \n()".contains,1<0,1<0);va[[V*]*]s=[];va[V*]l=[];for(t in ts){if(t in" \n"){}else if(t=="("){s=[l,*s];l=[];}else if(t==")"){l=[L(*l.reversed),*(s[0]else[])];s=s.rest;}else if(exists i=parseInteger(t),i>=0){l=[I(i),*l];}else{l=[S(t),*l];}}return l.reversed;}sh void run(){va value u="";while(exists l=process.readLine()){u=u+l+"\n";}V[]e=p(u);X c=G(map(b));for(v in e){print(v.vF(c));}}

いくつかの場所で2文字の識別子を使用していたので、さらに数バイトゴルフできたかもしれませんが、それらには多少意味のある1文字が不足しています。この方法でもセイロンのようには見えませんが...

これはオブジェクト指向の実装です。

Vクラスを実装する値インターフェイスがありますL(リスト-のCeylonシーケンシャルのラッパーV)、S(シンボル-文字列のラッパー)、I(整数-Ceylon整数のラッパー)およびB(組み込み関数またはマクロ、セイロン関数)。

equalsメソッド(およびhash実際にはシンボルにのみ必要なstring属性)と、出力用の標準属性を実装することにより、標準のCeylon等式表記を使用します。

私たちは、ブール属性持っているb(でオーバーライドデフォルトではtrueで、IそしてL空のリストの戻りfalseにする)、そして二つの方法l(呼び出し、すなわち関数としてこのオブジェクトを使用)と、vO(1つのステップを評価します)。どちらも値またはContinuationオブジェクトを返します。これにより、もう1つのステップの評価が可能になりvF、結果が継続でなくなるまで(完全に評価)ループします。

コンテキストインターフェイスを使用すると、変数にアクセスできます。Gグローバルコンテキスト(d組み込みを使用して変数を追加できる)とLC、ユーザー関数の式を評価するときにアクティブになる(グローバルコンテキストにフォールバックする)ローカルコンテキストの2つの実装があります。

シンボル評価はコンテキストにアクセスし、リスト(空でない場合)は最初に最初の要素を評価し、次にその呼び出しメソッドを呼び出して評価します。呼び出しは、リストとビルトインだけで実装されます-最初に引数を評価し(マクロではなく関数の場合)、実際の興味深いことを行います-ビルトインの場合はハードコーディングされたもので、リストの場合は新しいローカルコンテキストを作成してそれに続く。

ビルトインには、Shift Interpreterで使用したものと同様のトリックを使用しました。これにより、必要な引数の型で定義できますが、リフレクションを使用してジェネリックシーケンスで呼び出すことができます(型は呼び出し時にチェックされます)。これにより、関数/マクロ内での型変換/アサーションの面倒は回避されますが、メタモデルFunctionオブジェクトを取得できるようにトップレベルの関数が必要です。

p(パース)関数は、スペース、改行や括弧で文字列を分割し、その後、トークンをループし、スタックと実行されているリストを使用してリストを作成します。

インタプリタ(runエントリポイントであるメソッド内)は、式のリスト(値のみ)を取得し、それぞれを評価して、結果を出力します。


以下はコメント付きのバージョンで、フォーマッタを介して実行されます。

ゴルフを開始する前の以前のバージョン(およびリスト評価についての誤解はまだあります)がGithubリポジトリにあります。すぐにこれを配置します(元のバージョンが必要な場合は、最初のバージョンを確認してください)。

//  Tiny Lisp, tiny interpreter
//
// An interpreter for a tiny subset of Lisp, from which most of the
// rest of the language can be bootstrapped.
//
// Question:   https://codegolf.stackexchange.com/q/62886/2338
// My answer:  https://codegolf.stackexchange.com/a/63352/2338
//
import ceylon.language {
    sh=shared,
    va=variable,
    fo=formal,
    O=Object
}
import ceylon.language.meta.model {
    F=Function
}

// context

interface X {
    sh fo V v(S t);
    sh fo G g;
}
// global (mutable) context, with the buildins 
class G(va Map<S,V> m) satisfies X {
    // get entry throws error on undefined variables. 
    v(S t) => m[t] else nV;
    g => this;
    sh S d(S s, V v) {
        // error when already defined
        assert (!s in m);
        // building a new map is cheaper (code-golf wise) than having a mutable one.
        m = map { s->v, *m };
        return s;
    }
}

// This is simply a shorter way of writing "this is not an allowed operation".
// It will throw an exception when trying to access it.
// nV stands for "no value".
V nV => nothing;

// local context
class LC(G c, Map<S,V> m) satisfies X {
    g => c;
    v(S t) => m[t] else g.v(t);
    // sh actual String string => "[local: ``m``, global: ``g``]";
}

// continuation or value
alias C => V|Co;

// continuation
interface Co {
    sh fo C st();
}

// value
interface V {
    // use this as a function and call with arguments.
    // will not work for all types of stuff.
    sh fo C l(X c, V[] a);
    // check the truthiness. Defaults to true, as
    // only lists and integers can be falsy.
    sh default Boolean b => 0 < 1;
    // evaluate once (return either a value or a continuation).
    // will not work for all kinds of expression.
    sh fo C vO(X c);
    /// evaluate fully
    sh default V vF(X c) {
        va C v = vO(c);
        while (is Co n = v) {
            v = n.st();
        }
        assert (is V r = v);
        return r;
    }
}
class L(sh V* i) satisfies V {

    vO(X c) => if (nonempty i) then i[0].vF(c).l(c, i.rest) else this;
    equals(O o) => if (is L o) then i == o.i else 1 < 0;
    b => !i.empty;
    string => "(``" ".join(i)``)";
    hash => i.hash;

    sh actual C l(X c, V[] p) {
        value [h, ns, x] =
                i.size < 3
                then [f, i[0], i[1]]
                else [m, i[1], i[2]];
        // parameter names – either a single symbol, or a list of symbols.
        // If it is a list, we filter it to throw out any non-symbols.
        // Should throw an error if there are any, but this is harder.
        value n = if (is L ns) then [*ns.i.narrow<S>()] else ns;
        assert (is S|S[] n, is V x);
        V[] a = h(c, p);

        // local context
        LC lC = if (is S n) then
            LC(c.g, map { n -> L(*a) })
        else
            LC(c.g, map(zipEntries(n, a)));
        // return a continuation instead of actually
        // calling it here, to allow stack unwinding.
        return object satisfies Co {
            st() => x.vO(lC);
        };
    }
}

// symbol
class S(String n) satisfies V {
    // evaluate: resolve
    vO(X c) => c.v(this);
    // call is not allowed
    l(X c, V[] a) => nV;
    // equal if name is equal
    equals(O o) => if (is S o) then n == o.n else 1 < 0;
    hash => n.hash;
    string => n;
}

// integer
class I(sh Integer i) satisfies V {

    vO(X c) => this;
    l(X c, V[] a) => nV;
    equals(O o) => if (is I o) then i == o.i else 1 < 0;
    hash => i;
    b => !i.zero;
    string => i.string;
}

// argument handlers for functions or macros
V[] f(X c, V[] a) => [for (v in a) v.vF(c)];
V[] m(X c, V[] a) => a;

// build-in functions
// construct
L c(X c, V h, L t) => L(h, *t.i);
// head
V h(X c, L l) => l.i[0] else L();
// tail
V t(X c, L l) => L(*l.i.rest);
// subtract
I s(X c, I f, I s) => I(f.i - s.i);
// lessThan
I l(X c, I f, I s) => I(f.i < s.i then 1 else 0);
// equal
I e(X c, V v1, V v2) => I(v1 == v2 then 1 else 0);
// eval (returns potentially a continuation)
C v(X c, V v) => v.vO(c);

// build-in macros
// quote
V q(X c, V a) => a;
// if (also returns potentially a continuation)
C i(X c, V d, V t, V f) => d.vF(c).b then t.vO(c) else f.vO(c);
// define symbol in global context
S d(X c, S s, V x) => c.g.d(s, x.vF(c));

// buildin function or macro, made from a native function and an argument handler
class B<A>(F<C,A> nat, V[](X, V[]) h = f)
        satisfies V
        given A satisfies [X, V+] {
    vO(X c) => nV;
    string => nat.declaration.name;
    // this "apply" is a hack which breaks type safety ...
    // but it will be checked at runtime.
    l(X c, V[] a) => nat.apply(c, *h(c, a));
}

// define buildins
{<S->V>*} b => {
    S("c") -> B(`c`),
    S("h") -> B(`h`),
    S("t") -> B(`t`),
    S("s") -> B(`s`),
    S("l") -> B(`l`),
    S("e") -> B(`e`),
    S("v") -> B(`v`),
    S("q") -> B(`q`, m),
    S("i") -> B(`i`, m),
    S("d") -> B(`d`, m)
};

// parses a string into a list of expressions.
[V*] p(String inp) {
    // split string into tokens (retain separators, don't group them –
    // whitespace and empty strings will be sorted out later in the loop)
    value ts = inp.split(" \n()".contains, 1 < 0, 1 < 0);
    // stack of not yet finished nested lists, outer most at bottom
    va [[V*]*] s = [];
    // current list, in reverse order (because appending at the start is shorter)
    va [V*] l = [];
    for (t in ts) {
        if (t in " \n") {
            // do nothing for empty tokens
        } else if (t == "(") {
            // push the current list onto the stack, open a new list.
            s = [l, *s];
            l = [];
        } else if (t == ")") {
            // build a lisp list from the current list,
            // pop the latest list from the stack, append the created lisp list. 
            l = [L(*l.reversed), *(s[0] else [])];
            s = s.rest;
        } else if (exists i = parseInteger(t), i >= 0) {
            // append an integer to the current list.
            l = [I(i), *l];
        } else {
            // append a symbol to the current list.
            l = [S(t), *l];
        }
    }
    return l.reversed;
}

// Runs the interpreter.
// This handles input and output, calls the parser and evaluates the expressions.
sh void run() {
    va value u = "";
    while (exists l = process.readLine()) {
        u = u + l + "\n";
    }
    V[] e = p(u);
    // create global context
    X c = G(map(b));
    // iterate over the expressions, ...
    for (v in e) {
        // print("  '``v``' → ...");
        // ... evaluate each (fully) and print the result.
        print(v.vF(c));
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.