aboutsummaryrefslogtreecommitdiffhomepage
path: root/content/posts/2022-04-09/phperkaigi-2022-tokens.md
blob: 6d110dc320feac08b13a9dc93afa8cae65f0b188 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
---
title: "PHPerKaigi 2022 トークン問題の解説"
date: 2022-04-09T21:50:19+09:00
draft: false
tags: ["conference", "php", "phperkaigi"]
aliases: ['/posts/phperkaigi-2022-tokens/']
---


# はじめに

本日開始された [PHPerKaigi 2022](https://phperkaigi.jp/2022/) の PHPer チャレンジにおいて、弊社デジタルサーカスの問題を 3問作成した。この記事では、これらの問題の解説をおこなう。

リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens


注意: ネタバレ防止のため、2問目と 3問目はまだ解説を書いていない。


# 第1問 brainf_ck.php

ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。

```php
<?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

ソースコードのライセンスを示したこの部分だが、

```php
https://creativecommons.org/publicdomain/zero/1.0/
```

完全に合法な PHP のコードである。
`https:` 部分はラベル、`//` 以降は行コメントになっている。

### リテラルなしで数値を生成する

ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。
PHP では、型変換を利用することで任意の整数を作り出すことができる。

```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問目はまだ解説を書いていない。