From d30dfc89bf1b673b2fdc0638766b930adaec228c Mon Sep 17 00:00:00 2001
From: nsfisis
#include <iostream>
-
-[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
-[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
-[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
-[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
-[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
-[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
-[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
-[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
-[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
-[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
-[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
-[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
-[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
-// [[using]]
-int main() {
- std::cout << "Hello, World!" << std::endl;
-}
+ #include <iostream>
+
+[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
+[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
+[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
+[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
+[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
+[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
+[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
+[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
+[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
+[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
+[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
+[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
+[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
+// [[using]]
+int main() {
+ std::cout << "Hello, World!" << std::endl;
+}
+ -@@ -137,8 +138,10 @@ 上のコードでは
-[[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/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html b/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html index 91ec5d0a..f3a11356 100644 --- a/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html +++ b/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html @@ -14,8 +14,7 @@
【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment|REPL: Rest-Eat-Program Loop - - +@@ -78,13 +77,15 @@ Python でクロージャを作ろうと、次のようなコードを書いた。 - +def f(): - x = 0 - def g(): - x += 1 - g() - -f()++def f(): + x = 0 + def g(): + x += 1 + g() + +f()関数
-gから 関数fのスコープ内で定義された変数xを参照し、それに 1 を足そうとしている。 これを実行するとx += 1の箇所でエラーが発生する。 @@ -100,27 +101,31 @@ f() local変数xが代入前に参照された、とある。これは、fのxを参照するのではなく、新しく別の変数をg内に作ってしまっているため。前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。varを変数宣言のための構文として擬似的に利用している。+# 注: var は正しい Python の文法ではない。上記参照のこと -def f(): - var x # f の local変数 'x' を宣言 - x = 0 # x に 0 を代入 - def g(): # f の内部関数 g を定義 - var x # g の local変数 'x' を宣言 - # たまたま f にも同じ名前の変数があるが、それとは別の変数 - x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文) - # 加算する前の値を参照しようとするが、まだ代入されていないためエラー - g()++# 注: var は正しい Python の文法ではない。上記参照のこと +def f(): + var x # f の local変数 'x' を宣言 + x = 0 # x に 0 を代入 + def g(): # f の内部関数 g を定義 + var x # g の local変数 'x' を宣言 + # たまたま f にも同じ名前の変数があるが、それとは別の変数 + x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文) + # 加算する前の値を参照しようとするが、まだ代入されていないためエラー + g()当初の意図を表現するには、次のように書けばよい。
-+def f(): - x = 0 - def g(): - nonlocal x ## (*) - x += 1 - g()++def f(): + x = 0 + def g(): + nonlocal x ## (*) + x += 1 + g()
(*)のように、nonlocalを追加する。これにより一つ外側のスコープ (gの一つ外側 =f) で定義されているxを探しに行くようになる。 diff --git a/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html index 0e9ee932..cf2eb729 100644 --- a/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ b/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -14,8 +14,7 @@【Ruby】 自身を実行している処理系の種類を判定する|REPL: Rest-Eat-Program Loop - - +@@ -83,12 +82,14 @@ 上記ページの例から引用する: - +$ 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"++$ 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"それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。 @@ -208,10 +209,12 @@ 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"+diff --git a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index a11a2f15..76f7058c 100644 --- a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -14,8 +14,7 @@+/* +* Ruby engine. +*/ +#define MRUBY_RUBY_ENGINE "mruby"【Ruby】 then キーワードと case in|REPL: Rest-Eat-Program Loop - - +@@ -83,36 +82,40 @@ 使われることは稀だが、Ruby では thenがキーワードになっている。次のように使う: -+if cond then - puts "Y" -else - puts "N" -end++if cond then + puts "Y" +else + puts "N" +endこのキーワードが現れうる場所はいくつかあり、
-if、unless、rescue、case構文がそれに当たる。 上記のように、何か条件を書いた後thenを置き、式がそこで終了していることを示すマーカーとして機能する。+# Example: - -if x then - a -end - -unless x then - a -end - -begin - a -rescue then - b -end - -case x -when p then - a -end++# Example: + +if x then + a +end + +unless x then + a +end + +begin + a +rescue then + b +end + +case x +when p then + a +end@@ -121,17 +124,21 @@ 普通 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' -if true puts 'Hello, World!' end - ^~~~ -20:1: syntax error, unexpected `end', expecting end-of-input -...f true puts 'Hello, World!' end++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二つ目のメッセージは無視して一つ目を読むと、
-thenか;か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 @@ -141,8 +148,10 @@ if true puts 'Hello, World!' end ポイントは改行がthen(や;) の代わりとなることである。trueの後に改行を入れてみる。+if true -puts 'Hello, World!' end++if true +puts 'Hello, World!' end無事 Hello, World! と出力されるようになった。 @@ -155,21 +164,27 @@ puts 'Hello, World!'
if a b end+++if a b end-
thenも;も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。この例は二通りに解釈できる。+# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 -if a then -b -end+-+# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 +if a then +b +end+# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 -# その結果が truthy なら何もしない -if a(b) then -end++# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 +# その結果が truthy なら何もしない +if a(b) then +end-
then等はこの曖昧性を排除するためにあり、条件式はifからthen等までの間にある、ということを明確にする。 C系のif後に来る(/)や、Python の:、Rust/Go/Swift などの{も同じ役割を持つ。 @@ -190,39 +205,43 @@ b https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986+p_case_body : keyword_in -{ - SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); - p->command_start = FALSE; - $<ctxt>1 = p->ctxt; - p->ctxt.in_kwarg = 1; - $<tbl>$ = push_pvtbl(p); -} -{ - $<tbl>$ = push_pktbl(p); -} -p_top_expr then -{ - pop_pktbl(p, $<tbl>3); - pop_pvtbl(p, $<tbl>2); - p->ctxt.in_kwarg = $<ctxt>1.in_kwarg; -} -compstmt -p_cases -{ - /*%%%*/ - $$ = NEW_IN($4, $7, $8, &@$); - /*% %*/ - /*% ripper: in!($4, $7, escape_Qundef($8)) %*/ -} -;++p_case_body : keyword_in +{ + SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); + p->command_start = FALSE; + $<ctxt>1 = p->ctxt; + p->ctxt.in_kwarg = 1; + $<tbl>$ = push_pvtbl(p); +} +{ + $<tbl>$ = push_pktbl(p); +} +p_top_expr then +{ + pop_pktbl(p, $<tbl>3); + pop_pvtbl(p, $<tbl>2); + p->ctxt.in_kwarg = $<ctxt>1.in_kwarg; +} +compstmt +p_cases +{ + /*%%%*/ + $$ = NEW_IN($4, $7, $8, &@$); + /*% %*/ + /*% 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は文字通りin、p_top_exprはいわゆるパターン、thenはthenキーワードのことではなく、この記事でthen等と呼んでいるもの、つまりthenキーワード、;、改行のいずれかである。 @@ -232,36 +251,40 @@ p_cases これにより、case-whenによる従来の構文と同じように、then等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:+case x -in 1 then a -in 2 then b -in 3 then c -end - -case x -in 1 - a -in 2 - b -in 3 - c -end - -case x -in 1; a -in 2; b -in 3; c -end++case x +in 1 then a +in 2 then b +in 3 then c +end + +case x +in 1 + a +in 2 + b +in 3 + c +end + +case x +in 1; a +in 2; b +in 3; c +endところで、
-p_top_exprにはifによる guard clause が書けるので、その場合はif-thenと似たような見た目になる。+case x -in 0 then a -in n if n < 0 then b -in n then c -end++case x +in 0 then a +in n if n < 0 then b +in n then c +enddiff --git a/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html index 69cfb8a4..4ae7f235 100644 --- a/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ b/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -14,8 +14,7 @@ Rust のプリミティブ型はどこからやって来るか|REPL: Rest-Eat-Program Loop - - +@@ -73,26 +72,28 @@ Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 - +#![allow(non_camel_case_types)] -#![allow(dead_code)] - -struct bool; -struct char; -struct i8; -struct i16; -struct i32; -struct i64; -struct i128; -struct isize; -struct u8; -struct u16; -struct u32; -struct u64; -struct u128; -struct usize; -struct f32; -struct f64; -struct str;++#![allow(non_camel_case_types)] +#![allow(dead_code)] + +struct bool; +struct char; +struct i8; +struct i16; +struct i32; +struct i64; +struct i128; +struct isize; +struct u8; +struct u16; +struct u32; +struct u64; +struct u128; +struct usize; +struct f32; +struct f64; +struct str;では、普段単に
-boolと書いたとき、このboolは一体どこから来ているのか。rustc のソースを追ってみた。 @@ -127,60 +128,66 @@rustcはセルフホストされている (=rustc自身が Rust で書かれている) ので、boolやcharなどで適当に検索をかけてもノイズが多すぎて話にならない。しかし、お誂え向きなことにi128/u128というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使ってgit grepしてみる。+$ 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++$ 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 294659165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
-+$ git grep "\bi128\b" -... -rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); -...++$ 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. -/// -/// All other types are defined somewhere and possibly imported, but the primitive ones need -/// special handling, since they have no place of origin. -struct PrimitiveTypeTable { - primitive_types: FxHashMap<Symbol, PrimTy>, -} - -impl PrimitiveTypeTable { - fn new() -> PrimitiveTypeTable { - let mut table = FxHashMap::default(); - - table.insert(sym::bool, Bool); - table.insert(sym::char, Char); - table.insert(sym::f32, Float(FloatTy::F32)); - table.insert(sym::f64, Float(FloatTy::F64)); - table.insert(sym::isize, Int(IntTy::Isize)); - table.insert(sym::i8, Int(IntTy::I8)); - table.insert(sym::i16, Int(IntTy::I16)); - table.insert(sym::i32, Int(IntTy::I32)); - table.insert(sym::i64, Int(IntTy::I64)); - table.insert(sym::i128, Int(IntTy::I128)); - table.insert(sym::str, Str); - table.insert(sym::usize, Uint(UintTy::Usize)); - table.insert(sym::u8, Uint(UintTy::U8)); - table.insert(sym::u16, Uint(UintTy::U16)); - table.insert(sym::u32, Uint(UintTy::U32)); - table.insert(sym::u64, Uint(UintTy::U64)); - table.insert(sym::u128, Uint(UintTy::U128)); - Self { primitive_types: table } - } -}++/// 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. +struct PrimitiveTypeTable { + primitive_types: FxHashMap<Symbol, PrimTy>, +} + +impl PrimitiveTypeTable { + fn new() -> PrimitiveTypeTable { + let mut table = FxHashMap::default(); + + table.insert(sym::bool, Bool); + table.insert(sym::char, Char); + table.insert(sym::f32, Float(FloatTy::F32)); + table.insert(sym::f64, Float(FloatTy::F64)); + table.insert(sym::isize, Int(IntTy::Isize)); + table.insert(sym::i8, Int(IntTy::I8)); + table.insert(sym::i16, Int(IntTy::I16)); + table.insert(sym::i32, Int(IntTy::I32)); + table.insert(sym::i64, Int(IntTy::I64)); + table.insert(sym::i128, Int(IntTy::I128)); + table.insert(sym::str, Str); + table.insert(sym::usize, Uint(UintTy::Usize)); + table.insert(sym::u8, Uint(UintTy::U8)); + table.insert(sym::u16, Uint(UintTy::U16)); + table.insert(sym::u32, Uint(UintTy::U32)); + table.insert(sym::u64, Uint(UintTy::U64)); + table.insert(sym::u128, Uint(UintTy::U128)); + Self { primitive_types: table } + } +}これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、 @@ -196,27 +203,29 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
-+/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. -/// (略) -fn resolve_ident_in_lexical_scope( - &mut self, - mut ident: Ident, - ns: Namespace, - // (略) -) -> Option<LexicalScopeBinding<'a>> { - // (略) - - if ns == TypeNS { - if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) { - let binding = - (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root()) - .to_name_binding(self.arenas); - return Some(LexicalScopeBinding::Item(binding)); - } - } - - None -}++/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. +/// (略) +fn resolve_ident_in_lexical_scope( + &mut self, + mut ident: Ident, + ns: Namespace, + // (略) +) -> Option<LexicalScopeBinding<'a>> { + // (略) + + if ns == TypeNS { + if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) { + let binding = + (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root()) + .to_name_binding(self.arenas); + return Some(LexicalScopeBinding::Item(binding)); + } + } + + None +}関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。
-if ns == TypeNSのブロック内では、primitive_type_table(上記のPrimitiveTypeTable::new()で作られた変数) に含まれている識別子 (bool、i32など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 @@ -234,13 +243,15 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); 動作がわかったところで、例として次のコードを考える。+#![allow(non_camel_case_types)] - -struct bool; - -fn main() { - let _: bool = bool; -}++#![allow(non_camel_case_types)] + +struct bool; + +fn main() { + let _: bool = bool; +}ここで
main()のboolはstruct boolとして解決される。なぜなら、プリミティブ型の判定をする前にboolという名前の別の型が見つかるからだ。 diff --git a/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index 72c04ee1..ddf70cae 100644 --- a/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -14,8 +14,7 @@【Vim】 autocmd events の BufWrite/BufWritePre の違い|REPL: Rest-Eat-Program Loop - - +@@ -124,24 +123,30 @@ 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}, -{"BufReadCmd", EVENT_BUFREADCMD}, -{"BufReadPost", EVENT_BUFREADPOST},++{"BufRead", EVENT_BUFREADPOST}, +{"BufReadCmd", EVENT_BUFREADCMD}, +{"BufReadPost", EVENT_BUFREADPOST},https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
-+{"BufWrite", EVENT_BUFWRITEPRE}, -{"BufWritePost", EVENT_BUFWRITEPOST}, -{"BufWritePre", EVENT_BUFWRITEPRE},++{"BufWrite", EVENT_BUFWRITEPRE}, +{"BufWritePost", EVENT_BUFWRITEPOST}, +{"BufWritePre", EVENT_BUFWRITEPRE},@@ -154,20 +159,24 @@ https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124 - diff --git a/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html index 00a6265b..afee803f 100644 --- a/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html +++ b/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html @@ -14,8 +14,7 @@+aliases = { -BufCreate = 'BufAdd', -BufRead = 'BufReadPost', -BufWrite = 'BufWritePre', -FileEncoding = 'EncodingChanged', -},++aliases = { + BufCreate = 'BufAdd', + BufRead = 'BufReadPost', + BufWrite = 'BufWritePre', + FileEncoding = 'EncodingChanged', +},ところで、上では取り上げなかった
-FileEncodingだが、これは:help FileEncodingにしっかりと書いてある。+*FileEncoding* -FileEncoding Obsolete. It still works and is equivalent - to |EncodingChanged|.++*FileEncoding* +FileEncoding Obsolete. It still works and is equivalent + to |EncodingChanged|.Vimで選択した行の順番を入れ替える|REPL: Rest-Eat-Program Loop - - +@@ -69,11 +68,13 @@ TL; DR
-+" License: Public Domain - -command! -bar -range=% - \ Reverse - \ keeppatterns <line1>,<line2>g/^/m<line1>-1++" License: Public Domain + +command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1@@ -142,9 +143,11 @@ command! -bar -range=% なお、 :g/^/m0は全ての行を入れ替えるが、:N,Mg/^/mN-1とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 -+command! -bar -range=% - \ Reverse - \ <line1>,<line2>g/^/m<line1>-1++command! -bar -range=% + \ Reverse + \ <line1>,<line2>g/^/m<line1>-1これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 @@ -177,13 +180,15 @@ command! -bar -range=% 前述した
-:Reverseコマンドの定義を少し変えて、次のようにする:+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>)++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>)実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 @@ -234,9 +239,11 @@ command! -bar -range=%
command! -bar -range=%
- \ Reverse
- \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+ command! -bar -range=%
+ \ Reverse
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+
まさにこのための Exコマンド、:keeppatterns が存在する。:keeppatterns {command} のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
diff --git a/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
index b4875558..3469e8b1 100644
--- a/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
+++ b/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
@@ -14,8 +14,7 @@
<?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
+
+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 で動かせばトークンが得られる。 @@ -181,28 +182,30 @@ $🐘([ なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。
-+ + + + + + + + + +
-[
- > + + +
- > + + + + +
- > + + + + + + + + + + + +
- > + + + + + + + + + +
- < < < < -
-]
-> + + + + + .
-- - .
-> - - - .
-> - - - .
-- - .
-- .
-< .
-> > - - .
-+ + + + + + + .
-< - - - - .
-< .
-> + + .
-> - .
-< .
+ + + + + + + + + + +
+[
+ > + + +
+ > + + + + +
+ > + + + + + + + + + + + +
+ > + + + + + + + + + +
+ < < < < -
+]
+> + + + + + .
+- - .
+> - - - .
+> - - - .
+- - .
+- .
+< .
+> > - - .
++ + + + + + + .
+< - - - - .
+< .
+> + + .
+> - .
+< .
+ 実行結果はこちら: https://ideone.com/22VWmb @@ -271,7 +274,9 @@ $🐘([ ソースコードのライセンスを示したこの部分だが、
-https://creativecommons.org/publicdomain/zero/1.0/
+ https://creativecommons.org/publicdomain/zero/1.0/
+
完全に合法な PHP のコードである。 https: 部分はラベル、// 以降は行コメントになっている。
@@ -284,11 +289,13 @@ $🐘([
ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。PHP では、型変換を利用することで任意の整数を作り出すことができる。
assert(0 === +!![]);
-assert(1 === +![]);
-assert(2 === ![]+![]);
-assert(3 === ![]+![]+![]);
-assert(10 === +(![].+!![]));
+ assert(0 === +!![]);
+assert(1 === +![]);
+assert(2 === ![]+![]);
+assert(3 === ![]+![]+![]);
+assert(10 === +(![].+!![]));
+
[] に ! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 1 と 0 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 個足し合わせてももちろん 10 が作れる)。
@@ -329,40 +336,42 @@ $🐘([
ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
<?php
-
-/*********************************************************
- * This program displays a PHPer token. *
- * Guess 'N'. *
- * *
- * Hints: *
- * - N itself has no special meaning, e.g., 42, 8128, *
- * it is selected at random. *
- * - Each element of $token represents a single letter. *
- * - One letter consists of 5x5 cells. *
- * - Remember, the output is a complete PHPer token. *
- * *
- * License: *
- * https://creativecommons.org/publicdomain/zero/1.0/ *
- *********************************************************/
-const N = 0 /* Change it to your answer. */;
-assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
-
-$token = [
- 0x14B499C,
- 0x0BE34CC, 0x01C9C69,
- 0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
- 0x01C1C66, 0x0FC1C47, 0x01C1C66,
- 0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
-];
-foreach ($token as $x) {
- $x = $x ^ N;
-
- $x = sprintf('%025b', $x);
- $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
- $x = implode("\n", str_split($x, length: 5));
- echo "{$x}\n\n";
-}
+ <?php
+
+/*********************************************************
+ * This program displays a PHPer token. *
+ * Guess 'N'. *
+ * *
+ * Hints: *
+ * - N itself has no special meaning, e.g., 42, 8128, *
+ * it is selected at random. *
+ * - Each element of $token represents a single letter. *
+ * - One letter consists of 5x5 cells. *
+ * - Remember, the output is a complete PHPer token. *
+ * *
+ * License: *
+ * https://creativecommons.org/publicdomain/zero/1.0/ *
+ *********************************************************/
+const N = 0 /* Change it to your answer. */;
+assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+
+$token = [
+ 0x14B499C,
+ 0x0BE34CC, 0x01C9C69,
+ 0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
+ 0x01C1C66, 0x0FC1C47, 0x01C1C66,
+ 0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
+];
+foreach ($token as $x) {
+ $x = $x ^ N;
+
+ $x = sprintf('%025b', $x);
+ $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+ $x = implode("\n", str_split($x, length: 5));
+ echo "{$x}\n\n";
+}
+
さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。トークンを得るためには、ソースコードを読み、定数 N を特定する必要がある。
@@ -378,33 +387,43 @@ $🐘([
まずはソースコードを読んでいく。
$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文字ごとに区切ったあと、改行で結合している。
@@ -450,49 +469,55 @@ $🐘([
N は高々
assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+ assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+ なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
-<?php
-
-$x = 0x14B499C;
-
-$x = $x ^ N;
-
-$x = sprintf('%025b', $x);
-$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-$x = implode("\n", str_split($x, length: 5));
-
-assert($x ===
-" # # \n" .
-"#####\n" .
-" # # \n" .
-"#####\n" .
-" # # ");
+ <?php
+
+$x = 0x14B499C;
+
+$x = $x ^ N;
+
+$x = sprintf('%025b', $x);
+$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+$x = implode("\n", str_split($x, length: 5));
+
+assert($x ===
+" # # \n" .
+"#####\n" .
+" # # \n" .
+"#####\n" .
+" # # ");
+ この一連の変換に対する逆変換を考えると、次のようになる。
-<?php
-
-$x =
-" # # \n" .
-"#####\n" .
-" # # \n" .
-"#####\n" .
-" # # ";
-
-$x = implode('', explode("\n", $x));
-$x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x);
-$x = bindec($x);
-
-$n = $x ^ 0x14B499C;
-
-echo "N = $n\n";
+ <?php
+
+$x =
+" # # \n" .
+"#####\n" .
+" # # \n" .
+"#####\n" .
+" # # ";
+
+$x = implode('', explode("\n", $x));
+$x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x);
+$x = bindec($x);
+
+$n = $x ^ 0x14B499C;
+
+echo "N = $n\n";
+
これを実行すると、N が得られる。
@@ -506,41 +531,45 @@ $🐘([
ソースコードはこちら。
<?php
-
-// License: https://creativecommons.org/publicdomain/zero/1.0/
-// This is a quine-like program to generate a PHPer token.
-// Execute it like this: php toquine.php | php | php | php | ...
-
-$s = <<<'Q'
-<?cuc
-// Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/
-// Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra.
-// Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ...
-%f$f = %f;
-$f = fge_ebg13($f); $kf = [
-%f,
-];
-$g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr
-$g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], [' ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a";
-$jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
-cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
-Q;
-$s = str_rot13($s); $xs = [
-0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
-0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
-0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
-];
-$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));
+ <?php
+
+// License: https://creativecommons.org/publicdomain/zero/1.0/
+// This is a quine-like program to generate a PHPer token.
+// Execute it like this: php toquine.php | php | php | php | ...
+
+$s = <<<'Q'
+<?cuc
+// Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/
+// Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra.
+// Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ...
+%f$f = %f;
+$f = fge_ebg13($f); $kf = [
+%f,
+];
+$g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr
+$g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], [' ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a";
+$jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
+cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
+Q;
+$s = str_rot13($s); $xs = [
+0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
+0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
+0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
+];
+$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));
+ コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
-$ php toquine.php | php | php | php | ...
+ $ php toquine.php | php | php | php | ...
+ 実際にはもう少しパイプで繋げなければならない。 diff --git a/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html index d3d21364..ff722cda 100644 --- a/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html +++ b/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -13,8 +13,7 @@
$ term-banner 'Hello, World!' 'こんにちは、' '世界!'
+ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
+
diff --git a/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html b/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html
index 37c10d45..9ba40f11 100644
--- a/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html
+++ b/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html
@@ -14,8 +14,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 バイトとなった (末尾改行を含めずにカウント)。 @@ -131,15 +132,17 @@ こちらは改行とスペースを追加したバージョン:
-[<?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 ?? []);
+
+?>]
+ #\
-i\
-n\
-c\
-l\
-u\
-d\
-e\
-<\
-s\
-t\
-d\
-i\
-o\
-.\
-h\
->\
-/*
-*/
-i\
-n\
-t\
-/*
-*/
-m\
-a\
-i\
-n(
-){
-f\
-o\
-r(
-i\
-n\
-t\
-/*
-*/
-i=
-1;
-i<
-1\
-0\
-0;
-i\
-+\
-+)
-if
-(i
-%\
-15
-==
-0)
-p\
-r\
-i\
-n\
-t\
-f(
-"\
-F\
-i\
-z\
-z\
-B\
-u\
-z\
-z\
-%\
-c\
-",
-10
-);
-
-/* あとは同じように普通のプログラムを変形するだけなので省略 */
+ #\
+i\
+n\
+c\
+l\
+u\
+d\
+e\
+<\
+s\
+t\
+d\
+i\
+o\
+.\
+h\
+>\
+/*
+*/
+i\
+n\
+t\
+/*
+*/
+m\
+a\
+i\
+n(
+){
+f\
+o\
+r(
+i\
+n\
+t\
+/*
+*/
+i=
+1;
+i<
+1\
+0\
+0;
+i\
++\
++)
+if
+(i
+%\
+15
+==
+0)
+p\
+r\
+i\
+n\
+t\
+f(
+"\
+F\
+i\
+z\
+z\
+B\
+u\
+z\
+z\
+%\
+c\
+",
+10
+);
+
+/* あとは同じように普通のプログラムを変形するだけなので省略 */
+
バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
@@ -250,10 +251,12 @@ c\
また、2文字だと文字列がまともに書けないのも辛い。'' だけで2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
$a
-='
-a'
-;;
+ $a
+='
+a'
+;;
+
とすると $a は "\na" になるのだが、余計な改行が入ってしまう。
@@ -272,11 +275,13 @@ a'
まずは普通に書くとしよう。
<?php
-
-for ($i = 1; $i < 100; $i++) {
- echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
-}
+ <?php
+
+for ($i = 1; $i < 100; $i++) {
+ echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
+}
+
素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
@@ -289,14 +294,16 @@ a'
for は、3文字もある長いキーワードである。こんなものは使えない。array_ 系の関数を使って、適当に置き換えるとしよう。
<?php
-
-$s = range(1, 100);
-array_walk(
-$s,
-fn($i) =>
-printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
-);
+ <?php
+
+$s = range(1, 100);
+array_walk(
+$s,
+fn($i) =>
+printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
+);
+
array_walk や range、printf といった for よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echo は文 (statement) であり式 (expression) ではないので、式である printf に置き換えた。
@@ -309,18 +316,20 @@ fn($i) =>
range、array_walk、printf は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
<?php
-
-$r = 'range';
-$w = 'array_walk';
-$p = 'printf';
-
-$s = $r(1, 100);
-$w(
-$s,
-fn($i) =>
-$p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
-);
+ <?php
+
+$r = 'range';
+$w = 'array_walk';
+$p = 'printf';
+
+$s = $r(1, 100);
+$w(
+$s,
+fn($i) =>
+$p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
+);
+
これで関数を呼び出している所は短くなった。では、$r や $w や $p、また 'Fizz' や 'Buzz' はどうやって 1 行 2 文字に収めるのか。次のテクニックへ移ろう。
@@ -345,26 +354,30 @@ fn($i) =>
というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、 Fizz という文字列が欲しければ、次のようにする。
$f
-=F
-.i
-.z
-.z
-;;
+ $f
+=F
+.i
+.z
+.z
+;;
+
こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@ 演算子を使って抑制してやるとよい。
$f
-=@
-F.
-@i
-.#
-@z
-.#
-@z
-;;
+ $f
+=@
+F.
+@i
+.#
+@z
+.#
+@z
+;;
+
むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
@@ -381,66 +394,74 @@ F.
ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (&、|、^) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。
$a = "12345";
-$b = "world";
-
-// $a ^ $b は次のコードと同じ
-$result = '';
-for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
-$result .= $a[$i] ^ $b[$i];
-}
-
-echo $result;
-// => F]AXQ
+ $a = "12345";
+$b = "world";
+
+// $a ^ $b は次のコードと同じ
+$result = '';
+for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
+$result .= $a[$i] ^ $b[$i];
+}
+
+echo $result;
+// => F]AXQ
+ これを踏まえ、次のコードを見てみよう。
-$x = "x\nOm\n";
-$y = "\nk!\no";
-$r = $x ^ $y;
-echo "$r\n";
+ $x = "x\nOm\n";
+$y = "\nk!\no";
+$r = $x ^ $y;
+echo "$r\n";
+
実行すると、range が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。
$x
-='x
-Om
-';
-$y
-='
-k!
-o'
-;
-
-$r = $x ^ $y;
-echo "$r\n";
+ $x
+='x
+Om
+';
+$y
+='
+k!
+o'
+;
+
+$r = $x ^ $y;
+echo "$r\n";
+
さらに # を使って適当に調整すると、次のようになる。
$x
-=#
-'x
-Om
-';
-$y
-='
-k!
-o'
-;#
-$r
-=#
-$x
-^#
-$y
-;#
-
-echo "$r\n";
+ $x
+=#
+'x
+Om
+';
+$y
+='
+k!
+o'
+;#
+$r
+=#
+$x
+^#
+$y
+;#
+
+echo "$r\n";
+
1行あたり2文字で、range という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。
@@ -458,155 +479,157 @@ o'
完成したものがこちら。
<?php
-
-$x
-=#
-'i
-S'
-;;
-$y
-='
-b!
-';
-$c
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'x
-Om
-';
-$y
-='
-k!
-o'
-;#
-$r
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'k
-Sk
-~}
-Ma
-';
-$y
-='
-x!
-s!
-k!
-';
-$w
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'z
-Hd
-G'
-;#
-$y
-='
-x!
-~!
-';
-$p
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'L
-[p
-';
-$y
-='
-c!
-';
-$f
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'H
-[p
-';
-$y
-='
-_!
-';
-$b
-=#
-$x
-^#
-$y
-;#
-$b
-[1
-]=
-$c
-(#
-13
-*9
-);
-$s
-=#
-$r
-(1
-,(
-10
-**
-2)
-);
-$w
-(#
-$s
-,#
-fn
-(#
-$i
-)#
-=>
-$p
-((
-(#
-$i
-%3
-?#
-''
-:#
-$f
-).
-(#
-$i
-%5
-?#
-''
-:#
-$b
-)?
-:#
-$i
-)#
-.'
-')
-);
+ <?php
+
+$x
+=#
+'i
+S'
+;;
+$y
+='
+b!
+';
+$c
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'x
+Om
+';
+$y
+='
+k!
+o'
+;#
+$r
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'k
+Sk
+~}
+Ma
+';
+$y
+='
+x!
+s!
+k!
+';
+$w
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'z
+Hd
+G'
+;#
+$y
+='
+x!
+~!
+';
+$p
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'L
+[p
+';
+$y
+='
+c!
+';
+$f
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'H
+[p
+';
+$y
+='
+_!
+';
+$b
+=#
+$x
+^#
+$y
+;#
+$b
+[1
+]=
+$c
+(#
+13
+*9
+);
+$s
+=#
+$r
+(1
+,(
+10
+**
+2)
+);
+$w
+(#
+$s
+,#
+fn
+(#
+$i
+)#
+=>
+$p
+((
+(#
+$i
+%3
+?#
+''
+:#
+$f
+).
+(#
+$i
+%5
+?#
+''
+:#
+$b
+)?
+:#
+$i
+)#
+.'
+')
+);
+ shell_exec 関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
- <?php
-
-printf(`
-e\
-c\
-h\
-o\
-\
-1\
-2\
-3\
-`);
+ <?php
+
+printf(`
+e\
+c\
+h\
+o\
+\
+1\
+2\
+3\
+`);
+
なお、ここでは簡単のため出力に printf をそのまま使っているが、実際には printf という文字列を合成して可変関数で呼び出す。
@@ -663,56 +688,62 @@ o\
もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
<?php
-
-$c = 'chr';
-
-${
-'_
-'}
-=#
-$c
-(#
-32
-).
-$c
-(#
-92
-);
-
-printf(`
-e\
-c\
-h\
-o\
-${
-'_
-'}
-1\
-2\
-3\
-`);
+ <?php
+
+$c = 'chr';
+
+${
+'_
+'}
+=#
+$c
+(#
+32
+).
+$c
+(#
+92
+);
+
+printf(`
+e\
+c\
+h\
+o\
+${
+'_
+'}
+1\
+2\
+3\
+`);
+
先程と同じく、chr や printf を生成する部分は長くなるので省いた。
${
-'_
-'}
+ ${
+'_
+'}
+
は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。
e\
-c\
-h\
-o\
-\
-1\
-2\
-3\
+ e\
+c\
+h\
+o\
+\
+1\
+2\
+3\
+ これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 @@ -726,9 +757,11 @@ o\ ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
-${
-'_
-'}
+ ${
+'_
+'}
+ 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 diff --git a/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html index 8d72a4f2..efc80674 100644 --- a/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html +++ b/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html @@ -14,8 +14,7 @@
<?php
-
-$π = $argv[1] ?? null;
-if ($π === null) {
- exit('No input.');
-}
-$π = trim($π);
-if (!is_numeric($π)) {
- exit('Invalid input.');
-}
-
-$s = implode(array_map(chr(...), str_split($π, 2)));
-
-preg_match('/(\x23.+?) /', $s, $m);
-$t = $m[1] ?? '';
-
-if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
- echo "Token: {$t}\n";
-} else {
- echo "Failed.\n";
-}
+ <?php
+
+$π = $argv[1] ?? null;
+if ($π === null) {
+ exit('No input.');
+}
+$π = trim($π);
+if (!is_numeric($π)) {
+ exit('Invalid input.');
+}
+
+$s = implode(array_map(chr(...), str_split($π, 2)));
+
+preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';
+
+if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+ echo "Token: {$t}\n";
+} else {
+ 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.
+ だめだった。これを成功するまで繰り返す。 @@ -133,8 +138,10 @@ Failed. 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。
-$ php Q.php 3.1415926535897932
-Token: #YO
+ $ php Q.php 3.1415926535897932
+Token: #YO
+
めでたくトークン「#YO」が手に入った。
@@ -147,20 +154,24 @@ Token: #YO
短いので頭から追っていく。
$π = $argv[1] ?? null;
-if ($π === null) {
- exit('No input.');
-}
-$π = trim($π);
-if (!is_numeric($π)) {
- exit('Invalid input.');
-}
+ $π = $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) している。
@@ -170,13 +181,17 @@ $π = trim($π);
例えば、$π が '656667' だったとすると、65、66、67 に対応した 'A'、'B'、'C' へと変換され、'ABC' になる。
$π = '656667';
-$s = implode(array_map(chr(...), str_split($π, 2)));
-echo $s;
-// => ABC
+ $π = '656667';
+$s = implode(array_map(chr(...), str_split($π, 2)));
+echo $s;
+// => ABC
+ preg_match('/(\x23.+?) /', $s, $m);
-$t = $m[1] ?? '';
+ preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';
+
正規表現でマッチングしている。\x23 は # と同じであることに留意すると、この正規表現は「# から始まる 2 以上の長さ (含 #) の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。
@@ -186,11 +201,13 @@ $π = trim($π);
なお、# を直接書いていないのは、/#.+?) / と書くと、#.+?) という意図せぬトークンが登録されてしまうからである。
if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
- echo "Token: {$t}\n";
-} else {
- echo "Failed.\n";
-}
+ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+ echo "Token: {$t}\n";
+} else {
+ echo "Failed.\n";
+}
+ 最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 diff --git a/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html b/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html index e6644a58..cce709ac 100644 --- a/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html +++ b/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html @@ -14,8 +14,7 @@
$ 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 からサーバへのデプロイ用。
@@ -108,12 +109,14 @@ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
.ssh/config に設定しておく。
Host teika
- HostName **********
- User **********
- Port **********
- IdentityFile ~/.ssh/teika.key
- IdentitiesOnly yes
+ Host teika
+ HostName **********
+ User **********
+ Port **********
+ IdentityFile ~/.ssh/teika.key
+ IdentitiesOnly yes
+ sudo グループに追加して sudo できるようにし、su で切り替え。
- $ sudo adduser **********
-$ sudo adduser ********** sudo
-$ su **********
-$ cd
+ $ sudo adduser **********
+$ sudo adduser ********** sudo
+$ su **********
+$ cd
+ $ sudo hostname teika
+ $ sudo hostname teika
+ $ mkdir ~/.ssh
-$ chmod 700 ~/.ssh
-$ vi ~/.ssh/authorized_keys
+ $ mkdir ~/.ssh
+$ chmod 700 ~/.ssh
+$ vi ~/.ssh/authorized_keys
+
authorized_keys には、ローカルで生成した ~/.ssh/teika.key.pub と ~/.ssh/github2teika.key.pub の内容をコピーする。
@@ -160,8 +169,10 @@ $ vi ~/.ssh/authorized_keys
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
+ $ sudo systemctl restart sshd
-$ sudo systemctl status sshd
+ $ sudo systemctl restart sshd
+$ sudo systemctl status sshd
+ $ ssh teika
+ $ ssh teika
+ $ sudo ufw deny ssh
-$ sudo ufw allow *******
-$ sudo ufw enable
-$ sudo ufw reload
-$ sudo ufw status
+ $ sudo ufw deny ssh
+$ sudo ufw allow *******
+$ sudo ufw enable
+$ sudo ufw reload
+$ sudo ufw status
+ ここでもう一度 SSH の接続確認を挟む。 @@ -217,40 +234,50 @@ $ sudo ufw status 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
- HostName github.com
- User git
- Port 22
- IdentityFile ~/.ssh/github.key
- IdentitiesOnly yes
+ Host github.com
+ HostName github.com
+ User git
+ Port 22
+ IdentityFile ~/.ssh/github.key
+ IdentitiesOnly yes
+ 最後に接続できるか確認しておく。
-$ ssh -T github.com
+ $ ssh -T github.com
+ $ sudo apt update
-$ sudo apt upgrade
-$ sudo apt update
-$ sudo apt upgrade
-$ sudo apt autoremove
+ $ sudo apt update
+$ sudo apt upgrade
+$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt autoremove
+ $ sudo apt install docker docker-compose git make
+ $ sudo apt install docker docker-compose git make
+ $ sudo adduser ********** docker
+ $ sudo adduser ********** docker
+ $ sudo ufw allow 80/tcp
-$ sudo ufw allow 443/tcp
-$ sudo ufw reload
-$ sudo ufw status
+ $ sudo ufw allow 80/tcp
+$ sudo ufw allow 443/tcp
+$ sudo ufw reload
+$ sudo ufw status
+ $ cd
-$ git clone git@github.com:nsfisis/nsfisis.dev.git
-$ cd nsfisis.dev
-$ git submodule update --init
+ $ cd
+$ git clone git@github.com:nsfisis/nsfisis.dev.git
+$ cd nsfisis.dev
+$ git submodule update --init
+ $ docker-compose up -d acme-challenge
-$ make setup
+ $ docker-compose up -d acme-challenge
+$ make setup
+ $ make serve
+ $ make serve
+ <?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");?>
-<?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");?>
+ <?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");?>
+<?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 (自分自身と同じソースコードを出力するプログラム) になっている。 @@ -112,46 +113,52 @@ 実行してみると、次のような出力が得られる。
-#
-<?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");?>
-<?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");?>
+<?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");?>
+<?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");?>
-<?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");?>
-<?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");?>
+ #
+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");?>
+<?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
-O
-V
-E
-P
-H
-P
+ #
+W
+E
+L
+O
+V
+E
+P
+H
+P
+ トークン「#WELOVEPHP」が手に入った。 @@ -168,7 +175,9 @@ 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 で、ゼロ幅スペースである。
@@ -193,13 +202,17 @@ 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/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 @@
<?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 / 3 で 37 が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら f() を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。
diff --git a/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html b/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html
index 883b3ce5..f3eb6ae2 100644
--- a/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html
+++ b/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html
@@ -13,8 +13,7 @@
image/png を用いる。
- package main
-
-import (
- "image"
- _ "image/png"
- "io"
- "os"
-)
-
-func main() {
- inFile, err := os.Open("input.png")
- if err != nil {
- panic(err)
- }
- defer inFile.Close()
-
- img, _, err := image.Decode(inFile)
- if err != nil {
- panic(err)
- }
-
- outFile, err := os.Create("output.png")
- if err != nil {
- panic(err)
- }
- defer outFile.Close()
-
- writePng(outFile, img)
-}
-
-func writePng(w io.Writer, img image.Image) {
- width := uint32(img.Bounds().Dx())
- height := uint32(img.Bounds().Dy())
- writeSignature(w)
- writeChunkIhdr(w, width, height)
- writeChunkIdat(w, width, height, img)
- writeChunkIend(w)
-}
+ package main
+
+import (
+ "image"
+ _ "image/png"
+ "io"
+ "os"
+)
+
+func main() {
+ inFile, err := os.Open("input.png")
+ if err != nil {
+ panic(err)
+ }
+ defer inFile.Close()
+
+ img, _, err := image.Decode(inFile)
+ if err != nil {
+ panic(err)
+ }
+
+ outFile, err := os.Create("output.png")
+ if err != nil {
+ panic(err)
+ }
+ defer outFile.Close()
+
+ writePng(outFile, img)
+}
+
+func writePng(w io.Writer, img image.Image) {
+ width := uint32(img.Bounds().Dx())
+ height := uint32(img.Bounds().Dy())
+ writeSignature(w)
+ writeChunkIhdr(w, width, height)
+ writeChunkIdat(w, width, height, img)
+ writeChunkIend(w)
+}
+
以降は、writeSignature や writeChunkIhdr などを実装していく。
@@ -189,21 +190,23 @@
writeSignature の実装はこちら:
import "encoding/binary"
-
-func writeSignature(w io.Writer) {
- sig := [8]uint8{
- 0x89,
- 0x50, // P
- 0x4E, // N
- 0x47, // G
- 0x0D, // CR
- 0x0A, // LF
- 0x1A, // EOF (^Z)
- 0x0A, // LF
- }
- binary.Write(w, binary.BigEndian, sig)
-}
+ import "encoding/binary"
+
+func writeSignature(w io.Writer) {
+ sig := [8]uint8{
+ 0x89,
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // CR
+ 0x0A, // LF
+ 0x1A, // EOF (^Z)
+ 0x0A, // LF
+ }
+ binary.Write(w, binary.BigEndian, sig)
+}
+
encoding/binary パッケージの binary.Write を使い、固定の 8 バイトを書き込む。
@@ -238,55 +241,59 @@
CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている (D. Sample CRC implementation) ので、これを Go に移植する。
var (
- crcTable [256]uint32
- crcTableComputed bool
-)
-
-func makeCrcTable() {
- for n := 0; n < 256; n++ {
- c := uint32(n)
- for k := 0; k < 8; k++ {
- if (c & 1) != 0 {
- c = 0xEDB88320 ^ (c >> 1)
- } else {
- c = c >> 1
- }
- }
- crcTable[n] = c
- }
- crcTableComputed = true
-}
-
-func updateCrc(crc uint32, buf []byte) uint32 {
- if !crcTableComputed {
- makeCrcTable()
- }
-
- c := crc
- for n := 0; n < len(buf); n++ {
- c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
- }
- return c
-}
-
-func crc(buf []byte) uint32 {
- return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
-}
+ var (
+ crcTable [256]uint32
+ crcTableComputed bool
+)
+
+func makeCrcTable() {
+ for n := 0; n < 256; n++ {
+ c := uint32(n)
+ for k := 0; k < 8; k++ {
+ if (c & 1) != 0 {
+ c = 0xEDB88320 ^ (c >> 1)
+ } else {
+ c = c >> 1
+ }
+ }
+ crcTable[n] = c
+ }
+ crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+ if !crcTableComputed {
+ makeCrcTable()
+ }
+
+ c := crc
+ for n := 0; n < len(buf); n++ {
+ c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+ }
+ return c
+}
+
+func crc(buf []byte) uint32 {
+ return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+
できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。
func writeChunk(w io.Writer, chunkType string, data []byte) {
- typeAndData := make([]byte, 0, len(chunkType)+len(data))
- typeAndData = append(typeAndData, []byte(chunkType)...)
- typeAndData = append(typeAndData, data...)
-
- binary.Write(w, binary.BigEndian, uint32(len(data)))
- binary.Write(w, binary.BigEndian, typeAndData)
- binary.Write(w, binary.BigEndian, crc(typeAndData))
-}
+ func writeChunk(w io.Writer, chunkType string, data []byte) {
+ typeAndData := make([]byte, 0, len(chunkType)+len(data))
+ typeAndData = append(typeAndData, []byte(chunkType)...)
+ typeAndData = append(typeAndData, data...)
+
+ binary.Write(w, binary.BigEndian, uint32(len(data)))
+ binary.Write(w, binary.BigEndian, typeAndData)
+ binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+
仕様どおり、chunkType と data から CRC を計算し、data の長さと合わせて書き込んでいる。PNG では基本的に big endian を使うことに注意する。
@@ -372,20 +379,22 @@
今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
import "bytes"
-
-func writeChunkIhdr(w io.Writer, width, height uint32) {
- var buf bytes.Buffer
- binary.Write(&buf, binary.BigEndian, width)
- binary.Write(&buf, binary.BigEndian, height)
- binary.Write(&buf, binary.BigEndian, uint8(8))
- binary.Write(&buf, binary.BigEndian, uint8(2))
- binary.Write(&buf, binary.BigEndian, uint8(0))
- binary.Write(&buf, binary.BigEndian, uint8(0))
- binary.Write(&buf, binary.BigEndian, uint8(0))
-
- writeChunk(w, "IHDR", buf.Bytes())
-}
+ import "bytes"
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.BigEndian, width)
+ binary.Write(&buf, binary.BigEndian, height)
+ binary.Write(&buf, binary.BigEndian, uint8(8))
+ binary.Write(&buf, binary.BigEndian, uint8(2))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+
+ writeChunk(w, "IHDR", buf.Bytes())
+}
+ const adler32Base = 65521
-
-func updateAdler32(adler uint32, buf []byte) uint32 {
- s1 := adler & 0xFFFF
- s2 := (adler >> 16) & 0xFFFF
-
- for n := 0; n < len(buf); n++ {
- s1 = (s1 + uint32(buf[n])) % adler32Base
- s2 = (s2 + s1) % adler32Base
- }
- return (s2 << 16) + s1
-}
-
-func adler32(buf []byte) uint32 {
- return updateAdler32(1, buf)
-}
+ const adler32Base = 65521
+
+func updateAdler32(adler uint32, buf []byte) uint32 {
+ s1 := adler & 0xFFFF
+ s2 := (adler >> 16) & 0xFFFF
+
+ for n := 0; n < len(buf); n++ {
+ s1 = (s1 + uint32(buf[n])) % adler32Base
+ s2 = (s2 + s1) % adler32Base
+ }
+ return (s2 << 16) + s1
+}
+
+func adler32(buf []byte) uint32 {
+ return updateAdler32(1, buf)
+}
+ 「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。 @@ -473,30 +484,32 @@ 実際にこの手抜き zlib を実装したものがこちら:
-func encodeZlib(data []byte) []byte {
- var buf bytes.Buffer
-
- binary.Write(&buf, binary.BigEndian, uint8(0x78))
- binary.Write(&buf, binary.BigEndian, uint8(0x01))
- blockSize := 65535
- isFinalBlock := false
- for i := 0; !isFinalBlock; i++ {
- var block []byte
- if len(data) <= (i+1)*blockSize {
- block = data[i*blockSize:]
- isFinalBlock = true
- } else {
- block = data[i*blockSize : (i+1)*blockSize]
- }
- binary.Write(&buf, binary.BigEndian, isFinalBlock)
- binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
- binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
- binary.Write(&buf, binary.LittleEndian, block)
- }
- binary.Write(&buf, binary.BigEndian, adler32(data))
-
- return buf.Bytes()
-}
+ func encodeZlib(data []byte) []byte {
+ var buf bytes.Buffer
+
+ binary.Write(&buf, binary.BigEndian, uint8(0x78))
+ binary.Write(&buf, binary.BigEndian, uint8(0x01))
+ blockSize := 65535
+ isFinalBlock := false
+ for i := 0; !isFinalBlock; i++ {
+ var block []byte
+ if len(data) <= (i+1)*blockSize {
+ block = data[i*blockSize:]
+ isFinalBlock = true
+ } else {
+ block = data[i*blockSize : (i+1)*blockSize]
+ }
+ binary.Write(&buf, binary.BigEndian, isFinalBlock)
+ binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+ binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+ binary.Write(&buf, binary.LittleEndian, block)
+ }
+ binary.Write(&buf, binary.BigEndian, adler32(data))
+
+ return buf.Bytes()
+}
+ encodeZlib も使って実際に実装したものがこちら:
- func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
- var pixels bytes.Buffer
- for y := uint32(0); y < height; y++ {
- binary.Write(&pixels, binary.BigEndian, uint8(0))
- for x := uint32(0); x < width; x++ {
- r, g, b, _ := img.At(int(x), int(y)).RGBA()
- binary.Write(&pixels, binary.BigEndian, uint8(r))
- binary.Write(&pixels, binary.BigEndian, uint8(g))
- binary.Write(&pixels, binary.BigEndian, uint8(b))
- }
- }
-
- writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
-}
+ func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+ var pixels bytes.Buffer
+ for y := uint32(0); y < height; y++ {
+ binary.Write(&pixels, binary.BigEndian, uint8(0))
+ for x := uint32(0); x < width; x++ {
+ r, g, b, _ := img.At(int(x), int(y)).RGBA()
+ binary.Write(&pixels, binary.BigEndian, uint8(r))
+ binary.Write(&pixels, binary.BigEndian, uint8(g))
+ binary.Write(&pixels, binary.BigEndian, uint8(b))
+ }
+ }
+
+ writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+}
+ IEND くらいなので実装は簡単:
- func writeChunkIend(w io.Writer) {
- writeChunk(w, "IEND", nil)
-}
+ func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+}
+ package main
-
-import (
- "bytes"
- "encoding/binary"
- "image"
- _ "image/png"
- "io"
- "os"
-)
-
-func main() {
- inFile, err := os.Open("input.png")
- if err != nil {
- panic(err)
- }
- defer inFile.Close()
-
- img, _, err := image.Decode(inFile)
- if err != nil {
- panic(err)
- }
-
- outFile, err := os.Create("output.png")
- if err != nil {
- panic(err)
- }
- defer outFile.Close()
-
- writePng(outFile, img)
-}
-
-func writePng(w io.Writer, img image.Image) {
- width := uint32(img.Bounds().Dx())
- height := uint32(img.Bounds().Dy())
- writeSignature(w)
- writeChunkIhdr(w, width, height)
- writeChunkIdat(w, width, height, img)
- writeChunkIend(w)
-}
-
-func writeSignature(w io.Writer) {
- sig := [8]uint8{
- 0x89,
- 0x50, // P
- 0x4E, // N
- 0x47, // G
- 0x0D, // CR
- 0x0A, // LF
- 0x1A, // EOF (^Z)
- 0x0A, // LF
- }
- binary.Write(w, binary.BigEndian, sig)
-}
-
-func writeChunkIhdr(w io.Writer, width, height uint32) {
- var buf bytes.Buffer
- binary.Write(&buf, binary.BigEndian, width)
- binary.Write(&buf, binary.BigEndian, height)
- binary.Write(&buf, binary.BigEndian, uint8(8))
- binary.Write(&buf, binary.BigEndian, uint8(2))
- binary.Write(&buf, binary.BigEndian, uint8(0))
- binary.Write(&buf, binary.BigEndian, uint8(0))
- binary.Write(&buf, binary.BigEndian, uint8(0))
-
- writeChunk(w, "IHDR", buf.Bytes())
-}
-
-func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
- var pixels bytes.Buffer
- for y := uint32(0); y < height; y++ {
- binary.Write(&pixels, binary.BigEndian, uint8(0))
- for x := uint32(0); x < width; x++ {
- r, g, b, _ := img.At(int(x), int(y)).RGBA()
- binary.Write(&pixels, binary.BigEndian, uint8(r))
- binary.Write(&pixels, binary.BigEndian, uint8(g))
- binary.Write(&pixels, binary.BigEndian, uint8(b))
- }
- }
-
- writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
-}
-
-func encodeZlib(data []byte) []byte {
- var buf bytes.Buffer
-
- binary.Write(&buf, binary.BigEndian, uint8(0x78))
- binary.Write(&buf, binary.BigEndian, uint8(0x01))
- blockSize := 65535
- isFinalBlock := false
- for i := 0; !isFinalBlock; i++ {
- var block []byte
- if len(data) <= (i+1)*blockSize {
- block = data[i*blockSize:]
- isFinalBlock = true
- } else {
- block = data[i*blockSize : (i+1)*blockSize]
- }
- binary.Write(&buf, binary.BigEndian, isFinalBlock)
- binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
- binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
- binary.Write(&buf, binary.LittleEndian, block)
- }
- binary.Write(&buf, binary.BigEndian, adler32(data))
-
- return buf.Bytes()
-}
-
-func writeChunkIend(w io.Writer) {
- writeChunk(w, "IEND", nil)
-}
-
-func writeChunk(w io.Writer, chunkType string, data []byte) {
- typeAndData := make([]byte, 0, len(chunkType)+len(data))
- typeAndData = append(typeAndData, []byte(chunkType)...)
- typeAndData = append(typeAndData, data...)
-
- binary.Write(w, binary.BigEndian, uint32(len(data)))
- binary.Write(w, binary.BigEndian, typeAndData)
- binary.Write(w, binary.BigEndian, crc(typeAndData))
-}
-
-var (
- crcTable [256]uint32
- crcTableComputed bool
-)
-
-func makeCrcTable() {
- for n := 0; n < 256; n++ {
- c := uint32(n)
- for k := 0; k < 8; k++ {
- if (c & 1) != 0 {
- c = 0xEDB88320 ^ (c >> 1)
- } else {
- c = c >> 1
- }
- }
- crcTable[n] = c
- }
- crcTableComputed = true
-}
-
-func updateCrc(crc uint32, buf []byte) uint32 {
- if !crcTableComputed {
- makeCrcTable()
- }
-
- c := crc
- for n := 0; n < len(buf); n++ {
- c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
- }
- return c
-}
-
-func crc(buf []byte) uint32 {
- return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
-}
-
-const adler32Base = 65521
-
-func updateAdler32(adler uint32, buf []byte) uint32 {
- s1 := adler & 0xFFFF
- s2 := (adler >> 16) & 0xFFFF
-
- for n := 0; n < len(buf); n++ {
- s1 = (s1 + uint32(buf[n])) % adler32Base
- s2 = (s2 + s1) % adler32Base
- }
- return (s2 << 16) + s1
-}
-
-func adler32(buf []byte) uint32 {
- return updateAdler32(1, buf)
-}
+ package main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "image"
+ _ "image/png"
+ "io"
+ "os"
+)
+
+func main() {
+ inFile, err := os.Open("input.png")
+ if err != nil {
+ panic(err)
+ }
+ defer inFile.Close()
+
+ img, _, err := image.Decode(inFile)
+ if err != nil {
+ panic(err)
+ }
+
+ outFile, err := os.Create("output.png")
+ if err != nil {
+ panic(err)
+ }
+ defer outFile.Close()
+
+ writePng(outFile, img)
+}
+
+func writePng(w io.Writer, img image.Image) {
+ width := uint32(img.Bounds().Dx())
+ height := uint32(img.Bounds().Dy())
+ writeSignature(w)
+ writeChunkIhdr(w, width, height)
+ writeChunkIdat(w, width, height, img)
+ writeChunkIend(w)
+}
+
+func writeSignature(w io.Writer) {
+ sig := [8]uint8{
+ 0x89,
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // CR
+ 0x0A, // LF
+ 0x1A, // EOF (^Z)
+ 0x0A, // LF
+ }
+ binary.Write(w, binary.BigEndian, sig)
+}
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.BigEndian, width)
+ binary.Write(&buf, binary.BigEndian, height)
+ binary.Write(&buf, binary.BigEndian, uint8(8))
+ binary.Write(&buf, binary.BigEndian, uint8(2))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+
+ writeChunk(w, "IHDR", buf.Bytes())
+}
+
+func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+ var pixels bytes.Buffer
+ for y := uint32(0); y < height; y++ {
+ binary.Write(&pixels, binary.BigEndian, uint8(0))
+ for x := uint32(0); x < width; x++ {
+ r, g, b, _ := img.At(int(x), int(y)).RGBA()
+ binary.Write(&pixels, binary.BigEndian, uint8(r))
+ binary.Write(&pixels, binary.BigEndian, uint8(g))
+ binary.Write(&pixels, binary.BigEndian, uint8(b))
+ }
+ }
+
+ writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+}
+
+func encodeZlib(data []byte) []byte {
+ var buf bytes.Buffer
+
+ binary.Write(&buf, binary.BigEndian, uint8(0x78))
+ binary.Write(&buf, binary.BigEndian, uint8(0x01))
+ blockSize := 65535
+ isFinalBlock := false
+ for i := 0; !isFinalBlock; i++ {
+ var block []byte
+ if len(data) <= (i+1)*blockSize {
+ block = data[i*blockSize:]
+ isFinalBlock = true
+ } else {
+ block = data[i*blockSize : (i+1)*blockSize]
+ }
+ binary.Write(&buf, binary.BigEndian, isFinalBlock)
+ binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+ binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+ binary.Write(&buf, binary.LittleEndian, block)
+ }
+ binary.Write(&buf, binary.BigEndian, adler32(data))
+
+ return buf.Bytes()
+}
+
+func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+}
+
+func writeChunk(w io.Writer, chunkType string, data []byte) {
+ typeAndData := make([]byte, 0, len(chunkType)+len(data))
+ typeAndData = append(typeAndData, []byte(chunkType)...)
+ typeAndData = append(typeAndData, data...)
+
+ binary.Write(w, binary.BigEndian, uint32(len(data)))
+ binary.Write(w, binary.BigEndian, typeAndData)
+ binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+
+var (
+ crcTable [256]uint32
+ crcTableComputed bool
+)
+
+func makeCrcTable() {
+ for n := 0; n < 256; n++ {
+ c := uint32(n)
+ for k := 0; k < 8; k++ {
+ if (c & 1) != 0 {
+ c = 0xEDB88320 ^ (c >> 1)
+ } else {
+ c = c >> 1
+ }
+ }
+ crcTable[n] = c
+ }
+ crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+ if !crcTableComputed {
+ makeCrcTable()
+ }
+
+ c := crc
+ for n := 0; n < len(buf); n++ {
+ c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+ }
+ return c
+}
+
+func crc(buf []byte) uint32 {
+ return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+
+const adler32Base = 65521
+
+func updateAdler32(adler uint32, buf []byte) uint32 {
+ s1 := adler & 0xFFFF
+ s2 := (adler >> 16) & 0xFFFF
+
+ for n := 0; n < len(buf); n++ {
+ s1 = (s1 + uint32(buf[n])) % adler32Base
+ s2 = (s2 + s1) % adler32Base
+ }
+ return (s2 << 16) + s1
+}
+
+func adler32(buf []byte) uint32 {
+ return updateAdler32(1, buf)
+}
+ index.mjs の名前で保存しておくこと。
- import { readFile } from 'node:fs/promises';
-import PHPWasm from './php-wasm.mjs'
-
-const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
-
-const { ccall } = await PHPWasm();
-const result = ccall(
- 'php_wasm_run',
- 'number', ['string'],
- [code],
-);
-console.log(`exit code: ${result}`);
+ import { readFile } from 'node:fs/promises';
+import PHPWasm from './php-wasm.mjs'
+
+const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
+
+const { ccall } = await PHPWasm();
+const result = ccall(
+ 'php_wasm_run',
+ 'number', ['string'],
+ [code],
+);
+console.log(`exit code: ${result}`);
+
標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような php-wasm.mjs (とそこから呼び出される php-wasm.wasm) を作成する。
@@ -137,30 +138,32 @@
先ほどのコードでも使っていたエントリポイントである php_wasm_run を用意する。
#include <stdio.h>
-#include <emscripten.h>
-#include <Zend/zend_execute.h>
-#include <sapi/embed/php_embed.h>
-
-int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
- zend_result result;
-
- int argc = 1;
- char* argv[] = { "php.wasm", NULL };
-
- PHP_EMBED_START_BLOCK(argc, argv);
-
- result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
-
- PHP_EMBED_END_BLOCK();
-
- fprintf(stdout, "\n");
- fflush(stdout);
- fprintf(stderr, "\n");
- fflush(stderr);
-
- return result == SUCCESS ? 0 : 1;
-}
+ #include <stdio.h>
+#include <emscripten.h>
+#include <Zend/zend_execute.h>
+#include <sapi/embed/php_embed.h>
+
+int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
+ zend_result result;
+
+ int argc = 1;
+ char* argv[] = { "php.wasm", NULL };
+
+ PHP_EMBED_START_BLOCK(argc, argv);
+
+ result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
+
+ PHP_EMBED_END_BLOCK();
+
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+
+ return result == SUCCESS ? 0 : 1;
+}
+ ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。 @@ -185,49 +188,55 @@ まずは Emscripten 公式が提供している Docker イメージを使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
-FROM emscripten/emsdk:3.1.46 AS wasm-builder
+ FROM emscripten/emsdk:3.1.46 AS wasm-builder
+ 次に、php/php-src から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
-RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
-
-RUN apt-get update && \
- apt-get install -y --no-install-recommends \
- autoconf \
- bison \
- pkg-config \
- re2c \
- && \
- :
+ RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
+
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ autoconf \
+ bison \
+ pkg-config \
+ re2c \
+ && \
+ :
+ 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
-RUN cd php-src && \
- ./buildconf --force && \
- emconfigure ./configure \
- --disable-all \
- --disable-mbregex \
- --disable-fiber-asm \
- --disable-cli \
- --disable-cgi \
- --disable-phpdbg \
- --enable-embed=static \
- --enable-mbstring \
- --without-iconv \
- --without-libxml \
- --without-pcre-jit \
- --without-pdo-sqlite \
- --without-sqlite3 \
- && \
- EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
- mv libs/libphp.a .. && \
- make clean && \
- git clean -fd && \
- :
+ RUN cd php-src && \
+ ./buildconf --force && \
+ emconfigure ./configure \
+ --disable-all \
+ --disable-mbregex \
+ --disable-fiber-asm \
+ --disable-cli \
+ --disable-cgi \
+ --disable-phpdbg \
+ --enable-embed=static \
+ --enable-mbstring \
+ --without-iconv \
+ --without-libxml \
+ --without-pcre-jit \
+ --without-pdo-sqlite \
+ --without-sqlite3 \
+ && \
+ EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
+ mv libs/libphp.a .. && \
+ make clean && \
+ git clean -fd && \
+ :
+
ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
@@ -257,22 +266,24 @@
さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。Dockerfile と同じ場所に php-wasm.c という名前で保存し、次のようにする。
COPY php-wasm.c /src/
-
-RUN cd php-src && \
- emcc \
- -c \
- -o php-wasm.o \
- -I . \
- -I TSRM \
- -I Zend \
- -I main \
- ../php-wasm.c \
- && \
- mv php-wasm.o .. && \
- make clean && \
- git clean -fd && \
- :
+ COPY php-wasm.c /src/
+
+RUN cd php-src && \
+ emcc \
+ -c \
+ -o php-wasm.o \
+ -I . \
+ -I TSRM \
+ -I Zend \
+ -I main \
+ ../php-wasm.c \
+ && \
+ mv php-wasm.o .. && \
+ make clean && \
+ git clean -fd && \
+ :
+
emcc は cc (C コンパイラ/リンカ) の Emscripten 版で、-c は「コンパイル」の意。-o や -I は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
@@ -282,18 +293,20 @@
libphp.a と php-wasm.o が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。これにも emcc コマンドを使う。
RUN emcc \
- -s ENVIRONMENT=node \
- -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
- -s EXPORTED_RUNTIME_METHODS='["ccall"]' \
- -s EXPORT_ES6=1 \
- -s INITIAL_MEMORY=16777216 \
- -s INVOKE_RUN=0 \
- -s MODULARIZE=1 \
- -o php-wasm.js \
- php-wasm.o \
- libphp.a \
- ;
+ RUN emcc \
+ -s ENVIRONMENT=node \
+ -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
+ -s EXPORTED_RUNTIME_METHODS='["ccall"]' \
+ -s EXPORT_ES6=1 \
+ -s INITIAL_MEMORY=16777216 \
+ -s INVOKE_RUN=0 \
+ -s MODULARIZE=1 \
+ -o php-wasm.js \
+ php-wasm.o \
+ libphp.a \
+ ;
+
それぞれのフラグについて解説する。
@@ -335,14 +348,16 @@
といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。先ほど掲載した JavaScript のコードは、Dockerfile と同じディレクトリに index.mjs で配置すること。
FROM node:20.7
-
-WORKDIR /app
-COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
-COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
-COPY index.mjs /app/
-
-ENTRYPOINT ["node", "index.mjs"]
+ FROM node:20.7
+
+WORKDIR /app
+COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
+COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
+COPY index.mjs /app/
+
+ENTRYPOINT ["node", "index.mjs"]
+ Dockerfile、php-wasm.c、index.mjs を用意したら、Docker コンテナをビルドして実行する。
- $ docker build -t php-wasm .
-$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
-Hello, World!
-
-
-exit code: 0
+ $ docker build -t php-wasm .
+$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
+Hello, World!
+
+
+exit code: 0
+ namespace 宣言を挿入したい。具体的には、トップレベルの名前空間が MyNamespace であり、ファイル src/Foo/Bar/Baz.php を開いたときに、そのファイルが空であるなら、次のようなテンプレートが自動的に挿入されてほしい。
- <?php
-
-namespace MyNamespace\Foo\Bar;
+ <?php
+
+namespace MyNamespace\Foo\Bar;
+ $ nvim --version
-NVIM v0.9.2
-Build type: Release
-LuaJIT 2.1.1693350652
+ $ nvim --version
+NVIM v0.9.2
+Build type: Release
+LuaJIT 2.1.1693350652
+
今回は Lua で処理を記述したため、Vim では動作しない。以下の説明でも Neovim に絞って述べる。また、パス区切りがスラッシュである前提で記述したため、Windows には対応していない。
@@ -114,13 +117,15 @@ LuaJIT 2.1.1693350652
ファイルタイプは読み込んだあとに変更されることもあるので、ftplugin は複数回実行されうる。二重読み込みを防ぐために、did_ftplugin_<FILE_TYPE>_after というバッファローカル変数を定義しておくのが慣習となっている。
if vim.b.did_ftplugin_php_after then
- return
-end
-
--- ここに実際の処理を書く
-
-vim.b.did_ftplugin_php_after = true
+ if vim.b.did_ftplugin_php_after then
+ return
+end
+
+-- ここに実際の処理を書く
+
+vim.b.did_ftplugin_php_after = true
+ if vim.b.did_ftplugin_php_after then
- return
-end
-
--- base_dir を起点としてディレクトリを上向きに辿っていき、composer.json を探す
--- :help vim.fs.find()
-local function find_composer_json(base_dir)
- return vim.fs.find('composer.json', {
- path = base_dir,
- upward = true,
- -- ホームディレクトリまで到達したら探索を打ち切る
- stop = vim.loop.os_homedir(),
- type = 'file',
- })[1]
-end
-
--- JSON ファイルを読み込み、デコードして返す
--- :help readblob()
--- :help vim.json.decode
--- :help luaref-pcall()
-local function load_json(file_path)
- -- readblob() は Vim script では Blob オブジェクトを返すが、Lua から呼ぶと string に変換される
- local ok_read, content = pcall(vim.fn.readblob, file_path)
- if not ok_read then
- return nil
- end
- local ok_decode, obj = pcall(vim.json.decode, content)
- if not ok_decode then
- return nil
- end
- return obj
-end
-
--- 対象ファイルの置かれたディレクトリを基に namespace 宣言を生成する
--- :help nvim_buf_get_name()
--- :help vim.fs.dirname()
-local function generate_namespace_declaration()
- -- composer.json を探し、トップレベルの名前空間とディレクトリを特定する
- local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0))
- local path_to_composer_json = find_composer_json(current_dir)
- if not path_to_composer_json then
- return nil -- failed to locate composer.json
- end
- local composer_json = load_json(path_to_composer_json)
- if not composer_json then
- return nil -- failed to load composer.json
- end
- -- autoload.psr-4 を探し、型が期待される型と一致するかどうか調べる
- local psr4 = vim.tbl_get(composer_json, 'autoload', 'psr-4')
- if not psr4 then
- return nil -- autoload.psr-4 section is absent
- end
- if vim.tbl_count(psr4) ~= 1 then
- return nil -- psr-4 section is ambiguous
- end
- local psr4_namespace, psr4_dir
- for k, v in pairs(psr4) do
- psr4_namespace = k
- psr4_dir = v
- end
- if type(psr4_dir) == 'table' then
- if #psr4_dir == 1 then
- psr4_dir = psr4_dir[1]
- else
- return nil -- psr-4 section is ambiguous
- end
- end
- if type(psr4_namespace) ~= 'string' or type(psr4_dir) ~= 'string' then
- return nil -- psr-4 section is invalid
- end
- -- 末尾のスラッシュとバックスラッシュを取り除いておく
- if psr4_namespace:sub(-1, -1) == '\\' then
- psr4_namespace = psr4_namespace:sub(0, -2)
- end
- if psr4_dir:sub(-1, -1) == '/' then
- psr4_dir = psr4_dir:sub(0, -2)
- end
-
- -- 対象ファイルが置かれたディレクトリとトップレベルのディレクトリを比較し、その差分を名前空間とする
- local namespace_root_dir = vim.fs.dirname(path_to_composer_json) .. '/' .. psr4_dir
- if not vim.startswith(current_dir, namespace_root_dir) then
- return nil
- end
- local current_path_suffix = current_dir:sub(#namespace_root_dir + 1)
- local namespace = psr4_namespace .. current_path_suffix:gsub('/', '\\')
- return ("namespace %s;"):format(namespace)
-end
-
-local function generate_template()
- local lines = {
- '<?php',
- '',
- 'declare(strict_types=1);',
- '',
- }
- local namespace_decl = generate_namespace_declaration()
- if namespace_decl then
- lines[#lines + 1] = namespace_decl
- lines[#lines + 1] = ''
- end
- lines[#lines + 1] = ''
- return lines
-end
-
-if vim.fn.line('$') == 1 and vim.fn.getline(1) == '' then
- -- 対象ファイルが空なら、テンプレートを挿入してカーソルを末尾に移動させる
- -- :help setline()
- -- :help cursor()
- vim.fn.setline(1, generate_template())
- vim.fn.cursor('$', 0)
-end
-
-vim.b.did_ftplugin_php_after = true
+ if vim.b.did_ftplugin_php_after then
+ return
+end
+
+-- base_dir を起点としてディレクトリを上向きに辿っていき、composer.json を探す
+-- :help vim.fs.find()
+local function find_composer_json(base_dir)
+ return vim.fs.find('composer.json', {
+ path = base_dir,
+ upward = true,
+ -- ホームディレクトリまで到達したら探索を打ち切る
+ stop = vim.loop.os_homedir(),
+ type = 'file',
+ })[1]
+end
+
+-- JSON ファイルを読み込み、デコードして返す
+-- :help readblob()
+-- :help vim.json.decode
+-- :help luaref-pcall()
+local function load_json(file_path)
+ -- readblob() は Vim script では Blob オブジェクトを返すが、Lua から呼ぶと string に変換される
+ local ok_read, content = pcall(vim.fn.readblob, file_path)
+ if not ok_read then
+ return nil
+ end
+ local ok_decode, obj = pcall(vim.json.decode, content)
+ if not ok_decode then
+ return nil
+ end
+ return obj
+end
+
+-- 対象ファイルの置かれたディレクトリを基に namespace 宣言を生成する
+-- :help nvim_buf_get_name()
+-- :help vim.fs.dirname()
+local function generate_namespace_declaration()
+ -- composer.json を探し、トップレベルの名前空間とディレクトリを特定する
+ local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0))
+ local path_to_composer_json = find_composer_json(current_dir)
+ if not path_to_composer_json then
+ return nil -- failed to locate composer.json
+ end
+ local composer_json = load_json(path_to_composer_json)
+ if not composer_json then
+ return nil -- failed to load composer.json
+ end
+ -- autoload.psr-4 を探し、型が期待される型と一致するかどうか調べる
+ local psr4 = vim.tbl_get(composer_json, 'autoload', 'psr-4')
+ if not psr4 then
+ return nil -- autoload.psr-4 section is absent
+ end
+ if vim.tbl_count(psr4) ~= 1 then
+ return nil -- psr-4 section is ambiguous
+ end
+ local psr4_namespace, psr4_dir
+ for k, v in pairs(psr4) do
+ psr4_namespace = k
+ psr4_dir = v
+ end
+ if type(psr4_dir) == 'table' then
+ if #psr4_dir == 1 then
+ psr4_dir = psr4_dir[1]
+ else
+ return nil -- psr-4 section is ambiguous
+ end
+ end
+ if type(psr4_namespace) ~= 'string' or type(psr4_dir) ~= 'string' then
+ return nil -- psr-4 section is invalid
+ end
+ -- 末尾のスラッシュとバックスラッシュを取り除いておく
+ if psr4_namespace:sub(-1, -1) == '\\' then
+ psr4_namespace = psr4_namespace:sub(0, -2)
+ end
+ if psr4_dir:sub(-1, -1) == '/' then
+ psr4_dir = psr4_dir:sub(0, -2)
+ end
+
+ -- 対象ファイルが置かれたディレクトリとトップレベルのディレクトリを比較し、その差分を名前空間とする
+ local namespace_root_dir = vim.fs.dirname(path_to_composer_json) .. '/' .. psr4_dir
+ if not vim.startswith(current_dir, namespace_root_dir) then
+ return nil
+ end
+ local current_path_suffix = current_dir:sub(#namespace_root_dir + 1)
+ local namespace = psr4_namespace .. current_path_suffix:gsub('/', '\\')
+ return ("namespace %s;"):format(namespace)
+end
+
+local function generate_template()
+ local lines = {
+ '<?php',
+ '',
+ 'declare(strict_types=1);',
+ '',
+ }
+ local namespace_decl = generate_namespace_declaration()
+ if namespace_decl then
+ lines[#lines + 1] = namespace_decl
+ lines[#lines + 1] = ''
+ end
+ lines[#lines + 1] = ''
+ return lines
+end
+
+if vim.fn.line('$') == 1 and vim.fn.getline(1) == '' then
+ -- 対象ファイルが空なら、テンプレートを挿入してカーソルを末尾に移動させる
+ -- :help setline()
+ -- :help cursor()
+ vim.fn.setline(1, generate_template())
+ vim.fn.cursor('$', 0)
+end
+
+vim.b.did_ftplugin_php_after = true
+ $ sudo apt install wireguard
+ $ sudo apt install wireguard
+ 次に、WireGuard で使用する鍵を生成する。
-$ wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pub
-$ sudo chmod 600 /etc/wireguard/server.{key,pub}
+ $ wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pub
+$ sudo chmod 600 /etc/wireguard/server.{key,pub}
+ # クライアント 1 の場合
-[Interface]
-Address = 10.10.1.2/32
-PrivateKey = <クライアント 1 の秘密鍵>
-
-[Peer]
-PublicKey = <サーバの公開鍵>
-AllowedIPs = <サーバの外部 IP アドレス>/32
-Endpoint = <サーバの外部 IP アドレス>:51820
-
- # クライアント 2 の場合
-[Interface]
-Address = 10.10.1.3/32
-PrivateKey = <クライアント 2 の秘密鍵>
-
-[Peer]
-PublicKey = <サーバの公開鍵>
-AllowedIPs = <サーバの外部 IP アドレス>/32
-Endpoint = <サーバの外部 IP アドレス>:51820
+ # クライアント 1 の場合
+[Interface]
+Address = 10.10.1.2/32
+PrivateKey = <クライアント 1 の秘密鍵>
+
+[Peer]
+PublicKey = <サーバの公開鍵>
+AllowedIPs = <サーバの外部 IP アドレス>/32
+Endpoint = <サーバの外部 IP アドレス>:51820
+ # クライアント 2 の場合
+[Interface]
+Address = 10.10.1.3/32
+PrivateKey = <クライアント 2 の秘密鍵>
+
+[Peer]
+PublicKey = <サーバの公開鍵>
+AllowedIPs = <サーバの外部 IP アドレス>/32
+Endpoint = <サーバの外部 IP アドレス>:51820
+
PrivateKey や PublicKey は鍵ファイルのパスではなく中身を書くことに注意。
@@ -146,28 +153,34 @@ $ sudo chmod 600 /etc/wireguard/server.{key,pub}
一度サーバへ戻り、WireGuard の設定ファイルを書く。
$ sudo vim /etc/wireguard/wg0.conf
-
- [Interface]
-Address = 10.10.1.1/32
-SaveConfig = true
-PrivateKey = <サーバの秘密鍵>
-ListenPort = 51820
-
-[Peer]
-PublicKey = <クライアント 1 の公開鍵>
-AllowedIPs = 10.10.1.2/32
-
-[Peer]
-PublicKey = <クライアント 2 の公開鍵>
-AllowedIPs = 10.10.1.3/32
+ $ sudo vim /etc/wireguard/wg0.conf
+ [Interface]
+Address = 10.10.1.1/32
+SaveConfig = true
+PrivateKey = <サーバの秘密鍵>
+ListenPort = 51820
+
+[Peer]
+PublicKey = <クライアント 1 の公開鍵>
+AllowedIPs = 10.10.1.2/32
+
+[Peer]
+PublicKey = <クライアント 2 の公開鍵>
+AllowedIPs = 10.10.1.3/32
+ 次に、WireGuard のサービスを起動する。
-$ sudo systemctl enable wg-quick@wg0
-$ sudo systemctl start wg-quick@wg0
+ $ sudo systemctl enable wg-quick@wg0
+$ sudo systemctl start wg-quick@wg0
+ wg0 を通る通信を許可する。
- $ sudo ufw allow 51820/udp
-$ sudo ufw allow in on wg0
-$ sudo ufw allow out on wg0
+ $ sudo ufw allow 51820/udp
+$ sudo ufw allow in on wg0
+$ sudo ufw allow out on wg0
+
次に、80 や 443 などの必要なポートについて、wg0 を経由してのアクセスのみ許可する。
$ sudo ufw allow in on wg0 to any port 80 proto tcp
-$ sudo ufw allow in on wg0 to any port 443 proto tcp
+ $ sudo ufw allow in on wg0 to any port 80 proto tcp
+$ sudo ufw allow in on wg0 to any port 443 proto tcp
+
最後に、ufw を有効にする。
$ sudo ufw status
-$ sudo ufw enable
+ $ sudo ufw status
+$ sudo ufw enable
+ hello-world:
- stage: test
- image: alpine:latest
- script:
- - 'echo "Hello, World!"'
- rules:
- - if: '$CI_MERGE_REQUEST_IID'
- when: always
+ hello-world:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'echo "Hello, World!"'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+
ここで、script に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
@@ -106,14 +107,16 @@
では、次のようなケースだとどうなるか。
hello-world:
- stage: test
- image: alpine:latest
- script:
- - 'exit 1 | exit 0'
- rules:
- - if: '$CI_MERGE_REQUEST_IID'
- when: always
+ hello-world:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'exit 1 | exit 0'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+
失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
@@ -126,10 +129,12 @@
前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。
# On にする
-set -o pipefail
-# Off にする
-set +o pipefail
+ # On にする
+set -o pipefail
+# Off にする
+set +o pipefail
+ こうすると、パイプ全体が失敗するようになる。この設定は、デフォルトだと off になっている。 @@ -143,14 +148,16 @@ 次のような GitLab CI/CD ジョブが失敗してしまった。
-hoge:
- stage: test
- image: alpine:latest
- script:
- - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
- rules:
- - if: '$CI_MERGE_REQUEST_IID'
- when: always
+ hoge:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+
grep コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、pipefail が on になっていると、このジョブは失敗する。現在の pipefail がどうなっているか確かめるため set +o で全オプションを出力させたところ、pipefail が on になっていた。
@@ -160,20 +167,22 @@
しかし、先述したように Bash における pipefail のデフォルト値は off のはずだ。実際に、ローカルで alpine:latest を動かしてみたところ、
$ docker run --rm alpine:latest sh -c "set +o"
-set +o errexit
-set +o noglob
-set +o ignoreeof
-set +o monitor
-set +o noexec
-set +o xtrace
-set +o verbose
-set +o noclobber
-set +o allexport
-set +o notify
-set +o nounset
-set +o vi
-set +o pipefail
+ $ docker run --rm alpine:latest sh -c "set +o"
+set +o errexit
+set +o noglob
+set +o ignoreeof
+set +o monitor
+set +o noexec
+set +o xtrace
+set +o verbose
+set +o noclobber
+set +o allexport
+set +o notify
+set +o nounset
+set +o vi
+set +o pipefail
+
確かに pipefail は無効になっている。
@@ -190,9 +199,11 @@ set +o pipefail
.gitlab-ci.yml で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。そう仮説を立てて GitLab Runner のリポジトリ を調査したところ、ソースコード中の以下の箇所 で set -o pipefail していることが判明した (コメントは筆者による)。
// pipefail オプションが存在しない環境にも対応するため、
-// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
-buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
+ // pipefail オプションが存在しない環境にも対応するため、
+// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
+buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
+ pipefail が on になっていては困る場所だけ off にしてやればよい。
- hoge:
- stage: test
- image: alpine:latest
- script:
-+ - 'set +o pipefail'
- - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
-+ - 'set -o pipefail' # この例の場合、ここで終わりなので戻さなくてもよい
- rules:
- - if: '$CI_MERGE_REQUEST_IID'
- when: always
+ hoge:
+ stage: test
+ image: alpine:latest
+ script:
++ - 'set +o pipefail'
+ - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
++ - 'set -o pipefail' # この例の場合、ここで終わりなので戻さなくてもよい
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+ _composer 関数を定義しているファイルの冒頭にも書かれている。
- # - @todo We don't complete custom commands (including script aliases). This is
-# easy to do in the general case, but it probably requires some clever caching
-# to avoid introducing a noticeable lag to every completion operation, due to
-# the way command resolution works and the fact that discovering custom
-# commands requires making slow calls to Composer
+ # - @todo We don't complete custom commands (including script aliases). This is
+# easy to do in the general case, but it probably requires some clever caching
+# to avoid introducing a noticeable lag to every completion operation, due to
+# the way command resolution works and the fact that discovering custom
+# commands requires making slow calls to Composer
+ ~/.zshrc にすべて書く前提だが、autoload を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
- compdef _my_composer composer composer.phar
+ compdef _my_composer composer composer.phar
+
compdef は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。この場合は、composer コマンドや composer.phar コマンドに対して _my_composer を使って補完をおこなうよう定義している。
@@ -132,9 +135,11 @@
次に _my_composer を定義する。基本的にはデフォルトの composer コマンドの補完関数 (つまり _composer 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。
function _my_composer() {
- _composer "$@" || _files "$@"
-}
+ function _my_composer() {
+ _composer "$@" || _files "$@"
+}
+
_composer コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら _files を呼び出す。_files は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
diff --git a/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html b/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html
index e3f53399..1e9634dd 100644
--- a/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html
@@ -14,8 +14,7 @@
$ echo '[ 1 2 ]' | reparojson
-[ 1, 2 ]
-
-$ echo '[ 1, 2, ]' | reparojson
-[ 1, 2 ]
-
-$ echo '{ "foo": 1 "bar": 2 }' | reparojson
-{ "foo": 1, "bar": 2 }
-
-$ echo '{ "foo": 1, "bar": 2, }' | reparojson
-{ "foo": 1, "bar": 2 }
+ $ echo '[ 1 2 ]' | reparojson
+[ 1, 2 ]
+
+$ echo '[ 1, 2, ]' | reparojson
+[ 1, 2 ]
+
+$ echo '{ "foo": 1 "bar": 2 }' | reparojson
+{ "foo": 1, "bar": 2 }
+
+$ echo '{ "foo": 1, "bar": 2, }' | reparojson
+{ "foo": 1, "bar": 2 }
+ バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり: @@ -140,33 +141,35 @@ $ echo '{ "foo": 1, "bar": 2, }' | reparojson ここでは、nvim-lspconfig と efm-langserver を用いた設定例を紹介する。
-local lspconfig = require('lspconfig')
-
-lspconfig.efm.setup({
- init_options = { documentFormatting = true },
- settings = {
- rootMarkers = {".git/"},
- languages = {
- json = {
- {
- formatCommand = "reparojson -q",
- formatStdin = true,
- },
- },
- },
- }
-})
-
-vim.api.nvim_create_autocmd('LspAttach', {
- callback = function(e)
- vim.api.nvim_create_autocmd('BufWritePre', {
- buffer = e.buf,
- callback = function()
- vim.lsp.buf.format({ async = false })
- end
- })
- end,
-})
+ local lspconfig = require('lspconfig')
+
+lspconfig.efm.setup({
+ init_options = { documentFormatting = true },
+ settings = {
+ rootMarkers = {".git/"},
+ languages = {
+ json = {
+ {
+ formatCommand = "reparojson -q",
+ formatStdin = true,
+ },
+ },
+ },
+ }
+})
+
+vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(e)
+ vim.api.nvim_create_autocmd('BufWritePre', {
+ buffer = e.buf,
+ callback = function()
+ vim.lsp.buf.format({ async = false })
+ end
+ })
+ end,
+})
+
ほとんどは nvim-lspconfig と efm-langserver を使う際のボイラープレートだが、
2行目と3行目を入れ換えて以下のように編集した。
これは不正な JSON だが、このツールを通せば次のようになる。
もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。しかし、単なる行の入れ換えであれば formatCommand で -q フラグを指定していることに注意してほしい。このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。そのため、-q フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
@@ -179,28 +182,34 @@ vim.api.nvim_create_autocmd('LspAttach'
-
+ {
- "a": true,
- "b": false
-}
+ {
+ "a": true,
+ "b": false
+}
+ {
- "b": false
- "a": true,
-}
+ {
+ "b": false
+ "a": true,
+}
+ {
- "b": false,
- "a": true
-}
+ {
+ "b": false,
+ "a": true
+}ddp の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。
diff --git a/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html b/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
index 2d2c3761..aa6d2afb 100644
--- a/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
+++ b/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
@@ -14,8 +14,7 @@
text/template がある。この text/template における制御構造、with と range は次のように使われる。
# {{ .Title }}
-
-# User
-
-{{ with .User }}
- {{ .Name }} ({{ .ID }})
-{{ end }}
-
-# Items
-
-{{ range .Items }}
- - {{ . }}
-{{ end }}
+ # {{ .Title }}
+
+# User
+
+{{ with .User }}
+ {{ .Name }} ({{ .ID }})
+{{ end }}
+
+# Items
+
+{{ range .Items }}
+ - {{ . }}
+{{ end }}
+
text/template の . は、現在の操作対象を表す特殊なオブジェクトである。
@@ -97,18 +98,20 @@
つまりこのテンプレートは、次のような構造をレンダリングしている (Execute() の第2引数)。
tmpl.Execute(out, Params{
- Title: "foo",
- User: User{
- ID: 123,
- Name: "john",
- },
- Items: []string{
- "hoge",
- "piyo",
- "fuga",
- },
-})
+ tmpl.Execute(out, Params{
+ Title: "foo",
+ User: User{
+ ID: 123,
+ Name: "john",
+ },
+ Items: []string{
+ "hoge",
+ "piyo",
+ "fuga",
+ },
+})
+ with や range の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
- {{ with .User }}
- ここから .Title を参照するには?
-{{ end }}
-
-{{ range .Items }}
- ここから .User を参照するには?
-{{ end }}
+ {{ with .User }}
+ ここから .Title を参照するには?
+{{ end }}
+
+{{ range .Items }}
+ ここから .User を参照するには?
+{{ end }}
+
with や range は、. を自身の対象オブジェクトに変更するので、単に {{ with .User }} の中で .Title と書いても、それは User の Title プロパティを参照しているとみなされる。
@@ -133,7 +138,9 @@
text/template では変数が使えるので、テンプレートの先頭で
{{ $params := . }}
+ {{ $params := . }}
+
とでもしておけば実現は可能である。
@@ -150,13 +157,15 @@
常にトップレベルを指す特殊変数 $ を使えばよい。
{{ with .User }}
- {{ $.Title }}
-{{ end }}
-
-{{ range .Items }}
- {{ $.User.Name }}
-{{ end }}
+ {{ with .User }}
+ {{ $.Title }}
+{{ end }}
+
+{{ range .Items }}
+ {{ $.User.Name }}
+{{ end }}
+
$ は、テンプレートが実行されるときに渡されたオブジェクトを指す。これを使えば現在の . に関係なくトップレベルを参照できる。
diff --git a/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html b/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html
index 640f0af8..bb998a4b 100644
--- a/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html
+++ b/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html
@@ -14,8 +14,7 @@
<?php
-$s=<<<'Q'
-<?php
-%
-$s=<<<'Q'
-@$c=[`];
-$m="";for($k=0;$k<min(13,intdiv(__LINE__-119,80)+1);$k++){$C=str_replace("\n","",
-$c[$k]);$f=!0;foreach(str_split(base64_decode($C))as$l){$L=ord($l);$m.=str_repeat
-($f?"#":chr(32),$L&127);$f=!$f;if($L&128){$m.="\n";$f=!0;continue;}}}print(
-str_replace([chr(96),chr(37),chr(64)],[implode("\n",array_map(fn($C)=>"'".trim(
-chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
-Q;
-$c=['0AFOgQFOgQFOgQFOgQFOgQFEAgiBAUIECIEBQwQHgQE8AQYFBoEBOgQGBAaBAToEBwQFgQE6BQYFBIEB
-OwQHBASBATwEBgUDgQE8BQYEA4EBPQQGBAOBAT0FBgEFgQERBhsIBAQMgQERKQQFC4EBESkFAQ6BAREp
-FIEBESkUgQERKRSBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
-AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ4EPIEBDQY7gQENBjuBAQ0GO4EBDQU8gQENBTyBAQwG
-PIEBDAY8gQEMBjyBAQwGPIEBDAY8gQEMBjyBAQwHO4EBDAc7gQENBzqBAQ0IOYEBDgg4gQEOCiUCD4EB
-DwwdBw+BARAQDhEPgQERLg+BARItD4EBFCsPgQEXJRKBARsbGIEBToEBToEBToEBToEBToHQ',
-'0AFOgQFOgQFOgQEPASMFFoEBDwMhBRaBAQ4FIAUWgQEOBSAFFoEBDQUhBRaBAQ0FIQUWgQEMBSIFFoEB
-DAUiBRaBAQsFIwUWgQELBQgBGgUWgQEKBQkDGAUWgQEKBQgGBCoDgQEJBQkFBSoDgQEFAQMECQYFKgOB
-AQQDAQUJBQYqA4EBBAgJBQcqA4EBAwkJBRkFFoEBBAcJBRoFFoEBBQYIBRsFFoEBBgYHBRsFFoEBBwYF
-BRwFFoEBCAYDBR0FFoEBCQYCBR0FFoEBCgseBRaBAQsJHwUWgQEMByAFFoEBDAcFAxgFFoEBDQUFBBgF
-FoEBDQQGBRYGFoEBDAUHBAcmBYEBCwUIBQYmBYEBCwQKBAYmBYEBCgQLBQUmBYEBCQUMBS+BAQgFDAYv
-gQEDHC+BAQMdLoEBAx0ugQEDHi2BAQMJBAUIBC2BARAFCAQtgQEQBQgELYEBEAUJAS+BARAFESEHgQEQ
-BREhB4EBBwEIBQYBCiEHgQEHBAUFBAQJIQeBAQcEBQUEBAkEGAUHgQEGBQUFBAUIBBgFB4EBBgUFBQUE
-CAQYBQeBAQYFBQUFBAgEGAUHgQEGBAYFBQUHBBgFB4EBBgQGBQYEBwQYBQeBAQUFBgUGBQYEGAUHgQEF
-BQYFBwQGBBgFB4EBBQQHBQcEBgQYBQeBAQUEBwUHBQUEGAUHgQEEBQcFBwUFBBgFB4EBBAUHBQgEBQQY
-BQeBAQQECAUIBAUEGAUHgQEDBQgFCAEIBBgFB4EBAwUIBREEGAUHgQECBQkFEQQYBQeBAQIFCQURIQeB
-AQQCCgURIQeBARAFESEHgQEQBREhB4EBEAURIQeBARAFEQQYBQeBARAFEQQYBQeBARAFEQQYBQeBARAF
-EQQYBQeBAU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQEOAjEBDIEBDgUqBwqBAQ0FJwwJgQENBSETCIEBDQURAQcXDIEBDQURGxCBAQ0FERcU
-gQENBBIOBAUUgQEMBRIGDAUUgQEMBRIFDQUUgQEMBRIFDgQUgQEMBRIFDgQUgQEMBBMFDgQUgQELBRMF
-DgQUgQELBRMFDgUTgQELBRMFDgUTgQEDGQcFDgUTgQEDGwUoA4EBAxsFKAOBAQMbBSgDgQEDGwUoA4EB
-CgUKBQUFDwUSgQEKBAsEBgUQBBKBAQkFCwQGBRAFEYEBCQULBAYFEAURgQEJBQoFBgUQBRGBAQkFCgUG
-BREFEIEBCQQLBQYFEQUQgQEIBQsFBgUSBQkBBYEBCAULBQYFEgUJAwOBAQgFCwUGBQoFBAUIBAKBAQgF
-CwQHBQQLBAYHAwOBAQgECwUHFAUGBQQDgQEHBQsFAxgFBwQEA4EBBwULBQMVCQ4DgQEHBQsFAw8QDQOB
-AQcEDAUDCRcLBIEBBgULBQUCHwgFgQEGBQsFKAQHgQEGBQsFM4EBBgULBTOBAQYFCgUKIgiBAQUHCQUK
-IgiBAQUICAUKIgiBAQUKBgUKIgiBAQULBAULBRgFCIEBBA0DBQsFGAUIgQEEBQIHAgULBRgFCIEBBgMD
-DAwFGAUIgQENCwwFGAUIgQEOCgwFGAUIgQEQBw0FGAUIgQERBwwFGAUIgQERCAsiCIEBEAoKIgiBARAL
-CSIIgQEPDQgiCIEBDwUCBwcFGAUIgQEOBgMHBgUYBQiBAQ0GBQcFBRgFCIEBDAcGBgUFGAUIgQELBwgE
-BgUYBQiBAQoHCgMGBRgFCIEBCQcMAQcFGAUIgQEIBxUFGAUIgQEHCBUiCIEBBggWIgiBAQQJFyIIgQEF
-BxgiCIEBBQUaBRgFCIEBBgMbBRgFCIEBJAUYBQiBAU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEZBi+BARkGL4EBGgUvgQEaBS+BARoFL4EBGgUvgQEaBS+BARoF
-L4EBGgUvgQEaBRkBFYEBGgUZAxOBARoFDwEIBRKBARoFCwUIBxCBARoFBgoHCg6BARoVCQkNgQEJJgsJ
-C4EBCSYMCQqBAQohEgkIgQEKGxoIB4EBChUhCQWBARkFIwkEgQEZBSUGBYEBGQUmBAaBARkFKAIGgQEZ
-BTCBARkFMIEBGQUwgQEZBTCBARkFMIEBGQUwgQEZBTCBARkFCRAXgQEZBQQYFIEBGSMSgQEZJBGBARkS
-CAwPgQEXDhIJDoEBFQwYCA2BARMMGwcNgQESDB0HDIEBEA4eBgyBAQ8IAwQfBguBAQ4IBAQfBguBAQ0H
-BgQgBQuBAQwHBwQgBQuBAQsHCAUfBQuBAQoGCgUfBQuBAQoGCgUfBQuBAQkGCwUfBQuBAQkFDAUeBguB
-AQgGDAUeBguBAQgGDAUdBwuBAQgGDAUdBgyBAQgGDAUcBwyBAQkFDAUbBw2BAQkGCwUZCQ2BAQkHCgUY
-CQ6BAQoIBwYVCw+BAQsIBQcSDRCBAQwSChURgQENEQoTE4EBDhAKERWBARANDA4XgQESCwwLGoEBFQYO
-BSCBAU6BAU6BAU6BAU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEuBhqBAS4FG4EBLgUbgQEuBRuBARICGQYbgQEPBRkGG4EBDgYZ
-BRyBAQ8FGQUcgQEPBhgFHIEBDwYXBhyBARAFFwUdgQEQBRcFHYEBEAYNERqBAREFChcXgQERBQccFYEB
-EQYEIBOBARIFAg4DExGBARIRBwYEChCBARIOCgUHCQ+BARMKDQUJCA6BARIJDgYKCA2BAREIEAUNBwyB
-ARAJEAUOBgyBAQ8KDwYPBguBAQ4MDgUQBwqBAQ4MDgURBgqBAQ0GAgYMBRMFCoEBDAYEBQwFEwYJgQEM
-BgQFCwYTBgmBAQsGBQYKBRUFCYEBCgYHBQkGFQYIgQEKBQgGCAYVBgiBAQkGCQUIBRcFCIEBCQUKBgYG
-FwUIgQEJBQsFBgUYBQiBAQgFDAYEBhgFCIEBCAUNBQMGGQUIgQEIBQ0GAgYZBQiBAQcFDwwaBQiBAQcF
-DwwaBQiBAQcFEAobBQiBAQcFEAoaBgiBAQcFEQgbBgiBAQcFEgYcBgiBAQcFEQgbBQmBAQcFEAoZBgmB
-AQcFEAoZBgmBAQcFDwwXBgqBAQcFDg4VBwqBAQcGDAcCBxQGC4EBCAULBwQEFQcLgQEIBggIBgIVBwyB
-AQgIBAkHARUHDYEBCRMcCQ2BAQoRHAkOgQELDhwKD4EBDAwbChGBAQ4HGwwSgQEsDxOBASgRFYEBKQ4X
-gQEpDBmBASoIHIEBKwMggQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQE1DwqBASkbCoEBHiYKgQETMQqBAQc9CoEBBjQU
-gQEGIQQKGYEBBhcOBxyBAQcOFAcegQEHBhsHH4EBJwYhgQEmBiKBASUGFgEMgQEkBhUDDIEBIwYWBAuB
-ASMGFwQKgQEiBg8DBgQKgQEhBg8EBwQJgQEhBhAEBwQIgQEgBhEFBgQIgQEgBRMEBwQHgQEfBhQEBgUG
-gQEfBhQEBwQGgQEfBRYEBgIIgQEeBhYEEIEBHgYXBA+BAR4FGAMQgQEeBSuBAR0GK4EBHQYrgQEdBiuB
-AR0GK4EBHQYrgQEdBiuBAR0GK4EBHQYrgQEdBiuBAR4FK4EBHgYqgQEeBiqBAR4HKYEBHwYpgQEfByiB
-ASAHJ4EBIAcngQEhByaBASEJJIEBIgkjgQEjCiGBASQLH4EBJQwdgQEmDxmBASgTE4EBKhMRgQErEhGB
-AS4PEYEBMAwSgQE0CBKBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXATaBARQENoEBEgY2gQESBzWBARMGNYEBEwc0gQEUBjSB
-ARQGNIEBFQYzgQEVBiABEoEBFgYeAxGBARYGHAYQgQEWBxoHEIEBFwYYCg+BARcHFQsQgQEYBhMLEoEB
-GAYRCxSBARkGDgsWgQEZBgwLGIEBGgYJCxqBARoHBwocgQEbBgUKHoEBGwcCCiCBARwRIYEBHA8jgQEd
-DCWBAR0KJ4EBHAoogQEbCSqBARoILIEBGQgtgQEXCC+BARYIMIEBFQgxgQEVBzKBARQHM4EBEwc0gQES
-BzWBARIGNoEBEQY3gQERBjeBAREFOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQ
-BjiBARAHN4EBEAc3gQERBzaBAREINYEBEgkzgQETCiEDDYEBEw0WCw2BARQtDYEBFisNgQEXKg2BARon
-DYEBHSARgQEjDxyBAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXBDOBARcLLIEBFxQjgQEXIRaBARchFoEBGh4WgQEiFhaBASsN
-FoEBNgEXgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEkDR2BAR4WGoEBGR0YgQEVIxaBARApFYEB
-DRcLCxSBAQ4QFAkTgQEODBoIEoEBDgkeBxKBAQ4GIgcRgQEPAiYGEYEBNwcQgQE4BhCBATgGEIEBOAYQ
-gQE4BhCBATkFEIEBOQUQgQE5BRCBATgGEIEBOAYQgQE4BhCBATgGEIEBNwcQgQE3BhGBATcGEYEBNgcR
-gQE1BxKBATUHEoEBNAcTgQEzCBOBATIIFIEBMQgVgQEvCRaBAS4JF4EBLAoYgQEqCxmBAScMG4EBJQ0c
-gQEhDx6BARwTH4EBGBQigQEZESSBARoNJ4EBGgoqgQEbBS6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQFOgQFOgQFDAgmBAUEECYEBQgQIgQE7AQYFB4EBOQQGBAeBATkEBwQGgQE5BQYFBYEB
-OgQHBAWBATsEBgUEgQE7BQYEBIEBPAQGBASBATwFBgEGgQEQBhsIBAQNgQEQKQQFDIEBECkFAQ+BARAp
-FYEBECkVgQEQKRWBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
-AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ0EPYEBDAY8gQEMBjyBAQwGPIEBDAU9gQEMBT2BAQsG
-PYEBCwY9gQELBj2BAQsGPYEBCwY9gQELBj2BAQsHPIEBCwc8gQEMBzuBAQwIOoEBDQg5gQENCiUCEIEB
-DgwdBxCBAQ8QDhEQgQEQLhCBAREtEIEBEysQgQEWJROBARobGYEBToEBToEBToEBToEBToHQ',
-'0AFOgQFOgQFOgQFOgQFCAwmBAUEECYEBQQUIgQE5AwYFB4EBOAQHBAeBASkCDgQGBQaBASYGDQUGBAaB
-ASYGDgQHBAWBASYGDgUGBAWBAScFDwQHBASBAScGDwQGAwWBAScGDwUNgQEoBRAEDYEBKAUQAw6BASgG
-IIEBKQUQAw2BASkFDAcNgQEpBgYMDYEBCgMdFw2BAQsVAh8NgQELNQ6BAQsxEoEBCysYgQELJh2BARkI
-CwUdgQEsBhyBAS0FHIEBLQUcgQEuBRuBAS4FG4EBLwUagQEvBRqBATAFGYEBMAYYgQExBRiBATEGF4EB
-MgUXgQEyBhaBATMGFYEBNAUVgQE0BhSBATUGE4EBEAIWCgMHEoEBEAYSFRGBAQ8GExURgQEPBRQUEoEB
-DgYaDROBAQ4GIwQTgQEOBTuBAQ0GO4EBDQY7gQENBTyBAQ0FPIEBDQU8gQENBTyBAQ0FPIEBDQU8gQEN
-BjuBAQ0GO4EBDgY6gQEOBzmBAQ4IOIEBDwg3gQEQCh4BFYEBEQwVBxWBARInFYEBEyYVgQEVJBWBARgh
-FYEBGxoZgQFOgQFOgQFOgQFOgdA=',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEJBz6BAQkGP4EBCQY/gQEJBigDFIEB
-CQYlBhSBAQkGJQcTgQEJBiYHEoEBCQYnBhKBAQkGJwcRgQEJBigGEYEBCQYoBxCBAQkGKQYQgQEJBioG
-D4EBCQYqBg+BAQkGKgcOgQEJBisGDoEBCgUrBg6BAQoFLAYNgQEKBSwGDYEBCgUtBgyBAQoFLQYMgQEK
-BS0GDIEBCgUuBguBAQoFLgYLgQEKBS4GC4EBCgYtBguBAQoGLgYKgQEKBi4GCoEBCgYuBgqBAQoGLwYJ
-gQELBS8GCYEBCwUvBgmBAQsFLwYJgQELBhMBGgYJgQELBhMCGgYIgQELBhMDGQYIgQEMBRMEGAYIgQEM
-BhEGFwYIgQEMBhEGFwYIgQEMBhAGGQUIgQENBg8GGQUIgQENBg8GGQUIgQENBg4GGgILgQEOBg0GJ4EB
-DgcLBiiBAQ4HCgcogQEPBwgHKYEBEAcGCCmBARAKAQkqgQEREiuBARIRK4EBEhAsgQETDi2BARULLoEB
-FwcwgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQElBiOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYF
-I4EBJgUQBQ6BAQ4IEAUGDw6BAQ4yDoEBDjIOgQEOMg6BAQ4rFYEBGhIigQEmBSOBASYFI4EBJgUjgQEm
-BSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBRQCDYEBDQMWBQwKDYEBDQ8JHA2BAQ4zDYEBDjMN
-gQEOMg6BARAnF4EBJQYjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUj
-gQEmBSOBAR0EBQUjgQEXFCOBARQZIYEBEh4egQERIRyBARAJDBAZgQEPBxARF4EBDgYSExWBAQ4FEwYD
-CxSBAQ4FEwYFCxKBAQ0FFAYHChGBAQ0FFAYJCg+BAQ0FFAYKCg6BAQ0GEwYMCQ2BAQ4FEwYNCQyBAQ4G
-EQYQBg2BAQ4HDwcRBQ2BAQ8IDAgSAw6BAQ8bFAEPgQERGCWBARIWJoEBFBIogQEXDSqBAU6BAU6BAU6B
-AU6BAU6BAU6B0A==',
-'0AFOgQFOgQFOgQFOgQFOgQFOgQEpBh+BASkGH4EBKQYfgQEqBR+BASoFH4EBKgUfgQEqBR+BASoFH4EB
-KgUfgQEqBR+BARkuB4EBBkEHgQEHQAeBAQdAB4EBB0AHgQEHDBcFH4EBKgUfgQEqBR+BASoFH4EBKgUf
-gQEqBR+BASoFH4EBKgUfgQEgDx+BAR4RH4EBHBMfgQEbFB+BARoIBAkfgQEZBwkGH4EBGQYLBh6BARgG
-DQUegQEYBQ4GHYEBFwYPBR2BARcFEAUdgQEXBRAFHYEBFwUQBhyBARcFEAYcgQEXBQ8HHIEBFwUPBxyB
-ARcGDgccgQEXBg0IHIEBGAYMBx2BARgHCggdgQEZCAYKHYEBGhcdgQEbFh2BARwVHYEBHgsBBh6BASAG
-BAYegQEpBh+BASkGH4EBKAYggQEnByCBASYHIYEBJQcigQEkCCKBASIJI4EBIAokgQEeCyWBARwLJ4EB
-GQ0ogQEWDiqBARcMK4EBGAktgQEZBTCBARoCMoEBToEBToEBToEBToEBToEBToHQ',];
-$m="";for($k=0;$k<min(13,intdiv(__LINE__-119,80)+1);$k++){$C=str_replace("\n","",
-$c[$k]);$f=!0;foreach(str_split(base64_decode($C))as$l){$L=ord($l);$m.=str_repeat
-($f?"#":chr(32),$L&127);$f=!$f;if($L&128){$m.="\n";$f=!0;continue;}}}print(
-str_replace([chr(96),chr(37),chr(64)],[implode("\n",array_map(fn($C)=>"'".trim(
-chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
+ <?php
+$s=<<<'Q'
+<?php
+%
+$s=<<<'Q'
+@$c=[`];
+$m="";for($k=0;$k<min(13,intdiv(__LINE__-119,80)+1);$k++){$C=str_replace("\n","",
+$c[$k]);$f=!0;foreach(str_split(base64_decode($C))as$l){$L=ord($l);$m.=str_repeat
+($f?"#":chr(32),$L&127);$f=!$f;if($L&128){$m.="\n";$f=!0;continue;}}}print(
+str_replace([chr(96),chr(37),chr(64)],[implode("\n",array_map(fn($C)=>"'".trim(
+chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
+Q;
+$c=['0AFOgQFOgQFOgQFOgQFOgQFEAgiBAUIECIEBQwQHgQE8AQYFBoEBOgQGBAaBAToEBwQFgQE6BQYFBIEB
+OwQHBASBATwEBgUDgQE8BQYEA4EBPQQGBAOBAT0FBgEFgQERBhsIBAQMgQERKQQFC4EBESkFAQ6BAREp
+FIEBESkUgQERKRSBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
+AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ4EPIEBDQY7gQENBjuBAQ0GO4EBDQU8gQENBTyBAQwG
+PIEBDAY8gQEMBjyBAQwGPIEBDAY8gQEMBjyBAQwHO4EBDAc7gQENBzqBAQ0IOYEBDgg4gQEOCiUCD4EB
+DwwdBw+BARAQDhEPgQERLg+BARItD4EBFCsPgQEXJRKBARsbGIEBToEBToEBToEBToEBToHQ',
+'0AFOgQFOgQFOgQEPASMFFoEBDwMhBRaBAQ4FIAUWgQEOBSAFFoEBDQUhBRaBAQ0FIQUWgQEMBSIFFoEB
+DAUiBRaBAQsFIwUWgQELBQgBGgUWgQEKBQkDGAUWgQEKBQgGBCoDgQEJBQkFBSoDgQEFAQMECQYFKgOB
+AQQDAQUJBQYqA4EBBAgJBQcqA4EBAwkJBRkFFoEBBAcJBRoFFoEBBQYIBRsFFoEBBgYHBRsFFoEBBwYF
+BRwFFoEBCAYDBR0FFoEBCQYCBR0FFoEBCgseBRaBAQsJHwUWgQEMByAFFoEBDAcFAxgFFoEBDQUFBBgF
+FoEBDQQGBRYGFoEBDAUHBAcmBYEBCwUIBQYmBYEBCwQKBAYmBYEBCgQLBQUmBYEBCQUMBS+BAQgFDAYv
+gQEDHC+BAQMdLoEBAx0ugQEDHi2BAQMJBAUIBC2BARAFCAQtgQEQBQgELYEBEAUJAS+BARAFESEHgQEQ
+BREhB4EBBwEIBQYBCiEHgQEHBAUFBAQJIQeBAQcEBQUEBAkEGAUHgQEGBQUFBAUIBBgFB4EBBgUFBQUE
+CAQYBQeBAQYFBQUFBAgEGAUHgQEGBAYFBQUHBBgFB4EBBgQGBQYEBwQYBQeBAQUFBgUGBQYEGAUHgQEF
+BQYFBwQGBBgFB4EBBQQHBQcEBgQYBQeBAQUEBwUHBQUEGAUHgQEEBQcFBwUFBBgFB4EBBAUHBQgEBQQY
+BQeBAQQECAUIBAUEGAUHgQEDBQgFCAEIBBgFB4EBAwUIBREEGAUHgQECBQkFEQQYBQeBAQIFCQURIQeB
+AQQCCgURIQeBARAFESEHgQEQBREhB4EBEAURIQeBARAFEQQYBQeBARAFEQQYBQeBARAFEQQYBQeBARAF
+EQQYBQeBAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQEOAjEBDIEBDgUqBwqBAQ0FJwwJgQENBSETCIEBDQURAQcXDIEBDQURGxCBAQ0FERcU
+gQENBBIOBAUUgQEMBRIGDAUUgQEMBRIFDQUUgQEMBRIFDgQUgQEMBRIFDgQUgQEMBBMFDgQUgQELBRMF
+DgQUgQELBRMFDgUTgQELBRMFDgUTgQEDGQcFDgUTgQEDGwUoA4EBAxsFKAOBAQMbBSgDgQEDGwUoA4EB
+CgUKBQUFDwUSgQEKBAsEBgUQBBKBAQkFCwQGBRAFEYEBCQULBAYFEAURgQEJBQoFBgUQBRGBAQkFCgUG
+BREFEIEBCQQLBQYFEQUQgQEIBQsFBgUSBQkBBYEBCAULBQYFEgUJAwOBAQgFCwUGBQoFBAUIBAKBAQgF
+CwQHBQQLBAYHAwOBAQgECwUHFAUGBQQDgQEHBQsFAxgFBwQEA4EBBwULBQMVCQ4DgQEHBQsFAw8QDQOB
+AQcEDAUDCRcLBIEBBgULBQUCHwgFgQEGBQsFKAQHgQEGBQsFM4EBBgULBTOBAQYFCgUKIgiBAQUHCQUK
+IgiBAQUICAUKIgiBAQUKBgUKIgiBAQULBAULBRgFCIEBBA0DBQsFGAUIgQEEBQIHAgULBRgFCIEBBgMD
+DAwFGAUIgQENCwwFGAUIgQEOCgwFGAUIgQEQBw0FGAUIgQERBwwFGAUIgQERCAsiCIEBEAoKIgiBARAL
+CSIIgQEPDQgiCIEBDwUCBwcFGAUIgQEOBgMHBgUYBQiBAQ0GBQcFBRgFCIEBDAcGBgUFGAUIgQELBwgE
+BgUYBQiBAQoHCgMGBRgFCIEBCQcMAQcFGAUIgQEIBxUFGAUIgQEHCBUiCIEBBggWIgiBAQQJFyIIgQEF
+BxgiCIEBBQUaBRgFCIEBBgMbBRgFCIEBJAUYBQiBAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEZBi+BARkGL4EBGgUvgQEaBS+BARoFL4EBGgUvgQEaBS+BARoF
+L4EBGgUvgQEaBRkBFYEBGgUZAxOBARoFDwEIBRKBARoFCwUIBxCBARoFBgoHCg6BARoVCQkNgQEJJgsJ
+C4EBCSYMCQqBAQohEgkIgQEKGxoIB4EBChUhCQWBARkFIwkEgQEZBSUGBYEBGQUmBAaBARkFKAIGgQEZ
+BTCBARkFMIEBGQUwgQEZBTCBARkFMIEBGQUwgQEZBTCBARkFCRAXgQEZBQQYFIEBGSMSgQEZJBGBARkS
+CAwPgQEXDhIJDoEBFQwYCA2BARMMGwcNgQESDB0HDIEBEA4eBgyBAQ8IAwQfBguBAQ4IBAQfBguBAQ0H
+BgQgBQuBAQwHBwQgBQuBAQsHCAUfBQuBAQoGCgUfBQuBAQoGCgUfBQuBAQkGCwUfBQuBAQkFDAUeBguB
+AQgGDAUeBguBAQgGDAUdBwuBAQgGDAUdBgyBAQgGDAUcBwyBAQkFDAUbBw2BAQkGCwUZCQ2BAQkHCgUY
+CQ6BAQoIBwYVCw+BAQsIBQcSDRCBAQwSChURgQENEQoTE4EBDhAKERWBARANDA4XgQESCwwLGoEBFQYO
+BSCBAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEuBhqBAS4FG4EBLgUbgQEuBRuBARICGQYbgQEPBRkGG4EBDgYZ
+BRyBAQ8FGQUcgQEPBhgFHIEBDwYXBhyBARAFFwUdgQEQBRcFHYEBEAYNERqBAREFChcXgQERBQccFYEB
+EQYEIBOBARIFAg4DExGBARIRBwYEChCBARIOCgUHCQ+BARMKDQUJCA6BARIJDgYKCA2BAREIEAUNBwyB
+ARAJEAUOBgyBAQ8KDwYPBguBAQ4MDgUQBwqBAQ4MDgURBgqBAQ0GAgYMBRMFCoEBDAYEBQwFEwYJgQEM
+BgQFCwYTBgmBAQsGBQYKBRUFCYEBCgYHBQkGFQYIgQEKBQgGCAYVBgiBAQkGCQUIBRcFCIEBCQUKBgYG
+FwUIgQEJBQsFBgUYBQiBAQgFDAYEBhgFCIEBCAUNBQMGGQUIgQEIBQ0GAgYZBQiBAQcFDwwaBQiBAQcF
+DwwaBQiBAQcFEAobBQiBAQcFEAoaBgiBAQcFEQgbBgiBAQcFEgYcBgiBAQcFEQgbBQmBAQcFEAoZBgmB
+AQcFEAoZBgmBAQcFDwwXBgqBAQcFDg4VBwqBAQcGDAcCBxQGC4EBCAULBwQEFQcLgQEIBggIBgIVBwyB
+AQgIBAkHARUHDYEBCRMcCQ2BAQoRHAkOgQELDhwKD4EBDAwbChGBAQ4HGwwSgQEsDxOBASgRFYEBKQ4X
+gQEpDBmBASoIHIEBKwMggQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQE1DwqBASkbCoEBHiYKgQETMQqBAQc9CoEBBjQU
+gQEGIQQKGYEBBhcOBxyBAQcOFAcegQEHBhsHH4EBJwYhgQEmBiKBASUGFgEMgQEkBhUDDIEBIwYWBAuB
+ASMGFwQKgQEiBg8DBgQKgQEhBg8EBwQJgQEhBhAEBwQIgQEgBhEFBgQIgQEgBRMEBwQHgQEfBhQEBgUG
+gQEfBhQEBwQGgQEfBRYEBgIIgQEeBhYEEIEBHgYXBA+BAR4FGAMQgQEeBSuBAR0GK4EBHQYrgQEdBiuB
+AR0GK4EBHQYrgQEdBiuBAR0GK4EBHQYrgQEdBiuBAR4FK4EBHgYqgQEeBiqBAR4HKYEBHwYpgQEfByiB
+ASAHJ4EBIAcngQEhByaBASEJJIEBIgkjgQEjCiGBASQLH4EBJQwdgQEmDxmBASgTE4EBKhMRgQErEhGB
+AS4PEYEBMAwSgQE0CBKBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXATaBARQENoEBEgY2gQESBzWBARMGNYEBEwc0gQEUBjSB
+ARQGNIEBFQYzgQEVBiABEoEBFgYeAxGBARYGHAYQgQEWBxoHEIEBFwYYCg+BARcHFQsQgQEYBhMLEoEB
+GAYRCxSBARkGDgsWgQEZBgwLGIEBGgYJCxqBARoHBwocgQEbBgUKHoEBGwcCCiCBARwRIYEBHA8jgQEd
+DCWBAR0KJ4EBHAoogQEbCSqBARoILIEBGQgtgQEXCC+BARYIMIEBFQgxgQEVBzKBARQHM4EBEwc0gQES
+BzWBARIGNoEBEQY3gQERBjeBAREFOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQ
+BjiBARAHN4EBEAc3gQERBzaBAREINYEBEgkzgQETCiEDDYEBEw0WCw2BARQtDYEBFisNgQEXKg2BARon
+DYEBHSARgQEjDxyBAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXBDOBARcLLIEBFxQjgQEXIRaBARchFoEBGh4WgQEiFhaBASsN
+FoEBNgEXgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEkDR2BAR4WGoEBGR0YgQEVIxaBARApFYEB
+DRcLCxSBAQ4QFAkTgQEODBoIEoEBDgkeBxKBAQ4GIgcRgQEPAiYGEYEBNwcQgQE4BhCBATgGEIEBOAYQ
+gQE4BhCBATkFEIEBOQUQgQE5BRCBATgGEIEBOAYQgQE4BhCBATgGEIEBNwcQgQE3BhGBATcGEYEBNgcR
+gQE1BxKBATUHEoEBNAcTgQEzCBOBATIIFIEBMQgVgQEvCRaBAS4JF4EBLAoYgQEqCxmBAScMG4EBJQ0c
+gQEhDx6BARwTH4EBGBQigQEZESSBARoNJ4EBGgoqgQEbBS6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFDAgmBAUEECYEBQgQIgQE7AQYFB4EBOQQGBAeBATkEBwQGgQE5BQYFBYEB
+OgQHBAWBATsEBgUEgQE7BQYEBIEBPAQGBASBATwFBgEGgQEQBhsIBAQNgQEQKQQFDIEBECkFAQ+BARAp
+FYEBECkVgQEQKRWBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
+AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ0EPYEBDAY8gQEMBjyBAQwGPIEBDAU9gQEMBT2BAQsG
+PYEBCwY9gQELBj2BAQsGPYEBCwY9gQELBj2BAQsHPIEBCwc8gQEMBzuBAQwIOoEBDQg5gQENCiUCEIEB
+DgwdBxCBAQ8QDhEQgQEQLhCBAREtEIEBEysQgQEWJROBARobGYEBToEBToEBToEBToEBToHQ',
+'0AFOgQFOgQFOgQFOgQFCAwmBAUEECYEBQQUIgQE5AwYFB4EBOAQHBAeBASkCDgQGBQaBASYGDQUGBAaB
+ASYGDgQHBAWBASYGDgUGBAWBAScFDwQHBASBAScGDwQGAwWBAScGDwUNgQEoBRAEDYEBKAUQAw6BASgG
+IIEBKQUQAw2BASkFDAcNgQEpBgYMDYEBCgMdFw2BAQsVAh8NgQELNQ6BAQsxEoEBCysYgQELJh2BARkI
+CwUdgQEsBhyBAS0FHIEBLQUcgQEuBRuBAS4FG4EBLwUagQEvBRqBATAFGYEBMAYYgQExBRiBATEGF4EB
+MgUXgQEyBhaBATMGFYEBNAUVgQE0BhSBATUGE4EBEAIWCgMHEoEBEAYSFRGBAQ8GExURgQEPBRQUEoEB
+DgYaDROBAQ4GIwQTgQEOBTuBAQ0GO4EBDQY7gQENBTyBAQ0FPIEBDQU8gQENBTyBAQ0FPIEBDQU8gQEN
+BjuBAQ0GO4EBDgY6gQEOBzmBAQ4IOIEBDwg3gQEQCh4BFYEBEQwVBxWBARInFYEBEyYVgQEVJBWBARgh
+FYEBGxoZgQFOgQFOgQFOgQFOgdA=',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEJBz6BAQkGP4EBCQY/gQEJBigDFIEB
+CQYlBhSBAQkGJQcTgQEJBiYHEoEBCQYnBhKBAQkGJwcRgQEJBigGEYEBCQYoBxCBAQkGKQYQgQEJBioG
+D4EBCQYqBg+BAQkGKgcOgQEJBisGDoEBCgUrBg6BAQoFLAYNgQEKBSwGDYEBCgUtBgyBAQoFLQYMgQEK
+BS0GDIEBCgUuBguBAQoFLgYLgQEKBS4GC4EBCgYtBguBAQoGLgYKgQEKBi4GCoEBCgYuBgqBAQoGLwYJ
+gQELBS8GCYEBCwUvBgmBAQsFLwYJgQELBhMBGgYJgQELBhMCGgYIgQELBhMDGQYIgQEMBRMEGAYIgQEM
+BhEGFwYIgQEMBhEGFwYIgQEMBhAGGQUIgQENBg8GGQUIgQENBg8GGQUIgQENBg4GGgILgQEOBg0GJ4EB
+DgcLBiiBAQ4HCgcogQEPBwgHKYEBEAcGCCmBARAKAQkqgQEREiuBARIRK4EBEhAsgQETDi2BARULLoEB
+FwcwgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQElBiOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYF
+I4EBJgUQBQ6BAQ4IEAUGDw6BAQ4yDoEBDjIOgQEOMg6BAQ4rFYEBGhIigQEmBSOBASYFI4EBJgUjgQEm
+BSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBRQCDYEBDQMWBQwKDYEBDQ8JHA2BAQ4zDYEBDjMN
+gQEOMg6BARAnF4EBJQYjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUj
+gQEmBSOBAR0EBQUjgQEXFCOBARQZIYEBEh4egQERIRyBARAJDBAZgQEPBxARF4EBDgYSExWBAQ4FEwYD
+CxSBAQ4FEwYFCxKBAQ0FFAYHChGBAQ0FFAYJCg+BAQ0FFAYKCg6BAQ0GEwYMCQ2BAQ4FEwYNCQyBAQ4G
+EQYQBg2BAQ4HDwcRBQ2BAQ8IDAgSAw6BAQ8bFAEPgQERGCWBARIWJoEBFBIogQEXDSqBAU6BAU6BAU6B
+AU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQEpBh+BASkGH4EBKQYfgQEqBR+BASoFH4EBKgUfgQEqBR+BASoFH4EB
+KgUfgQEqBR+BARkuB4EBBkEHgQEHQAeBAQdAB4EBB0AHgQEHDBcFH4EBKgUfgQEqBR+BASoFH4EBKgUf
+gQEqBR+BASoFH4EBKgUfgQEgDx+BAR4RH4EBHBMfgQEbFB+BARoIBAkfgQEZBwkGH4EBGQYLBh6BARgG
+DQUegQEYBQ4GHYEBFwYPBR2BARcFEAUdgQEXBRAFHYEBFwUQBhyBARcFEAYcgQEXBQ8HHIEBFwUPBxyB
+ARcGDgccgQEXBg0IHIEBGAYMBx2BARgHCggdgQEZCAYKHYEBGhcdgQEbFh2BARwVHYEBHgsBBh6BASAG
+BAYegQEpBh+BASkGH4EBKAYggQEnByCBASYHIYEBJQcigQEkCCKBASIJI4EBIAokgQEeCyWBARwLJ4EB
+GQ0ogQEWDiqBARcMK4EBGAktgQEZBTCBARoCMoEBToEBToEBToEBToEBToEBToHQ',];
+$m="";for($k=0;$k<min(13,intdiv(__LINE__-119,80)+1);$k++){$C=str_replace("\n","",
+$c[$k]);$f=!0;foreach(str_split(base64_decode($C))as$l){$L=ord($l);$m.=str_repeat
+($f?"#":chr(32),$L&127);$f=!$f;if($L&128){$m.="\n";$f=!0;continue;}}}print(
+str_replace([chr(96),chr(37),chr(64)],[implode("\n",array_map(fn($C)=>"'".trim(
+chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
+ $ echo "#iwillblog" | php Q1.png >/dev/null
+ $ echo "#iwillblog" | php Q1.png >/dev/null
+ 無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。 @@ -151,7 +152,9 @@ まずは素直に画像として見てみよう。全体は QR コードになっている。適当な QR コードリーダで読み込むと、次のようなテキストが表示されるはずだ。
-Guess password. $ echo "password" | php Q1.png >/dev/null
+ Guess password. $ echo "password" | php Q1.png >/dev/null
+ メッセージは、この画像の実行方法とこの問題でやるべきこと (パスワードの推測) を示している。 @@ -168,8 +171,10 @@ 不正なパスワードを使って実行してみると、次のようなエラーメッセージが表示される。
-$ echo "foo" | php Q1.png >/dev/null
-401 Unauthorized
+ $ echo "foo" | php Q1.png >/dev/null
+401 Unauthorized
+
すでに「解き方」の節で示したように、パスワードである PHPer トークンは「#iwillblog」である。これを与えて実行すると正解のトークンが得られる。
@@ -258,23 +263,27 @@
strings コマンドを使うと、隠されたデータを簡単に閲覧できる。
IHDR
--HHc
-<PLTE
-IDATx
-IEND
-<?php
-error_reporting(-1);
-$b = unpack('C*', file_get_contents(__FILE__));
-$w = $b[20]+2;
-$h = $b[24]+2;
-// (以下略)
+ IHDR
+-HHc
+<PLTE
+IDATx
+IEND
+<?php
+error_reporting(-1);
+$b = unpack('C*', file_get_contents(__FILE__));
+$w = $b[20]+2;
+$h = $b[24]+2;
+// (以下略)
+
IHDR や IEND が PNG 画像の一部で、<?php からが実際のプログラムになっている。もちろんこれを PHP プログラムとして動かすと、PHP タグより前にある PNG 画像としてのデータはそのまま標準出力へと出力されてしまう。それを防ぐため、QR コードを読み込んだときの実行方法
Guess password. $ echo "password" | php Q1.png >/dev/null
+ Guess password. $ echo "password" | php Q1.png >/dev/null
+
には標準出力を捨てるよう >/dev/null と指定されている。
@@ -291,107 +300,109 @@ $h = $b[24]+2;
画像の正体がわかったところで、画像に隠されていた PHP プログラムについて見ていこう。先ほどは一部しか記載しなかったので、全体を載せる。なお、ある程度ゴルフしながら書いたので、空白こそ残しているものの可読性は非常に低いことと思う。
<?php
-error_reporting(-1);
-$b = unpack('C*', file_get_contents(__FILE__));
-$w = $b[20]+2;
-$h = $b[24]+2;
-$cs = [];
-for ($y = 0; $y < $h; $y++)
- for ($x = 0; $x < $w; $x++)
- $cs[$y*$w + $x] = ($x*$y === 0 || $x === $w-1 || $y === $h-1)
- ? 0
- : $b[122+($y-1)*($w-1)+$x-1];
-$i = stream_isatty(STDIN)
- ? []
- : array_map(ord(...), str_split(trim((string) fgets(STDIN))));
-$m = [];
-$pc = 1*$w+1;
-$dp = 0;
-$cc = 1;
-$c0 = 1;
-$b = 0;
-$ns = 0;
-$o = '';
-while (true) {
- $ns++;
- if ($ns > 1e5) {
- echo "infinite loop detected\n";
- break;
- $c1 = $cs[$pc];
- $y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6;
- $x = (3 + $c1%3 - $c0%3) % 3;
- match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) {
- 1 => $m[] = $b,
- 2 => array_pop($m),
- 3 => $m[] = array_pop($m) + array_pop($m),
- 4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)),
- 5 => $m[] = array_pop($m) * array_pop($m),
- 8 => $m[] = array_pop($m) === 0 ? 1 : 0,
- 11 => $cc *= pow(-1, array_pop($m)),
- 12 => $m[] = $m[count($m)-1],
- 13 => $m = (fn($n, $d, $m, $l) => [
- ...array_slice($m, 0, $l-$d),
- ...array_reverse([
- ...array_reverse(array_slice($m, $l-$d, $d-$n)),
- ...array_reverse(array_slice($m, $l-$n)),
- ]),
- ])(array_pop($m), array_pop($m), $m, count($m)),
- 15 => !empty($i) and $m[] = array_shift($i),
- 16 => $o .= sprintf('%d', array_pop($m)),
- 17 => $o .= sprintf('%c', array_pop($m)),
- default => 'nop',
- };
- $c0 = $c1;
- for ($j = 0; $j < 8; $j++) {
- $v = [];
- if ($c1 === 1) {
- $x = $pc % $w;
- $y = intdiv($pc, $w);
- $e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp];
- $z = [1, $w, -1, -$w][$dp];
- for ($ep = $pc; $ep !== $e; $ep += $z)
- if ($cs[$ep] !== 1) break;
- $ep -= $z;
- $pc = $ep;
- } else {
- $q = [$pc];
- $ep = $pc;
- while (!empty($q)) {
- $qq = array_pop($q);
- $v[$qq] = true;
- foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) {
- if ($cs[$qp] !== $c1) continue;
- if (isset($v[$qp])) continue;
- $q[] = $qp;
- $qx = $qp % $w;
- $qy = intdiv($qp, $w);
- $x = $ep % $w;
- $y = intdiv($ep, $w);
- if (
- ($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc)))
- || ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc)))
- || ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc)))
- || ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc)))
- )
- $ep = $qp;
- }
- }
- }
- $np = $ep + [1, $w, -1, -$w][$dp];
- if ($cs[$np] !== 0) {
- $b = count(array_keys($v));
- $pc = $np;
- break;
- }
- if ($j === 7) break 2;
- if ($j % 2 === 0) $cc = -$cc;
- if ($j % 2 === 1) $dp = ($dp+1) % 4;
-// The original Piet image is wrong: it outputs 403 error for invalid passwords.
-// Failure of authentication should be notified by 401, not 403.
-// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
-// Piet any longer at that time.
-fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+ <?php
+error_reporting(-1);
+$b = unpack('C*', file_get_contents(__FILE__));
+$w = $b[20]+2;
+$h = $b[24]+2;
+$cs = [];
+for ($y = 0; $y < $h; $y++)
+ for ($x = 0; $x < $w; $x++)
+ $cs[$y*$w + $x] = ($x*$y === 0 || $x === $w-1 || $y === $h-1)
+ ? 0
+ : $b[122+($y-1)*($w-1)+$x-1];
+$i = stream_isatty(STDIN)
+ ? []
+ : array_map(ord(...), str_split(trim((string) fgets(STDIN))));
+$m = [];
+$pc = 1*$w+1;
+$dp = 0;
+$cc = 1;
+$c0 = 1;
+$b = 0;
+$ns = 0;
+$o = '';
+while (true) {
+ $ns++;
+ if ($ns > 1e5) {
+ echo "infinite loop detected\n";
+ break;
+ $c1 = $cs[$pc];
+ $y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6;
+ $x = (3 + $c1%3 - $c0%3) % 3;
+ match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) {
+ 1 => $m[] = $b,
+ 2 => array_pop($m),
+ 3 => $m[] = array_pop($m) + array_pop($m),
+ 4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)),
+ 5 => $m[] = array_pop($m) * array_pop($m),
+ 8 => $m[] = array_pop($m) === 0 ? 1 : 0,
+ 11 => $cc *= pow(-1, array_pop($m)),
+ 12 => $m[] = $m[count($m)-1],
+ 13 => $m = (fn($n, $d, $m, $l) => [
+ ...array_slice($m, 0, $l-$d),
+ ...array_reverse([
+ ...array_reverse(array_slice($m, $l-$d, $d-$n)),
+ ...array_reverse(array_slice($m, $l-$n)),
+ ]),
+ ])(array_pop($m), array_pop($m), $m, count($m)),
+ 15 => !empty($i) and $m[] = array_shift($i),
+ 16 => $o .= sprintf('%d', array_pop($m)),
+ 17 => $o .= sprintf('%c', array_pop($m)),
+ default => 'nop',
+ };
+ $c0 = $c1;
+ for ($j = 0; $j < 8; $j++) {
+ $v = [];
+ if ($c1 === 1) {
+ $x = $pc % $w;
+ $y = intdiv($pc, $w);
+ $e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp];
+ $z = [1, $w, -1, -$w][$dp];
+ for ($ep = $pc; $ep !== $e; $ep += $z)
+ if ($cs[$ep] !== 1) break;
+ $ep -= $z;
+ $pc = $ep;
+ } else {
+ $q = [$pc];
+ $ep = $pc;
+ while (!empty($q)) {
+ $qq = array_pop($q);
+ $v[$qq] = true;
+ foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) {
+ if ($cs[$qp] !== $c1) continue;
+ if (isset($v[$qp])) continue;
+ $q[] = $qp;
+ $qx = $qp % $w;
+ $qy = intdiv($qp, $w);
+ $x = $ep % $w;
+ $y = intdiv($ep, $w);
+ if (
+ ($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc)))
+ || ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc)))
+ || ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc)))
+ || ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc)))
+ )
+ $ep = $qp;
+ }
+ }
+ }
+ $np = $ep + [1, $w, -1, -$w][$dp];
+ if ($cs[$np] !== 0) {
+ $b = count(array_keys($v));
+ $pc = $np;
+ break;
+ }
+ if ($j === 7) break 2;
+ if ($j % 2 === 0) $cc = -$cc;
+ if ($j % 2 === 1) $dp = ($dp+1) % 4;
+// The original Piet image is wrong: it outputs 403 error for invalid passwords.
+// Failure of authentication should be notified by 401, not 403.
+// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
+// Piet any longer at that time.
+fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+ これは一体なんなのか。ずばり、難解プログラミング言語の一つ Piet のインタプリタである。Piet はピエト・モンドリアン (『赤・青・黄のコンポジション』などで知られる抽象画家) の作品にインスピレーションを受けて作られた、画像をソースコードとするプログラミング言語である。インタプリタは画像の各ピクセルの上を進みながら、色等に応じて特定の処理をおこなっていく。ここでは詳しい言語仕様については解説しないので、気になる方は Wikipedia の記事「Piet」 などを参照してほしい。 @@ -401,7 +412,9 @@ $h = $b[24]+2; プログラムの冒頭にあるこの箇所
-$b = unpack('C*', file_get_contents(__FILE__));
+ $b = unpack('C*', file_get_contents(__FILE__));
+
で __FILE__ つまりこの画像ファイルを読み込んでいる。先ほど Piet は画像をソースコードにしていると説明した。そう、今回の問題の画像ファイル Q1.png は、PHP 製 Piet インタプリタであると同時に、Piet のソースコード画像でもあるのだ。QR コード中央のカラフルな部分が Piet の命令になっている。
@@ -460,11 +473,13 @@ $h = $b[24]+2;
ところで、先ほど掲載した Piet のインタプリタのソースコード末尾には次のような箇所がある。
// The original Piet image is wrong: it outputs 403 error for invalid passwords.
-// Failure of authentication should be notified by 401, not 403.
-// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
-// Piet any longer at that time.
-fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+ // The original Piet image is wrong: it outputs 403 error for invalid passwords.
+// Failure of authentication should be notified by 401, not 403.
+// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
+// Piet any longer at that time.
+fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+ コメントにも書かれているが、この Piet のソースコード画像には誤りがあった。本来 HTTP のステータスコードを真似るのなら、認証の失敗には 401 を返さなければならない。しかし、Piet のソースは 403 を返すように書いてしまっていた。そのことに私が気付いたのは PHPerKaigi 2023 が開催されるひと月前で、その時点で私はこの Piet のソースコードを (ちょうどこの記事でそうなっているのと同じように) 読解できなくなっていた。さらに悪いことに、正しいメッセージ「401 Unauthorized」は元の「403 Forbidden」よりも3文字長い。3文字出力が長くなるということは、それだけ Piet で塗るべきピクセルが増えることを意味する。もはや3文字追加で出力するだけの余白はこの画像に残されていなかった (と思う。腕ききの Piet プログラマならできるかもしれないので挑戦してみてほしい)。 diff --git a/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html b/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html index a8f72a21..87efea12 100644 --- a/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html +++ b/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html @@ -14,8 +14,7 @@
<< という文字列をキーに指定することで、マップをマージすることができた。
- x: &base
- a: 123
-# => { "x": { "a": 123 } }
-
-y:
- <<: *base
- b: 456
-# => { "y": { "a": 123, "b": 456 } }
+ x: &base
+ a: 123
+# => { "x": { "a": 123 } }
+
+y:
+ <<: *base
+ b: 456
+# => { "y": { "a": 123, "b": 456 } }
+ 1.2 からはこれができなくなる。 diff --git a/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html b/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html index e929808e..ee924cdc 100644 --- a/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html +++ b/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html @@ -14,8 +14,7 @@
a.txt
- a1
-a2
-a3
+ a1
+a2
+a3
+
b.txt
b1
-b2
-b3
+ b1
+b2
+b3
+
ab.txt
a1
-b1
-a2
-b2
-a3
-b3
+ a1
+b1
+a2
+b2
+a3
+b3
+
ちょうど Python や Haskell などにある zip 関数のような動きをさせたい。
@@ -114,8 +119,10 @@ b3
記事タイトルに書いたように、paste コマンドを使うと実現できる。
$ paste -d '\
-' a.txt b.txt > ab.txt
+ $ paste -d '\
+' a.txt b.txt > ab.txt
+
paste コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら -d で指定した文字で区切って出力する。-d は区切り文字の指定で、デフォルトだとタブ区切りになる。
@@ -125,22 +132,26 @@ b3
ファイル名には - を指定でき、その場合は標準入力から読み込んで出力する。このとき paste - - のように複数回 - を指定すると、指定した回数の行ごとに連結することができる。例えば ab.txt だとこうなる。
$ paste - - < ab.txt
-a1 b1
-a2 b2
-a3 b3
+ $ paste - - < ab.txt
+a1 b1
+a2 b2
+a3 b3
+ これは標準入力を使うとき特有の挙動で、単に同じファイル名を指定してもこうはならない。
-$ paste ab.txt ab.txt
-a1 a1
-b1 b1
-a2 a2
-b2 b2
-a3 a3
-b3 b3
+ $ paste ab.txt ab.txt
+a1 a1
+b1 b1
+a2 a2
+b2 b2
+a3 a3
+b3 b3
+ ときどき便利。 diff --git a/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html b/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html index 5b645245..ef12bbfe 100644 --- a/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html +++ b/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html @@ -14,8 +14,7 @@