はじめに

本日開始された 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 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 個足し合わせてももちろん 10 が作れる)。

if 文なしで条件分岐

三項演算子ないし match 式を使うことで、if を一切書かずに条件分岐ができる。 また、&& / || も使えることがある。 遅延評価が不要なケースでは、[$t, $f][$cond] のような形で分岐することもできる。

whilefor 文なしでループ

不動点コンビネータを使う (説明は省略)。ここでは、一般に Z コンビネータとして知られるものを使った ($z)。

プログラム全体

Brainf*ck。以上。

第2問 riddle.php

前述のとおり、ネタバレ防止のため、2問目はまだ解説を書いていない。

第3問 toquine.php

前述のとおり、ネタバレ防止のため、3問目はまだ解説を書いていない。