From 98682c7a8792e7e79e487fea5024d25cee5aa310 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 15 Mar 2023 01:36:13 +0900 Subject: fix(nuldoc):
 contained unnecessary whitespaces inside it

---
 nuldoc-src/html.ts                                 |  14 +--
 .../index.html                                     |  12 +--
 .../python-unbound-local-error/index.html          |  18 ++--
 .../ruby-detect-running-implementation/index.html  |  12 +--
 .../ruby-then-keyword-and-case-in/index.html       |  68 +++++--------
 .../rust-where-are-primitive-types-from/index.html |  36 +++----
 .../index.html                                     |  30 ++----
 .../vim-swap-order-of-selected-lines/index.html    |  24 ++---
 .../2022-04-09/phperkaigi-2022-tokens/index.html   |  76 +++++----------
 .../index.html                                     |   4 +-
 .../php-conference-okinawa-code-golf/index.html    |  10 +-
 .../index.html                                     | 102 +++++++-------------
 .../phperkaigi-2023-unused-token-quiz-1/index.html |  52 ++++------
 .../setup-server-for-this-site/index.html          | 106 +++++++--------------
 .../phperkaigi-2023-unused-token-quiz-2/index.html |  36 +++----
 .../phperkaigi-2023-unused-token-quiz-3/index.html |  40 +++-----
 16 files changed, 209 insertions(+), 431 deletions(-)

diff --git a/nuldoc-src/html.ts b/nuldoc-src/html.ts
index 2832490..5e573ad 100644
--- a/nuldoc-src/html.ts
+++ b/nuldoc-src/html.ts
@@ -160,7 +160,7 @@ function elementNodeToHtmlText(e: Element, ctx: Context): string {
       }
     }
     s += ">";
-    if (dtd.type === "block") {
+    if (dtd.type === "block" && e.name !== "pre") {
       s += "\n";
     }
   }
@@ -171,7 +171,7 @@ function elementNodeToHtmlText(e: Element, ctx: Context): string {
     ctx.isInPre = true;
   }
   forEachChild(e, (c) => {
-    if (dtd.type === "block") {
+    if (dtd.type === "block" && !ctx.isInPre) {
       if (isInlineNode(c)) {
         if (needsIndent(prevChild)) {
           s += indent(ctx);
@@ -191,11 +191,13 @@ function elementNodeToHtmlText(e: Element, ctx: Context): string {
 
   ctx.indentLevel -= 1;
   if (e.name !== "__root__" && !dtd.auto_closing) {
-    if (dtd.type === "block") {
-      if (needsLineBreak(prevChild)) {
-        s += "\n";
+    if (e.name !== "pre") {
+      if (dtd.type === "block") {
+        if (needsLineBreak(prevChild)) {
+          s += "\n";
+        }
+        s += indent(ctx);
       }
-      s += indent(ctx);
     }
     s += ``;
     if (dtd.type === "block") {
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 ae44da9..ed91b45 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
@@ -56,8 +56,7 @@
             タイトル落ち。まずはこのコードを見て欲しい。
           

-
-            #include <iostream>
+          
#include <iostream>
 
 [[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
 [[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
@@ -75,8 +74,7 @@
 // [[using]]
 int main() {
 std::cout << "Hello, World!" << std::endl;
-}
-          
+}

@@ -126,10 +124,8 @@ std::cout << "Hello, World!" << std::endl; 上のコードでは[[using]]をコメントアウトしているが、これはusingキーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。

-
-            // using の例
-[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文
-          
+
// using の例
+[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文

C++17 の仕様も見てみる (正確には標準化前のドラフト)。 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 27c2d05..0516d6f 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 @@ -60,15 +60,13 @@ Python でクロージャを作ろうと、次のようなコードを書いた。

-
-            def f():
+          
def f():
 x = 0
 def g():
 x += 1
 g()
 
-f()
-          
+f()

関数gから 関数fのスコープ内で定義された変数xを参照し、それに 1 を足そうとしている。 これを実行するとx += 1の箇所でエラーが発生する。 @@ -84,8 +82,7 @@ f() local変数xが代入前に参照された、とある。これは、fxを参照するのではなく、新しく別の変数をg内に作ってしまっているため。 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。varを変数宣言のための構文として擬似的に利用している。

-
-            # 注: var は正しい Python の文法ではない。上記参照のこと
+          
# 注: var は正しい Python の文法ではない。上記参照のこと
 def f():
 var x           #  f の local変数 'x' を宣言
 x = 0           #  x に 0 を代入
@@ -94,21 +91,18 @@ var x       #  g の local変数 'x' を宣言
 #  たまたま f にも同じ名前の変数があるが、それとは別の変数
 x += 1      #  x に 1 を加算 (x = x + 1 の糖衣構文)
 #  加算する前の値を参照しようとするが、まだ代入されていないためエラー
-g()
-          
+g()

当初の意図を表現するには、次のように書けばよい。

-
-            def f():
+          
def f():
 x = 0
 def g():
 nonlocal x   ## (*)
 x += 1
-g()
-          
+g()

(*)のように、nonlocalを追加する。これにより一つ外側のスコープ (gの一つ外側 =f) で定義されているxを探しに行くようになる。 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 ed7ccd5..1163674 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 @@ -65,14 +65,12 @@ 上記ページの例から引用する:

-
-            $ ruby-1.9.1 -ve 'p RUBY_ENGINE'
+          
$ ruby-1.9.1 -ve 'p RUBY_ENGINE'
 ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux]
 "ruby"
 $ jruby -ve 'p RUBY_ENGINE'
 jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]
-"jruby"
-          
+"jruby"

それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。 @@ -192,12 +190,10 @@ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java] mruby 該当部分のソースより引用:

-
-            /*
+          
/*
 * Ruby engine.
 */
-#define MRUBY_RUBY_ENGINE  "mruby"
-          
+#define MRUBY_RUBY_ENGINE "mruby"
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 9b1df47..ed8d9e8 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 @@ -65,20 +65,17 @@ 使われることは稀だが、Ruby ではthenがキーワードになっている。次のように使う:

-
-              if cond then
+            
if cond then
   puts "Y"
   else
   puts "N"
-  end
-            
+ end

このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase構文がそれに当たる。 上記のように、何か条件を書いた後thenを置き、式がそこで終了していることを示すマーカーとして機能する。

-
-              # Example:
+            
# Example:
 
 if x then
 a
@@ -97,8 +94,7 @@ end
 case x
 when p then
 a
-end
-            
+end
@@ -107,21 +103,17 @@ end 普通 Ruby のコードでthenを書くことはない。なぜか。次のコードを実行してみるとわかる。

-
-              if true puts 'Hello, World!' end
-            
+
if true puts 'Hello, World!' end

次のような構文エラーが出力される。

-
-              20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n'
+            
20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n'
   if true puts 'Hello, World!' end
   ^~~~
   20:1: syntax error, unexpected `end', expecting end-of-input
-  ...f true puts 'Hello, World!' end
-            
+ ...f true puts 'Hello, World!' end

二つ目のメッセージは無視して一つ目を読むと、then;か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 @@ -131,10 +123,8 @@ end ポイントは改行がthen(や;) の代わりとなることである。trueの後に改行を入れてみる。

-
-              if true
-puts 'Hello, World!' end
-            
+
if true
+puts 'Hello, World!' end

無事 Hello, World! と出力されるようになった。 @@ -147,27 +137,21 @@ puts 'Hello, World!' end なぜthen;や改行 (以下 「then等」) が必要なのだろうか。次の例を見てほしい:

-
-              if a b end
-            
+
if a b end

then;も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 この例は二通りに解釈できる。

-
-              # a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
+            
# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
 if a then
 b
-end
-            
+end
-
-              # a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
+            
# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
 # その結果が truthy なら何もしない
 if a(b) then
-end
-            
+end

then等はこの曖昧性を排除するためにあり、条件式はifからthen等までの間にある、ということを明確にする。 C系のif後に来る(/)や、Python の:、Rust/Go/Swift などの{も同じ役割を持つ。 @@ -188,8 +172,7 @@ end https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986

-
-              p_case_body : keyword_in
+            
p_case_body : keyword_in
   {
   SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
   p->command_start = FALSE;
@@ -214,17 +197,14 @@ end
   /*% %*/
   /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
   }
-  ;
-            
+ ;

簡略版:

-
-              p_case_body : keyword_in p_top_expr then compstmt p_cases
-;
-            
+
p_case_body : keyword_in p_top_expr then compstmt p_cases
+;

ここで、keyword_inは文字通りinp_top_exprはいわゆるパターン、thenthenキーワードのことではなく、この記事でthen等と呼んでいるもの、つまりthenキーワード、;、改行のいずれかである。 @@ -234,8 +214,7 @@ end これにより、case-whenによる従来の構文と同じように、then等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:

-
-              case x
+            
case x
 in 1 then a
 in 2 then b
 in 3 then c
@@ -254,20 +233,17 @@ case x
 in 1; a
 in 2; b
 in 3; c
-end
-            
+end

ところで、p_top_exprにはifによる guard clause が書けるので、その場合はif-thenと似たような見た目になる。

-
-              case x
+            
case x
 in 0 then a
 in n if n < 0 then b
 in n then c
-end
-            
+end
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 8bcb923..5b1f86a 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 @@ -55,8 +55,7 @@ Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。

-
-              #![allow(non_camel_case_types)]
+            
#![allow(non_camel_case_types)]
 #![allow(dead_code)]
 
 struct bool;
@@ -75,8 +74,7 @@ struct u128;
 struct usize;
 struct f32;
 struct f64;
-struct str;
-            
+struct str;

では、普段単にboolと書いたとき、このboolは一体どこから来ているのか。rustc のソースを追ってみた。 @@ -111,34 +109,29 @@ struct str; rustcはセルフホストされている (=rustc自身が Rust で書かれている) ので、boolcharなどで適当に検索をかけてもノイズが多すぎて話にならない。 しかし、お誂え向きなことにi128/u128というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使ってgit grepしてみる。

-
-              $ git grep "\bi128\b" | wc      # i128
+            
$ git grep "\bi128\b" | wc      # i128
 165    1069   15790
 
 $ git grep "\bu128\b" | wc      # u128
 293    2127   26667
 
 $ git grep "\bbool\b" | wc      # cf. bool の結果
-3563   23577  294659
-            
+3563 23577 294659

165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。

-
-              $ git grep "\bi128\b"
+            
$ git grep "\bi128\b"
 ...
 rustc_resolve/src/lib.rs:        table.insert(sym::i128, Int(IntTy::I128));
-...
-            
+...

rustc_resolveというのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。

-
-              /// Interns the names of the primitive types.
+            
/// Interns the names of the primitive types.
 ///
 /// All other types are defined somewhere and possibly imported, but the primitive ones need
 /// special handling, since they have no place of origin.
@@ -169,8 +162,7 @@ table.insert(sym::u64, Uint(UintTy::U64));
 table.insert(sym::u128, Uint(UintTy::U128));
 Self { primitive_types: table }
 }
-}
-            
+}

これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、 @@ -186,8 +178,7 @@ Self { primitive_types: table } とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。

-
-                  /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
+            
    /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
 /// (略)
 fn resolve_ident_in_lexical_scope(
 &mut self,
@@ -207,8 +198,7 @@ return Some(LexicalScopeBinding::Item(binding));
 }
 
 None
-}
-            
+}

関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。if ns == TypeNSのブロック内では、primitive_type_table(上記のPrimitiveTypeTable::new()で作られた変数) に含まれている識別子 (booli32など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 @@ -226,15 +216,13 @@ None 動作がわかったところで、例として次のコードを考える。

-
-              #![allow(non_camel_case_types)]
+            
#![allow(non_camel_case_types)]
 
 struct bool;
 
 fn main() {
 let _: bool = bool;
-}
-            
+}

ここでmain()boolstruct boolとして解決される。なぜなら、プリミティブ型の判定をする前にboolという名前の別の型が見つかるからだ。 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 db28b1c..8e34b4d 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 @@ -112,30 +112,24 @@ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86

-
-                    {"BufAdd",      EVENT_BUFADD},
-{"BufCreate",   EVENT_BUFADD},
-              
+
    {"BufAdd",      EVENT_BUFADD},
+{"BufCreate",   EVENT_BUFADD},

https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97

-
-                    {"BufRead",     EVENT_BUFREADPOST},
+              
    {"BufRead",     EVENT_BUFREADPOST},
 {"BufReadCmd",  EVENT_BUFREADCMD},
-{"BufReadPost", EVENT_BUFREADPOST},
-              
+{"BufReadPost", EVENT_BUFREADPOST},

https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105

-
-                    {"BufWrite",    EVENT_BUFWRITEPRE},
+              
    {"BufWrite",    EVENT_BUFWRITEPRE},
 {"BufWritePost",    EVENT_BUFWRITEPOST},
-{"BufWritePre", EVENT_BUFWRITEPRE},
-              
+{"BufWritePre", EVENT_BUFWRITEPRE},
@@ -148,24 +142,20 @@ https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124

-
-                  aliases = {
+              
  aliases = {
 BufCreate = 'BufAdd',
 BufRead = 'BufReadPost',
 BufWrite = 'BufWritePre',
 FileEncoding = 'EncodingChanged',
-},
-              
+},

ところで、上では取り上げなかったFileEncodingだが、これは:help FileEncodingにしっかりと書いてある。

-
-                                                                           *FileEncoding*
+              
                                                           *FileEncoding*
 FileEncoding                    Obsolete.  It still works and is equivalent
-to |EncodingChanged|.
-              
+to |EncodingChanged|.
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 8a3b304..f4e2b71 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 @@ -115,11 +115,9 @@ なお、:g/^/m0は全ての行を入れ替えるが、:N,Mg/^/mN-1とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

-
-                command! -bar -range=%
+              
command! -bar -range=%
 \ Reverse
-\ <line1>,<line2>g/^/m<line1>-1
-              
+\ <line1>,<line2>g/^/m<line1>-1

これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 @@ -152,15 +150,13 @@ 前述した:Reverseコマンドの定義を少し変えて、次のようにする:

-
-              function! s:reverse_lines(from, to) abort
+            
function! s:reverse_lines(from, to) abort
   execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1)
   endfunction
 
   command! -bar -range=%
   \ Reverse
-  \ call <SID>reverse_lines(<line1>, <line2>)
-            
+ \ call <SID>reverse_lines(<line1>, <line2>)

実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 @@ -211,11 +207,9 @@

-
-              command! -bar -range=%
+            
command! -bar -range=%
   \ Reverse
-  \ keeppatterns <line1>,<line2>g/^/m<line1>-1
-            
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1

まさにこのための Exコマンド、:keeppatternsが存在する。:keeppatterns {command}のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 @@ -230,13 +224,11 @@

コピペ用再掲

-
-              " License: Public Domain
+            
" License: Public Domain
 
   command! -bar -range=%
   \ Reverse
-  \ keeppatterns <line1>,<line2>g/^/m<line1>-1
-            
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1
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 d16739b..bbeacb2 100644 --- a/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html +++ b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html @@ -66,8 +66,7 @@ ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。

-
-              <?php
+            
<?php
 
   declare(strict_types=0O1);
 
@@ -135,8 +134,7 @@
   $👉, $👍, $👍, $📝,
   $👉, $👎, $📝,
   $👈, $📝,
-  ]);
-            
+ ]);

この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 @@ -165,8 +163,7 @@ なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。

-
-                  + + + + + + + + + +
+                
+ + + + + + + + + +
 [
 > + + +
 > + + + + +
@@ -187,8 +184,7 @@
 < .
 > + + .
 > - .
-< .
-                
+< .

実行結果はこちら:https://ideone.com/22VWmb @@ -271,9 +267,7 @@ ソースコードのライセンスを示したこの部分だが、

-
-                  https://creativecommons.org/publicdomain/zero/1.0/
-                
+
https://creativecommons.org/publicdomain/zero/1.0/

完全に合法な PHP のコードである。https:部分はラベル、//以降は行コメントになっている。 @@ -286,13 +280,11 @@ ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。

-
-                  assert(0 === +!![]);
+                
assert(0 === +!![]);
 assert(1 === +![]);
 assert(2 === ![]+![]);
 assert(3 === ![]+![]+![]);
-assert(10 === +(![].+!![]));
-                
+assert(10 === +(![].+!![]));

[]!を適用するとtrueが返ってくる。それに+を適用すると、boolからintヘの型変換が走り、1が生成される。10はさらにトリッキーだ。まず10を作り、.で文字列として結合する ('10')。これに+を適用すると、stringからintへの型変換が走り、10が生まれる (コード量に頓着しないなら、1を 10 個足し合わせてももちろん 10 が作れる)。 @@ -333,8 +325,7 @@ assert(10 === +(![].+!![])); ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。

-
-              <?php
+            
<?php
 
   /*********************************************************
   * This program displays a PHPer token.                  *
@@ -367,8 +358,7 @@ assert(10 === +(![].+!![]));
   $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
   $x = implode("\n", str_split($x, length: 5));
   echo "{$x}\n\n";
-  }
-            
+ }

さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 トークンを得るためには、ソースコードを読み、定数Nを特定する必要がある。 @@ -384,43 +374,33 @@ assert(10 === +(![].+!![])); まずはソースコードを読んでいく。

-
-                $token = [
+              
$token = [
   // 略
-  ];
-              
+ ];

数値からなる$tokenがあり、各要素をループしている。

-
-                  $x = $x ^ N;
-              
+
  $x = $x ^ N;

まずは排他的論理和 (xor) を取り、

-
-                  $x = sprintf('%025b', $x);
-              
+
  $x = sprintf('%025b', $x);

二進数に変換して、

-
-                  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-              
+
  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);

0 を空白に、1 を#にし、

-
-                  $x = implode("\n", str_split($x, length: 5));
-              
+
  $x = implode("\n", str_split($x, length: 5));

5文字ごとに区切ったあと、改行で結合している。 @@ -474,16 +454,13 @@ assert(10 === +(![].+!![])); Nは高々

-
-                assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
-              
+
assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);

なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。

-
-                <?php
+              
<?php
 
   $x = 0x14B499C;
 
@@ -498,15 +475,13 @@ assert(10 === +(![].+!![]));
   "#####\n" .
   " # # \n" .
   "#####\n" .
-  " # # ");
-              
+ " # # ");

この一連の変換に対する逆変換を考えると、次のようになる。

-
-                <?php
+              
<?php
 
 $x =
 " # # \n" .
@@ -521,8 +496,7 @@ $x = bindec($x);
 
 $n = $x ^ 0x14B499C;
 
-echo "N = $n\n";
-              
+echo "N = $n\n";

これを実行すると、Nが得られる。 @@ -536,8 +510,7 @@ echo "N = $n\n"; ソースコードはこちら。

-
-              <?php
+            
<?php
 
   // License: https://creativecommons.org/publicdomain/zero/1.0/
   // This is a quine-like program to generate a PHPer token.
@@ -565,16 +538,13 @@ echo "N = $n\n";
   $t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
   $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));
-            
+ printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));

コメントにもあるとおり、次のようにして実行すれば答えがでてくる。

-
-              $ php toquine.php | php | php | php | ...
-            
+
$ php toquine.php | php | php | php | ...

実際にはもう少しパイプで繋げなければならない。 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 bf174b8..be694e9 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 @@ -43,9 +43,7 @@ こんなものを作った。

-
-              $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
-            
+
$ term-banner 'Hello, World!' 'こんにちは、' '世界!'

image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner のスクリーンショット] 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 fbac1f0..ea60e83 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 @@ -111,9 +111,7 @@ 書いたものがこちら:

-
-              [<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
-            
+
[<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]

しめて 123 バイトとなった (末尾改行を含めずにカウント)。 @@ -123,8 +121,7 @@ こちらは改行とスペースを追加したバージョン:

-
-              [<?php
+            
[<?php
 
   $n = $argv[1];
   foreach ([1e4, 5e3, 2e3, 1e3, 500, 100, 50, 10, 5, 1] as $x)
@@ -132,8 +129,7 @@
   $r[] = $x;
   echo implode(', ', $r ?? []);
 
-  ?>]
-            
+ ?>]
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 abdb48e..f7ce047 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 @@ -125,8 +125,7 @@ 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。

-
-              #\
+            
#\
   i\
   n\
   c\
@@ -199,8 +198,7 @@
   10
   );
 
-  /* あとは同じように普通のプログラムを変形するだけなので省略 */
-            
+ /* あとは同じように普通のプログラムを変形するだけなので省略 */

バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 @@ -260,12 +258,10 @@ また、2文字だと文字列がまともに書けないのも辛い。''だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので

-
-              $a
+            
$a
 ='
 a'
-;;
-            
+;;

とすると$a"\na"になるのだが、余計な改行が入ってしまう。 @@ -284,13 +280,11 @@ a' まずは普通に書くとしよう。

-
-                <?php
+              
<?php
 
     for ($i = 1; $i < 100; $i++) {
     echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
-    }
-              
+ }

素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 @@ -303,16 +297,14 @@ a' forは、3文字もある長いキーワードである。こんなものは使えない。array_系の関数を使って、適当に置き換えるとしよう。

-
-                <?php
+              
<?php
 
   $s = range(1, 100);
   array_walk(
   $s,
   fn($i) =>
   printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
-  );
-              
+ );

array_walkrangeprintfといったforよりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echoは文 (statement) であり式 (expression) ではないので、式であるprintfに置き換えた。 @@ -325,8 +317,7 @@ a' rangearray_walkprintfは長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。

-
-                <?php
+              
<?php
 
   $r = 'range';
   $w = 'array_walk';
@@ -337,8 +328,7 @@ a'
   $s,
   fn($i) =>
   $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
-  );
-              
+ );

これで関数を呼び出している所は短くなった。では、$r$w$p、また'Fizz''Buzz'はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 @@ -365,21 +355,18 @@ a' というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、Fizzという文字列が欲しければ、次のようにする。

-
-                $f
+              
$f
 =F
 .i
 .z
 .z
-;;
-              
+;;

こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@演算子を使って抑制してやるとよい。

-
-                $f
+              
$f
 =@
 F.
 @i
@@ -387,8 +374,7 @@ F.
 @z
 .#
 @z
-;;
-              
+;;

むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。 @@ -405,8 +391,7 @@ F. ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (&|^) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。

-
-                $a = "12345";
+              
$a = "12345";
 $b = "world";
 
 // $a ^ $b は次のコードと同じ
@@ -416,26 +401,22 @@ $result .= $a[$i] ^ $b[$i];
 }
 
 echo $result;
-// => F]AXQ
-              
+// => F]AXQ

これを踏まえ、次のコードを見てみよう。

-
-                $x = "x\nOm\n";
+              
$x = "x\nOm\n";
 $y = "\nk!\no";
 $r = $x ^ $y;
-echo "$r\n";
-              
+echo "$r\n";

実行すると、rangeが表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。

-
-                $x
+              
$x
 ='x
 Om
 ';
@@ -446,15 +427,13 @@ o'
 ;
 
 $r = $x ^ $y;
-echo "$r\n";
-              
+echo "$r\n";

さらに#を使って適当に調整すると、次のようになる。

-
-                $x
+              
$x
 =#
 'x
 Om
@@ -471,8 +450,7 @@ $x
 $y
 ;#
 
-echo "$r\n";
-              
+echo "$r\n";

1行あたり2文字で、rangeという文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 @@ -490,8 +468,7 @@ echo "$r\n"; 完成したものがこちら。

-
-              <?php
+            
<?php
 
   $x
   =#
@@ -639,8 +616,7 @@ echo "$r\n";
   )#
   .'
   ')
-  );
-            
+ );
@@ -660,8 +636,7 @@ echo "$r\n"; PHP では、バッククォートを使ってシェルを呼び出せる。これはshell_exec関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。

-
-              <?php
+            
<?php
 
   printf(`
   e\
@@ -672,8 +647,7 @@ echo "$r\n";
   1\
   2\
   3\
-  `);
-            
+ `);

なお、ここでは簡単のため出力にprintfをそのまま使っているが、実際にはprintfという文字列を合成して可変関数で呼び出す。 @@ -701,8 +675,7 @@ echo "$r\n"; もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。

-
-              <?php
+            
<?php
 
 $c = 'chr';
 
@@ -730,33 +703,28 @@ ${
 1\
 2\
 3\
-`);
-            
+`);

先程と同じく、chrprintfを生成する部分は長くなるので省いた。

-
-              ${
+            
${
 '_
-'}
-            
+'}

は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。

-
-              e\
+            
e\
 c\
 h\
 o\
 \
 1\
 2\
-3\
-            
+3\

これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 @@ -770,11 +738,9 @@ o\ ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。

-
-              ${
+            
${
 '_
-'}
-            
+'}

最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 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 a2bf44c..db0e7b6 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 @@ -68,8 +68,7 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
-              <?php
+            
<?php
 
 $π = $argv[1] ?? null;
 if ($π === null) {
@@ -89,8 +88,7 @@ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
 echo "Token: {$t}\n";
 } else {
 echo "Failed.\n";
-}
-            
+}
@@ -99,19 +97,15 @@ echo "Failed.\n"; ソースを見るとわかるとおり、$argv[1]を参照している。それをなる変数に代入しているので、円周率を渡してみる。

-
-              $ php Q.php 3.14
-  Failed.
-            
+
$ php Q.php 3.14
+  Failed.

失敗してしまった。精度を上げてみる。

-
-              $ php Q.php 3.1415
-Failed.
-            
+
$ php Q.php 3.1415
+Failed.

だめだった。これを成功するまで繰り返す。 @@ -121,10 +115,8 @@ Failed. 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。

-
-              $ php Q.php 3.1415926535897932
-Token: #YO
-            
+
$ php Q.php 3.1415926535897932
+Token: #YO

めでたくトークン「#YO」が手に入った。 @@ -137,24 +129,20 @@ Token: #YO 短いので頭から追っていく。

-
-              $π = $argv[1] ?? null;
+            
$π = $argv[1] ?? null;
   if ($π === null) {
   exit('No input.');
   }
   $π = trim($π);
   if (!is_numeric($π)) {
   exit('Invalid input.');
-  }
-            
+ }

入力のバリデーション部分。数値のみ受け付ける。

-
-              $s = implode(array_map(chr(...), str_split($π, 2)));
-            
+
$s = implode(array_map(chr(...), str_split($π, 2)));

を 2 文字ごとに区切り (str_split)、数値を ASCII コードと見做して文字に変換 (chr) して結合 (implode) している。 @@ -164,17 +152,13 @@ Token: #YO 例えば、'656667'だったとすると、656667に対応した'A''B''C'へと変換され、'ABC'になる。

-
-              $π = '656667';
+            
$π = '656667';
 $s = implode(array_map(chr(...), str_split($π, 2)));
 echo $s;
-// => ABC
-            
+// => ABC
-
-              preg_match('/(\x23.+?) /', $s, $m);
-$t = $m[1] ?? '';
-            
+
preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';

正規表現でマッチングしている。\x23#と同じであることに留意すると、この正規表現は「#から始まる 2 以上の長さ (含#) の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。 @@ -184,13 +168,11 @@ $t = $m[1] ?? ''; なお、#を直接書いていないのは、/#.+?) /と書くと、#.+?)という意図せぬトークンが登録されてしまうからである。

-
-              if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+            
if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
 echo "Token: {$t}\n";
 } else {
 echo "Failed.\n";
-}
-            
+}

最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 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 6e002c3..71c6e44 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 @@ -73,10 +73,8 @@ ローカルマシンで鍵を生成する。

-
-                $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
-  $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
-              
+
$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
+  $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key

teika.keyはローカルからサーバへの接続用、github2teika.keyは、GitHub Actions からサーバへのデプロイ用。 @@ -89,13 +87,11 @@ .ssh/configに設定しておく。

-
-                Host teika
+              
Host teika
   HostName **********
   User **********
   Port **********
-  IdentityFile ~/.ssh/teika.key
-              
+ IdentityFile ~/.ssh/teika.key
@@ -114,28 +110,22 @@ 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。sudoグループに追加してsudoできるようにし、suで切り替え。

-
-                $ sudo adduser **********
+              
$ sudo adduser **********
     $ sudo adduser ********** sudo
     $ su **********
-    $ cd
-              
+ $ cd

ホスト名を変える

-
-                $ sudo hostname teika
-              
+
$ sudo hostname teika

公開鍵を置く

-
-                $ mkdir ~/.ssh
+              
$ mkdir ~/.ssh
   $ chmod 700 ~/.ssh
-  $ vi ~/.ssh/authorized_keys
-              
+ $ vi ~/.ssh/authorized_keys

authorized_keysには、ローカルで生成した~/.ssh/teika.key.pub~/.ssh/github2teika.key.pubの内容をコピーする。 @@ -148,10 +138,8 @@ SSH の設定を変更し、少しでも安全にしておく。

-
-                $ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
-  $ sudo vi /etc/ssh/sshd_config
-              
+
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
+  $ sudo vi /etc/ssh/sshd_config
  • @@ -177,10 +165,8 @@ そして設定を反映。

    -
    -                $ sudo systemctl restart sshd
    -$ sudo systemctl status sshd
    -              
    +
    $ sudo systemctl restart sshd
    +$ sudo systemctl status sshd
@@ -189,9 +175,7 @@ $ sudo systemctl status sshd 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。

-
-                $ ssh teika
-              
+
$ ssh teika
@@ -200,13 +184,11 @@ $ sudo systemctl status sshd デフォルトの 22 番を閉じ、設定したポートだけ空ける。

-
-                $ sudo ufw deny ssh
+              
$ sudo ufw deny ssh
   $ sudo ufw allow *******
   $ sudo ufw enable
   $ sudo ufw reload
-  $ sudo ufw status
-              
+ $ sudo ufw status

ここでもう一度 SSH の接続確認を挟む。 @@ -219,48 +201,38 @@ $ sudo systemctl status sshd GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。

-
-                $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
-$ cat ~/.ssh/github.key.pub
-              
+
$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
+$ cat ~/.ssh/github.key.pub

GitHub の設定画面から、この公開鍵を追加する。

-
-                $ vi ~/.ssh/config
-              
+
$ vi ~/.ssh/config

設定はこう。

-
-                Host github.com
+              
Host github.com
 HostName github.com
 User git
-IdentityFile ~/.ssh/github.key
-              
+IdentityFile ~/.ssh/github.key

最後に接続できるか確認しておく。

-
-                ssh -T github.com
-              
+
ssh -T github.com

パッケージの更新

-
-                $ sudo apt update
+              
$ sudo apt update
   $ sudo apt upgrade
   $ sudo apt update
   $ sudo apt upgrade
-  $ sudo apt autoremove
-              
+ $ sudo apt autoremove
@@ -275,16 +247,12 @@ IdentityFile ~/.ssh/github.key

使うソフトウェアのインストール

-
-                $ sudo apt install docker docker-compose git make
-              
+
$ sudo apt install docker docker-compose git make

メインユーザが Docker を使えるように

-
-                sudo adduser ********** docker
-              
+
sudo adduser ********** docker
@@ -293,37 +261,29 @@ IdentityFile ~/.ssh/github.key 80 番と 443 番を空ける。

-
-                $ sudo ufw allow 80/tcp
+              
$ sudo ufw allow 80/tcp
     $ sudo ufw allow 443/tcp
     $ sudo ufw reload
-    $ sudo ufw status
-              
+ $ sudo ufw status

リポジトリのクローン

-
-                $ cd
+              
$ cd
   $ git clone git@github.com:nsfisis/nsfisis.dev.git
   $ cd nsfisis.dev
-  $ git submodule update --init
-              
+ $ git submodule update --init

certbot で証明書取得

-
-                $ docker-compose up -d acme-challenge
-  $ make setup
-              
+
$ docker-compose up -d acme-challenge
+  $ make setup

サーバを稼動させる

-
-                $ make serve
-              
+
$ make serve
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 7aa97a9..808f1b4 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 @@ -72,8 +72,7 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
-              <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+            
<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -82,8 +81,7 @@
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-  <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-            
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>

"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。 @@ -96,8 +94,7 @@ 実行してみると、次のような出力が得られる。

-
-              #
+            
#
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -106,15 +103,13 @@
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
   <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-  <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-            
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>

1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。

-
-              #
+            
#
 W
 <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
 <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -123,15 +118,13 @@ W
 <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
 <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
 <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-            
+<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>

今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。

-
-              #
+            
#
 W
 E
 L
@@ -140,8 +133,7 @@ V
 E
 P
 H
-P
-            
+P

トークン「#WELOVEPHP」が手に入った。 @@ -158,9 +150,7 @@ P Vim で開くと次のようになる (1 行目を抜粋)。

-
-              <?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-            
+
<?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>

<200b>と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。 @@ -185,17 +175,13 @@ P 続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて<200b>と記載する。

-
-              fn($s)=>chr(strlen($s)/3)
-            
+
fn($s)=>chr(strlen($s)/3)

PHP のstrlen()は文字列のバイト数を返す。1 行目の$sは以下の内容となっており、

-
-              $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'
-            
+
$s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'

このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは#の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。 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 index 3100b8e..81d673f 100644 --- 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 @@ -82,8 +82,7 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
-              <?php
+            
<?php
     try {
     f(g() / __LINE__);
     } catch (Throwable $e) {
@@ -194,8 +193,7 @@
 
     function g() {
     return __LINE__;
-      }
-            
+ }

"Catchline" と名付けた作品。実行するとトークン#base64_decode('SGVsbG8sIFdvcmxkIQ==')が得られる。 @@ -239,8 +237,7 @@ このうち 1つ目のケースは、finally節の中でエラーを投げると PHP 処理系が勝手に$previousを設定してくれる。

-
-                <?php
+              
<?php
 
       try {
       try {
@@ -253,8 +250,7 @@
       // => Error 2
       echo $e->getPrevious()->getMessage() . PHP_EOL;
       // => Error 1
-        }
-              
+ }

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

-
-                <?php
+              
<?php
       try {
       f(g() / __LINE__);
       } catch (Throwable $e) {
@@ -276,8 +271,7 @@
       printf('%c', $e->getLine() + 23);
       }
       echo "\n";
-        }
-              
+ }

出力をおこなうcatch節を見てみると、Throwable::getPrevious()を呼び出してエラーチェインを辿り、Throwable::getLine()でエラーが発生した行数を取得している。その行数に23なるマジックナンバーを足し、フォーマット指定子%cで出力している。 @@ -287,9 +281,7 @@ フォーマット指定子%cは、整数を ASCII コードと見做して印字する。トークン#base64_decode('SGVsbG8sIFdvcmxkIQ==')bであれば、ASCII コード98なので、75 行目で発生したエラー、

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

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

-
-                function f(int $i) {
+              
function f(int $i) {
       if ($i < 0) f();
       try {
       match ($i) {
@@ -326,24 +317,19 @@
       } finally {
       f($i - 1);
       }
-        }
-              
+ }

前述のように、finally節でエラーを投げると PHP 処理系が$previousを設定する。ここでは、エラーを繋げるためにf()を再帰呼び出ししている。最初にf()を呼び出している箇所を確認すると、

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

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