はじめに
本日開始された PHPerKaigi 2022 の PHPer チャレンジにおいて、弊社デジタルサーカスの問題を 3問作成した。この記事では、これらの問題の解説をおこなう。
リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens
注意: ネタバレ防止のため、2問目と 3問目はまだ解説を書いていない。
第1問 brainf_ck.php
ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
<?php
declare(strict_types=0O1);
namespace Dgcircus\PHPerKaigi\Y2022;
/**
* @todo
* Run this program to acquire a PHPer token.
*/
https://creativecommons.org/publicdomain/zero/1.0/
\error_reporting(~+!'We are hiring!');
$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
$id = \spl_object_id(...);
$put = fn($c) => \printf('%c', $c);
$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
$b => $loop(++$pc, ++$n),
$e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
default => $loop(++$pc, $n),
})($pc, -![])],
default => [$mp, ++$pc],
};
$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+!![] => [$mp, ++$pc],
default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
$e => $loop(--$pc, ++$n),
$b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
default => $loop(--$pc, $n),
})($pc, -![])],
};
$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
$🐘([
$👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
$🤡,
$👉, $👍, $👍, $👍,
$👉, $👍, $👍, $👍, $👍, $👍,
$👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
$👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
$👈, $👈, $👈, $👈, $👎,
$🎪,
$👉, $👍, $👍, $👍, $👍, $👍, $📝,
$👎, $👎, $📝,
$👉, $👎, $👎, $👎, $📝,
$👉, $👎, $👎, $👎, $📝,
$👎, $👎, $📝,
$👎, $📝,
$👈, $📝,
$👉, $👉, $👎, $👎, $📝,
$👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
$👈, $👎, $👎, $👎, $👎, $📝,
$👈, $📝,
$👉, $👍, $👍, $📝,
$👉, $👎, $📝,
$👈, $📝,
]);
この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
解説
絵文字
まず目につくのは大量の絵文字だろう。 PHP は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
URL
ソースコードのライセンスを示したこの部分だが、
https://creativecommons.org/publicdomain/zero/1.0/
完全に合法な PHP のコードである。
https: 部分はラベル、// 以降は行コメントになっている。
リテラルなしで数値を生成する
ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。
assert(0 === +!![]);
assert(1 === +![]);
assert(2 === ![]+![]);
assert(3 === ![]+![]+![]);
assert(10 === +(![].+!![]));
[] に ! を適用すると TRUE が返ってくる。それに +
を適用すると、bool から int ヘの型変換が走り、1 が生成される。10
はさらにトリッキーだ。まず 1 と 0 を作り、. で文字列として結合する
('10')。これに + を適用すると、string から int
への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10
個足し合わせてももちろん 10 が作れる)。
if 文なしで条件分岐
三項演算子ないし match 式を使うことで、if を一切書かずに条件分岐ができる。
また、&& / || も使えることがある。
遅延評価が不要なケースでは、[$t, $f][$cond] のような形で分岐することもできる。
while、for 文なしでループ
不動点コンビネータを使う (説明は省略)。ここでは、一般に Z
コンビネータとして知られるものを使った ($z)。
プログラム全体
Brainf*ck。以上。
第2問 riddle.php
前述のとおり、ネタバレ防止のため、2問目はまだ解説を書いていない。
第3問 toquine.php
前述のとおり、ネタバレ防止のため、3問目はまだ解説を書いていない。