From d30dfc89bf1b673b2fdc0638766b930adaec228c Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 29 Mar 2025 00:47:55 +0900 Subject: feat(blog/nuldoc): migrate syntax highlighter from highlight.js to shiki.js --- .../phperkaigi-2023-unused-token-quiz-3/index.html | 341 +++++++++++---------- 1 file changed, 177 insertions(+), 164 deletions(-) (limited to 'vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3') diff --git a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html index 38e6a3f6..6cad79cb 100644 --- a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html +++ b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 3|REPL: Rest-Eat-Program Loop - - +
@@ -96,118 +95,120 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
<?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__;
-}
+
+
<?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==') が得られる。 @@ -247,20 +248,22 @@ このうち 1つ目のケースは、 finally 節の中でエラーを投げると PHP 処理系が勝手に $previous を設定してくれる。

-
<?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
-}
+
+
<?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
+}
+

この知識を元に、トークンの出力部を解析してみる。 @@ -273,15 +276,17 @@ 出力部をコメントや改行を追加して再掲する:

-
<?php
-try {
-  f(g() / __LINE__);
-} catch (Throwable $e) {
-  while ($e = $e->getPrevious()) {
-    printf('%c', $e->getLine() + 23);
-  }
-  echo "\n";
-}
+
+
<?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 で出力している。 @@ -291,7 +296,9 @@ フォーマット指定子 %c は、整数を ASCII コード と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==')b であれば、ASCII コード 98 なので、75 行目で発生したエラー、

-
1, 20 => 0 / 0,
+
+
1, 20 => 0 / 0,
+

によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 @@ -308,38 +315,44 @@ f() の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):

-
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);
-  }
-}
+
+
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() を呼び出している箇所を確認すると、

-
<?php
-try {
-  f(g() / __LINE__); // 3 行目
+
+
<?php
+try {
+  f(g() / __LINE__); // 3 行目
+
-
function g() {
-  return __LINE__; // 111 行目
-}
+
+
function g() {
+  return __LINE__; // 111 行目
+}
+

f() には 111 / 337 が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら f() を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 -- cgit v1.2.3-70-g09d2