kasei_sanのブログ

かせいさんのIT系のおぼえがきです。胡乱の方はnoteとtwitterへ

Rubyで書く! Quine AA!

こちらの記事を元に自分で作ってみた時の記録です

mickey24.hatenablog.com

ソースコード

     eval$s=%w'     
   b="BAhsK2Lgf4    
 D/D/7/8/9///j/B/9  
/8P8H/3/w/wf/f/7/7/ 
///+//P/   j/Af5/AOA
PAPwAAM     CHH3z4wY
cffPjBh     x98+MGHH
/z/wf8/     /P+D/z/g
nwMAAAA     PAPgBAA8
AAADAPw     D8A4A/AP
gDgB8A+  AGAHwD4AeA/
AP4D4D8A /gMAAOD8A/7
/4P8f/v/D/z/88MMPP/z
 www8//PDDDz/88AMA  
   APD/wP8//v//D/   
     7/////f///8x8A 
             /wPg/z/
              8/wP/P
                    
  wA=";    n=Mars   
  hal.l    oad(b.   
  unpac    k("m")   
  [0]);    x=20;y   
  =74;e    ="eval   
  $s=%w    "<<39<   
  <($s*    3);o="   
  ";j=-1;0.upto(x   
  *y){|i|o<<((n[i]  
  ==1)?e[j+=1]:32)  
   ;o<<((i%x==x-1)  
     ?10:"")}  ;o[  
                    
        -10,        
       6]=""<       
        <39<        
                    
      <".join"      
      ;puts(o)      
       ;#b="BA      
       hsK2Lgf      
       4D/D/7       
       /8/9//       
       /j/B/9       
       /8P8H/       
     3/w/wf/f/      
     7/7////+/      
     /P/j/Af5/      
     AOAPAPwAA      
                    
 MCH  H3z4wYcf      
 fPjBhx98+MGHH/z    
 /wf8//P+D/z/gnwM   
 AAAAPAPgBAA8AAADA  
  PwD8A4A/APgDgB8A  
  +AGAHw    D4AeA/  
  AP4D4D    8A/gMA  
  AOD8A/    7/4P8f  
  /v/D/z    /88MMP  
  P/zwww    8//PDD  
  Dz/88A    MAAPD/  
  wP8//v    //D/7/  
                    
    ////f///8x8A    
  /wPg/z/8/wP/PwA=  
 ";n=Marshal.load(b.
unpack("     m")[0])
;x=20;y=74;e="eval$s
=%w"<<39<<($s*3);o= 
"";j=-1;0.upto(x*y  
){|i|o<<(           
(n[i]==1)?          
 e[j+=1]:32);o<<((  
  i%x==x-1)?10:"")  
    };o[-10,'.join  

キモいですね!

作り方

以下の手順に沿ってコードを書いていく

  1. QuineにしたいAAを用意
  2. AAをBase64文字列に変換
  3. 「Base64文字列からAAを表示する」ジェネレーターを作成
  4. 「AAジェネレータ自身のコードをAAにする」ジェネレーターを作成
  5. 「AAジェネレーターをAAのQuineにする」ジェネレータを作成

AAのコードを実行可能にして、さらにQuine化

1. QuineにしたいAAを用意

このサイトで文字列をAAにできるので、これを元に作成。おすすめのフォントはdoh

https://www.askapache.com/online-tools/figlet-ascii/

aa=<<EOS
00000111111111100000
00011111111111110000
01111111111111111100
11111111111111111110
11111111000111111111
11111110000011111111
11111110000011111111
11111110000011111111
11111110000011111111
11111110000011111111
11111110011111111111
11111111011111111111
11111111111111111111
01111111111111111100
00011111111111111000
00000111111111111110
00000000000001111111
00000000000000111111
00000000000000000000
00111110000111111000
00111110000111111000
00111110000111111000
00111110000111111000
00111110000111111000
00111110000111111000
00111110000111111000
00111111111111111000
00111111111111111100
00111111111111111100
00011111111111111100
00000111111110011100
00000000000000000000
00000000111100000000
00000001111110000000
00000000111100000000
00000000000000000000
00000011111111000000
00000011111111000000
00000001111111000000
00000001111111000000
00000001111110000000
00000001111110000000
00000001111110000000
00000001111110000000
00000111111111000000
00000111111111000000
00000111111111000000
00000111111111000000
00000000000000000000
01110011111111000000
01111111111111110000
01111111111111111000
01111111111111111100
00111111111111111100
00111111000011111100
00111111000011111100
00111111000011111100
00111111000011111100
00111111000011111100
00111111000011111100
00111111000011111100
00000000000000000000
00001111111111110000
00111111111111111100
01111111111111111111
11111111000001111111
11111111111111111111
11111111111111111110
11111111111111111100
11111111100000000000
11111111110000000000
01111111111111111100
00111111111111111100
00001111111111111100
EOS

解説

  • このAAの1 の部分に後ほどソースコードが入る
  • ソースコードの文字数は当然ながら、AAで表現できる文字数よりも少ない必要がある
  • ソースコードにAAのデータも入るため、base64に変換して、格納可能にする

2. AAをBase64文字列に変換

普通に [Marshal.dump(bits)].pack("m") を使って変換

bits = aa.gsub("\n", "").reverse.to_i(2)
bin = [Marshal.dump(bits)].pack("m").gsub("\n", "")
puts bin #=>BAhsK2Lgf4D/D/7/8/9///j/B/9/8P8H/3/w/wf/f/7/7////+//P/j/Af5/AOAPAPwAAMCHH3z4wYcffPjBhx98+MGHH/z/wf8//P+D/z/gnwMAAAAPAPgBAA8AAADAPwD8A4A/APgDgB8A+AGAHwD4AeA/AP4D4D8A/gMAAOD8A/7/4P8f/v/D/z/88MMPP/zwww8//PDDDz/88AMAAPD/wP8//v//D/7/////f///8x8A/wPg/z/8/wP/PwA=

作成したBase64文字列は次のコードで使用するのでコピーする

3. 「Base64文字列からAAを表示する」ジェネレーターを作成

ジェネレータの概要

  1. Base64文字列にしたデータをデコード
  2. デコードしたBase64文字列を元に、0ならば空白。1ならば '1' を出力
  3. もともとのAAのサイズを元に、一定数出力したら改行

コード

# ↓AAをBase64文字列にしたデータ
b="BAhsK2Lgf4D/D/7/8/9///j/B/9/8P8H/3/w/wf/f/7/7////+//P/j/Af5/AOAPAPwAAMCHH3z4wYcffPjBhx98+MGHH/z/wf8//P+D/z/gnwMAAAAPAPgBAA8AAADAPwD8A4A/APgDgB8A+AGAHwD4AeA/AP4D4D8A/gMAAOD8A/7/4P8f/v/D/z/88MMPP/zwww8//PDDDz/88AMAAPD/wP8//v//D/7/////f///8x8A/wPg/z/8/wP/PwA="

# Base64文字列をデコード
n=Marshal.load(b.unpack("m")[0])

# AAのサイズ
x=20;y=74

# 出力結果が格納される変数
o=""

# デコードしたBase64文字列を展開
# 0ならば空白。1ならば '1' を出力
# 後でQuine化するときに改行や空白は使えないので数値で表現
0.upto(x*y){|i|
  o<<((n[i]==1)?'1':32) # 32 は空白
  o<<((i%x==x-1)?10:"") # 10 は改行
}
puts(o)

実行結果

     1111111111     
   1111111111111    
 11111111111111111  
1111111111111111111 
11111111   111111111
1111111     11111111
1111111     11111111
1111111     11111111
1111111     11111111
1111111     11111111
1111111  11111111111
11111111 11111111111
11111111111111111111
 11111111111111111  
   11111111111111   
     11111111111111 
             1111111
              111111
                    
  11111    111111   
  11111    111111   
  11111    111111   
  11111    111111   
  11111    111111   
  11111    111111   
  11111    111111   
  111111111111111   
  1111111111111111  
  1111111111111111  
   111111111111111  
     11111111  111  
                    
        1111        
       111111       
        1111        
                    
      11111111      
      11111111      
       1111111      
       1111111      
       111111       
       111111       
       111111       
       111111       
     111111111      
     111111111      
     111111111      
     111111111      
                    
 111  11111111      
 111111111111111    
 1111111111111111   
 11111111111111111  
  1111111111111111  
  111111    111111  
  111111    111111  
  111111    111111  
  111111    111111  
  111111    111111  
  111111    111111  
  111111    111111  
                    
    111111111111    
  1111111111111111  
 1111111111111111111
11111111     1111111
11111111111111111111
1111111111111111111 
111111111111111111  
111111111           
1111111111          
 11111111111111111  
  1111111111111111  
    11111111111111  
 

このAAジェネレータを元に、「AAジェネレータ自身のコードをAAにする」ジェネレーターを作成

ややこしくなってまいりました

4. 「AAジェネレータ自身のコードをAAにする」ジェネレーターを作成

概要

  • eval$s=%w'〜'.join を使って、$sをAAの材料にするジェネレータを作成する
  • の中にはAAジェネレータが入る

ソースコード

eval$s=%w'b="BAhsK2Lgf4D/D/7/8/9///j/B/9/8P8H/3/w/wf/f/7/7////+//P/j/Af5/AOAPAPwAAMCHH3z4wYcffPjBhx98+MGHH/z/wf8//P+D/z/gnwMAAAAPAPgBAA8AAADAPwD8A4A/APgDgB8A+AGAHwD4AeA/AP4D4D8A/gMAAOD8A/7/4P8f/v/D/z/88MMPP/zwww8//PDDDz/88AMAAPD/wP8//v//D/7/////f///8x8A/wPg/z/8/wP/PwA=";
n=Marshal.load(b.unpack("m")[0]);
x=20;
y=74;
e=$s*3;
o="";
j=-1;
0.upto(x*y){|i|o<<((n[i]==1)?e[j+=1]:32);o<<((i%x==x-1)?10:"")};
puts(o);
#
'.join

解説

  • eval$s=%w'〜'.join を使って、%$(すなわちジェネレータのコード) をAAにしている
    • %w'〜'.join することで、AAに余分な改行や空白が入らないようにしている
    • 結果としてコードには、改行や空白や ' をいれることはできない
  • e=$s*3; で、AA化するソースコードが、AAの文字数より多くなるように水増ししている
    • 10行目の # により、2回目以降出力されるコードはコメント扱いになる
  • 0.upto(x*y){|i|o<<((n[i]==1)?e[j+=1]:32);o<<((i%x==x-1)?10:"")}; にて、e に格納されたソースコードをAAに流し込んでいる

実行結果

     b="BAhsK2L     
   gf4D/D/7/8/9/    
 //j/B/9/8P8H/3/w/  
wf/f/7/7////+//P/j/ 
Af5/AOAP   APwAAMCHH
3z4wYcf     fPjBhx98
+MGHH/z     /wf8//P+
D/z/gnw     MAAAAPAP
gBAA8AA     ADAPwD8A
4A/APgD     gB8A+AGA
HwD4AeA  /AP4D4D8A/g
MAAOD8A/ 7/4P8f/v/D/
z/88MMPP/zwww8//PDDD
 z/88AMAAPD/wP8//v  
   //D/7/////f///   
     8x8A/wPg/z/8/w 
             P/PwA="
              ;n=Mar
                    
  shal.    load(b   
  .unpa    ck("m"   
  )[0])    ;x=20;   
  y=74;    e=$s*3   
  ;o=""    ;j=-1;   
  0.upt    o(x*y)   
  {|i|o    <<((n[   
  i]==1)?e[j+=1]:   
  32);o<<((i%x==x-  
  1)?10:"")};puts(  
   o)b="BAhsK2Lgf4  
     D/D/7/8/  9//  
                    
        /j/B        
       /9/8P8       
        H/3/        
                    
      w/wf/f/7      
      /7////+/      
       /P/j/Af      
       5/AOAPA      
       PwAAMC       
       HH3z4w       
       YcffPj       
       Bhx98+       
     MGHH/z/wf      
     8//P+D/z/      
     gnwMAAAAP      
     APgBAA8AA      
                    
 ADA  PwD8A4A/      
 APgDgB8A+AGAHwD    
 4AeA/AP4D4D8A/gM   
 AAOD8A/7/4P8f/v/D  
  /z/88MMPP/zwww8/  
  /PDDDz    /88AMA  
  APD/wP    8//v//  
  D/7///    //f///  
  8x8A/w    Pg/z/8  
  /wP/Pw    A=";n=  
  Marsha    l.load  
  (b.unp    ack("m  
                    
    ")[0]);x=20;    
  y=74;e=$s*3;o=""  
 ;j=-1;0.upto(x*y){|
i|o<<((n     [i]==1)
?e[j+=1]:32);o<<((i%
x==x-1)?10:"")};put 
s(o)b="BAhsK2Lgf4D  
/D/7/8/9/           
//j/B/9/8P          
 8H/3/w/wf/f/7/7//  
  //+//P/j/Af5/AOA  
    PAPwAAMCHH3z4w  

ここからジェネレータさらに加工して、上記AAを実行できるようにする

5. 「AAジェネレーターをAAのQuineにする」ジェネレータを作成

  • Syntax Error 回避のために、上記のコードを文字列として結合して、eval する処理を追加する
  • さらに、それをQuine化する

ベースとなるQuine

eval$s=%w'o="eval$s=%w"<<39<<$s<<39<<".join";puts(o)'.join
  • 39 は、'%w'〜'の中で'を使用するために数値を使用

このQuineは、o="...の前に任意のコードを埋めることができる

eval$s=%w'a="aaa";o="eval$s=%w"<<39<<$s<<39<<".join";puts(o)'.join

それを利用して以下のように、AAを作成するコードをQuineに埋め込む

ソースコード

eval$s=%w'b="BAhsK2Lgf4D/D/7/8/9///j/B/9/8P8H/3/w/wf/f/7/7////+//P/j/Af5/AOAPAPwAAMCHH3z4wYcffPjBhx98+MGHH/z/wf8//P+D/z/gnwMAAAAPAPgBAA8AAADAPwD8A4A/APgDgB8A+AGAHwD4AeA/AP4D4D8A/gMAAOD8A/7/4P8f/v/D/z/88MMPP/zwww8//PDDDz/88AMAAPD/wP8//v//D/7/////f///8x8A/wPg/z/8/wP/PwA=";
n=Marshal.load(b.unpack("m")[0]);
x=20;
y=74;
e="eval$s=%w"<<39<<($s*3);
o="";
j=-1;
0.upto(x*y){|i|o<<((n[i]==1)?e[j+=1]:32);o<<((i%x==x-1)?10:"")};
o[-10,6]=""<<39<<".join";
puts(o);
#
'.join

解説

  • e="eval$s=%w"<<39<<($s*3); が、Quineの先頭部分
  • o[-10,6]=""<<39<<".join"; で、AAで出力されるコードの末尾を '.join に置き換えて、Quineの末尾にしている

実行結果

     eval$s=%w'     
   b="BAhsK2Lgf4    
 D/D/7/8/9///j/B/9  
/8P8H/3/w/wf/f/7/7/ 
///+//P/   j/Af5/AOA
PAPwAAM     CHH3z4wY
cffPjBh     x98+MGHH
/z/wf8/     /P+D/z/g
nwMAAAA     PAPgBAA8
AAADAPw     D8A4A/AP
gDgB8A+  AGAHwD4AeA/
AP4D4D8A /gMAAOD8A/7
/4P8f/v/D/z/88MMPP/z
 www8//PDDDz/88AMA  
   APD/wP8//v//D/   
     7/////f///8x8A 
             /wPg/z/
              8/wP/P
                    
  wA=";    n=Mars   
  hal.l    oad(b.   
  unpac    k("m")   
  [0]);    x=20;y   
  =74;e    ="eval   
  $s=%w    "<<39<   
  <($s*    3);o="   
  ";j=-1;0.upto(x   
  *y){|i|o<<((n[i]  
  ==1)?e[j+=1]:32)  
   ;o<<((i%x==x-1)  
     ?10:"")}  ;o[  
                    
        -10,        
       6]=""<       
        <39<        
                    
      <".join"      
      ;puts(o)      
       ;#b="BA      
       hsK2Lgf      
       4D/D/7       
       /8/9//       
       /j/B/9       
       /8P8H/       
     3/w/wf/f/      
     7/7////+/      
     /P/j/Af5/      
     AOAPAPwAA      
                    
 MCH  H3z4wYcf      
 fPjBhx98+MGHH/z    
 /wf8//P+D/z/gnwM   
 AAAAPAPgBAA8AAADA  
  PwD8A4A/APgDgB8A  
  +AGAHw    D4AeA/  
  AP4D4D    8A/gMA  
  AOD8A/    7/4P8f  
  /v/D/z    /88MMP  
  P/zwww    8//PDD  
  Dz/88A    MAAPD/  
  wP8//v    //D/7/  
                    
    ////f///8x8A    
  /wPg/z/8/wP/PwA=  
 ";n=Marshal.load(b.
unpack("     m")[0])
;x=20;y=74;e="eval$s
=%w"<<39<<($s*3);o= 
"";j=-1;0.upto(x*y  
){|i|o<<(           
(n[i]==1)?          
 e[j+=1]:32);o<<((  
  i%x==x-1)?10:"")  
    };o[-10,'.join  
 

これで完成!!

ポイント

「AAのデータとアスキーアートジェネレーターをAAのQuineにする」ジェネレータを作って、AAのQuineを作成している

  • AAをBase64にしてコードに埋め込んでいる
  • AAの文字数とソースコードの文字数の不一致を何とかするために、コードの末尾をコメントにしてコードを3倍にしたものをAAに使用している
    • そのため、末尾の5文字はコメントのハズなので、'.join で置換しても問題がない
  • 基本となる Quine は eval$s=%w'puts"eval$s=%w"<<39<<$s<<39<<".join"'.join
    • eval$s='〜'.join というコードのために、' と空白文字が使用できない

感想

evalを使ったQuineは脳が裏返る感じがする