マルチクインジェネレータ

Habrのの月に、4年前に行った開発を共有したいと思います。



優れたマルチクインはエンジニアリングの仕事ですが、...

「電信線への干渉」の次のバッチを見ると、普通のプログラマーは驚いているだけです。

カバーを破り、ホワイトスペースやブレインファクを含むあらゆるプログラミング言語のセットで、複雑さの度合いの異なるマルチクインをどれだけ簡単に作成できるかを示したいと思います。



プログラムのテキストとその実行結果を扱っているので、この問題を複数行にわたる関数として見るのは自然でしょう。



機能



どのような機能がありますか?

まず、 インタープリターです。プログラムのテキストを受け取り、コンパイルして実行し、標準出力からテキストを返します。

この関数を単にRUNと呼びます。

1つの言語に限定されないため、実際には、RUNpython、RUNc、RUNbrainfuckなどの家族全員がいます。

これらの機能がさまざまな方法で実装されていることは明らかですが、どのように正確に-これは関係ありません。 結局のところ、私たちは同じ言語で独自の言語のインタープリターを作成するわけではありません。

一般に、ストリング方程式を解くので、主な実装は頭の中になります。



第二に、重要な関数ファミリーはデコレーターライターです。 デコレータは任意の文字列を受け取り、その中の特殊文字を置き換えます。その後、ライターは文字列を引用符で囲みます。

L x = qopen + D x + qclose

デコレータの重要な特性は、文字列の加算(連結)操作の加算性です:E(x + y)= Ex + Ey。



ちなみに、すぐに同意します。記録をコンパクトにするために、関数は大文字で、文字列は小文字で、Fx-関数Fをxに適用し、FGx-FをGxに適用します。



クイン



Cの最小のクインを見ると、

char*f="char*f=%c%s%c;main(){printf(f,34,f,34,10);}%c";main(){printf(f,34,f,34,10);}
      
      





その後、特徴的なパターンが表示されます。 ここでは、プログラムの2つのフラグメントが2回繰り返されます-1回はトップレベルテキストとして、もう1回は文字列リテラルとして。



そして書き留めます: quine = a + b + Ea + c + Ee + d + e







まったく同じ方法で、Pythonでクインを作成できます。

 s1,s2 = 's1,s2 = ', "\nprint s1+repr(s1)+', '+repr(s2)+s2" print s1+repr(s1)+', '+repr(s2)+s2
      
      





または、もう少し明確かつ詳細に:

 # this is quine s1 = '# this is quine' s2 = 'print s1\nprint "s1="+repr(s1)\nprint "s2="+repr(s2)\nprint s2' print s1 print "s1="+repr(s1) print "s2="+repr(s2) print s2
      
      





部分文字列aおよびeは、プログラムで生成するのに非現実的で、情報量の多い大きなテキストです。 サブストリングb、c、d-接着剤。通常は引用符、キャリッジ転送などで構成されます。



明快さといえば。 プログラムテキストは、モノリシックな行としてではなく、行のリストとして便利に考えることができます。 別の記事で、私は間違いなくこれに戻り、リストを操作する方法をいくつか示します。

それまでの間、馬に戻ります。

RUN関数はquineに対して定義されています: RUN quine = quine





つまり、クインはRUN機能の固定点です。

馬を少し要約しましょう。 任意の方法で任意の文字列を出力する方法を学ぶとどうなりますか?

 Q(F,f,g,h,i,j) = a + b + Ef + c + Ej + d + e --  a,b,c,d,e -  () F  g,h,i. RUN Q(F,f,g,h,i,j) = f + g + Ff + h + Fj + i + j
      
      





同じPythonで:

 # this is Q def F(s) : '''     F   ''' s1 = 'Ef' s2 = 'Ej' print s1 + 'Eg' + F(s1) + 'Eh' + F(s2) + 'Ei' + s2 #  Ef, Eg, Eh, Ei, Ej —  -
      
      





クインは置換方程式の解です: RUN Q(F,f,g,h,i,j) = Q(F,f,g,h,i,j)





 RUN Q(F,f,g,h,i,j) = f + b + Ff + c + Fj + d + e Q(F,f,g,h,i,j) = a + b + Ff + c + Ej + d + e
      
      





F = Eの場合、つまり、ソフトウェアの装飾方法は、言語の規則に従った装飾と一致します。 テキストの断片-何が何と一致するかは明らかです。



別の余談:同じ言語内であっても、デコレータは非常に異なる場合があります。 たとえば、文字列を数値の配列として表すことができます。

次に、「行」104、97、98、114の出力はprint '' .join(map(chr)、xs)であり、装飾された形式の出力はprint '、'。Join(map(str)、xs)です。



注:RUNおよびQ関数は文字列に対して定義されます(Qは一般に文字列および関数に対して定義される高階関数です)が、その実装は、私たちが書いているターゲットプログラミング言語の外側にあります。 関数Eは、ターゲット言語のテキストとして実装する必要があります!



プリンター



次に、別の非常に単純なプログラムを見てみましょう。 このプログラムは、指定されたテキストを印刷します。

 printer = p + Sq + r RUN printer = q
      
      





ここで、Sは装飾されたテキストストレージの何らかの方法です。



完全に任意のテキスト用のプリンターを作成できるため、関数を作成します。

 P(q) = p + Sq + r
      
      





もちろん、不変式: RUN P(q) = q





つまり、プリンターはインタープリターの逆機能です!



Cで:

 #include <stdio.h> int main() { printf("%s", "hello\nhabr"); return 0; }
      
      





Pythonの場合:

 print """ hello RSDN """
      
      





つまり、ここでの関数Sは古き良きEと一致します(Pythonでは、さらに簡単です。改行をエスケープする必要はありません)。

そして、ここでは-一致しません:

 #include <stdio.h> int main() { putchar(114);putchar(115);putchar(100);putchar(110); return 0; }
      
      





プリンターPは、その要素-pおよびr-が関数のパラメーターに依存しないという点で、Q quineと比較して有利です。



プリンターからメタプリンターを作成するのは簡単です。テキストを印刷するプログラムを印刷するプログラムです。

 Pmeta (q) = PPq = P(p + Sq + r) = p + S(p + Sq + r) + r = p + Sp + SSq + Sr + r pmeta = p + Sp Smeta = SS rmeta = Sr + r RUN (RUN( Pmeta(q) )) = q
      
      







多言語プリンターの作成を私たちを妨げるものはありません。

 Pbilingua(q) = P'P"q = P'(p" + S"q + r") = p' + S'p" + S'S"q + S'r" + r' RUN"(RUN'(Pbilingua(q))) = RUN"(RUN'(P'P"(q))) = RUN"(P"(q)) = q
      
      







そして、一般的に、使用できる言語の数( multi-printer ):

 Pmulti(q) = pmulti + Smultiq + rmulti pmulti = p1 + S1p2 + S1S2p3 + S1S2S3p4 Smulti = S1S2S3S4 rmulti = S1S2S3r4 + S1S2r3 + S1r2 + r1
      
      







必要なのは、デコレータの機能を構成する方法を学ぶことだけです。 (これについて-以下)。



完全を期すためにもう1つのプリンター: ヌルプリンター P0(テキスト)=テキスト

 p0 = "" r0 = "" S0 = id
      
      







ピンポン



それでは、プリンター(またはマルチプリンター)を渡って、クインを見てみましょう。

ここでは、方程式の解がすでに必要です。

これらのプログラムをpingとpongと呼びましょう。

 RUN ping = pong RUN pong = ping
      
      







そして、pingをプリンターP(pong)にし、pongをもっと複雑なものにします。 P(ping)の場合、文字列は無限に拡張されることになり、最終的な解決策が必要になります。

したがって、pong = Q(F、f、g、h、i、j)とします。

代替:

 ping = P pong = p + S pong + r = p + S(a + b + Ef + c + Ej + d + e) + r = p + Sa + Sb + SEf + Sc + SEj + Sd + Se + r = (p + Sa + Sb) + SEf + Sc + SEj + (Sd + Se + r) ping = RUN pong = f + g + Ff + h + Fj + i + j
      
      





たとえば、なぜf = p+Sa+Sb



であり、 g=Sa+Sb



ではありませんか?

事実、fとjは再帰用に特別に設計されているため、pongソースコードの断片(サブストリングa、b、d、e)を簡単に言及できますが、g、h、iの再帰は望ましくありません。



だから我々は得た

pong = a+b + E(p+Sa+Sb) + c + E(Sd+Se+r) + d+e







そして、aまたはeの腸のどこかで関数F = SEと文字列EScを隠しています。

注:eにEScが含まれる場合、cはeから独立しているため、再帰は発生しません。



これらの「腸の中に隠れている」ことを取り除こうではありませんか。 別の機能を使用してください。

させて

 pong = R(F,x,y,z) = a + Ex + b + Ez + c + Ey + d RUN R(F,x,y,z) = x + Fx + y + Fz + z P pong = p+Sa + SEx + Sb + SEz + Sc+SEy+Sd+r RUN pong = x + Fx + y + Fz + z
      
      







だから我々は得た:

 F = SE x = p+Sa y = Sb z = Sc+SEy+Sd+r = Sc + SESb + Sd + r
      
      







ピンポンの定義を少し変えると、

 pong = R(F,x,y,z) = a + Ex + b + Ey + b + Ez + c RUN R(F,x,y,z) = x + Fx + y + Fy + y + Fz + z
      
      





つまり、文字列定数のリストを作成した場合、二重の装飾は削除されます。



 P pong = p+Sa + SEx + Sb + SEy + Sb + SEz + Sc+r RUN pong = x + Fx + y + Fy + y + Fz + z
      
      







したがって、 P = {S, p,r}



R = {E,a,b,c}



に空白がある場合、すぐにそれらをピンポンに変換します。 また、Pがマルチプリンターになり得ることを忘れないでください。 その後、ピンポンはn + 1の周期で振動します。ここで、nはマルチプリンターの多重度です。 Pがヌルプリンターの場合、ピンポンは1の周期で振動し、(だれが考えたでしょうか?)馬になります。



最後に残っているのは、構成SEを作成することです。

正式には、タスクはこのように聞こえます。

指定:Pongプログラミング言語。 デコレータEおよびS、この言語にネイティブのE、S any。

Find:SEデコレータを実装するPongサブルーチンテキスト。

同時に、開発環境の言語でデコレーターEとSEを実装します。 結局のところ、マルチクインの生成を自動化したいのですか?



これを行うために、デコレータがどのように配置されるかを見てみましょう。

デコレータは、F(a + b)= Fa + Fbの追加により分配的です。

文字列を基本部分、つまり単一文字の文字列に分割すると、次のようになります

F(abcd…) = Fa + Fb + Fc + Fd + …





デコレータは結合的です:

FG(abcd…) = FGa + FGb + FGc + FGd + …







したがって、デコレータを各文字のコーディングテーブルとして提示し、同じ数のキーを持つがより長い値を持つテーブルにコンポジションを再計算できます。



したがって、作業計画は次のとおりでした。

与えられた:



私たちは行動します:

  1. テーブルからS1S2...



    計算するマルチプリンター{S,p,r}



    を見つけます。
  2. テーブルF = SE



    を見つけます。
  3. 必要に応じて、最小のアルファベット-p、r、Sa、Sb、Sc、Sdを構成する文字のセットを見つけます。 これは、すべてのASCIIのコーディングテーブルや、Unicodeのコードテーブルを積み重ねないようにするためです。
  4. テーブルをaまたはcに貼り付けます。
  5. x=(p+Sa), y=(Sb), z=(Sc+r)



    を見つけます。
  6. ピンポンを形成します: a+Ex+b+Ey+b+Ey+c





それだけです!



そして、これらのステップはすべて手作業で行うには時間がかかりすぎるため、クインジェネレーターを作成しましょう。

ここでは、初心者向けに、(現時点では)pythonとsiからマルチクインを作成するpythonコード。

それを他の言語で補うのに10分かかります。

 #-*- coding: utf-8 -*- #  - ,     def I(c) : ''' id-,    ''' return c def C(c) : '''      ''' if c=='"' or c=='\\' or ord(c)<32 or ord(c)>126 : return '\\%03o' % ord(c) # ,  ,      #           , #     : \x24bad -    chr(0x24BAD),    $bad else : return c def decor(F,s) : '''     ''' return ''.join(map(F,s)) #       def compose(F,G) : '''   ''' return lambda c : decor(F,G(c)) #  -  (S,p,r) def make_printer(S, tpl, tag = '<-T->') : '''      ( <-T-> ,   ) ''' p,r = tpl.split(tag) return S,p,r nul_printer = (I,'','') def show_printer(prn, t) : '''       t ''' S,p,r = prn return p + decor(S,t) + r def meta_printer(prn1, prn2) : '''   ''' S1,p1,r1 = prn1 S2,p2,r2 = prn2 S = compose(S1,S2) p = p1 + decor(S1,p2) r = decor(S1,r2) + r1 return S,p,r #  - ,      -  (E, am, b, cm) #  am  cm -  decorator -> string def make_quiner(E, M, tpl, tagX = '<-X->', tagF = '<-F->') : '''       <-X->       x,y,z,  <-F-> ,     F  E -  ,  M      ''' a,b,b_,c = tpl.split(tagX) assert b==b_ am = lambda F : a.replace(tagF, M(F)) if tagF in a else a cm = lambda F : c.replace(tagF, M(F)) if tagF in c else c return E,am,b,cm def show_quiner(qnr, F,x,y,z) : '''         a,Ex,b,Ey,b,Ez,c --  x,Fx,y,Fy,y,Fz,z -- ,     (RUN) ''' E,am,b,cm = qnr a,c = am(F), cm(F) ex,ey,ez = decor(E,x), decor(E,y), decor(E,z) return a + ex + b + ey + b + ez + c def show_quiner_printer(qnr,prn) : '''      p+Sa,SEx,Sb,SEy,Sb,SEz,Sc+r --   x , Fx, y , Fy, y , Fz, z --    ''' E,am,b,cm = qnr S,p,r = prn F = compose(S,E) a,c = am(F), cm(F) x = p + decor(S,a) y = decor(S,b) z = decor(S,c) + r ex,ey,ez = decor(E,x), decor(E,y), decor(E,z) return a + ex + b + ey + b + ez + c ############################################################# #     : c_quine_tpl = '''/* C quine */ #include <stdio.h> const char* f[128] = {<-F->}; const char* xyz[3] = {"<-X->", "<-X->", "<-X->"}; void ps(const char* s) { while(*s) putchar(*s++); } void pm(const char* s) { while(*s) ps(f[*s++]); } int main() { ps(xyz[0]); /* x */ pm(xyz[0]); /* Fx */ ps(xyz[1]); /* y */ pm(xyz[1]); /* Fy */ ps(xyz[1]); /* y */ pm(xyz[2]); /* Fz */ ps(xyz[2]); /* z */ return 0; } ''' def c_quine_M(F) : '''   -     ''' codes = [ '"%s"' % decor(C,decor(F,chr(i))) for i in xrange(128) ] return ', '.join(codes) c_quiner = make_quiner(C, c_quine_M, c_quine_tpl) #    ,           py_quine_tpl = '''#!/usr/bin/python import sys m = [ <-F-> ] xyz = [ "<-X->", "<-X->", "<-X->" ] def ps(s) : sys.stdout.write(s) def pm(s) : for c in s : ps(m[ord(c)]) ps(xyz[0]) pm(xyz[0]) ps(xyz[1]) pm(xyz[1]) ps(xyz[1]) pm(xyz[2]) ps(xyz[2]) ''' py_quiner = make_quiner(C, c_quine_M, py_quine_tpl) #     ################### #     : c_printer_tpl = '''#include <stdio.h> int main() { printf("%s", "<-T->"); return 0; } ''' c_printer = make_printer(C, c_printer_tpl) py_printer_tpl = '''import sys sys.stdout.write("<-T->") ''' py_printer = make_printer(C, py_printer_tpl) #################### # !    c_c_printer = meta_printer(c_printer, c_printer) py_py_printer = meta_printer(py_printer, py_printer) #  1  c_quine = show_quiner_printer(c_quiner, nul_printer) py_quine = show_quiner_printer(py_quiner, nul_printer) #  2  c_c_quine = show_quiner_printer(c_quiner, c_printer) py_py_quine = show_quiner_printer(py_quiner, py_printer) #  2  -  c_py_quine = show_quiner_printer(c_quiner, py_printer) py_c_quine = show_quiner_printer(py_quiner, c_printer) #  3  - -   c_c_c_quine = show_quiner_printer(c_quiner, c_c_printer) py_py_py_quine = show_quiner_printer(py_quiner, py_py_printer) c_py_py_quine = show_quiner_printer(c_quiner, py_py_printer) py_c_c_quine = show_quiner_printer(py_quiner, c_c_printer) sys.stdout.write(py_py_py_quine) # ,  , - ...
      
      





ソースとその成果物はバケットにあります: bitbucket.org/nickolaym/quines



もちろん、マシンによって生成されたマルチキンは、恵みに違いはありません。 そのサイズは5438バイトで、エンコードテーブルがその大部分を占めます。



(アプローチの普遍性と機械生成の可能性を維持しながら)よりコンパクトにする方法-この問題を自分で解決することを提案します。



あなたがそれを好めば、私はこのトピックの詳細を書きます:





4年前のRSDNでの私の意識の流れもご覧ください。 http://rsdn.ru/forum/etude/3604693



All Articles