aboutsummaryrefslogtreecommitdiffhomepage
path: root/content/posts/2022-04-09/phperkaigi-2022-tokens.xml
diff options
context:
space:
mode:
Diffstat (limited to 'content/posts/2022-04-09/phperkaigi-2022-tokens.xml')
-rw-r--r--content/posts/2022-04-09/phperkaigi-2022-tokens.xml204
1 files changed, 102 insertions, 102 deletions
diff --git a/content/posts/2022-04-09/phperkaigi-2022-tokens.xml b/content/posts/2022-04-09/phperkaigi-2022-tokens.xml
index 352eba0..898d7ea 100644
--- a/content/posts/2022-04-09/phperkaigi-2022-tokens.xml
+++ b/content/posts/2022-04-09/phperkaigi-2022-tokens.xml
@@ -23,21 +23,21 @@
</info>
<section xml:id="intro">
<title>はじめに</title>
- <simpara>
+ <para>
本日開始された <link xl:href="https://phperkaigi.jp/2022/">PHPerKaigi 2022</link> の PHPer
チャレンジにおいて、弊社
<link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> の問題を
3問作成した。この記事では、これらの問題の解説をおこなう。
- </simpara>
- <simpara>
+ </para>
+ <para>
リポジトリはこちら: <link xl:href="https://github.com/nsfisis/PHPerKaigi2022-tokens">https://github.com/nsfisis/PHPerKaigi2022-tokens</link>
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck">
<title>第1問 brainf_ck.php</title>
- <simpara>
+ <para>
ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
<?php
@@ -111,31 +111,31 @@
]);
]]>
</programlisting>
- <simpara>
+ <para>
この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
- </simpara>
+ </para>
<section xml:id="q1-brainfuck--commentary">
<title>解説</title>
<section xml:id="q1-brainfuck--commentary--emoji">
<title>絵文字</title>
- <simpara>
+ <para>
まず目につくのは大量の絵文字だろう。 PHP
は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--brainfuck">
<title>プログラム全体</title>
- <simpara>
+ <para>
Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck
とは、難解プログラミング言語のひとつであり、ここで説明するよりも
Wikipedia の該当ページを読んだ方がよい。
- </simpara>
- <simpara>
+ </para>
+ <para>
<link xl:href="https://ja.wikipedia.org/wiki/Brainfuck">https://ja.wikipedia.org/wiki/Brainfuck</link>
- </simpara>
- <simpara>
+ </para>
+ <para>
なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。
- </simpara>
+ </para>
<literallayout class="monospaced">
<![CDATA[
+ + + + + + + + + +
@@ -162,12 +162,12 @@
< .
]]>
</literallayout>
- <simpara>
+ <para>
実行結果はこちら: <link xl:href="https://ideone.com/22VWmb">https://ideone.com/22VWmb</link>
- </simpara>
- <simpara>
+ </para>
+ <para>
それぞれの絵文字で表された関数が、各命令に対応している。
- </simpara>
+ </para>
<itemizedlist>
<listitem><literal>$👉</literal>: <literal>&gt;</literal></listitem>
<listitem><literal>$👈</literal>: <literal>&lt;</literal></listitem>
@@ -177,51 +177,51 @@
<listitem><literal>$🤡</literal>: <literal>[</literal></listitem>
<listitem><literal>$🎪</literal>: <literal>]</literal></listitem>
</itemizedlist>
- <simpara>
+ <para>
<literal>,</literal> (入力) に対応する関数はない
(このプログラムでは使わないので用意していない)。
- </simpara>
- <simpara>
+ </para>
+ <para>
なお、<literal>$🐘</literal> はいわゆる main 関数であり、プログラムの実行部分である。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--emoji-selection">
<title>絵文字の選択</title>
- <simpara>
+ <para>
おおよそ意味に合致するよう選んでいるが、<literal>$🤡</literal> と <literal>$🎪</literal>
は弊社デジタルサーカスにちなんでいる。 また、<literal>$🐘</literal> は PHP
のマスコットの象に由来する。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--strict-types">
<title>strict_types</title>
- <simpara>
+ <para>
<literal>declare</literal> 文の <literal>strict_types</literal> に指定できるのは、<literal>0</literal> か <literal>1</literal>
の数値リテラルだが、 <literal>0x0</literal> や <literal>0b1</literal> のような値も受け付ける。 今回は、PHP
8.1 から追加された、<literal>0O</literal> または <literal>0o</literal> から始まる八進数リテラルを使った。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--url">
<title>URL</title>
- <simpara>
+ <para>
ソースコードのライセンスを示したこの部分だが、
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
https://creativecommons.org/publicdomain/zero/1.0/
]]>
</programlisting>
- <simpara>
+ <para>
完全に合法な PHP のコードである。 <literal>https:</literal> 部分はラベル、<literal>//</literal>
以降は行コメントになっている。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--numbers">
<title>リテラルなしで数値を生成する</title>
- <simpara>
+ <para>
ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。
PHP では、型変換を利用することで任意の整数を作り出すことができる。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
assert(0 === +!![]);
@@ -231,53 +231,53 @@
assert(10 === +(![].+!![]));
]]>
</programlisting>
- <simpara>
+ <para>
<literal>[]</literal> に <literal>!</literal> を適用すると <literal>true</literal> が返ってくる。それに <literal>+</literal>
を適用すると、<literal>bool</literal> から <literal>int</literal> ヘの型変換が走り、<literal>1</literal> が生成される。<literal>10</literal>
はさらにトリッキーだ。まず <literal>1</literal> と <literal>0</literal> を作り、<literal>.</literal> で文字列として結合する
(<literal>'10'</literal>)。これに <literal>+</literal> を適用すると、<literal>string</literal> から <literal>int</literal>
への型変換が走り、<literal>10</literal> が生まれる (コード量に頓着しないなら、<literal>1</literal> を 10
個足し合わせてももちろん 10 が作れる)。
- </simpara>
- <simpara>
+ </para>
+ <para>
また、<literal>error_reporting</literal> に指定しているのは <literal>-1</literal> である。 これは、<literal>!</literal>
によって文字列を <literal>false</literal> にし、<literal>+</literal> によって <literal>false</literal> を <literal>0</literal>
にし、さらにビット反転して <literal>-1</literal> にしている。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--conditionals">
<title><literal>if</literal> 文なしで条件分岐</title>
- <simpara>
+ <para>
三項演算子ないし <literal>match</literal> 式を使うことで、<literal>if</literal>
を一切書かずに条件分岐ができる。 また、<literal>&amp;&amp;</literal> / <literal>||</literal> も使えることがある。
遅延評価が不要なケースでは、<literal>[$t, $f][$cond]</literal>
のような形で分岐することもできる。
- </simpara>
+ </para>
</section>
<section xml:id="q1-brainfuck--commentary--loops">
<title><literal>while</literal>、<literal>for</literal> 文なしでループ</title>
- <simpara>
+ <para>
不動点コンビネータを使って無名再帰する
(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に
Z コンビネータとして知られるものを使った (<literal>$z</literal>)。
- </simpara>
- <simpara>
+ </para>
+ <para>
実際のところ、<literal>$🤡</literal> や <literal>$🎪</literal>、<literal>$🐘</literal> は、一度 Scheme (Lisp の一種)
で書いてから PHP に翻訳する形で記述した。
- </simpara>
- <simpara>
+ </para>
+ <para>
なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは)
ので、 あまりに長い brainf*ck
プログラムを書くとスタックオーバーフローする。
- </simpara>
+ </para>
</section>
</section>
</section>
<section xml:id="q2-riddle">
<title>第2問 riddle.php</title>
- <simpara>
+ <para>
ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
<?php
@@ -316,19 +316,19 @@
}
]]>
</programlisting>
- <simpara>
+ <para>
さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。
トークンを得るためには、ソースコードを読み、定数 <literal>N</literal>
を特定する必要がある。
- </simpara>
- <simpara>
+ </para>
+ <para>
ここでは、私の想定解を解説する。
- </simpara>
+ </para>
<section xml:id="q2-riddle--code-reading">
<title>読解</title>
- <simpara>
+ <para>
まずはソースコードを読んでいく。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
$token = [
@@ -336,77 +336,77 @@
];
]]>
</programlisting>
- <simpara>
+ <para>
数値からなる <literal>$token</literal> があり、各要素をループしている。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
$x = $x ^ N;
]]>
</programlisting>
- <simpara>
+ <para>
まずは排他的論理和 (xor) を取り、
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
$x = sprintf('%025b', $x);
]]>
</programlisting>
- <simpara>
+ <para>
二進数に変換して、
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
]]>
</programlisting>
- <simpara>
+ <para>
0 を空白に、1 を <literal>#</literal> にし、
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
$x = implode("\n", str_split($x, length: 5));
]]>
</programlisting>
- <simpara>
+ <para>
5文字ごとに区切ったあと、改行で結合している。
- </simpara>
+ </para>
</section>
<section xml:id="q2-riddle--hint">
<title>ヒント</title>
- <simpara>
+ <para>
次に、ソースコードに書いてあるヒントを読んでいく。
- </simpara>
+ </para>
<itemizedlist>
<listitem><literal>N</literal> それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている</listitem>
<listitem><literal>$token</literal> の各要素は、1文字を表す</listitem>
<listitem>1文字は 5x5 のセルからなる</listitem>
<listitem>出力されるのは、完全な PHPer トークンである</listitem>
</itemizedlist>
- <simpara>
+ <para>
ここで、PHPer トークンは必ず <literal>#</literal> 記号から始まることを思いだすと、
<literal>$token</literal> の最初の数字 <literal>0x14B499C</literal> は、変換の結果 <literal>#</literal>
になるのではないかと予想される (なお、このことは、リポジトリの README
ファイルに追加ヒントとして書かれている)。
- </simpara>
+ </para>
</section>
<section xml:id="q2-riddle--solve">
<title>解く</title>
- <simpara>
+ <para>
ここまでわかれば、あと一歩で解ける。すなわち、<literal>0x14B499C</literal> が <literal>#</literal>
に変換されるような <literal>N</literal> を見つければよい。
- </simpara>
- <simpara>
+ </para>
+ <para>
<literal>N</literal> は高々
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
]]>
</programlisting>
- <simpara>
+ <para>
なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
<?php
@@ -427,9 +427,9 @@
" # # ");
]]>
</programlisting>
- <simpara>
+ <para>
この一連の変換に対する逆変換を考えると、次のようになる。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
<?php
@@ -450,16 +450,16 @@
echo "N = $n\n";
]]>
</programlisting>
- <simpara>
+ <para>
これを実行すると、<literal>N</literal> が得られる。
- </simpara>
+ </para>
</section>
</section>
<section xml:id="q3-toquine">
<title>第3問 toquine.php</title>
- <simpara>
+ <para>
ソースコードはこちら。
- </simpara>
+ </para>
<programlisting language="php" linenumbering="unnumbered">
<![CDATA[
<?php
@@ -493,71 +493,71 @@
printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
]]>
</programlisting>
- <simpara>
+ <para>
コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
- </simpara>
+ </para>
<programlisting language="shell-session" linenumbering="unnumbered">
<![CDATA[
$ php toquine.php | php | php | php | ...
]]>
</programlisting>
- <simpara>
+ <para>
実際にはもう少しパイプで繋げなければならない。
- </simpara>
+ </para>
<section xml:id="q3-toquine--commentary">
<title>解説</title>
<section xml:id="q3-toquine--commentary--quine">
<title>プログラム全体</title>
- <simpara>
+ <para>
コメントにもあるとおり、これは quine (風) のプログラムになっている。
Quine
とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。
- </simpara>
- <simpara>
+ </para>
+ <para>
このプログラムは、実行すると自身とほとんど同じプログラムを出力する。
異なるのはトークンになっている部分のみである。
- </simpara>
+ </para>
</section>
<section xml:id="q3-toquine--commentary--tokens">
<title>トークン</title>
- <simpara>
+ <para>
<literal>$xs</literal> がトークンに対応している。変換のロジックは <literal>riddle.php</literal>
とほぼ同じなので省略する。
- </simpara>
+ </para>
</section>
<section xml:id="q3-toquine--commentary--states">
<title>状態保持</title>
- <simpara>
+ <para>
トークンの何文字目まで出力したかを、ソースコードを変えずに (quine
なので) 覚えておく必要がある。
このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、<literal><emphasis>LINE</emphasis></literal>
から情報を取得している。
- </simpara>
+ </para>
</section>
<section xml:id="q3-toquine--commentary--rot-13">
<title>ROT 13</title>
- <simpara>
+ <para>
Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。
これがあまり美しくないので、<literal>toquine.php</literal> では、ROT 13
変換を使って難読化した。
- </simpara>
- <simpara>
+ </para>
+ <para>
それにしてもなぜこんなものが標準ライブラリに……。
- </simpara>
+ </para>
</section>
</section>
</section>
<section xml:id="outro">
<title>おわりに</title>
- <simpara>
+ <para>
解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
- </simpara>
- <simpara>
+ </para>
+ <para>
今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、
来年は 5問、より面白い問題を持っていきます。
- </simpara>
- <simpara>
+ </para>
+ <para>
実はもう作りはじめているので、どうか来年もありますように……。
- </simpara>
+ </para>
</section>
</article>