Quine書いた (2024)

最終更新:7 日前

Quine JavaScript

久々にQuineを書いた。

Quine(クワイン)というのは、プログラミングを使ったアートみたいなもので、実行すると出力される内容が自分自身のソースコードと一致する自己言及的なプログラムのこと。たとえば print("hello") という内容のプログラムはQuineではない。これを実行すると、たぶん hello と出力され、当然 print("hello") という文字列とは異なる。今度は不足を補おうとして print('print("hello")') というプログラムを書いてみても、あるいはこの調子でどんどん長くしていっても、やっぱり一致することはない。このように、Quineを書くこと自体がまずそんなに簡単ではない。その要件を満たしたうえで、ソースコードの見た目が何らかのデザインになっているとか、一工夫加えることでアートとして完成する。なお、あまりに知名度が低すぎて、Quineを書いて人に見せるときに自分でQuineの凄さを説明しないと何も伝わらない、という致命的な欠点があることで知られている(嘘)。

今回書いたのはLegalscapeロゴの形を象ったJavaScript製のQuine

(W=          ()=>(E='!    !!!!!O5',      m='f      NZ3          ~Wff',p='    Pxr@&PB,       ',R=      'replace'    ,e=`PlPqU
#1e         `,V=(cc)=>   '!'.repeat     (cc*8)     ,r=         '/d{z@U}8'   ,t=s=>(s[      ((R))]     (/\s/gs,''  )),l=`0R@_
Ji{         @`,          o=             '*  )r     M>9         \\           #'             ,L  =`            %Sn  _PW       
R+`         ,i=         '8K             u1  99X    "',         g=          "7I            \\?  'V            @0"  ,n=       
'9p         #b_         <7             s',   P=    'V8         S;{         @B             x'   ,f=           `2J  ahw       
i@[         `,a='HUHe.  6~   V',w='    +n    \\    5e~         LE',s='CI   Av             oB    I$     ',M={264:  135,259:24
,32         :13,18:13,  8:      13,   134    :16   ,56           :13,256:  23            ,3:    13,   123:13,66   :13,257:87
,99         :13         ,[       92   ]:13,248:+   109                ,[4  *7            *9      ]:   109         ,[9       
*17         +5]         :17      ,[   184]:15,244  :87                 ,[  29*          9]:      84   ,[+         263       
 ]:+        22,          [2*    131  ]:+       13}  ,D=               `7*   dj=         }'       (${  V(4         )}G       
 tCIFRsTb?  T[GpO%${V(    1)}7\"o.H  !vd       ${V  (1)}FdZ<Q  FsM${V(1)}    .aTcH1!'   ~~        ~~  ~~~          ~s-'I'aZ+
   ($}?g3/    RPN0h#S;     )`,S=[   ...         t(`   (W=${W}  )()`)],C=      s=>[...  t(s        )]  [0,           'reduce'
                                                                                       ](         (A,                       
                                                                                      c,)          =>                       
                                                                                      A*           94n                      
 +BigInt(c.charCodeAt(0)-33),0n),(by,YK,..._)=>console.log([...(C(_.join(''))+C(D))['to'            +'String'](12)].flatMap(
   (c,i)=>(X=M[i-33]??parseInt(c,12)+1,i%2?'\x20'.repeat(X):S.splice(0,X))).join('')[R]             (/(.{124})/g,'$1\n')))  
                                                                                                                            
         (     W, e,     E, m, p, o, w, e, r,     L, e, g, a, l,     P, r, o, f, e, s, s, i, o, n, a, l, s     ))()

これを適当な処理系で実行すると、ソースコードと同じ内容が出力される。

$ node quine.js > output
$ diff -s quine.js output
Files quine.js and output are identical

見ての通り、ポイントは最後の行。元のロゴにある「WE EMPOWER LEGAL PROFESSIONALS」という文字列が、それぞれ一文字ずつ変数として定義され、関数呼び出しの引数リストを形成している。もちろんこれは無意味な装飾ではない。このプログラムは全体で、この引数の文字列を結合して得られる文字列をデコードし、ロゴの形に整形されたソースコードを復元するものになっている。

prettifyしたソースコードを見れば分かるように、このコードには全体で1文しか登場しない。これでセミコロン有り派もセミコロン無し派も満足の、いわばJavaScript版広辞苑前文方式が達成されている(適当)(後付け)。

(W = () =>
  ((E = '!    !!!!!O5'),
  (m = 'f      NZ3          ~Wff'),
  (p = '    Pxr@&PB,       '),
  (R = 'replace'),
  (e = `PlPqU\n#1e         `),
  (V = (cc) => '!'.repeat(cc * 8)),
  (r = '/d{z@U}8'),
  (t = (s) => s[R](/\s/gs, '')),
  (l = `0R@_\nJi{         @`),
  (o = '*  )r     M>9         \\           #'),
  (L = `            %Sn  _PW       \nR+`),
  (i = '8K             u1  99X    "'),
  (g = "7I            \\?  'V            @0"),
  (n = '9p         #b_         <7             s'),
  (P = 'V8         S;{         @B             x'),
  (f = `2J  ahw       \ni@[         `),
  (a = 'HUHe.  6~   V'),
  (w = '    +n    \\    5e~         LE'),
  (s = 'CI   Av             oB    I$     '),
  (M = {
    264: 135, 259: 24, 32: 13, 18: 13, 8: 13, 134: 16, 56: 13, 256: 23, 3: 13, 123: 13, 66: 13, 257: 87,
    99: 13, [92]: 13, 248: +109, [4 * 7 * 9]: 109, [9 * 17 + 5]: 17,
    [184]: 15, 244: 87, [29 * 9]: 84, [+263]: +22, [2 * 131]: +13,
  }),
  (D = `7*   dj=         }'       (${V(4)}G       \n tCIFRsTb?  T[GpO%${V(1)}7\"o.H  !vd       ${V(1)}FdZ<Q  FsM${V(1)}    .aTcH1!'   ~~        ~~  ~~~          ~s-'I'aZ+\n   ($}?g3/    RPN0h#S;     )`),
  (S = [...t(`   (W=${W}  )()`)]),
  (C = (s) => [...t(s)][(0, 'reduce')]((A, c) => A * 94n + BigInt(c.charCodeAt(0) - 33), 0n)),
  (by, YK, ..._) =>
    console.log(
      [...(C(_.join('')) + C(D))['to' + 'String'](12)]
        .flatMap((c, i) => ((X = M[i - 33] ?? parseInt(c, 12) + 1), i % 2 ? '\x20'.repeat(X) : S.splice(0, X)))
        .join('')
        [R](/(.{124})/g, '$1\n'),
    ))(W, e, E, m, p, o, w, e, r, L, e, g, a, l, P, r, o, f, e, s, s, i, o, n, a, l, s))();

データをデコードし文字列生成する処理自体はそこまで難しくないのだが、その処理全体をさらにロゴの形に整形しても動かないといけない(たとえば (x)⎵=>++x という位置にスペースが入るのはよくても (x)=⎵>++x は文法エラーになる)ので、その調整が作る上での本丸であった。まずデータを圧縮しいかにコードを短くするかを考え、極限まで短くした後は、都合の悪いところでコードが切れないようにひたすら array.reducearray[0, 'reduce'] みたいな書き換えを頑張って思いつく作業を繰り返し、最後にサインを入れたり変数の順番を入れ替えたりして見栄えを整える微調整を施して完成。

ちなみに、ロゴの形のデータは、画像ファイルを用意して、ImageMagickでPNMに変換すると比較的簡単に得られる。

height=30
magick logo.png -background white -flatten -geometry x${height} -compress none logo.pbm

作ってみて、データ部分とロジック部分の比率がちょっとデータに寄りすぎかなあ、と思うけれど、そもそも相場を知らないので、まあいいや。

Xで コメントする

Mentions