Acme::EyeDropsでアナタも記号プログラマー!B!

最近、全裸で転職しました。sugyanです。

まだまだPerlのことはよく分からないトーシロですが、大好きなモジュールを紹介したいと思います。

はじめに

Perlは非常に柔軟な言語で、TMTOWTD【There's More Than One Way To Do It.】と言われる通り、同じ処理をするプログラムでも色んな書き方ができます。

例えばプログラムを予約語だけで書いてみたり、記号だけで書いてみたりできちゃうわけですね。

そんなスーパーハッカーみたいなことをやってみたい!という願望はPerl好きの変態な皆さんなら誰しも持っているのではないでしょうか!?

今回紹介するモジュールAcme::EyeDropsは、そんな変態プログラミングをお手伝いしてくれるモジュールです。

CPANからインストールできます。最新バージョンは1.55です。

使い方(初級編)

まずは何も考えず普通のプログラムを用意しましょう。

use strict;
use warnings;

print "Hello world!\n";

誰もが一度は書く、ハローワールドですね。これを'hello.pl'とでも名付けて保存しましょう。

そして、それとは別に以下のようなプログラムを書きます。

use strict;
use warnings;

use Acme::EyeDrops 'sightly';

print sightly({
    Shape      => 'camel',
    SourceFile => 'hello.pl',
});

先程の'hello.pl'と同じディレクトリで実行してみましょう。

$ perl eyedrops.pl
1 shapes completed.
eval eval '"'.


                                      ('['^"\.").(
           ('[')^                   '(').('`'|'%').
        ('{'^'[').                ('['^'(').('['^'/'
 ).('['^')'  ).('`'              |')').('`'|'#').('['
^'/').';'.('!'^'+').            ('['^'.').('['^"\(").(
'`'|'%').('{'^"\[").(          '['^',').('`'|'!').('['
  ^')').('`'|('.')).(        '`'|')').('`'|'.').(('`')|
       "'").('['^'(')      .';'.('!'^'+').('!'^'+').('['
      ^'+').('['^')'     ).('`'|')').('`'|'.').('['^'/').
     ('{'^'[').'\\'.   '"'.('`'^'(').('`'|'%').('`'|',').(
    '`'|',').("\`"|   '/').('{'^'[').('['^',').('`'|('/')).(
    '['^')').("\`"|  ',').('`'|'$').'!'.'\\'.'\\'.('`'|"\.").
    '\\'.'"'.';'.(  '!'^'+').'"';$:='.'^'~';$~='@'|'(';$^=')'
    ^'[';$/=('`')|  '.';$,='('^'}';$\='`'|'!';$:=')'^('}');$~=
    '*'|'`';$^='+'^'_';$/='&'|'@';$,='['&'~';$\=','^'|';$:='.'
    ^'~';$~='@'|'(';$^=')'^'[';$/='`'|'.';$,='('^'}';$\='`'|'!'
     ;$:=')'^'}';$~='*'|'`';$^='+'^'_';$/='&'|'@';$,='['&'~';$\
     =','^'|';$:='.'^'~';$~='@'|'(';$^=')'^'[';$/='`'|'.';$,='('
      ^'}';$\='`'|'!';$:=')'^'}';$~='*'|'`';$^='+'^'_';$/='&'|'@'
       ;$,='['&'~';$\=','^'|';$:='.'^'~';$~='@'|'(';$^=')'^'[';$/
        ='`'|'.';$,='('^'}';$\='`'|'!';$:=')'^'}';$~='*'|'`'; $^=
         '+'^'_';$/='&'|'@';$,='['&'~';$\=','^'|';$:='.'^'~'  ;$~
          ='@'|'(';$^=')'^'[';$/='`'|'.';$,='('^'}' ;$\='`'|  '!'
            ;$:=')'^'}';$~='*'|'`';$^='+'^"\_";$/=  '&'|'@'   ;$,
             ='['& '~';$\=','^'|';$:='.'^"\~";$~=   '@'|'('   ;$^
                   =')'^'[';$/='`'|'.';$,=('(')^    '}';$\=   '`'
                   |'!';$:=    ')'^'}';$~ ="\*"|     '`';$^   =(
                   ('+'))^     "\_";$/=   ('&')|     '@';$,  =(
                   ('['))&     "\~";$\=   "\,"^       "\|";  (
                   ($:))=      '.'^'~';   ($~)         ='@'
                   |"\(";     $^=(')')^   '[';         ($/)
                    ='`'|     "\.";$,=    '('^         '}';
                    ($\)=     '`'|'!'     ;$:=         ')'^
                    '}';     $~='*'       |'`'         ;$^=
                    '+'^     '_'          ;$/=         '&'|
                    '@';      $,=         '['          &((
                    '~'        ));       $\=           ','
                    ^((         '|'     ));            $:=
                    '.'          ^((   '~'              ))
                    ;(             ($~))=               ((
                    ((              '@'))               ))
                    |+             "\(";$^=             ((
                   ')'            ))^+ "\[";            $/
                  =((           '`'))|  '.';           $,=
                 '('^         "\}";$\=   '`'          |'!'
               ;($:)=                                (')')^
             "\}";$~=                               '*'|'`'

なんと、ラクダが現れました!しかも記号だらけです!

この記号で作られたラクダ、Perlプログラムとして実行可能なのです。

試しにpipeでそのままperlに渡してみましょう(出力をファイル保存して実行しても一緒です)。

$ perl eyedrops.pl | perl
1 shapes completed.
Hello world!
$ perl eyedrops.pl > hoge.pl
1 shapes completed.
$ perl hoge.pl
Hello world!

もとの'hello.pl'で期待される実行結果と一緒ですね!

このように、Acme::Eyedropsモジュールは「プログラムを指定した形(模様?)の記号プログラムに変換してくれる」モジュールなのです。

使い方(中級編)

まずはラクダ以外の形ではできないの?という疑問があると思います。

'camel'と指定していた部分を変えてみましょう。

use strict;
use warnings;

use Acme::EyeDrops 'sightly';

print sightly({
    Shape      => 'smiley',
    SourceFile => 'hello.pl',
});

実行すると…

$ perl eyedrops.pl
1 shapes completed.
eval eval '"'.


                ('['^'.').('['^
           '(').('`'|'%').('{'^'[').
        ('['^'(').('['^'/').('['^')').(
      '`'|')').('`'|'#').('['^'/')."\;".(
    '!'^'+').('['^'.').('['^'(').('`'|'%').
   ('{'^'[').    ('['^(',')).(    '`'|'!').(
  '['^')').(      '`'|"\.").(      '`'|')').(
 '`'|"\.").(      '`'|"\'").(      '['^('(')).
';'.('!'^'+')    .('!'^"\+").(    '['^'+').('['
^')').('`'|')').('`'|'.').('['^'/').('{'^"\[").
'\\'.'"'.('`'^'(').('`'|'%').('`'|',').('`'|','
).('`'|'/').('{'^'[').('['^',').('`'|'/').('['^
')').('`'|',').('`'|'$').'!'.'\\'.'\\'.('`'|'.'
).''.  '\\'.'"'.';'.('!'^'+').'"';$:='.'  ^'~';
($~)=   '@'|'(';$^=')'^'[';$/='`'|"\.";   ($,)=
('(')^    '}';$\='`'|'!';$:=')'^"\}";    $~='*'
 |"\`";     $^='+'^'_';$/='&'|"\@";     $,='['
  &"\~";      $\=','^'|';$:="\."^      '~';$~
   =('@')|        '(';$^=')'^        '[';$/=
    '`'|"\.";                     $,=('(')^
      '}';$\='`'|             '!';$:=')'^
        '}';$~='*'|'`';$^='+'^('_');$/=
           '&'|'@';$,='['&'~';$\=','
                ^'|';$:='.'^'~'

ちょ、ちょっと怖いけどスマイルが出てきますね…。

これも、もとの'hello.pl'と等価な記号プログラムです。

さて、この'Shape'に指定できる形は何種類あるのでしょう?

Acme::EyeDropsがインストールされているディレクトリに'EyeDrops/*.eye'というファイルがたくさん入っており、これが出力のテンプレートとして使われます。

以下のコマンドで一覧表示されると思います。

$ find $(perldoc -l Acme::EyeDrops | sed s/.pm//) -name '*.eye'

93種類くらいあるのでいちいち紹介しません。色々試してみて楽しんで下さい。

ちなみに'all'と指定すると全部繋げてくれるようなので、それだけでお腹一杯になれるかも知れません。それ以外にも'triangle'とかちょっと特殊なものを指定することができたりもします。詳しくはPODを参照して下さい。

ところで出力されるこの記号プログラム、完全に記号だけかと思いきや、冒頭に'eval eval'と謎の呪文が書いてありますね。

このままだとちょっと残念なので、完全に記号だけのプログラムにしてしまいましょう。sightly関数の引数に'Regex => 1'を渡してやります。

use strict;
use warnings;

use Acme::EyeDrops 'sightly';

print sightly({
    Shape      => 'smiley',
    SourceFile => 'hello.pl',
    Regex      => 1,
});

こうするだけで、完全に記号しか現れないプログラムを得ることができます(ただし、これを使うと元のソースによっては上手く変換できないこともあるらしいので要注意です)。

他にもまだまだカスタマイズするためのオプションがあります。

例えば、いちいち変換するためのスクリプトを用意するのは面倒ですよね。そんなときには'SourceString'オプションでソースコードの文字列を指定してやることができます。

例えば、出来上がる形を横向きにしてみたいというとき。そんなときは'Rotate'オプションで90°単位の回転をさせることができます。

例えば、毎回'1 shapes completed.'が出力されるのはウザイ、というとき。'InformHandler'を指定することで出力を抑制させることができます。

use strict;
use warnings;

use Acme::EyeDrops 'sightly';

print sightly({
    Shape         => 'smiley',
    SourceString  => 'print "Hello smiley!\n"',
    Regex         => 1,
    Rotate        => 90,
    InformHandler => sub {},
});

こんなカンジで書いてやれば、'Hello smiley!'と出力する、横向きスマイリーな記号プログラムを作れます。お試しあれ。

このように、「よくこんなネタモジュールにこれだけオプション作ったなーw」と感心してしまうくらい、たくさんオプションがあります。ヒマな方はPODをじっくり読んでみるといいと思います。

使い方(上級編?)

さて、ここまで使い込んでくると、自分で出力の形を定義したくなったりもしてきますよね!

もちろんそれも出来ます。

私は普段、こんなアイコンを色々なところで使っているのですが、せっかくなのでこれを使って自分オリジナルの記号プログラムを作ってみることにしました。

自分で指定した形に記号プログラムを作るには、'ShapeString'にその形を表現する文字列を指定します。

上述した.eyeファイルたちを見てみるとわかりますが、'#'記号でそれぞれの形を表現しています。

$ cat $(perldoc -l Acme::EyeDrops | sed s/.pm//)/a.eye
   ######
 ##   ####
####  ####
 ##   ####
   #######
 #### ####
####  ####
#### #####
#####  ####

これに倣って自分で作ってみます。

まずは自分のアイコンを64x40くらいのサイズに調整します(縦長になることを想定して横長気味に)。

これをImageMagickについているconvertコマンドでpbmファイルに変換しました。

$ convert sugyan.png sugyan.pbm

pbmファイルは白黒の2値で画像を表現してくれるわかりやすい形式です。

$ head -c 16 sugyan.pbm | hexdump -C
00000000  50 34 0a 36 34 20 34 30  0a 00 00 00 00 00 00 00  |P4.64 40........|
00000010

最初の9byteはサイズを表しているだけの部分で不要ですね。それ以外の部分を簡単なワンライナーを使って'#'と' 'だけで表してみましょう。

$ tail -c +10 sugyan.pbm | perl -ne 'print tr/01/ #/ ? $_ : "\n" for split /(.{64})/, unpack("B*", $_)'

                                                                
                                                                
                           ##                                   
                           ###                                  
                           ###                                  
                          ############                          
                ######################                          
           ##########       ###                                 
          #####             ###                                 
           #                ###                                 
                      #########                                 
                    ###########                                 
                   ####   #####                                 
                  ####     ####                                 
                  ###      ####                                 
                  ###      ####                                 
                  ###     ######         ##     ##              
                   #############         ###    ###             
                     ####### ###         ####    ###            
                             ####         ###    ####           
                              ##########  ###     ###           
                          ############     ###    ###           
                     ######### ###         ###     ###          
                    ######      ###      ######     ##          
                                 ##############      #          
                                ########                        
                            #########                           
                       #########  ###                           
                      ######       ###                          
                                   ####                         
                                   ####                         
                                   ###                          
                     ###                                        
                      ####                                      
                        #####                                   
                           #####                                
                              #####                             
                                                                
                                                                

ここまでできれば、あとはAcme::EyeDropsに任せるだけですね。最後までワンライナーでやっちゃいましょう。

$ tail -c +10 sugyan.pbm | perl -ne 'print tr/01/ #/ ? $_ : "\n" for split /(.{64})/, unpack("B*", $_)' | perl -MAcme::EyeDrops=sightly -0ne 'print sightly({ ShapeString => $_, SourceString => "print qq[sugyan\n]", Regex => 1 })'

                                                                
                                                                
                           ''                                   
                           =~(                                  
                           '('                                  
                          .'?'.('{').(                          
                '`'|'%').('['^('-')).(                          
           '`'|'!').(       '`'                                 
          |',')             .((                                 
           (                '"'                                 
                      ))).('['^                                 
                    '+').("\["^                                 
                   ')')   .('`'                                 
                  |')'     ).+(                                 
                  '`'      |'.'                                 
                  ).(      '['^                                 
                  '/'     ).('{'         ^+     ((              
                   '['))).("\["^         '*'    ).(             
                     '['^'*' ).+         '['.    (((            
                             '[')         )^+    '(')           
                              .('['^'.')  .+(     '`'           
                          |"'").("\["^     '"'    ).(           
                     '`'|'!'). (((         '`'     ))|          
                    '.').(      '!'      ^'+').     ((          
                                 ']')).'"'.'}'.      (          
                                ')'));$:                        
                            ='.'^'~';                           
                       $~=('@')|  '('                           
                      ;($^)=       ')'                          
                                   ^'['                         
                                   ;$/=                         
                                   '`'                          
                     |((                                        
                      '.')                                      
                        );$,=                                   
                           "\("^                                
                              "\}";                             
                                                                
                                                                
                                                                

これで、自分のアイコンの形をした、自分の名前を出力する、自分だけの記号プログラムの完成です!

皆さんも作ってみてはいかがでしょうか?

そういえば

そもそもどうして記号だけでプログラムが書けるのか、というところにまったく触れていませんでした。

ヒトコトで説明すると、「任意の文字列を排他的論理和を使って記号だけで表現し、拡張正規表現を使ってそれをevalする」ということになります。

'regex_eval_sightly'を使うと変形を行わずに記号への変換だけを行ってくれます。

use strict;
use warnings;

use Acme::EyeDrops 'regex_eval_sightly';

print regex_eval_sightly('print "Hello world!\n"');

参考文献

以下のページがとても丁寧に解説しているのでオススメです。

兼雑記 - Perl記号ゴルフとAcme::EyeDrops

まとめ

まったく役に立ちそうにない話を長々と書いてしまいました!ごめんなさい!!

明日はhaoyayoiさんです!どうぞお楽しみに!