aboutsummaryrefslogtreecommitdiffhomepage
path: root/content/posts/2022-04-09
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2022-11-19 14:23:32 +0900
committernsfisis <nsfisis@gmail.com>2022-11-19 14:25:59 +0900
commit6209453817da9922f28bac1bb1522c6d380630ab (patch)
tree19e0699e751af387d549d6720ca215c8065b3c0c /content/posts/2022-04-09
parent0cafa073914b5e0b162b735a7f8445fb2aa8a604 (diff)
downloadblog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.tar.gz
blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.tar.zst
blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.zip
Hugo to Asciidoctor
Diffstat (limited to 'content/posts/2022-04-09')
-rw-r--r--content/posts/2022-04-09/phperkaigi-2022-tokens.adoc (renamed from content/posts/2022-04-09/phperkaigi-2022-tokens.md)244
1 files changed, 126 insertions, 118 deletions
diff --git a/content/posts/2022-04-09/phperkaigi-2022-tokens.md b/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc
index 5640b8f..51d9a36 100644
--- a/content/posts/2022-04-09/phperkaigi-2022-tokens.md
+++ b/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc
@@ -1,28 +1,24 @@
----
-title: "PHPerKaigi 2022 トークン問題の解説"
-date: 2022-04-09T21:50:19+09:00
-lastmod: 2022-04-16
-tags: ["conference", "php", "phperkaigi"]
-summary: |
- PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。
-changelog:
- 2022-04-09: 公開
- 2022-04-16: 2問目、3問目の解説を追加、1問目に加筆
----
-
-# はじめに
-
-本日開始された [PHPerKaigi 2022](https://phperkaigi.jp/2022/) の PHPer チャレンジにおいて、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) の問題を 3問作成した。この記事では、これらの問題の解説をおこなう。
+= PHPerKaigi 2022 トークン問題の解説
+:tags: conference, php, phperkaigi
+:description: PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。
+:revision-1: 2022-04-09 公開
+:revision-2: 2022-04-16 2問目、3問目の解説を追加、1問目に加筆
-リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens
+== はじめに
+本日開始された https://phperkaigi.jp/2022/[PHPerKaigi 2022] の PHPer
+チャレンジにおいて、弊社
+https://www.dgcircus.com/[デジタルサーカス株式会社] の問題を
+3問作成した。この記事では、これらの問題の解説をおこなう。
+リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens
-# 第1問 brainf_ck.php
+== 第1問 brainf_ck.php
ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
-```php
+[source,php]
+----
<?php
declare(strict_types=0O1);
@@ -92,29 +88,28 @@ $🐘([
$👉, $👎, $📝,
$👈, $📝,
]);
-```
+----
この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
+=== 解説
-## 解説
-
-### 絵文字
+==== 絵文字
-まず目につくのは大量の絵文字だろう。
-PHP は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
+まず目につくのは大量の絵文字だろう。 PHP
+は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
+==== プログラム全体
-### プログラム全体
-
-Brainf\*ck のインタプリタとプログラムになっている。
-Brainf\*ck とは、難解プログラミング言語のひとつであり、ここで説明するよりも Wikipedia の該当ページを読んだ方がよい。
+Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck
+とは、難解プログラミング言語のひとつであり、ここで説明するよりも
+Wikipedia の該当ページを読んだ方がよい。
https://ja.wikipedia.org/wiki/Brainfuck
-なお、brainf\*ck プログラムを普通の書き方で書くと、次のようになる。
+なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。
-```
+....
+ + + + + + + + + +
[
> + + +
@@ -137,7 +132,7 @@ https://ja.wikipedia.org/wiki/Brainfuck
> + + .
> - .
< .
-```
+....
実行結果はこちら: https://ideone.com/22VWmb
@@ -151,48 +146,48 @@ https://ja.wikipedia.org/wiki/Brainfuck
* `$🤡`: `[`
* `$🎪`: `]`
-`,` (入力) に対応する関数はない (このプログラムでは使わないので用意していない)。
+`,` (入力) に対応する関数はない
+(このプログラムでは使わないので用意していない)。
なお、`$🐘` はいわゆる main 関数であり、プログラムの実行部分である。
+==== 絵文字の選択
-### 絵文字の選択
-
-おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪` は弊社デジタルサーカスにちなんでいる。
-また、`$🐘` は PHP のマスコットの象に由来する。
-
+おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪`
+は弊社デジタルサーカスにちなんでいる。 また、`$🐘` は PHP
+のマスコットの象に由来する。
-### strict_types
+==== strict_types
-`declare` 文の `strict_types` に指定できるのは、`0` か `1` の数値リテラルだが、
-`0x0` や `0b1` のような値も受け付ける。
-今回は、PHP 8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。
+`declare` 文の `strict_types` に指定できるのは、`0` か `1`
+の数値リテラルだが、 `0x0` や `0b1` のような値も受け付ける。 今回は、PHP
+8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。
-
-### URL
+==== URL
ソースコードのライセンスを示したこの部分だが、
-```php
+[source,php]
+----
https://creativecommons.org/publicdomain/zero/1.0/
-```
-
-完全に合法な PHP のコードである。
-`https:` 部分はラベル、`//` 以降は行コメントになっている。
+----
+完全に合法な PHP のコードである。 `https:` 部分はラベル、`//`
+以降は行コメントになっている。
-### リテラルなしで数値を生成する
+==== リテラルなしで数値を生成する
ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。
PHP では、型変換を利用することで任意の整数を作り出すことができる。
-```php
+[source,php]
+----
assert(0 === +!![]);
assert(1 === +![]);
assert(2 === ![]+![]);
assert(3 === ![]+![]+![]);
assert(10 === +(![].+!![]));
-```
+----
`[]` に `!` を適用すると `true` が返ってくる。それに `+`
を適用すると、`bool` から `int` ヘの型変換が走り、`1` が生成される。`10`
@@ -201,32 +196,36 @@ assert(10 === +(![].+!![]));
への型変換が走り、`10` が生まれる (コード量に頓着しないなら、`1` を 10
個足し合わせてももちろん 10 が作れる)。
-また、`error_reporting` に指定しているのは `-1` である。
-これは、`!` によって文字列を `false` にし、`+` によって `false` を `0` にし、さらにビット反転して `-1` にしている。
-
+また、`error_reporting` に指定しているのは `-1` である。 これは、`!`
+によって文字列を `false` にし、`+` によって `false` を `0`
+にし、さらにビット反転して `-1` にしている。
-### `if` 文なしで条件分岐
+==== `if` 文なしで条件分岐
-三項演算子ないし `match` 式を使うことで、`if` を一切書かずに条件分岐ができる。
-また、`&&` / `||` も使えることがある。
-遅延評価が不要なケースでは、`[$t, $f][$cond]` のような形で分岐することもできる。
+三項演算子ないし `match` 式を使うことで、`if`
+を一切書かずに条件分岐ができる。 また、`&&` / `||` も使えることがある。
+遅延評価が不要なケースでは、`[$t, $f][$cond]`
+のような形で分岐することもできる。
-### `while`、`for` 文なしでループ
+==== `while`、`for` 文なしでループ
-不動点コンビネータを使って無名再帰する (詳しい説明は省略する。これらの単語で検索してほしい)。
-ここでは、一般に Z コンビネータとして知られるものを使った (`$z`)。
+不動点コンビネータを使って無名再帰する
+(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に
+Z コンビネータとして知られるものを使った (`$z`)。
-実際のところ、`$🤡` や `$🎪`、`$🐘` は、一度 Scheme (Lisp の一種) で書いてから PHP に翻訳する形で記述した。
+実際のところ、`$🤡` や `$🎪`、`$🐘` は、一度 Scheme (Lisp の一種)
+で書いてから PHP に翻訳する形で記述した。
-なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) ので、
-あまりに長い brainf\*ck プログラムを書くとスタックオーバーフローする。
+なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは)
+ので、 あまりに長い brainf*ck
+プログラムを書くとスタックオーバーフローする。
-
-# 第2問 riddle.php
+== 第2問 riddle.php
ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
-```php
+[source,php]
+----
<?php
/*********************************************************
@@ -261,77 +260,86 @@ foreach ($token as $x) {
$x = implode("\n", str_split($x, length: 5));
echo "{$x}\n\n";
}
-```
+----
さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。
-トークンを得るためには、ソースコードを読み、定数 `N` を特定する必要がある。
+トークンを得るためには、ソースコードを読み、定数 `N`
+を特定する必要がある。
ここでは、私の想定解を解説する。
-
-## 読解
+=== 読解
まずはソースコードを読んでいく。
-```php
+[source,php]
+----
$token = [
// 略
];
-```
+----
数値からなる `$token` があり、各要素をループしている。
-```php
+[source,php]
+----
$x = $x ^ N;
-```
+----
まずは排他的論理和 (xor) を取り、
-```php
+[source,php]
+----
$x = sprintf('%025b', $x);
-```
+----
二進数に変換して、
-```php
+[source,php]
+----
$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-```
+----
0 を空白に、1 を `#` にし、
-```php
+[source,php]
+----
$x = implode("\n", str_split($x, length: 5));
-```
+----
5文字ごとに区切ったあと、改行で結合している。
-
-## ヒント
+=== ヒント
次に、ソースコードに書いてあるヒントを読んでいく。
-* `N` それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている
+* `N` それ自体は、42 や 8128
+といったような特別な意味を持たず、ランダムに決められている
* `$token` の各要素は、1文字を表す
* 1文字は 5x5 のセルからなる
* 出力されるのは、完全な PHPer トークンである
ここで、PHPer トークンは必ず `#` 記号から始まることを思いだすと、
-`$token` の最初の数字 `0x14B499C` は、変換の結果 `#` になるのではないかと予想される (なお、このことは、リポジトリの README ファイルに追加ヒントとして書かれている)。
-
+`$token` の最初の数字 `0x14B499C` は、変換の結果 `#`
+になるのではないかと予想される (なお、このことは、リポジトリの README
+ファイルに追加ヒントとして書かれている)。
-## 解く
+=== 解く
-ここまでわかれば、あと一歩で解ける。すなわち、`0x14B499C` が `#` に変換されるような `N` を見つければよい。
+ここまでわかれば、あと一歩で解ける。すなわち、`0x14B499C` が `#`
+に変換されるような `N` を見つければよい。
`N` は高々
-```php
+[source,php]
+----
assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
-```
+----
なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
-```php
+[source,php]
+----
<?php
$x = 0x14B499C;
@@ -348,11 +356,12 @@ assert($x ===
" # # \n" .
"#####\n" .
" # # ");
-```
+----
この一連の変換に対する逆変換を考えると、次のようになる。
-```php
+[source,php]
+----
<?php
$x =
@@ -369,17 +378,16 @@ $x = bindec($x);
$n = $x ^ 0x14B499C;
echo "N = $n\n";
-```
+----
これを実行すると、`N` が得られる。
-
-
-# 第3問 toquine.php
+== 第3問 toquine.php
ソースコードはこちら。
-```php
+[source,php]
+----
<?php
// License: https://creativecommons.org/publicdomain/zero/1.0/
@@ -409,49 +417,49 @@ $t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs
$t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
-```
+----
コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
-```shell-session
+[source,shell-session]
+----
$ php toquine.php | php | php | php | ...
-```
+----
実際にはもう少しパイプで繋げなければならない。
+=== 解説
-## 解説
-
-### プログラム全体
+==== プログラム全体
コメントにもあるとおり、これは quine (風) のプログラムになっている。
-Quine とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。
+Quine
+とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。
このプログラムは、実行すると自身とほとんど同じプログラムを出力する。
異なるのはトークンになっている部分のみである。
+==== トークン
-### トークン
+`$xs` がトークンに対応している。変換のロジックは `riddle.php`
+とほぼ同じなので省略する。
-`$xs` がトークンに対応している。変換のロジックは `riddle.php` とほぼ同じなので省略する。
+==== 状態保持
+トークンの何文字目まで出力したかを、ソースコードを変えずに (quine
+なので) 覚えておく必要がある。
+このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__`
+から情報を取得している。
-### 状態保持
-
-トークンの何文字目まで出力したかを、ソースコードを変えずに (quine なので) 覚えておく必要がある。
-このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__` から情報を取得している。
-
-
-### ROT 13
+==== ROT 13
Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。
-これがあまり美しくないので、`toquine.php` では、ROT 13 変換を使って難読化した。
+これがあまり美しくないので、`toquine.php` では、ROT 13
+変換を使って難読化した。
それにしてもなぜこんなものが標準ライブラリに……。
-
-
-# おわりに
+== おわりに
解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。