読者です 読者をやめる 読者になる 読者になる

PHPで<?="{K]X^\31\31EYG_Q\31"^"".M_PI;

今月上旬にPHP開発者の一人Andrea Fauldsさんが以下のツイートをしていました。

このtweetに含まれる、

<?="{K]X^\31\31EYG_Q\31"^"".M_PI;

と記述されたファイルをPHPで実行すると、

Hello, world!

とプログラムの初歩で有名な一節が出力されます。3v4lでの実行結果

もちろんAndreaさんが言ってるvery beginner-friendly languageというのは冗談だと思いますが、 私も「あー^ (ハット)が2つあるのが紛らわしいけどまあちょっと難読な"Hello, world!"だろうね!」 ...とは全く思えないので確認がてら雑な解説をします。

PHPにはあまり詳しくない人向けの解説

上のコードを置き換えると

<?php
echo "{K]X^\31\31EYG_Q\31" ^ "".M_PI;

さらに置き換えると

<?php
echo "{K]X^\31\31EYG_Q\31" ^ (string) M_PI;

となります。PHPにちなむところについてそれぞれ見ていくと、

&、| そして ^ 演算子の左右のオペランドが文字列の場合、その演算は、文字列を構成する文字の ASCII 値を
使って行います。その結果は文字列になります。
[PHP: ビット演算子 - Manual](http://php.net/manual/ja/language.operators.bitwise.php)

とのことです。↓の返信が元のtweetについてますとおり、

PHPでのプログラムではまず ^ での文字列の排他的論理和は使われないかなと思います。

ここまで踏まえて、なぞの文字列・円周率の文字・そして"Hello, world!"の桁を揃えると

{K]X^ \31\31 EYG_Q\31
3.141 5  9   265358   98
Hello ,      world!

となります。

ここまで来れば、PHPには詳しくない方は分かるのかと思われます。


文字列のビット演算ってどうなってるの人向けの説明

バイナリ処理や文字列演算になじみがないと、上の変換内容が理解しにくい部分があります。

PHPにて解釈のためのスクリプトに落とし込んでみます。 上述のPHP: ビット演算子 - Manual に沿いASCII値を利用とのことで、文字のASCII値を取得するのにord()関数を、またdecbin()関数を利用することで10進数を2進数に変換できます。

よって、個々の文字を二進数フォーマットで表記するには、

<?php
"0b" .  sprintf("%08d", decbin(ord($chr)));

と変換が行えます。*1

なぞの文字列・円周率の文字をそれぞれ二進数フォーマットにて変換した表が下記であり、

なぞの文字列    0b01111011      {
円周率の文字    0b00110011      3
変換結果        0b01001000      H
------------------------------------
なぞの文字列    0b01001011      K
円周率の文字    0b00101110      .
変換結果        0b01100101      e
------------------------------------
なぞの文字列    0b01011101      ]
円周率の文字    0b00110001      1
変換結果        0b01101100      l
------------------------------------
なぞの文字列    0b01011000      X
円周率の文字    0b00110100      4
変換結果        0b01101100      l
------------------------------------
なぞの文字列    0b01011110      ^
円周率の文字    0b00110001      1
変換結果        0b01101111      o
------------------------------------
なぞの文字列    0b00011001
円周率の文字    0b00110101      5
変換結果        0b00101100      ,
------------------------------------
なぞの文字列    0b00011001
円周率の文字    0b00111001      9
変換結果        0b00100000
------------------------------------
なぞの文字列    0b01000101      E
円周率の文字    0b00110010      2
変換結果        0b01110111      w
------------------------------------
なぞの文字列    0b01011001      Y
円周率の文字    0b00110110      6
変換結果        0b01101111      o
------------------------------------
なぞの文字列    0b01000111      G
円周率の文字    0b00110101      5
変換結果        0b01110010      r
------------------------------------
なぞの文字列    0b01011111      _
円周率の文字    0b00110011      3
変換結果        0b01101100      l
------------------------------------
なぞの文字列    0b01010001      Q
円周率の文字    0b00110101      5
変換結果        0b01100100      d
------------------------------------
なぞの文字列    0b00011001
円周率の文字    0b00111000      8
変換結果        0b00100001      !
------------------------------------

文字列の排他的論理和の結果としてHello, world!であることを確認できます。

上記確認出力用のスクリプトは↓です。

<?php

$text = "{K]X^\31\31EYG_Q\31";

foreach (preg_split("//", $text, -1, PREG_SPLIT_NO_EMPTY) as $i => $hello) {
    $pichr =  substr((string) M_PI, $i, 1);

    echo "なぞの文字列\t0b", chr2b($hello), "\t", $hello, PHP_EOL;
    echo "円周率の文字\t0b", chr2b($pichr), "\t", $pichr, PHP_EOL;

    $chr = xorchr($hello, $pichr);
    echo "変換結果\t0b",     chr2b($chr),   "\t", $chr,   PHP_EOL;
    echo "------------------------------------", PHP_EOL;
}

function chr2b($chr) {
    return sprintf("%08d", decbin(ord($chr)));
}

function xorchr($a, $b) {
    $a = "0b" . chr2b($a);
    $b = "0b" . chr2b($b);
    $xor = eval("return $a ^ $b ;");
    return chr($xor);
}

*1:二進数フォーマットは PHP 5.4 で可能になりました。