aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--NOTE.md2
-rw-r--r--content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc279
-rw-r--r--lib/command.rb4
-rw-r--r--public/posts/2021-03-05/my-first-post/index.html1
-rw-r--r--public/posts/2021-03-30/phperkaigi-2021/index.html1
-rw-r--r--public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html1
-rw-r--r--public/posts/2021-10-02/python-unbound-local-error/index.html1
-rw-r--r--public/posts/2021-10-02/ruby-detect-running-implementation/index.html1
-rw-r--r--public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html1
-rw-r--r--public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html1
-rw-r--r--public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html1
-rw-r--r--public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html1
-rw-r--r--public/posts/2022-04-09/phperkaigi-2022-tokens/index.html1
-rw-r--r--public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html1
-rw-r--r--public/posts/2022-05-01/phperkaigi-2022/index.html1
-rw-r--r--public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html1
-rw-r--r--public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html1
-rw-r--r--public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html1
-rw-r--r--public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html1
-rw-r--r--public/posts/2022-10-28/setup-server-for-this-site/index.html1
-rw-r--r--public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html1
-rw-r--r--public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html460
-rw-r--r--public/posts/index.html22
-rw-r--r--public/tags/php/index.html16
-rw-r--r--public/tags/phperkaigi/index.html16
-rw-r--r--templates/document__post.html.erb9
26 files changed, 820 insertions, 6 deletions
diff --git a/NOTE.md b/NOTE.md
index c8c6098..88c5e55 100644
--- a/NOTE.md
+++ b/NOTE.md
@@ -12,7 +12,7 @@ Create a new post.
```
$ mkdir -p content/posts/$(date +'%Y-%m-%d')
-$ touch content/posts/$(date +'%Y-%m-%d')/[TITLE].md
+$ touch content/posts/$(date +'%Y-%m-%d')/[TITLE].adoc
```
## TODO
diff --git a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc
new file mode 100644
index 0000000..29a86a1
--- /dev/null
+++ b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc
@@ -0,0 +1,279 @@
+= PHPerKaigi 2023: ボツになったトークン問題 その 3
+:tags: php, phperkaigi
+:description: 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 \
+ ボツになった問題を公開する (その 3)。
+:revision-1: 2023-01-10 公開
+
+
+== はじめに
+
+2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の https://phperkaigi.jp/2023/[PHPerKaigi 2023] において、
+昨年と同様に、弊社 https://www.dgcircus.com/[デジタルサーカス株式会社] からトークン問題を出題予定である。
+
+昨年のトークン問題の記事はこちら: link:/posts/2022-04-09/phperkaigi-2022-tokens/[PHPerKaigi 2022 トークン問題の解説]
+
+すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+
+10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
+
+* その 1 はこちら: link:/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/[PHPerKaigi 2023: ボツになったトークン問題 その 1]
+* その 2 はこちら: link:/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/[PHPerKaigi 2023: ボツになったトークン問題 その 2]
+
+
+== 問題
+
+注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+
+[source,php]
+----
+<?php
+try {
+ f(g() / __LINE__);
+} catch (Throwable $e) {
+ while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23);
+ echo "\n";
+}
+function f(int $i) {
+ if ($i < 0) f();
+ try {
+ match ($i) {
+ 0 => 0 / 0,
+
+
+
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
+
+
+
+
+
+
+
+
+
+
+ 6 => 0 / 0,
+
+ 5 => 0 / 0,
+
+ 22 => 0 / 0,
+
+
+
+
+ 34, 35 => 0 / 0,
+
+
+
+
+
+
+
+
+ 25 => 0 / 0,
+ 17, 21 => 0 / 0,
+
+ 24, 32 => 0 / 0,
+
+
+
+
+
+
+
+ 33 => 0 / 0,
+
+ 16 => 0 / 0,
+
+
+ 18 => 0 / 0,
+
+
+
+
+
+
+
+
+ 7 => 0 / 0,
+
+ 2 => 0 / 0,
+ 1, 20 => 0 / 0,
+ 10, 28 => 0 / 0,
+ 8, 12, 26 => 0 / 0,
+ 4, 9, 13 => 0 / 0,
+
+
+
+
+
+ 31 => 0 / 0,
+
+ 29 => 0 / 0,
+
+ 11 => 0 / 0,
+
+
+
+ 3, 19, 23 => 0 / 0,
+
+
+ 27 => 0 / 0,
+
+ 30 => 0 / 0,
+ };
+ } finally {
+ f($i - 1);
+ }
+}
+
+
+
+
+
+
+
+function g() {
+ return __LINE__;
+}
+----
+
+"Catchline" と名付けた作品。実行するとトークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` が得られる。
+
+トークンは PHP の式になっていて、評価すると `Hello, World!` という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
+
+== 解説
+
+=== 概要
+
+例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。
+
+=== 例外オブジェクトの連鎖
+
+https://www.php.net/class.Exception[`Exception`] や https://www.php.net/class.Error[`Error`] には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:
+
+* エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める
+* 内部エラーをラップして作られたエラーに、内部エラーの情報を含める
+
+このうち 1つ目のケースは、 `finally` 節の中でエラーを投げると PHP 処理系が勝手に `$previous` を設定してくれる。
+
+[source,php]
+----
+<?php
+
+try {
+ try {
+ throw new Exception("Error 1");
+ } finally {
+ throw new Exception("Error 2");
+ }
+} catch (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+ // => Error 2
+ echo $e->getPrevious()->getMessage() . PHP_EOL;
+ // => Error 1
+}
+----
+
+この知識を元に、トークンの出力部を解析してみる。
+
+=== 出力部の解析
+
+出力部をコメントや改行を追加して再掲する:
+
+[source,php]
+----
+<?php
+try {
+ f(g() / __LINE__);
+} catch (Throwable $e) {
+ while ($e = $e->getPrevious()) {
+ printf('%c', $e->getLine() + 23);
+ }
+ echo "\n";
+}
+----
+
+出力をおこなう `catch` 節を見てみると、 `Throwable::getPrevious()` を呼び出してエラーチェインを辿り、 `Throwable::getLine()` でエラーが発生した行数を取得している。その行数に `23` なるマジックナンバーを足し、フォーマット指定子 `%c` で出力している。
+
+フォーマット指定子 `%c` は、整数を ASCII コードfootnote:[RAS syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、
+
+```
+ 1, 20 => 0 / 0,
+```
+
+によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。
+
+それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。
+
+=== データ構成部の解析
+
+`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):
+
+[source,php]
+----
+function f(int $i) {
+ if ($i < 0) f();
+ try {
+ match ($i) {
+ 0 => 0 / 0, // 12 行目
+
+
+
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
+
+ // (略)
+
+ 30 => 0 / 0, // 97 行目
+ };
+ } finally {
+ f($i - 1);
+ }
+}
+----
+
+前述のように、 `finally` 節でエラーを投げると PHP 処理系が `$previous` を設定する。ここでは、エラーを繋げるために `f()` を再帰呼び出ししている。最初に `f()` を呼び出している箇所を確認すると、
+
+[source,php]
+----
+<?php
+try {
+ f(g() / __LINE__); // 3 行目
+----
+
+[source,php]
+----
+function g() {
+ return __LINE__; // 111 行目
+}
+----
+
+`f()` には `111 / 3` で `37` が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら `f()` を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。
+
+エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に
+
+. `f()` の引数が足りないことによる呼び出し失敗
+. `f(0)` の呼び出しで発生したゼロ除算
+. `f(1)` の呼び出しで発生したゼロ除算
+. ...
+. `f(37)` の呼び出しで発生したゼロ除算
+
+となっている。出力の際は `catch` したエラーの `getPrevious()` から処理を始めるので、1 番目の `f()` によるエラーは無視され、 `f(0)` によるエラー、 `f(1)` によるエラー、 `f(2)` によるエラー、と出力が進む。
+
+`f()` に `0` を渡したときは 12 行目にある `match` の `0` でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 `12 + 23` は `35`、ASCII コードでは `#` である。これがトークンの 1文字目にあたる。
+
+
+== おわりに
+
+「行数」というのはトークン文字列をデコードする対象として優れている。
+
+* トークンの一部や全部が陽に現れない
+* `pass:[__LINE__]` で容易に取得できる
+
+しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 `pass:[__LINE__]` を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして `Throwable` を思いつき、続けてエラーオブジェクトには `$previous` があることを思い出した。
+
+今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で `Throwable` なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、`0/0` のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
diff --git a/lib/command.rb b/lib/command.rb
index dc2561a..3a47669 100644
--- a/lib/command.rb
+++ b/lib/command.rb
@@ -155,11 +155,11 @@ module NulDoc
when 'site-name'; @config[:site_name]
when 'lang'; 'ja-JP' # TODO
when 'copyright-year'; @config[:site_copyright_year]
- when 'description'; '記事一覧'
+ when 'description'; '投稿した記事の一覧'
else raise "Unknown attr: #{name}"
end
end
- def title; 'Posts'; end
+ def title; '投稿一覧'; end
def posts; @posts; end
end).new(@config, posts.reverse, stylesheets)
)
diff --git a/public/posts/2021-03-05/my-first-post/index.html b/public/posts/2021-03-05/my-first-post/index.html
index 6ca0ae8..8f55fed 100644
--- a/public/posts/2021-03-05/my-first-post/index.html
+++ b/public/posts/2021-03-05/my-first-post/index.html
@@ -61,6 +61,7 @@ mollit anim id est laborum.</p>
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-03-30/phperkaigi-2021/index.html b/public/posts/2021-03-30/phperkaigi-2021/index.html
index 965f0c9..a16b49e 100644
--- a/public/posts/2021-03-30/phperkaigi-2021/index.html
+++ b/public/posts/2021-03-30/phperkaigi-2021/index.html
@@ -1095,6 +1095,7 @@ private のみ * 依存関係の制御が困難</p>
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
index a3b1d50..d844baa 100644
--- a/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
+++ b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
@@ -204,6 +204,7 @@ considered an identifier.</p>
<p>調べた感想: 字句解析器か構文解析器が辛そう</p>
</div>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/python-unbound-local-error/index.html b/public/posts/2021-10-02/python-unbound-local-error/index.html
index b08be62..bc93332 100644
--- a/public/posts/2021-10-02/python-unbound-local-error/index.html
+++ b/public/posts/2021-10-02/python-unbound-local-error/index.html
@@ -126,6 +126,7 @@
の一つ外側 = <code>f</code>) で定義されている <code>x</code> を探しに行くようになる。</p>
</div>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html
index d248feb..dac7277 100644
--- a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html
+++ b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html
@@ -162,6 +162,7 @@ CRuby) については執筆時現在 (2020/12/8) も <code>'ruby'</code>
</div>
</div>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html
index b702b57..62f6c43 100644
--- a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html
+++ b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html
@@ -357,6 +357,7 @@ if true puts 'Hello, World!' end
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html
index 008c79f..bbcda80 100644
--- a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html
+++ b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html
@@ -297,6 +297,7 @@ ident) を現在のレキシカルスコープ内で解決 (resolve) する。
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html
index aafd8ab..9b040ee 100644
--- a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html
+++ b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html
@@ -269,6 +269,7 @@ FileEncoding Obsolete. It still works and is equivalent
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
index 8e9c3ce..8b94185 100644
--- a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
+++ b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
@@ -321,6 +321,7 @@ command<span class="p">!</span> <span class="p">-</span>bar <span class="p">-</s
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
index 7cd2d26..4f8d30d 100644
--- a/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
+++ b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
@@ -761,6 +761,7 @@ Quine
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html
index 64cf418..c3e430c 100644
--- a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html
+++ b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html
@@ -192,6 +192,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-05-01/phperkaigi-2022/index.html b/public/posts/2022-05-01/phperkaigi-2022/index.html
index e6f5972..edb70c2 100644
--- a/public/posts/2022-05-01/phperkaigi-2022/index.html
+++ b/public/posts/2022-05-01/phperkaigi-2022/index.html
@@ -284,6 +284,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html
index 5158536..6d0e261 100644
--- a/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html
+++ b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html
@@ -231,6 +231,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html b/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html
index 2d48422..e5690b5 100644
--- a/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html
+++ b/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html
@@ -120,6 +120,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
index a1d8cf9..5a9bce7 100644
--- a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
+++ b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
@@ -864,6 +864,7 @@ o\
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html
index fe5b6a5..5184372 100644
--- a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html
+++ b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html
@@ -261,6 +261,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-10-28/setup-server-for-this-site/index.html b/public/posts/2022-10-28/setup-server-for-this-site/index.html
index 67587e1..e9e2c0b 100644
--- a/public/posts/2022-10-28/setup-server-for-this-site/index.html
+++ b/public/posts/2022-10-28/setup-server-for-this-site/index.html
@@ -500,6 +500,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html b/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html
index 439586b..a40a5e6 100644
--- a/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html
+++ b/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html
@@ -247,6 +247,7 @@
</div>
</section>
</div>
+
</article>
</main>
<footer class="footer">
diff --git a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
new file mode 100644
index 0000000..1ba170e
--- /dev/null
+++ b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
@@ -0,0 +1,460 @@
+<!DOCTYPE html>
+<html lang="ja-JP">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="author" content="nsfisis">
+ <meta name="copyright" content="&copy; nsfisis">
+ <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。">
+ <meta name="keywords" content="PHP,PHPerKaigi">
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+ <title>PHPerKaigi 2023: ボツになったトークン問題 その 3 | REPL: Rest-Eat-Program Loop</title>
+
+ <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f">
+
+ <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525">
+
+ <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144">
+
+ </head>
+ <body class="single">
+ <header class="header">
+ <nav class="nav">
+ <p class="logo">
+ <a href="/">REPL: Rest-Eat-Program Loop</a>
+ </p>
+ </nav>
+ </header>
+ <main class="main">
+ <article class="post-single">
+ <header class="post-header">
+ <h1 class="post-title">PHPerKaigi 2023: ボツになったトークン問題 その 3</h1>
+
+ <ul class="post-tags">
+
+ <li class="tag">
+ <a href="/tags/php/">PHP</a>
+ </li>
+
+ <li class="tag">
+ <a href="/tags/phperkaigi/">PHPerKaigi</a>
+ </li>
+
+ </ul>
+
+ </header>
+ <div class="post-content">
+ <section>
+ <h2 id="changelog">更新履歴</h2>
+ <ol>
+
+ <li class="revision">
+ <time datetime="2023-01-10">2023-01-10</time>: 公開
+ </li>
+
+ </ol>
+ </section>
+ <section class="section-1">
+ <h2 id="" class="section-header">
+
+ はじめに
+
+ </h2>
+ <div class="section-body">
+ <div class="paragraph">
+<p>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <a href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a> において、
+昨年と同様に、弊社 <a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> からトークン問題を出題予定である。</p>
+</div>
+<div class="paragraph">
+<p>昨年のトークン問題の記事はこちら: <a href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</a></p>
+</div>
+<div class="paragraph">
+<p>すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。</p>
+</div>
+<div class="paragraph">
+<p>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>その 1 はこちら: <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</a></p>
+</li>
+<li>
+<p>その 2 はこちら: <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">PHPerKaigi 2023: ボツになったトークン問題 その 2</a></p>
+</li>
+</ul>
+</div>
+ </div>
+</section>
+<section class="section-1">
+ <h2 id="" class="section-header">
+
+ 問題
+
+ </h2>
+ <div class="section-body">
+ <div class="paragraph">
+<p>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</p>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="php"><span class="o">&lt;?</span><span class="n">php</span>
+<span class="k">try</span> <span class="p">{</span>
+ <span class="nf">f</span><span class="p">(</span><span class="nf">g</span><span class="p">()</span> <span class="o">/</span> <span class="k">__LINE__</span><span class="p">);</span>
+<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Throwable</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
+ <span class="k">while</span> <span class="p">(</span><span class="nv">$e</span> <span class="o">=</span> <span class="nv">$e</span><span class="o">-&gt;</span><span class="nf">getPrevious</span><span class="p">())</span> <span class="nb">printf</span><span class="p">(</span><span class="s1">'%c'</span><span class="p">,</span> <span class="nv">$e</span><span class="o">-&gt;</span><span class="nf">getLine</span><span class="p">()</span> <span class="o">+</span> <span class="mi">23</span><span class="p">);</span>
+ <span class="k">echo</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
+<span class="p">}</span>
+<span class="k">function</span> <span class="n">f</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$i</span><span class="p">)</span> <span class="p">{</span>
+ <span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="nf">f</span><span class="p">();</span>
+ <span class="k">try</span> <span class="p">{</span>
+ <span class="k">match</span> <span class="p">(</span><span class="nv">$i</span><span class="p">)</span> <span class="p">{</span>
+ <span class="mi">0</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+ <span class="mi">15</span><span class="p">,</span> <span class="mi">36</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">14</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">37</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+
+
+
+
+
+
+
+ <span class="mi">6</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">5</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">22</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+
+ <span class="mi">34</span><span class="p">,</span> <span class="mi">35</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+
+
+
+
+
+ <span class="mi">25</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">17</span><span class="p">,</span> <span class="mi">21</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">24</span><span class="p">,</span> <span class="mi">32</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+
+
+
+
+ <span class="mi">33</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">16</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+ <span class="mi">18</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+
+
+
+
+
+ <span class="mi">7</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">2</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">1</span><span class="p">,</span> <span class="mi">20</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">10</span><span class="p">,</span> <span class="mi">28</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">26</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">4</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">13</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+
+
+ <span class="mi">31</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">29</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">11</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+
+ <span class="mi">3</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">23</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+
+ <span class="mi">27</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="mi">30</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="p">};</span>
+ <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
+ <span class="nf">f</span><span class="p">(</span><span class="nv">$i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
+ <span class="p">}</span>
+<span class="p">}</span>
+
+
+
+
+
+
+
+<span class="k">function</span> <span class="n">g</span><span class="p">()</span> <span class="p">{</span>
+ <span class="k">return</span> <span class="k">__LINE__</span><span class="p">;</span>
+<span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>"Catchline" と名付けた作品。実行するとトークン <code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code> が得られる。</p>
+</div>
+<div class="paragraph">
+<p>トークンは PHP の式になっていて、評価すると <code>Hello, World!</code> という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。</p>
+</div>
+ </div>
+</section>
+<section class="section-1">
+ <h2 id="" class="section-header">
+
+ 解説
+
+ </h2>
+ <div class="section-body">
+ <section class="section-2">
+ <h3 id="" class="section-header">
+
+ 概要
+
+ </h3>
+ <div class="section-body">
+ <div class="paragraph">
+<p>例外が発生した行数にデータをエンコードし、それを <code>catch</code> で捕まえて表示している。</p>
+</div>
+ </div>
+</section>
+<section class="section-2">
+ <h3 id="" class="section-header">
+
+ 例外オブジェクトの連鎖
+
+ </h3>
+ <div class="section-body">
+ <div class="paragraph">
+<p><a href="https://www.php.net/class.Exception"><code>Exception</code></a> や <a href="https://www.php.net/class.Error"><code>Error</code></a> には <code>$previous</code> というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める</p>
+</li>
+<li>
+<p>内部エラーをラップして作られたエラーに、内部エラーの情報を含める</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>このうち 1つ目のケースは、 <code>finally</code> 節の中でエラーを投げると PHP 処理系が勝手に <code>$previous</code> を設定してくれる。</p>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="php"><span class="o">&lt;?</span><span class="n">php</span>
+
+<span class="k">try</span> <span class="p">{</span>
+ <span class="k">try</span> <span class="p">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nc">Exception</span><span class="p">(</span><span class="s2">"Error 1"</span><span class="p">);</span>
+ <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nc">Exception</span><span class="p">(</span><span class="s2">"Error 2"</span><span class="p">);</span>
+ <span class="p">}</span>
+<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Exception</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
+ <span class="k">echo</span> <span class="nv">$e</span><span class="o">-&gt;</span><span class="nf">getMessage</span><span class="p">()</span> <span class="mf">.</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
+ <span class="c1">// =&gt; Error 2</span>
+ <span class="k">echo</span> <span class="nv">$e</span><span class="o">-&gt;</span><span class="nf">getPrevious</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">getMessage</span><span class="p">()</span> <span class="mf">.</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
+ <span class="c1">// =&gt; Error 1</span>
+<span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>この知識を元に、トークンの出力部を解析してみる。</p>
+</div>
+ </div>
+</section>
+<section class="section-2">
+ <h3 id="" class="section-header">
+
+ 出力部の解析
+
+ </h3>
+ <div class="section-body">
+ <div class="paragraph">
+<p>出力部をコメントや改行を追加して再掲する:</p>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="php"><span class="o">&lt;?</span><span class="n">php</span>
+<span class="k">try</span> <span class="p">{</span>
+ <span class="nf">f</span><span class="p">(</span><span class="nf">g</span><span class="p">()</span> <span class="o">/</span> <span class="k">__LINE__</span><span class="p">);</span>
+<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Throwable</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
+ <span class="k">while</span> <span class="p">(</span><span class="nv">$e</span> <span class="o">=</span> <span class="nv">$e</span><span class="o">-&gt;</span><span class="nf">getPrevious</span><span class="p">())</span> <span class="p">{</span>
+ <span class="nb">printf</span><span class="p">(</span><span class="s1">'%c'</span><span class="p">,</span> <span class="nv">$e</span><span class="o">-&gt;</span><span class="nf">getLine</span><span class="p">()</span> <span class="o">+</span> <span class="mi">23</span><span class="p">);</span>
+ <span class="p">}</span>
+ <span class="k">echo</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
+<span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>出力をおこなう <code>catch</code> 節を見てみると、 <code>Throwable::getPrevious()</code> を呼び出してエラーチェインを辿り、 <code>Throwable::getLine()</code> でエラーが発生した行数を取得している。その行数に <code>23</code> なるマジックナンバーを足し、フォーマット指定子 <code>%c</code> で出力している。</p>
+</div>
+<div class="paragraph">
+<p>フォーマット指定子 <code>%c</code> は、整数を ASCII コード<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup> と見做して印字する。トークン <code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code> の <code>b</code> であれば、ASCII コード <code>98</code> なので、75 行目で発生したエラー、</p>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code> 1, 20 =&gt; 0 / 0,</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。</p>
+</div>
+<div class="paragraph">
+<p>それでは、エラーチェインを作る箇所、関数 <code>f()</code> を見ていく。</p>
+</div>
+ </div>
+</section>
+<section class="section-2">
+ <h3 id="" class="section-header">
+
+ データ構成部の解析
+
+ </h3>
+ <div class="section-body">
+ <div class="paragraph">
+<p><code>f()</code> の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):</p>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="php"><span class="k">function</span> <span class="n">f</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$i</span><span class="p">)</span> <span class="p">{</span>
+ <span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="nf">f</span><span class="p">();</span>
+ <span class="k">try</span> <span class="p">{</span>
+ <span class="k">match</span> <span class="p">(</span><span class="nv">$i</span><span class="p">)</span> <span class="p">{</span>
+ <span class="mi">0</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> <span class="c1">// 12 行目</span>
+
+
+
+ <span class="mi">15</span><span class="p">,</span> <span class="mi">36</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">14</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+ <span class="mi">37</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span>
+
+ <span class="c1">// (略)</span>
+
+ <span class="mi">30</span> <span class="o">=&gt;</span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> <span class="c1">// 97 行目</span>
+ <span class="p">};</span>
+ <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
+ <span class="nf">f</span><span class="p">(</span><span class="nv">$i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
+ <span class="p">}</span>
+<span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>前述のように、 <code>finally</code> 節でエラーを投げると PHP 処理系が <code>$previous</code> を設定する。ここでは、エラーを繋げるために <code>f()</code> を再帰呼び出ししている。最初に <code>f()</code> を呼び出している箇所を確認すると、</p>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="php"><span class="o">&lt;?</span><span class="n">php</span>
+<span class="k">try</span> <span class="p">{</span>
+ <span class="nf">f</span><span class="p">(</span><span class="nf">g</span><span class="p">()</span> <span class="o">/</span> <span class="k">__LINE__</span><span class="p">);</span> <span class="c1">// 3 行目</span></code></pre>
+</div>
+</div>
+<div id="source." class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="php"><span class="k">function</span> <span class="n">g</span><span class="p">()</span> <span class="p">{</span>
+ <span class="k">return</span> <span class="k">__LINE__</span><span class="p">;</span> <span class="c1">// 111 行目</span>
+<span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p><code>f()</code> には <code>111 / 3</code> で <code>37</code> が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら <code>f()</code> を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。</p>
+</div>
+<div class="paragraph">
+<p>エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><code>f()</code> の引数が足りないことによる呼び出し失敗</p>
+</li>
+<li>
+<p><code>f(0)</code> の呼び出しで発生したゼロ除算</p>
+</li>
+<li>
+<p><code>f(1)</code> の呼び出しで発生したゼロ除算</p>
+</li>
+<li>
+<p>&#8230;&#8203;</p>
+</li>
+<li>
+<p><code>f(37)</code> の呼び出しで発生したゼロ除算</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>となっている。出力の際は <code>catch</code> したエラーの <code>getPrevious()</code> から処理を始めるので、1 番目の <code>f()</code> によるエラーは無視され、 <code>f(0)</code> によるエラー、 <code>f(1)</code> によるエラー、 <code>f(2)</code> によるエラー、と出力が進む。</p>
+</div>
+<div class="paragraph">
+<p><code>f()</code> に <code>0</code> を渡したときは 12 行目にある <code>match</code> の <code>0</code> でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 <code>12 + 23</code> は <code>35</code>、ASCII コードでは <code>#</code> である。これがトークンの 1文字目にあたる。</p>
+</div>
+ </div>
+</section>
+ </div>
+</section>
+<section class="section-1">
+ <h2 id="" class="section-header">
+
+ おわりに
+
+ </h2>
+ <div class="section-body">
+ <div class="paragraph">
+<p>「行数」というのはトークン文字列をデコードする対象として優れている。</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>トークンの一部や全部が陽に現れない</p>
+</li>
+<li>
+<p><code>__LINE__</code> で容易に取得できる</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 <code>__LINE__</code> を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして <code>Throwable</code> を思いつき、続けてエラーオブジェクトには <code>$previous</code> があることを思い出した。</p>
+</div>
+<div class="paragraph">
+<p>今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で <code>Throwable</code> なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、<code>0/0</code> のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。</p>
+</div>
+ </div>
+</section>
+ </div>
+
+ <div id="footnotes">
+
+ <div class="footnote" id="_footnotedef_1">
+ <a href="#_footnoteref_1">1</a>. RAS syndrome
+ </div>
+
+ </div>
+
+ </article>
+ </main>
+ <footer class="footer">
+ &copy; 2021 nsfisis
+ </footer>
+ </body>
+</html>
diff --git a/public/posts/index.html b/public/posts/index.html
index cce86d9..1945b88 100644
--- a/public/posts/index.html
+++ b/public/posts/index.html
@@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="nsfisis">
<meta name="copyright" content="&copy; 2021 nsfisis">
- <meta name="description" content="記事一覧">
+ <meta name="description" content="投稿した記事の一覧">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
- <title>Posts | REPL: Rest-Eat-Program Loop</title>
+ <title>投稿一覧 | REPL: Rest-Eat-Program Loop</title>
<link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f">
@@ -26,10 +26,26 @@
</header>
<main class="main">
<header class="page-header">
- <h1>Posts</h1>
+ <h1>投稿一覧</h1>
</header>
<article class="post-entry">
+ <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/">
+ <header class="entry-header">
+ <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。
+ </p>
+ </section>
+ <footer class="entry-footer">
+ Posted on <time datetime="2023-01-10">2023-01-10</time>
+ </footer>
+ </a>
+ </article>
+
+ <article class="post-entry">
<a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">
<header class="entry-header">
<h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2>
diff --git a/public/tags/php/index.html b/public/tags/php/index.html
index 094f012..efba016 100644
--- a/public/tags/php/index.html
+++ b/public/tags/php/index.html
@@ -30,6 +30,22 @@
</header>
<article class="post-entry">
+ <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/">
+ <header class="entry-header">
+ <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。
+ </p>
+ </section>
+ <footer class="entry-footer">
+ Posted on <time datetime="2023-01-10">2023-01-10</time>
+ </footer>
+ </a>
+ </article>
+
+ <article class="post-entry">
<a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">
<header class="entry-header">
<h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2>
diff --git a/public/tags/phperkaigi/index.html b/public/tags/phperkaigi/index.html
index 2fed4df..953f38e 100644
--- a/public/tags/phperkaigi/index.html
+++ b/public/tags/phperkaigi/index.html
@@ -30,6 +30,22 @@
</header>
<article class="post-entry">
+ <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/">
+ <header class="entry-header">
+ <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。
+ </p>
+ </section>
+ <footer class="entry-footer">
+ Posted on <time datetime="2023-01-10">2023-01-10</time>
+ </footer>
+ </a>
+ </article>
+
+ <article class="post-entry">
<a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">
<header class="entry-header">
<h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2>
diff --git a/templates/document__post.html.erb b/templates/document__post.html.erb
index a52ac9a..a48a5f4 100644
--- a/templates/document__post.html.erb
+++ b/templates/document__post.html.erb
@@ -59,6 +59,15 @@
</section>
<%= _content %>
</div>
+ <% if footnotes? %>
+ <div id="footnotes">
+ <% for footnote in footnotes %>
+ <div class="footnote" id="_footnotedef_<%= footnote.index %>">
+ <a href="#_footnoteref_<%= footnote.index %>"><%= footnote.index %></a>. <%= footnote.text %>
+ </div>
+ <% end %>
+ </div>
+ <% end %>
</article>
</main>
<footer class="footer">