The 22nd IOCCC: Most lazy SKIer のエントリ

ref: http://ioccc.org/2013/endoh1.c

#ifndef SKI
#define A(x)B(x##0)B(x##1)B(x##2)B(x##3)B(x##4)B(x##5)
#define B(x)C(x##0)C(x##1)C(x##2)C(x##3)C(x##4)C(x##5)
#define C(x)D(x##0)D(x##1)D(x##2)D(x##3)D(x##4)D(x##5)
#define D(x)Z(x##0)Z(x##1)Z(x##2)Z(x##3)Z(x##4)Z(x##5)
#define p(x)(*(*(*(*(*(*x)())())())())())()

#include <stdio.h>
#include <stdlib.h>

#define S (Y(s+6))
#define K (Y(s+4))
#define I (Y(s+2))




#define Z(x)\
f x;f Y(v);g\
   j;f \
   x##z(                              f\
    y){y        =y?!    x?x=          y:Y(     m(*(    w)&\
    x,((         g)y)  (0))    ):x;return y;    /*ab  c*/}
    /**/          typedef             void       *v ;int
    n=0;           typedef            v*         w;typed\
ef v(*g)();v     s[]=  {0,0,                    s+6,  s+2,
s+4,s,s+3,s+    5,s+    1};w                   m( v    l ,v




r){w    e=ma\
lloc    (siz\
eof(   v)*3
);*e  =r;e                                           [1
]=l;return      (e[2    ]=s,   e);}    /*de          fghi     jk*/    typ\
edef v(p(        p(p(  p(p(    p(p(    p(p(   p(p(f)))) )))    ))))  );A(
x)w   u(){        return n      --?m  (j(0           ),u(       )):s+3;}
w z(   w e)       {w a,b ,       c,d;for             (d         =e=m(e,0
);n=    (w)d[    2]-s  ,n?*       (n>1?                        n<4?  b:a:
c)?n    <2?c[   1]=m    (*a,      *c),                        *c=m    (*b,
                                 *c):
                                n>5?


  #undef Z
#define Z(x\
) &x    ##z,
a[1]=                                                               m(
 *a,!*d?        d[1]    =c=m   (0,0    ),n=   getchar(),n=          n<0?
    256:n,c      [2]=  s+6,    *d=u    ():*   d),*a=d[1]:    n<5?n<3?n=z(*
       a)-s,      putchar(      (255  <n)?         exit             (n-4
*64)    ,0:n      ),fflush       (stdout)        ,c[1               ]=
*b:((n-3?b:c     )[1]  =*a)       :(2[*        a=z(*a)+1,a
  ]=s+5),d      =e:0    :e;d      =d[1        ])c=b,b=a,a=
                                 d;a=
                                *(w*

                                      )e                               [1
                                     ];                                 ;;
     /*lm    no*/   return a;}f(     *h   [])(    )={A   (x)0};f Y(v    x)
      {g y  =(g)    h[n++] ;y(!     y?    exit(   puts   ("out of c"     ""
       "losure"          )),x       :x     );return(f         )y/*       pq
       r*/;}int        main         ()       {z(((g         )(S          S(
      j=(g  )S(S     (K S)K))(K(    S        I I))        (S(K(S I))(    S(
     K K)    (S(K   (S(S(K(Y(s+1     ))      )(S(        S S(K I)) (K   (Y
                                     (s     +5))                        ))
                                      ))   )K))                        )(





#endif

#if (!defined(__INCLUDE_LEVEL__) || !__INCLUDE_LEVEL__) && !defined(SKI)

K(I(I(I(S I I(S(K(S(S(K S)(S(S S S(K(S(K S)))(S(S S S(K(S(K S)))(S(K(S(S(K S)(S(S(K S)K)(K(S(S(K S)(S(K(S(S(S I(K K))(K K))))(S(K K)(S(S(K S)(S(K(S(S I(K K))))(S(K K)(S(S(K K))(K(S(S(S(S(K S)K)))I(S(S(K S)K)I)))))))(S(K K)(S(S(K K))(K(S(K(S(S(K S)K)I))(S(S(K S)K)(S I I(S(S(K S)K)I)))))))))))(S(K K)(S(K K)(S(S(K K))(K(S(K(S(S(K S)K)I))(S(S I I)I(S(S(K S)K)I)))))))))))))(S(K K)(S(K K)(S(K(S(S I(K(S(S I(K(K I)))(K S))))))K)))))(K(K(K K)))))(K(K(K(K I))))))))(S(K(S(K K)))(S(S(K S)(S(K K)(S(K S)(S(S(K K))I))))(K(S(K(S(S(K S)(S(K(S I))K))))(S(K K)K))))))(S(S I(K(S I I(S I I(S(S(K S)K)I)))))(K(K K)))S K K K I I I K K K I I I K K K I I I K K K I I K K K K K S K I I I K I K I I I K I K)I I I K I K I I I K I I I K I I S K I I I I I K I I I I I K I K I I I K I I I K I I S K I I)I K I K I I I K I K I I I K I K I I I K I I I K I I S K K K I I I K K K I I I K K K I I I K K K I I K K K K K S S K I I I K S)K I I K S K K K S K I I K S K I I I K S S K K K K K S K I I I I S K K K K K S K S K K K K K(K K))

#define SKI
#endif

#ifdef SKI
(Y(s)))))(0));return 0;}
#else
#define SKI
#endif

使い方

単体でもコンパイル・実行できるんですが、

$ gcc -o endoh1 endoh1.c
$ ./endoh1
@@@@@
@
@@@@@
    @
@@@@@

@   @
@  @
@@@
@  @
@   @

@@@@@  @@@   @@@   @@@   @@@
  @   @   @ @   @ @   @ @   @
  @   @   @ @     @     @
  @   @   @ @   @ @   @ @   @
@@@@@  @@@   @@@   @@@   @@@

これは本題ではありません。


このプログラムは、C コンパイラに Lazy K のコードを食わせていじめるためのライブラリです。これがあれば、C コンパイラしかない状況で Lazy K を楽しむことができます。*1
Lazy K ってのは SKI コンビネータを遅延評価で云々という言語なんですが*2、要するに関数 3 つだけ (S と K と I) の難解言語です。見た目はこんな感じ。

K(S(S I(K(S(K(S I I(S(S(K S)K)I))) ... ))))

これの最初と最後に #include をくっつける。

#include <endoh1.c>

K(S(S I(K(S(K(S I I(S(S(K S)K)I))) ... ))))

#include <endoh1.c>

これだけで valid な C プログラムになります。この通り。

$ gcc -o hello -xc hello.lazy
$ ./hello
Hello, IOCCC!

変な行入れたら Lazy K じゃなくなるじゃないか!という Lazy K ファンの方もご安心を。Lazy K において # は行コメントですので、そのまま実行できます。

$ lazy hello.lazy
Hello, IOCCC!

つまり、Lazy K と C の polyglot (と言えなくもない) です。

解説

#define S (s)
#define K (k)
#define I (i)

とマクロ定義することで

S (K I)

(s) ((k) (i))

となり、冗長な括弧をとると

s(k(i))

あれっこれ普通の C じゃん、というのが肝です。ここから苦労した点はいっぱいあって

  • C に再帰型がない → 「関数ポインタを返す関数ポインタを返す関数ポインタを返す...多分 50 回くらい繰り返し...を返す関数ポインタ」みたいな型で代用
  • C にクロージャがない → クロージャを表す関数を 1000 個くらい定義して回避
  • 大量の関数定義はサイズ制限に合わない → マクロを駆使して畳み込み

結果、C コンパイラいじめの塊になりました。詳細はドキュメントを見てください。

なお、処理系部分は項書き換えっぽいアプローチで実装してます (コードの形状が示す通り) 。おかげでかなり小さくなっていると思う。

狙いと戦略

これは一番頑張った作品で、ネタにもコンパイラいじめ度にも自信ありました。が、掲載順位から察するに、それほど評価されなかった印象です。地味だからですかね。去年の金賞も地味なんだけどなあ。残念。

*1:普通に C で Lazy K インタプリタを書けばいいって?あーあー聞こえない

*2:詳しくは公式サイトを見てください。