From 9d5ec5e3bc01c6174dea048e118edee579c36565 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 7 Feb 2026 23:06:23 +0900 Subject: fix(style): fix codeblock style for rouge --- services/nuldoc/lib/nuldoc/markdown/transform.rb | 5 +- services/nuldoc/public/about/404.html | 2 +- services/nuldoc/public/about/index.html | 2 +- services/nuldoc/public/about/style.css | 14 +- services/nuldoc/public/blog/404.html | 2 +- services/nuldoc/public/blog/posts/2/index.html | 6 +- .../blog/posts/2021-03-05/my-first-post/index.html | 5 +- .../posts/2021-03-30/phperkaigi-2021/index.html | 2 +- .../index.html | 58 +- .../python-unbound-local-error/index.html | 51 +- .../ruby-detect-running-implementation/index.html | 45 +- .../ruby-then-keyword-and-case-in/index.html | 204 +++--- .../rust-where-are-primitive-types-from/index.html | 199 +++--- .../index.html | 41 +- .../vim-swap-order-of-selected-lines/index.html | 42 +- .../2022-04-09/phperkaigi-2022-tokens/index.html | 419 ++++++------ .../index.html | 5 +- .../posts/2022-05-01/phperkaigi-2022/index.html | 2 +- .../php-conference-okinawa-code-golf/index.html | 24 +- .../index.html | 2 +- .../index.html | 753 ++++++++++----------- .../phperkaigi-2023-unused-token-quiz-1/index.html | 105 ++- .../setup-server-for-this-site/index.html | 130 ++-- .../phperkaigi-2023-unused-token-quiz-2/index.html | 95 ++- .../phperkaigi-2023-unused-token-quiz-3/index.html | 333 +++++---- .../rewrite-this-blog-generator/index.html | 2 +- .../index.html | 696 ++++++++++--------- .../2023-04-04/phperkaigi-2023-report/index.html | 2 +- .../2023-06-25/phpconfuk-2023-report/index.html | 2 +- .../compile-php-runtime-to-wasm/index.html | 252 ++++--- .../index.html | 2 +- .../blog/posts/2023-12-03/isucon-13/index.html | 2 +- .../posts/2023-12-31/2023-reflections/index.html | 2 +- .../index.html | 260 ++++--- .../index.html | 100 ++- .../2024-02-10/yapcjapan-2024-report/index.html | 2 +- .../2024-02-22/phpkansai-2024-report/index.html | 2 +- .../2024-03-17/phperkaigi-2024-report/index.html | 2 +- .../posts/2024-03-20/my-bucket-list/index.html | 2 +- .../phpcon-odawara-2024-report/index.html | 2 +- .../pipefail-option-in-gitlab-ci-cd/index.html | 119 ++-- .../index.html | 23 +- .../2024-05-11/phpconkagawa-2024-report/index.html | 2 +- .../2024-06-19/scalamatsuri-2024-report/index.html | 2 +- .../reparojson-fix-only-json-formatter/index.html | 107 ++- .../index.html | 87 ++- .../posts/2024-09-28/mncore-challenge-1/index.html | 2 +- .../posts/2024-12-04/cohackpp-report/index.html | 249 ++++--- .../posts/2024-12-33/2024-reflections/index.html | 2 +- .../phperkaigi-2023-tokens-q1/index.html | 256 ++++--- .../index.html | 19 +- .../phpcon-nagoya-2025-report/index.html | 2 +- .../index.html | 58 +- .../http-1-1-send-multiple-same-headers/index.html | 2 +- .../trick-2025-most-ruby-on-ruby-award/index.html | 153 ++--- .../index.html | 7 +- .../make-tiny-self-hosted-c-compiler/index.html | 80 ++- .../blog/posts/2025-06-14/baba-is-you/index.html | 2 +- .../partial-surrender-to-ebooks/index.html | 2 +- .../index.html | 61 +- .../index.html | 2 +- .../posts/2025-11-27/anybatross-writeup/index.html | 94 ++- .../archive-dynamic-site-with-wget/index.html | 94 ++- .../posts/2025-12-31/2025-reflections/index.html | 2 +- .../development-environment-2026/index.html | 2 +- .../rewrite-this-site-generator-2026/index.html | 2 +- services/nuldoc/public/blog/posts/3/index.html | 2 +- services/nuldoc/public/blog/posts/4/index.html | 2 +- services/nuldoc/public/blog/posts/5/index.html | 6 +- services/nuldoc/public/blog/posts/6/index.html | 6 +- services/nuldoc/public/blog/posts/index.html | 6 +- services/nuldoc/public/blog/style.css | 14 +- services/nuldoc/public/blog/tags/c/index.html | 2 +- services/nuldoc/public/blog/tags/ci-cd/index.html | 2 +- .../nuldoc/public/blog/tags/code-golf/index.html | 2 +- .../nuldoc/public/blog/tags/cohackpp/index.html | 2 +- .../nuldoc/public/blog/tags/composer/index.html | 2 +- .../nuldoc/public/blog/tags/conference/index.html | 2 +- services/nuldoc/public/blog/tags/cpp/index.html | 2 +- services/nuldoc/public/blog/tags/float/index.html | 2 +- services/nuldoc/public/blog/tags/game/index.html | 2 +- services/nuldoc/public/blog/tags/gitlab/index.html | 2 +- services/nuldoc/public/blog/tags/go/index.html | 2 +- services/nuldoc/public/blog/tags/http/index.html | 2 +- services/nuldoc/public/blog/tags/index.html | 2 +- services/nuldoc/public/blog/tags/isucon/index.html | 2 +- services/nuldoc/public/blog/tags/macos/index.html | 2 +- .../public/blog/tags/mncore-challenge/index.html | 2 +- services/nuldoc/public/blog/tags/neovim/index.html | 2 +- .../public/blog/tags/note-to-self/index.html | 2 +- services/nuldoc/public/blog/tags/ouj/index.html | 2 +- services/nuldoc/public/blog/tags/perl/index.html | 2 +- services/nuldoc/public/blog/tags/php/index.html | 2 +- .../public/blog/tags/phpcon-nagoya/index.html | 2 +- .../public/blog/tags/phpcon-odawara/index.html | 2 +- .../nuldoc/public/blog/tags/phpconfuk/index.html | 2 +- .../public/blog/tags/phpconkagawa/index.html | 2 +- .../public/blog/tags/phpconokinawa/index.html | 2 +- .../nuldoc/public/blog/tags/phperkaigi/index.html | 2 +- .../nuldoc/public/blog/tags/phpkansai/index.html | 2 +- services/nuldoc/public/blog/tags/piet/index.html | 2 +- services/nuldoc/public/blog/tags/python/index.html | 2 +- services/nuldoc/public/blog/tags/ruby/index.html | 2 +- .../nuldoc/public/blog/tags/rubykaigi/index.html | 2 +- services/nuldoc/public/blog/tags/rust/index.html | 2 +- services/nuldoc/public/blog/tags/scala/index.html | 2 +- .../public/blog/tags/scalamatsuri/index.html | 2 +- .../nuldoc/public/blog/tags/speedcubing/index.html | 2 +- services/nuldoc/public/blog/tags/trick/index.html | 2 +- services/nuldoc/public/blog/tags/vim/index.html | 2 +- services/nuldoc/public/blog/tags/wasm/index.html | 2 +- .../nuldoc/public/blog/tags/wireguard/index.html | 2 +- services/nuldoc/public/blog/tags/yaml/index.html | 2 +- services/nuldoc/public/blog/tags/yapc/index.html | 2 +- services/nuldoc/public/blog/tags/zsh/index.html | 2 +- services/nuldoc/public/default/404.html | 2 +- services/nuldoc/public/default/index.html | 2 +- services/nuldoc/public/default/style.css | 14 +- services/nuldoc/public/slides/404.html | 2 +- .../2023-01-18/phpstudy-tokyo-148/index.html | 2 +- .../2023-02-15/phpstudy-tokyo-149/index.html | 2 +- .../2023-03-15/phpstudy-tokyo-150/index.html | 2 +- .../slides/2023-03-24/phperkaigi-2023/index.html | 2 +- .../2023-03-25/phperkaigi-2023-tokens/index.html | 2 +- .../2023-04-12/phpstudy-tokyo-151/index.html | 2 +- .../2023-06-21/phpstudy-tokyo-153/index.html | 2 +- .../2023-06-23/phpconfuk-2023-eve/index.html | 2 +- .../2023-07-26/phpstudy-tokyo-154/index.html | 2 +- .../2023-08-24/phpstudy-tokyo-155/index.html | 2 +- .../2023-10-25/phpstudy-tokyo-157/index.html | 2 +- .../2024-01-24/phpstudy-tokyo-160/index.html | 2 +- .../slides/2024-03-08/phperkaigi-2024/index.html | 2 +- .../slides/slides/2024-03-15/ya8-2024/index.html | 2 +- .../2024-04-13/phpcon-odawara-2024/index.html | 2 +- .../2024-04-25/phpstudy-tokyo-163/index.html | 2 +- .../2024-07-18/phpstudy-tokyo-166/index.html | 2 +- .../2024-10-30/phpstudy-tokyo-169/index.html | 2 +- .../slides/slides/2024-11-30/cohackpp/index.html | 2 +- .../2025-02-22/phpcon-nagoya-2025/index.html | 2 +- .../slides/2025-03-23/phperkaigi-2025/index.html | 2 +- .../2025-04-12/phpcon-odawara-2025/index.html | 2 +- .../slides/2025-07-26/techramen-25-conf/index.html | 2 +- .../2025-10-29/phpstudy-tokyo-180/index.html | 2 +- .../slides/2025-11-24/phpconkagawa-2025/index.html | 2 +- services/nuldoc/public/slides/slides/index.html | 2 +- services/nuldoc/public/slides/style.css | 14 +- services/nuldoc/public/slides/tags/c/index.html | 2 +- .../nuldoc/public/slides/tags/cohackpp/index.html | 2 +- .../public/slides/tags/conference/index.html | 2 +- services/nuldoc/public/slides/tags/index.html | 2 +- services/nuldoc/public/slides/tags/php/index.html | 2 +- .../public/slides/tags/phpcon-nagoya/index.html | 2 +- .../public/slides/tags/phpcon-odawara/index.html | 2 +- .../nuldoc/public/slides/tags/phpconfuk/index.html | 2 +- .../public/slides/tags/phpconkagawa/index.html | 2 +- .../public/slides/tags/phperkaigi/index.html | 2 +- .../public/slides/tags/phpstudy-tokyo/index.html | 2 +- .../nuldoc/public/slides/tags/techramen/index.html | 2 +- services/nuldoc/public/slides/tags/wasm/index.html | 2 +- services/nuldoc/public/slides/tags/ya8/index.html | 2 +- services/nuldoc/static/_all/style.css | 6 +- 161 files changed, 2667 insertions(+), 2882 deletions(-) diff --git a/services/nuldoc/lib/nuldoc/markdown/transform.rb b/services/nuldoc/lib/nuldoc/markdown/transform.rb index c11eb541..89bdcbff 100644 --- a/services/nuldoc/lib/nuldoc/markdown/transform.rb +++ b/services/nuldoc/lib/nuldoc/markdown/transform.rb @@ -312,9 +312,10 @@ module Nuldoc lexer = Rouge::Lexer.find(language) || Rouge::Lexers::PlainText.new lexer = lexer.new if lexer.is_a?(Class) formatter = Rouge::Formatters::HTMLInline.new('github.light') + line_formatter = Rouge::Formatters::HTMLLinewise.new(formatter, class: 'codeblock-line') tokens = lexer.lex(source) - inner_html = formatter.format(tokens) - "
#{inner_html}\n
" + inner_html = line_formatter.format(tokens) + "
#{inner_html.chomp.sub(/\n<\/div>\z/, '')}
" end def generate_table_of_contents diff --git a/services/nuldoc/public/about/404.html b/services/nuldoc/public/about/404.html index 466c34d4..0cc57490 100644 --- a/services/nuldoc/public/about/404.html +++ b/services/nuldoc/public/about/404.html @@ -14,7 +14,7 @@ Page Not Found|nsfisis.dev - +
diff --git a/services/nuldoc/public/about/index.html b/services/nuldoc/public/about/index.html index fa6942a0..65b83d9d 100644 --- a/services/nuldoc/public/about/index.html +++ b/services/nuldoc/public/about/index.html @@ -14,7 +14,7 @@ About|nsfisis.dev - +
diff --git a/services/nuldoc/public/about/style.css b/services/nuldoc/public/about/style.css index eba86a34..4090151a 100644 --- a/services/nuldoc/public/about/style.css +++ b/services/nuldoc/public/about/style.css @@ -181,18 +181,18 @@ code { font-size: 0.9rem; } -.shiki code { +.codeblock code { background-color: unset; padding: 0; } /* https://github.com/shikijs/shiki/issues/3 */ -.shiki code { +.codeblock.numbered code { counter-reset: codeblock-line-number; counter-increment: codeblock-line-number 0; } -.numbered .shiki code .line::before { +.codeblock.numbered code .codeblock-line::before { content: counter(codeblock-line-number); counter-increment: codeblock-line-number; width: 2rem; @@ -280,7 +280,7 @@ h1 { .admonition { background-color: #f5f5f5; - border: 1px solid #d1d1d1; + border: 1px solid #ddd; padding: 1rem 1.5rem; margin: 1rem 0; } @@ -335,8 +335,8 @@ img { gap: 1rem; margin: 2rem 0; padding: 1rem 0; - border-top: 1px solid #d1d1d1; - border-bottom: 1px solid #d1d1d1; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } .pagination-page { @@ -368,7 +368,7 @@ img { color: #fff; } -.pagination-elipsis { +.pagination-ellipsis { color: #999; } diff --git a/services/nuldoc/public/blog/404.html b/services/nuldoc/public/blog/404.html index 4fe3c882..054f6a29 100644 --- a/services/nuldoc/public/blog/404.html +++ b/services/nuldoc/public/blog/404.html @@ -14,7 +14,7 @@ Page Not Found|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2/index.html b/services/nuldoc/public/blog/posts/2/index.html index 986ad21c..2c32f9bf 100644 --- a/services/nuldoc/public/blog/posts/2/index.html +++ b/services/nuldoc/public/blog/posts/2/index.html @@ -15,7 +15,7 @@ 投稿一覧 (2ページ目)|REPL: Rest-Eat-Program Loop - +
@@ -56,7 +56,7 @@
3
-
+
@@ -310,7 +310,7 @@
3
-
+
diff --git a/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html b/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html index 8687faeb..a64233b8 100644 --- a/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html +++ b/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html @@ -14,7 +14,7 @@ My First Post|REPL: Rest-Eat-Program Loop - +
@@ -175,8 +175,7 @@
-
puts "Hello, World!"
-
+
puts "Hello, World!"

emph strong diff --git a/services/nuldoc/public/blog/posts/2021-03-30/phperkaigi-2021/index.html b/services/nuldoc/public/blog/posts/2021-03-30/phperkaigi-2021/index.html index a9dc22a6..f9dd00a4 100644 --- a/services/nuldoc/public/blog/posts/2021-03-30/phperkaigi-2021/index.html +++ b/services/nuldoc/public/blog/posts/2021-03-30/phperkaigi-2021/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2021|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/services/nuldoc/public/blog/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html index 03eef3a9..01221534 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html @@ -15,7 +15,7 @@ 【C++】 属性構文の属性名にはキーワードが使える|REPL: Rest-Eat-Program Loop - +
@@ -72,43 +72,40 @@ タイトル落ち。まずはこのコードを見て欲しい。

-
#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; +
}

コンパイラのバージョン

-
$ clang++ –version Apple clang version 11.0.0
-(clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model:
-posix InstalledDir:
-/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
-
+
$ clang++ –version Apple clang version 11.0.0 +
(clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: +
posix InstalledDir: +
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

コンパイルコマンド (C++17 指定)

-
$ clang –std=c++17 hoge.cpp
-
+
$ clang –std=c++17 hoge.cpp

この記事から得られるものはこれ以上ないので以下は蛇足になる。 @@ -138,9 +135,8 @@ posix InstalledDir: 上のコードでは [[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/services/nuldoc/public/blog/posts/2021-10-02/python-unbound-local-error/index.html b/services/nuldoc/public/blog/posts/2021-10-02/python-unbound-local-error/index.html index 22d4740b..b712c378 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/python-unbound-local-error/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/python-unbound-local-error/index.html @@ -15,7 +15,7 @@ 【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment|REPL: Rest-Eat-Program Loop - +

@@ -75,14 +75,13 @@ 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 の箇所でエラーが発生する。 @@ -96,29 +95,27 @@ local変数 x が代入前に参照された、とある。これは、fx を参照するのではなく、新しく別の変数を 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/services/nuldoc/public/blog/posts/2021-10-02/ruby-detect-running-implementation/index.html b/services/nuldoc/public/blog/posts/2021-10-02/ruby-detect-running-implementation/index.html index 45327155..4d9d8dee 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -15,7 +15,7 @@ 【Ruby】 自身を実行している処理系の種類を判定する|REPL: Rest-Eat-Program Loop - +

@@ -81,13 +81,12 @@ 上記ページの例から引用する:

-
$ 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 に良い質問と回答があった。 @@ -97,17 +96,16 @@

-
| RUBY_ENGINE | Implementation    |
-|:-----------:|:------------------|
-| <undefined> | MRI < 1.9         |
-| 'ruby'      | MRI >= 1.9 or REE |
-| 'jruby'     | JRuby             |
-| 'macruby'   | MacRuby           |
-| 'rbx'       | Rubinius          |
-| 'maglev'    | MagLev            |
-| 'ironruby'  | IronRuby          |
-| 'cardinal'  | Cardinal          |
-
+
| RUBY_ENGINE | Implementation | +
|:-----------:|:------------------| +
| <undefined> | MRI < 1.9 | +
| 'ruby' | MRI >= 1.9 or REE | +
| 'jruby' | JRuby | +
| 'macruby' | MacRuby | +
| 'rbx' | Rubinius | +
| 'maglev' | MagLev | +
| 'ironruby' | IronRuby | +
| 'cardinal' | Cardinal |

@@ -123,11 +121,10 @@

version.h
-
/*
- * Ruby engine.
- */
-#define MRUBY_RUBY_ENGINE  "mruby"
-
+
/* +
* Ruby engine. +
*/ +
#define MRUBY_RUBY_ENGINE "mruby"
diff --git a/services/nuldoc/public/blog/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/services/nuldoc/public/blog/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index d4a3070b..fcda0ecc 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -15,7 +15,7 @@ 【Ruby】 then キーワードと case in|REPL: Rest-Eat-Program Loop - +
@@ -103,38 +103,36 @@ 使われることは稀だが、Ruby では then がキーワードになっている。次のように使う:

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

このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase 構文がそれに当たる。 上記のように、何か条件を書いた後 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
@@ -143,19 +141,17 @@ 普通 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; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 @@ -164,9 +160,8 @@ if true puts 'Hello, World!' end ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。

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

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

-
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 などの { も同じ役割を持つ。 @@ -217,41 +209,39 @@ if true puts 'Hello, World!' end

parse.y
-
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 は文字通り inp_top_expr はいわゆるパターン、thenthen キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり then キーワード、;、改行のいずれかである。 @@ -260,38 +250,36 @@ 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 +
end
diff --git a/services/nuldoc/public/blog/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/services/nuldoc/public/blog/posts/2021-10-02/rust-where-are-primitive-types-from/index.html index 3b9f71fa..3978c8eb 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -15,7 +15,7 @@ Rust のプリミティブ型はどこからやって来るか|REPL: Rest-Eat-Program Loop - +
@@ -88,27 +88,26 @@ 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 のソースを追ってみた。 @@ -135,25 +134,23 @@ rustc はセルフホストされている (= rustc 自身が Rust で書かれている) ので、boolchar などで適当に検索をかけてもノイズが多すぎて話にならない。しかし、お誂え向きなことに 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 294659

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

-
$ 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 というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 @@ -162,75 +159,72 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));

rustc_resolve/src/lib.rs
-
/// 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 にも、

-
All other types are defined somewhere and possibly imported, but the
-primitive ones need special handling, since they have no place of
-origin.
-
+
All other types are defined somewhere and possibly imported, but the +
primitive ones need special handling, since they have no place of +
origin.

とある。次はこの 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() で作られた変数) に含まれている識別子 (booli32 など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 @@ -245,14 +239,13 @@ origin. 動作がわかったところで、例として次のコードを考える。

-
#![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()boolstruct bool として解決される。なぜなら、プリミティブ型の判定をする前に bool という名前の別の型が見つかるからだ。 diff --git a/services/nuldoc/public/blog/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/services/nuldoc/public/blog/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index b620d8ab..c13d2535 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -15,7 +15,7 @@ 【Vim】 autocmd events の BufWrite/BufWritePre の違い|REPL: Rest-Eat-Program Loop - +

@@ -146,9 +146,8 @@
src/autocmd.c
-
{"BufAdd",      EVENT_BUFADD},
-{"BufCreate",   EVENT_BUFADD},
-
+
{"BufAdd", EVENT_BUFADD}, +
{"BufCreate", EVENT_BUFADD},

https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97 @@ -157,10 +156,9 @@

src/autocmd.c
-
{"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 @@ -169,10 +167,9 @@

src/autocmd.c
-
{"BufWrite",    EVENT_BUFWRITEPRE},
-{"BufWritePost",    EVENT_BUFWRITEPOST},
-{"BufWritePre", EVENT_BUFWRITEPRE},
-
+
{"BufWrite", EVENT_BUFWRITEPRE}, +
{"BufWritePost", EVENT_BUFWRITEPOST}, +
{"BufWritePre", EVENT_BUFWRITEPRE},
@@ -187,13 +184,12 @@
src/nvim/auevents.lua
-
aliases = {
-  BufCreate = 'BufAdd',
-  BufRead = 'BufReadPost',
-  BufWrite = 'BufWritePre',
-  FileEncoding = 'EncodingChanged',
-},
-
+
aliases = { +
BufCreate = 'BufAdd', +
BufRead = 'BufReadPost', +
BufWrite = 'BufWritePre', +
FileEncoding = 'EncodingChanged', +
},

ところで、上では取り上げなかった FileEncoding だが、これは :help FileEncoding にしっかりと書いてある。 @@ -202,10 +198,9 @@

:help FileEncoding
-
                                                              *FileEncoding*
-FileEncoding                    Obsolete.  It still works and is equivalent
-                                to |EncodingChanged|.
-
+
*FileEncoding* +
FileEncoding Obsolete. It still works and is equivalent +
to |EncodingChanged|.
diff --git a/services/nuldoc/public/blog/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/services/nuldoc/public/blog/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html index 58e78a70..8c00497d 100644 --- a/services/nuldoc/public/blog/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html +++ b/services/nuldoc/public/blog/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html @@ -15,7 +15,7 @@ Vimで選択した行の順番を入れ替える|REPL: Rest-Eat-Program Loop - +
@@ -102,12 +102,11 @@

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
@@ -165,10 +164,9 @@ command! :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

これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 @@ -202,14 +200,13 @@ command! :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>)

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

-
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/services/nuldoc/public/blog/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/services/nuldoc/public/blog/posts/2022-04-09/phperkaigi-2022-tokens/index.html index c9e83d70..3a8e0490 100644 --- a/services/nuldoc/public/blog/posts/2022-04-09/phperkaigi-2022-tokens/index.html +++ b/services/nuldoc/public/blog/posts/2022-04-09/phperkaigi-2022-tokens/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2022 トークン問題の解説|REPL: Rest-Eat-Program Loop - +

@@ -166,76 +166,75 @@
brainf_ck.php
-
<?php
-
-declare(strict_types=0O1);
-
-namespace Dgcircus\PHPerKaigi\Y2022;
-
-/**
- * @todo
- * Run this program to acquire a PHPer token.
- */
-
-https://creativecommons.org/publicdomain/zero/1.0/
-
-\error_reporting(~+!'We are hiring!');
-
-$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
-$id = \spl_object_id(...);
-$put = fn($c) => \printf('%c', $c);
-$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
-
-$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
-$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
-$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
-$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
-$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
-$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
-  +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
-    $b => $loop(++$pc, ++$n),
-    $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
-    default => $loop(++$pc, $n),
-  })($pc, -![])],
-  default => [$mp, ++$pc],
-};
-$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
-  +!![] => [$mp, ++$pc],
-  default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
-    $e => $loop(--$pc, ++$n),
-    $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
-    default => $loop(--$pc, $n),
-  })($pc, -![])],
-};
-$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
-  isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
-)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
-
-$🐘([
-  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $🤡,
-  $👉, $👍, $👍, $👍,
-  $👉, $👍, $👍, $👍, $👍, $👍,
-  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $👈, $👈, $👈, $👈, $👎,
-  $🎪,
-  $👉, $👍, $👍, $👍, $👍, $👍, $📝,
-  $👎, $👎, $📝,
-  $👉, $👎, $👎, $👎, $📝,
-  $👉, $👎, $👎, $👎, $📝,
-  $👎, $👎, $📝,
-  $👎, $📝,
-  $👈, $📝,
-  $👉, $👉, $👎, $👎, $📝,
-  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
-  $👈, $👎, $👎, $👎, $👎, $📝,
-  $👈, $📝,
-  $👉, $👍, $👍, $📝,
-  $👉, $👎, $📝,
-  $👈, $📝,
-]);
-
+
<?php +
+
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 で動かせばトークンが得られる。 @@ -260,29 +259,28 @@ なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。

-
+ + + + + + + + + +
-[
-  > + + +
-  > + + + + +
-  > + + + + + + + + + + + +
-  > + + + + + + + + + +
-  < < < < -
-]
-> + + + + + .
-- - .
-> - - - .
-> - - - .
-- - .
-- .
-< .
-> > - - .
-+ + + + + + + .
-< - - - - .
-< .
-> + + .
-> - .
-< .
-
+
+ + + + + + + + + + +
[ +
> + + + +
> + + + + + +
> + + + + + + + + + + + + +
> + + + + + + + + + + +
< < < < - +
] +
> + + + + + . +
- - . +
> - - - . +
> - - - . +
- - . +
- . +
< . +
> > - - . +
+ + + + + + + . +
< - - - - . +
< . +
> + + . +
> - . +
< .

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

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

完全に合法な PHP のコードである。 https: 部分はラベル、// 以降は行コメントになっている。 @@ -351,12 +348,11 @@ ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。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 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 個足し合わせてももちろん 10 が作れる)。 @@ -394,41 +390,40 @@

riddle.php
-
<?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 を特定する必要がある。 @@ -442,38 +437,33 @@ まずはソースコードを読んでいく。

-
$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文字ごとに区切ったあと、改行で結合している。 @@ -511,52 +501,49 @@ 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 が得られる。 @@ -572,43 +559,41 @@

toquine.php
-
<?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/services/nuldoc/public/blog/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/services/nuldoc/public/blog/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html index e99f28c2..305b6c1c 100644 --- a/services/nuldoc/public/blog/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html +++ b/services/nuldoc/public/blog/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -14,7 +14,7 @@ term-banner: ターミナルにバナーを表示するツールを書いた|REPL: Rest-Eat-Program Loop - +

@@ -81,8 +81,7 @@ こんなものを作った。

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

term-banner が動作している様子のスクリーンショット diff --git a/services/nuldoc/public/blog/posts/2022-05-01/phperkaigi-2022/index.html b/services/nuldoc/public/blog/posts/2022-05-01/phperkaigi-2022/index.html index bef17e01..21d3c29a 100644 --- a/services/nuldoc/public/blog/posts/2022-05-01/phperkaigi-2022/index.html +++ b/services/nuldoc/public/blog/posts/2022-05-01/phperkaigi-2022/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2022|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/services/nuldoc/public/blog/posts/2022-08-27/php-conference-okinawa-code-golf/index.html index 848739b4..08f16b61 100644 --- a/services/nuldoc/public/blog/posts/2022-08-27/php-conference-okinawa-code-golf/index.html +++ b/services/nuldoc/public/blog/posts/2022-08-27/php-conference-okinawa-code-golf/index.html @@ -15,7 +15,7 @@ PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた|REPL: Rest-Eat-Program Loop - +
@@ -144,8 +144,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 バイトとなった (末尾改行を含めずにカウント)。 @@ -154,16 +153,15 @@ こちらは改行とスペースを追加したバージョン:

-
[<?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 ?? []); +
+
?>]
diff --git a/services/nuldoc/public/blog/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html b/services/nuldoc/public/blog/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html index eb7bc81d..534eab4c 100644 --- a/services/nuldoc/public/blog/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html +++ b/services/nuldoc/public/blog/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html @@ -14,7 +14,7 @@ 弊社の PHP Foundation への寄付に寄せて|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/services/nuldoc/public/blog/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html index f55ad018..3eceaa68 100644 --- a/services/nuldoc/public/blog/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html +++ b/services/nuldoc/public/blog/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html @@ -15,7 +15,7 @@ 【PHP】 fizzbuzz を書く。1行あたり2文字で。|REPL: Rest-Eat-Program Loop - +
@@ -158,81 +158,80 @@ 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。

-
#\
-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 +
); +
+
/* あとは同じように普通のプログラムを変形するだけなので省略 */

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

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

とすると $a"\na" になるのだが、余計な改行が入ってしまう。 @@ -295,12 +293,11 @@ 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 とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 @@ -312,15 +309,14 @@ 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_walkrangeprintf といった for よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echo は文 (statement) であり式 (expression) ではないので、式である printf に置き換えた。 @@ -332,19 +328,18 @@ a' rangearray_walkprintf は長すぎるのでどうにかせねばならない。ここで、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 文字に収めるのか。次のテクニックへ移ろう。 @@ -366,28 +361,26 @@ a' というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という 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 での動作を要件に課したところがある。 @@ -402,70 +395,66 @@ a' ずばり、文字列同士のビット演算を使う。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 という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 @@ -481,156 +470,155 @@ $y 完成したものがこちら。

-
<?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 +
)# +
.' +
') +
);
@@ -648,19 +636,18 @@ $i PHP では、バッククォートを使ってシェルを呼び出せる。これは shell_exec 関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。

-
<?php
-
-printf(`
-e\
-c\
-h\
-o\
-\
-1\
-2\
-3\
-`);
-
+
<?php +
+
printf(` +
e\ +
c\ +
h\ +
o\ +
\ +
1\ +
2\ +
3\ +
`);

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

-
<?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\ +
`);

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

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

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

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

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

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

最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 diff --git a/services/nuldoc/public/blog/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/services/nuldoc/public/blog/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html index 62a903dd..e57d15ce 100644 --- a/services/nuldoc/public/blog/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html +++ b/services/nuldoc/public/blog/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 1|REPL: Rest-Eat-Program Loop - +

@@ -102,28 +102,27 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
<?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"; +
}
@@ -132,17 +131,15 @@ ソースを見るとわかるとおり、$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.

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

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

めでたくトークン「#YO」が手に入った。 @@ -165,22 +161,20 @@ 短いので頭から追っていく。

-
 = $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) している。 @@ -189,16 +183,14 @@ 例えば、'656667' だったとすると、656667 に対応した '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 におけるトークンである。 @@ -207,12 +199,11 @@ なお、# を直接書いていないのは、/#.+?) / と書くと、#.+?) という意図せぬトークンが登録されてしまうからである。

-
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/services/nuldoc/public/blog/posts/2022-10-28/setup-server-for-this-site/index.html b/services/nuldoc/public/blog/posts/2022-10-28/setup-server-for-this-site/index.html index 0b079332..9c50825d 100644 --- a/services/nuldoc/public/blog/posts/2022-10-28/setup-server-for-this-site/index.html +++ b/services/nuldoc/public/blog/posts/2022-10-28/setup-server-for-this-site/index.html @@ -15,7 +15,7 @@ 【備忘録】 このサイト用の VPS をセットアップしたときのメモ|REPL: Rest-Eat-Program Loop - +

@@ -176,9 +176,8 @@ ローカルマシンで鍵を生成する。

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

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

-
Host teika
-    HostName **********
-    User **********
-    Port **********
-    IdentityFile ~/.ssh/teika.key
-    IdentitiesOnly yes
-
+
Host teika +
HostName ********** +
User ********** +
Port ********** +
IdentityFile ~/.ssh/teika.key +
IdentitiesOnly yes
@@ -214,27 +212,24 @@ 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。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 の内容をコピーする。 @@ -246,9 +241,8 @@ SSH の設定を変更し、少しでも安全にしておく。

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

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

-
$ ssh teika
-
+
$ ssh teika
@@ -286,12 +278,11 @@ デフォルトの 22 番を閉じ、設定したポートだけ空ける。

-
$ 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 の接続確認を挟む。 @@ -303,46 +294,41 @@ 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
@@ -357,15 +343,13 @@

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

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

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

-
$ sudo adduser ********** docker
-
+
$ sudo adduser ********** docker
@@ -374,36 +358,32 @@ 80 番と 443 番を空ける。

-
$ 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

certbot で証明書取得

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

サーバを稼動させる

-
$ make serve
-
+
$ make serve
diff --git a/services/nuldoc/public/blog/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html b/services/nuldoc/public/blog/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html index 72ae576b..c8a6ef29 100644 --- a/services/nuldoc/public/blog/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html +++ b/services/nuldoc/public/blog/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 2|REPL: Rest-Eat-Program Loop - +
@@ -105,17 +105,16 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?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 (自分自身と同じソースコードを出力するプログラム) になっている。 @@ -127,49 +126,46 @@ 実行してみると、次のような出力が得られる。

-
#
-<?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」が手に入った。 @@ -184,8 +180,7 @@ W 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 で、ゼロ幅スペースである。 @@ -207,15 +202,13 @@ W 続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは 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/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html index d21af422..ecf1e478 100644 --- a/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html +++ b/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 3|REPL: Rest-Eat-Program Loop - +

@@ -121,119 +121,118 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
<?php
-try {
-  f(g() / __LINE__);
-} catch (Throwable $e) {
-  while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23);
-  echo "\n";
-}
-function f(int $i) {
-  if ($i < 0) f();
-  try {
-    match ($i) {
-      0 => 0 / 0,
-
-
-
-      15, 36 => 0 / 0,
-      14 => 0 / 0,
-      37 => 0 / 0,
-
-
-
-
-
-
-
-
-
-
-      6 => 0 / 0,
-
-      5 => 0 / 0,
-
-      22 => 0 / 0,
-
-
-
-
-      34, 35 => 0 / 0,
-
-
-
-
-
-
-
-
-      25 => 0 / 0,
-      17, 21 => 0 / 0,
-
-      24, 32 => 0 / 0,
-
-
-
-
-
-
-
-      33 => 0 / 0,
-
-      16 => 0 / 0,
-
-
-      18 => 0 / 0,
-
-
-
-
-
-
-
-
-      7 => 0 / 0,
-
-      2 => 0 / 0,
-      1, 20 => 0 / 0,
-      10, 28 => 0 / 0,
-      8, 12, 26 => 0 / 0,
-      4, 9, 13 => 0 / 0,
-
-
-
-
-
-      31 => 0 / 0,
-
-      29 => 0 / 0,
-
-      11 => 0 / 0,
-
-
-
-      3, 19, 23 => 0 / 0,
-
-
-      27 => 0 / 0,
-
-      30 => 0 / 0,
-    };
-  } finally {
-    f($i - 1);
-  }
-}
-
-
-
-
-
-
-
-function g() {
-  return __LINE__;
-}
-
+
<?php +
try { +
f(g() / __LINE__); +
} catch (Throwable $e) { +
while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23); +
echo "\n"; +
} +
function f(int $i) { +
if ($i < 0) f(); +
try { +
match ($i) { +
0 => 0 / 0, +
+
+
+
15, 36 => 0 / 0, +
14 => 0 / 0, +
37 => 0 / 0, +
+
+
+
+
+
+
+
+
+
+
6 => 0 / 0, +
+
5 => 0 / 0, +
+
22 => 0 / 0, +
+
+
+
+
34, 35 => 0 / 0, +
+
+
+
+
+
+
+
+
25 => 0 / 0, +
17, 21 => 0 / 0, +
+
24, 32 => 0 / 0, +
+
+
+
+
+
+
+
33 => 0 / 0, +
+
16 => 0 / 0, +
+
+
18 => 0 / 0, +
+
+
+
+
+
+
+
+
7 => 0 / 0, +
+
2 => 0 / 0, +
1, 20 => 0 / 0, +
10, 28 => 0 / 0, +
8, 12, 26 => 0 / 0, +
4, 9, 13 => 0 / 0, +
+
+
+
+
+
31 => 0 / 0, +
+
29 => 0 / 0, +
+
11 => 0 / 0, +
+
+
+
3, 19, 23 => 0 / 0, +
+
+
27 => 0 / 0, +
+
30 => 0 / 0, +
}; +
} finally { +
f($i - 1); +
} +
} +
+
+
+
+
+
+
+
function g() { +
return __LINE__; +
}

“Catchline” と名付けた作品。実行するとトークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') が得られる。 @@ -267,21 +266,20 @@ このうち 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 +
}

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

-
<?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 で出力している。 @@ -311,8 +308,7 @@ フォーマット指定子 %c は、整数を ASCII コード[1] と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==')b であれば、ASCII コード 98 なので、75 行目で発生したエラー、

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

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

-
function f(int $i) {
-  if ($i < 0) f();
-  try {
-    match ($i) {
-      0 => 0 / 0, // 12 行目
-
-
-
-      15, 36 => 0 / 0,
-      14 => 0 / 0,
-      37 => 0 / 0,
-
-      // (略)
-
-      30 => 0 / 0, // 97 行目
-    };
-  } finally {
-    f($i - 1);
-  }
-}
-
+
function f(int $i) { +
if ($i < 0) f(); +
try { +
match ($i) { +
0 => 0 / 0, // 12 行目 +
+
+
+
15, 36 => 0 / 0, +
14 => 0 / 0, +
37 => 0 / 0, +
+
// (略) +
+
30 => 0 / 0, // 97 行目 +
}; +
} finally { +
f($i - 1); +
} +
}

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

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

f() には 111 / 337 が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら f() を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 diff --git a/services/nuldoc/public/blog/posts/2023-03-10/rewrite-this-blog-generator/index.html b/services/nuldoc/public/blog/posts/2023-03-10/rewrite-this-blog-generator/index.html index c1b43466..db9fea79 100644 --- a/services/nuldoc/public/blog/posts/2023-03-10/rewrite-this-blog-generator/index.html +++ b/services/nuldoc/public/blog/posts/2023-03-10/rewrite-this-blog-generator/index.html @@ -15,7 +15,7 @@ このブログのジェネレータを書き直した|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html b/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html index 1d323964..4b482fb7 100644 --- a/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html +++ b/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html @@ -14,7 +14,7 @@ PNG 画像の最小構成エンコーダを実装する|REPL: Rest-Eat-Program Loop - +
@@ -135,45 +135,44 @@ 以下のソースコードをベースにする。今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ 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) +
}

以降は、writeSignaturewriteChunkIhdr などを実装していく。 @@ -216,22 +215,21 @@ 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 バイトを書き込む。 @@ -260,57 +258,55 @@ 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)) +
}

仕様どおり、chunkTypedata から CRC を計算し、data の長さと合わせて書き込んでいる。PNG では基本的に big endian を使うことに注意する。 @@ -388,21 +384,20 @@ 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは 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()) +
}
@@ -436,23 +431,22 @@ Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている ( 9. Appendix: Sample code ) ので、Go に移植する。

-
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 の実装をサボるために使う。 @@ -478,31 +472,30 @@ 実際にこの手抜き 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() +
}
@@ -517,21 +510,20 @@ 先ほどの 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())) +
}
@@ -544,10 +536,9 @@ 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単:

-
func writeChunkIend(w io.Writer) {
-	writeChunk(w, "IEND", nil)
-}
-
+
func writeChunkIend(w io.Writer) { +
writeChunk(w, "IEND", nil) +
}
@@ -557,181 +548,180 @@ 最後に全ソースコードを再掲しておく。

-
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) +
}
diff --git a/services/nuldoc/public/blog/posts/2023-04-04/phperkaigi-2023-report/index.html b/services/nuldoc/public/blog/posts/2023-04-04/phperkaigi-2023-report/index.html index 8061db33..1d451e16 100644 --- a/services/nuldoc/public/blog/posts/2023-04-04/phperkaigi-2023-report/index.html +++ b/services/nuldoc/public/blog/posts/2023-04-04/phperkaigi-2023-report/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2023-06-25/phpconfuk-2023-report/index.html b/services/nuldoc/public/blog/posts/2023-06-25/phpconfuk-2023-report/index.html index 32cc00e0..5cf3ab36 100644 --- a/services/nuldoc/public/blog/posts/2023-06-25/phpconfuk-2023-report/index.html +++ b/services/nuldoc/public/blog/posts/2023-06-25/phpconfuk-2023-report/index.html @@ -15,7 +15,7 @@ PHP カンファレンス福岡 2023 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2023-10-02/compile-php-runtime-to-wasm/index.html b/services/nuldoc/public/blog/posts/2023-10-02/compile-php-runtime-to-wasm/index.html index 7879f5df..33ad3d2b 100644 --- a/services/nuldoc/public/blog/posts/2023-10-02/compile-php-runtime-to-wasm/index.html +++ b/services/nuldoc/public/blog/posts/2023-10-02/compile-php-runtime-to-wasm/index.html @@ -15,7 +15,7 @@ PHP の処理系を Emscripten で WebAssembly にコンパイルする|REPL: Rest-Eat-Program Loop - +
@@ -142,19 +142,18 @@
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) を作成する。 @@ -168,31 +167,30 @@ 先ほどのコードでも使っていたエントリポイントである 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点ある。 @@ -215,16 +213,15 @@ デフォルトの出力方法は index.mjs の中で PHPWasm() を呼ぶとき、stdoutstderr というオプションを渡せば変更できる。

-
const { ccall } = await PHPWasm({
-  stdout: (c) => {
-    if (c === null) {
-      // flush the standard output.
-    } else {
-      // output c to the standard output.
-    }
-  },
-});
-
+
const { ccall } = await PHPWasm({ +
stdout: (c) => { +
if (c === null) { +
// flush the standard output. +
} else { +
// output c to the standard output. +
} +
}, +
});

cnull か 1バイト符号つき整数を取り、null が flush 要求を意味する。 @@ -244,52 +241,49 @@ まずは 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 && \ +
:

ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。 @@ -313,23 +307,22 @@ さて、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 && \ +
:

emcccc (C コンパイラ/リンカ) の Emscripten 版で、-c は「コンパイル」の意。-o-I は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。 @@ -338,19 +331,18 @@ libphp.aphp-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 \ +
;

それぞれのフラグについて解説する。 @@ -383,15 +375,14 @@ といっても、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"]
@@ -401,13 +392,12 @@ Dockerfilephp-wasm.cindex.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
diff --git a/services/nuldoc/public/blog/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html b/services/nuldoc/public/blog/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html index a2749ba9..af1d4335 100644 --- a/services/nuldoc/public/blog/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html +++ b/services/nuldoc/public/blog/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html @@ -15,7 +15,7 @@ 放送大学に入学しました|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2023-12-03/isucon-13/index.html b/services/nuldoc/public/blog/posts/2023-12-03/isucon-13/index.html index 0447e7fb..bbf6aab4 100644 --- a/services/nuldoc/public/blog/posts/2023-12-03/isucon-13/index.html +++ b/services/nuldoc/public/blog/posts/2023-12-03/isucon-13/index.html @@ -15,7 +15,7 @@ ISUCON 13 に参加した|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html b/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html index 126add6f..d8ba47f7 100644 --- a/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html +++ b/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html @@ -14,7 +14,7 @@ 2023年の振り返り|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html b/services/nuldoc/public/blog/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html index 46780269..8e538579 100644 --- a/services/nuldoc/public/blog/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html +++ b/services/nuldoc/public/blog/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html @@ -15,7 +15,7 @@ 【Neovim】 空の PHP ファイルに namespace 宣言を挿入する|REPL: Rest-Eat-Program Loop - +
@@ -100,20 +100,18 @@ Neovim で空の PHP ファイルを開いたとき、そのファイルが置かれているディレクトリの構造に基づいて、自動的に 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 には対応していない。 @@ -137,14 +135,13 @@ 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
@@ -176,120 +173,119 @@ LuaJIT 2.1.1693350652 実装を簡単にするため、Composer を用いない場合や PSR 4 以外のオートロード規則を使う場合には対応しない。少々長くなるが、以下にスクリプト全文を載せる。

-
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
diff --git a/services/nuldoc/public/blog/posts/2024-02-03/install-wireguard-on-personal-server/index.html b/services/nuldoc/public/blog/posts/2024-02-03/install-wireguard-on-personal-server/index.html index 60a72a00..68b5a90a 100644 --- a/services/nuldoc/public/blog/posts/2024-02-03/install-wireguard-on-personal-server/index.html +++ b/services/nuldoc/public/blog/posts/2024-02-03/install-wireguard-on-personal-server/index.html @@ -15,7 +15,7 @@ 【備忘録】 個人用サーバに WireGuard を導入する|REPL: Rest-Eat-Program Loop - +
@@ -119,16 +119,14 @@ まずは個人用サービスをホストしている Ubuntu のサーバに WireGuard をインストールする。

-
$ 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}
@@ -137,28 +135,26 @@ $ sudo chmod 600 /etc/wireguard/server.{key,pub} 公式サイトから各 OS 向けのクライアントソフトウェアを入手し、インストールする。次に、設定をおこなう。

-
# クライアント 1 の場合
-[Interface]
-Address = 10.10.1.2/32
-PrivateKey = <クライアント 1 の秘密鍵>
-
-[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
-
+
# クライアント 2 の場合 +
[Interface] +
Address = 10.10.1.3/32 +
PrivateKey = <クライアント 2 の秘密鍵> +
+
[Peer] +
PublicKey = <サーバの公開鍵> +
AllowedIPs = <サーバの外部 IP アドレス>/32 +
Endpoint = <サーバの外部 IP アドレス>:51820

PrivateKeyPublicKey は鍵ファイルのパスではなく中身を書くことに注意。 @@ -170,32 +166,29 @@ $ sudo chmod 600 /etc/wireguard/server.{key,pub} 一度サーバへ戻り、WireGuard の設定ファイルを書く。

-
$ sudo vim /etc/wireguard/wg0.conf
-
+
$ 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
-
+
[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
@@ -204,26 +197,23 @@ $ sudo systemctl start wg-quick@wg0 続けてファイアウォールを設定する。まずは WireGuard が使用する UDP のポートを開き、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
diff --git a/services/nuldoc/public/blog/posts/2024-02-10/yapcjapan-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-02-10/yapcjapan-2024-report/index.html index 2040806a..3563c3aa 100644 --- a/services/nuldoc/public/blog/posts/2024-02-10/yapcjapan-2024-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-02-10/yapcjapan-2024-report/index.html @@ -15,7 +15,7 @@ YAPC::Hiroshima 2024 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-02-22/phpkansai-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-02-22/phpkansai-2024-report/index.html index cfe88e49..cadb6c54 100644 --- a/services/nuldoc/public/blog/posts/2024-02-22/phpkansai-2024-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-02-22/phpkansai-2024-report/index.html @@ -15,7 +15,7 @@ PHPカンファレンス関西 2024 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-03-17/phperkaigi-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-03-17/phperkaigi-2024-report/index.html index 1e0f129d..26eb9c8d 100644 --- a/services/nuldoc/public/blog/posts/2024-03-17/phperkaigi-2024-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-03-17/phperkaigi-2024-report/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2024 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-03-20/my-bucket-list/index.html b/services/nuldoc/public/blog/posts/2024-03-20/my-bucket-list/index.html index b23b1392..716b280e 100644 --- a/services/nuldoc/public/blog/posts/2024-03-20/my-bucket-list/index.html +++ b/services/nuldoc/public/blog/posts/2024-03-20/my-bucket-list/index.html @@ -14,7 +14,7 @@ 死ぬまでに作る自作○○一覧あるいは人生の TODO リスト|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html index 42d76723..d11414dc 100644 --- a/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html @@ -15,7 +15,7 @@ PHP カンファレンス小田原 2024 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html b/services/nuldoc/public/blog/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html index 97ea0199..8d2370e6 100644 --- a/services/nuldoc/public/blog/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html +++ b/services/nuldoc/public/blog/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html @@ -15,7 +15,7 @@ 【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている|REPL: Rest-Eat-Program Loop - +
@@ -116,15 +116,14 @@ 例:

-
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 以外になる) と、即座に実行が停止され、ジョブは失敗する。 @@ -133,15 +132,14 @@ では、次のようなケースだとどうなるか。

-
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 になる。 @@ -153,11 +151,10 @@ 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。

-
# On にする
-set -o pipefail
-# Off にする
-set +o pipefail
-
+
# On にする +
set -o pipefail +
# Off にする +
set +o pipefail

こうすると、パイプ全体が失敗するようになる。この設定は、デフォルトだと off になっている。 @@ -170,15 +167,14 @@ 次のような 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 になっていた。 @@ -187,21 +183,20 @@ しかし、先述したように 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 は無効になっている。 @@ -216,10 +211,9 @@ 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")
@@ -228,17 +222,16 @@ set +o pipefail 通常の Bash スクリプトを書く場合と同様に、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
diff --git a/services/nuldoc/public/blog/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html b/services/nuldoc/public/blog/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html index 065a11da..118c0d58 100644 --- a/services/nuldoc/public/blog/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html +++ b/services/nuldoc/public/blog/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html @@ -15,7 +15,7 @@ 【Zsh】 Composer のカスタムコマンドに対する Zsh 補完で引数にファイルを補完させる|REPL: Rest-Eat-Program Loop - +
@@ -116,12 +116,11 @@ このことは、先ほどリンクを載せた _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
@@ -139,8 +138,7 @@ まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。以下は ~/.zshrc にすべて書く前提だが、autoload を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。

-
compdef _my_composer composer composer.phar
-
+
compdef _my_composer composer composer.phar

compdef は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。この場合は、composer コマンドや composer.phar コマンドに対して _my_composer を使って補完をおこなうよう定義している。 @@ -149,10 +147,9 @@ 次に _my_composer を定義する。基本的にはデフォルトの composer コマンドの補完関数 (つまり _composer 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。

-
function _my_composer() {
-    _composer "$@" || _files "$@"
-}
-
+
function _my_composer() { +
_composer "$@" || _files "$@" +
}

_composer コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら _files を呼び出す。_files は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。 diff --git a/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html index 7f4108cf..d04b02c4 100644 --- a/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html @@ -15,7 +15,7 @@ PHP カンファレンス香川 2024 参加レポ|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2024-06-19/scalamatsuri-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-06-19/scalamatsuri-2024-report/index.html index ed185463..558ffbcb 100644 --- a/services/nuldoc/public/blog/posts/2024-06-19/scalamatsuri-2024-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-06-19/scalamatsuri-2024-report/index.html @@ -15,7 +15,7 @@ ScalaMatsuri 2024 参加レポ|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html b/services/nuldoc/public/blog/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html index 4856480e..009ca8f9 100644 --- a/services/nuldoc/public/blog/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html +++ b/services/nuldoc/public/blog/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html @@ -15,7 +15,7 @@ reparojson: 文法エラーを直すだけの JSON フォーマッタを作った|REPL: Rest-Eat-Program Loop - +
@@ -106,18 +106,17 @@ 次のように動作する。

-
$ 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 時点で修正対象の文法エラーは次のとおり: @@ -149,34 +148,33 @@ $ echo '{ "foo": 1, "bar": 2, }' | reparojson ここでは、 nvim-lspconfigefm-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 を使う際のボイラープレートだが、formatCommand-q フラグを指定していることに注意してほしい。このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。そのため、-q フラグを指定して、修正されたときも exit code 0 で終了するようにしている。 @@ -188,31 +186,28 @@ $ echo '{ "foo": 1, "bar": 2, }' | reparojson このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、

-
   {
-      "a": true,
-      "b": false
-   }
-
+
{ +
"a": true, +
"b": false +
}

2行目と3行目を入れ換えて以下のように編集した。

-
   {
-      "b": false
-      "a": true,
-   }
-
+
{ +
"b": false +
"a": true, +
}

これは不正な JSON だが、このツールを通せば次のようになる。

-
   {
-      "b": false,
-      "a": true
-   }
-
+
{ +
"b": false, +
"a": true +
}

もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。しかし、単なる行の入れ換えであれば ddp の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。 diff --git a/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html b/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html index 5bdb0cc9..951f1c81 100644 --- a/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html +++ b/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html @@ -15,7 +15,7 @@ 【Go】 text/template の with や range の内側から外側の "." にアクセスする|REPL: Rest-Eat-Program Loop - +

@@ -90,20 +90,19 @@ Go には、標準ライブラリにテンプレートライブラリ text/template がある。この text/template における制御構造、withrange は次のように使われる。

-
# {{ .Title }}
-
-# User
-
-{{ with .User }}
-  {{ .Name }} ({{ .ID }})
-{{ end }}
-
-# Items
-
-{{ range .Items }}
-  - {{ . }}
-{{ end }}
-
+
# {{ .Title }} +
+
# User +
+
{{ with .User }} +
{{ .Name }} ({{ .ID }}) +
{{ end }} +
+
# Items +
+
{{ range .Items }} +
- {{ . }} +
{{ end }}

text/template. は、現在の操作対象を表す特殊なオブジェクトである。 @@ -115,19 +114,18 @@ つまりこのテンプレートは、次のような構造をレンダリングしている (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", +
}, +
})
@@ -136,14 +134,13 @@ 今回おこないたいのは、withrange の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。

-
{{ with .User }}
-  ここから .Title を参照するには?
-{{ end }}
-
-{{ range .Items }}
-  ここから .User を参照するには?
-{{ end }}
-
+
{{ with .User }} +
ここから .Title を参照するには? +
{{ end }} +
+
{{ range .Items }} +
ここから .User を参照するには? +
{{ end }}

withrange は、. を自身の対象オブジェクトに変更するので、単に {{ with .User }} の中で .Title と書いても、それは UserTitle プロパティを参照しているとみなされる。 @@ -152,8 +149,7 @@ text/template では変数が使えるので、テンプレートの先頭で

-
{{ $params := . }}
-
+
{{ $params := . }}

とでもしておけば実現は可能である。 @@ -168,14 +164,13 @@ 常にトップレベルを指す特殊変数 $ を使えばよい。

-
{{ with .User }}
-  {{ $.Title }}
-{{ end }}
-
-{{ range .Items }}
-  {{ $.User.Name }}
-{{ end }}
-
+
{{ with .User }} +
{{ $.Title }} +
{{ end }} +
+
{{ range .Items }} +
{{ $.User.Name }} +
{{ end }}

$ は、テンプレートが実行されるときに渡されたオブジェクトを指す。これを使えば現在の . に関係なくトップレベルを参照できる。 diff --git a/services/nuldoc/public/blog/posts/2024-09-28/mncore-challenge-1/index.html b/services/nuldoc/public/blog/posts/2024-09-28/mncore-challenge-1/index.html index 3e0d0fef..c7e54b71 100644 --- a/services/nuldoc/public/blog/posts/2024-09-28/mncore-challenge-1/index.html +++ b/services/nuldoc/public/blog/posts/2024-09-28/mncore-challenge-1/index.html @@ -15,7 +15,7 @@ MN-Core Challenge #1 参加レポ|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2024-12-04/cohackpp-report/index.html b/services/nuldoc/public/blog/posts/2024-12-04/cohackpp-report/index.html index e864775d..15496f4d 100644 --- a/services/nuldoc/public/blog/posts/2024-12-04/cohackpp-report/index.html +++ b/services/nuldoc/public/blog/posts/2024-12-04/cohackpp-report/index.html @@ -15,7 +15,7 @@ 紅白ぺぱ合戦に参加&LTしました|REPL: Rest-Eat-Program Loop - +
@@ -157,130 +157,129 @@ https://github.com/nsfisis/cohackpp/blob/main/congrats.php

-
<?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));
diff --git a/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html b/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html index 86f96380..0453296c 100644 --- a/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html +++ b/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html @@ -14,7 +14,7 @@ 2024年の振り返り|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html b/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html index 510f3062..25859d88 100644 --- a/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html +++ b/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023 トークン問題解説 (1/5)|REPL: Rest-Eat-Program Loop - +
@@ -167,8 +167,7 @@ まずはトークンを得る方法を解説抜きで説明する。次のように実行する。

-
$ echo "#iwillblog" | php Q1.png >/dev/null
-
+
$ echo "#iwillblog" | php Q1.png >/dev/null

無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。 @@ -182,8 +181,7 @@ まずは素直に画像として見てみよう。全体は QR コードになっている。適当な QR コードリーダで読み込むと、次のようなテキストが表示されるはずだ。

-
Guess password. $ echo "password" | php Q1.png >/dev/null
-
+
Guess password. $ echo "password" | php Q1.png >/dev/null

メッセージは、この画像の実行方法とこの問題でやるべきこと (パスワードの推測) を示している。 @@ -198,9 +196,8 @@ 不正なパスワードを使って実行してみると、次のようなエラーメッセージが表示される。

-
$ echo "foo" | php Q1.png >/dev/null
-401 Unauthorized
-
+
$ echo "foo" | php Q1.png >/dev/null +
401 Unauthorized

すでに 「解き方」の節 で示したように、パスワードである PHPer トークンは「#iwillblog」である。これを与えて実行すると正解のトークンが得られる。 @@ -270,25 +267,23 @@ 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; +
// (以下略)

IHDRIEND が 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 と指定されている。 @@ -303,108 +298,107 @@ $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」 などを参照してほしい。 @@ -413,8 +407,7 @@ $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 の命令になっている。 @@ -466,12 +459,11 @@ $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/services/nuldoc/public/blog/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html b/services/nuldoc/public/blog/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html index 83ae95f3..2df63490 100644 --- a/services/nuldoc/public/blog/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html +++ b/services/nuldoc/public/blog/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html @@ -15,7 +15,7 @@ 【YAML】YAML 1.1 と YAML 1.2 の主な破壊的変更|REPL: Rest-Eat-Program Loop - +

@@ -128,15 +128,14 @@ YAML 1.1 では、<< という文字列をキーに指定することで、マップをマージすることができた。

-
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/services/nuldoc/public/blog/posts/2025-02-24/phpcon-nagoya-2025-report/index.html b/services/nuldoc/public/blog/posts/2025-02-24/phpcon-nagoya-2025-report/index.html index 5801dab7..356df5af 100644 --- a/services/nuldoc/public/blog/posts/2025-02-24/phpcon-nagoya-2025-report/index.html +++ b/services/nuldoc/public/blog/posts/2025-02-24/phpcon-nagoya-2025-report/index.html @@ -15,7 +15,7 @@ PHP カンファレンス名古屋 2025 参加レポ|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2025-03-27/zip-function-like-command-paste-command/index.html b/services/nuldoc/public/blog/posts/2025-03-27/zip-function-like-command-paste-command/index.html index 052365e5..14e09c96 100644 --- a/services/nuldoc/public/blog/posts/2025-03-27/zip-function-like-command-paste-command/index.html +++ b/services/nuldoc/public/blog/posts/2025-03-27/zip-function-like-command-paste-command/index.html @@ -15,7 +15,7 @@ zip 関数のようなコマンド paste|REPL: Rest-Eat-Program Loop - +
@@ -80,31 +80,28 @@ 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 関数のような動きをさせたい。 @@ -116,9 +113,8 @@ b3 記事タイトルに書いたように、paste コマンドを使うと実現できる。

-
$ paste -d '\
-' a.txt b.txt > ab.txt
-
+
$ paste -d '\ +
' a.txt b.txt > ab.txt

paste コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら -d で指定した文字で区切って出力する。-d は区切り文字の指定で、デフォルトだとタブ区切りになる。 @@ -127,24 +123,22 @@ 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/services/nuldoc/public/blog/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html b/services/nuldoc/public/blog/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html index 39545759..436b3823 100644 --- a/services/nuldoc/public/blog/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html +++ b/services/nuldoc/public/blog/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html @@ -15,7 +15,7 @@ 【HTTP】HTTP/1.1 で同じヘッダを2回送るとどうなるか|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html b/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html index 79e31d70..01cecbe3 100644 --- a/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html +++ b/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html @@ -15,7 +15,7 @@ RubyKaigi 2025 の TRICK で入賞した|REPL: Rest-Eat-Program Loop - +
@@ -161,8 +161,7 @@ 表示している。つまり、Ruby プログラムにルビを振った作品である。例えば、先頭の2行目の require は次のような HTML で構成されている。

-
<ruby class="IDENTIFIER">require<rp class="">(</rp><rt class="">リクワイア</rt><rp class="">)</rp></ruby>
-
+
<ruby class="IDENTIFIER">require<rp class="">(</rp><rt class="">リクワイア</rt><rp class="">)</rp></ruby>

順に使ったテクニックを解説していく。 @@ -173,12 +172,11 @@ 改めて quine について説明する。Quine とは、自身のソースコードを出力するようなプログラムである。Ruby では様々な方法で quine を書くことができるが、この作品で使っている基本形は以下のようなものである。

-
eval $s=<<'EOS'
-print "eval $s=<<'EOS'\n"
-print $s
-print "EOS\n"
-EOS
-
+
eval $s=<<'EOS' +
print "eval $s=<<'EOS'\n" +
print $s +
print "EOS\n" +
EOS

変数 $s に 2 行目、3 行目、4 行目が入っており、それに加えて 1 行目と 5 行目を出力すれば元のソースコードが得られる。実際には $s を加工してシンタックスハイライトや振り仮名を振ることになる。 @@ -193,24 +191,23 @@ print "EOS\nPrism を利用している。Prism.lex() を使うとトークナイズができるので、トークンに付いているソースコード位置の情報を使いつつ元のソースコードを復元する。

-
y = 1                 # 現在の行
-x = 0                 # 現在の列
-Prism.lex($s).value[..-2].each {|t, *|
-  l = t.location
-  r = l.start_line    # トークンの開始行
-  if y < r            # 改行が必要なら
-    p "\n" * (r - y)  #   改行を挿入して
-    x = 0             #   列の先頭へ戻る
-  end
-  c = l.start_column  # トークンの開始列
-  if x < c            # 空白が必要なら
-    p " " * (c - x)   #   空白を挿入
-  end
-  p ruby(t)           # トークン本体を出力
-  y = l.end_line      # 現在行を更新
-  x = l.end_column    # 現在列を更新
-}
-
+
y = 1 # 現在の行 +
x = 0 # 現在の列 +
Prism.lex($s).value[..-2].each {|t, *| +
l = t.location +
r = l.start_line # トークンの開始行 +
if y < r # 改行が必要なら +
p "\n" * (r - y) # 改行を挿入して +
x = 0 # 列の先頭へ戻る +
end +
c = l.start_column # トークンの開始列 +
if x < c # 空白が必要なら +
p " " * (c - x) # 空白を挿入 +
end +
p ruby(t) # トークン本体を出力 +
y = l.end_line # 現在行を更新 +
x = l.end_column # 現在列を更新 +
}

補足: 変数名がやたら短いのは、このあとの振り仮名データの量を削減するため。 @@ -219,21 +216,20 @@ print "EOS\n -

    <style>
-      /* ... */
-
-      .COMMENT {
-        color: #777;
-        font-style: italic;
-      }
-
-      .CONSTANT, .GLOBAL_VARIABLE, .INSTANCE_VARIABLE, .IDENTIFIER {
-        color: #088;
-      }
-
-      /* ... */
-    </style>
-
+
<style> +
/* ... */ +
+
.COMMENT { +
color: #777; +
font-style: italic; +
} +
+
.CONSTANT, .GLOBAL_VARIABLE, .INSTANCE_VARIABLE, .IDENTIFIER { +
color: #088; +
} +
+
/* ... */ +
</style>

トークン種別の列挙にはそれなりに文字数を使ってしまうのだが、今回の TRICK のレギュレーションでは index.html にサイズ制限がなかったので好きに色を付けることができた。 @@ -245,28 +241,27 @@ print "EOS\n -

def rt(t)
-  r = {
-    :"&&" => "1136",
-    :"=" => "04199275",
-    :"||" => "623147",
-    :$s => "41750825",
-    :* => "111775",
-    # ...
-    type: "310455",
-    utf_8: "70923803920853080440",
-    value: "48746992",
-    x: "08351525",
-    y: "7904",
-  }
-  kana(
-    r[:"#{t.type}"] ||
-    r[s = :"#{t.value.downcase}"] ||
-    s.end_with?(":") && r[:"#{s[..-2]}"] ||
-    nil
-  )
-end
-
+
def rt(t) +
r = { +
:"&&" => "1136", +
:"=" => "04199275", +
:"||" => "623147", +
:$s => "41750825", +
:* => "111775", +
# ... +
type: "310455", +
utf_8: "70923803920853080440", +
value: "48746992", +
x: "08351525", +
y: "7904", +
} +
kana( +
r[:"#{t.type}"] || +
r[s = :"#{t.value.downcase}"] || +
s.end_with?(":") && r[:"#{s[..-2]}"] || +
nil +
) +
end

トークンの種類 (t.type) またはトークンの文字列表現そのもの (t.value.downcase) を使ってテーブルを引いて振り仮名へ変換している。このテーブルのキー部分そのものにも振り仮名を振るために、トークンが : で終わっていれば : を取り除いて振り仮名を得ている (例: "value:""value""48746992")。 @@ -275,27 +270,25 @@ print "EOS\n -

def kana(s)
-  s
-    &.scan(/.{2}/)
-    &.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)}
-    &.*("")
-end
-
+
def kana(s) +
s +
&.scan(/.{2}/) +
&.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)} +
&.*("") +
end

例えば value に対応する振り仮名データ "48746992" であれば、次のような変換を経て振り仮名へと展開される。

-
  s
-    # => "48746992"
-    &.scan(/.{2}/)
-    # => ["48", "74", "69", "92"]
-    &.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)}
-    # => ["バ", "リ", "ュ", "ー"]
-    &.*("")
-    # => "バリュー"
-
+
s +
# => "48746992" +
&.scan(/.{2}/) +
# => ["48", "74", "69", "92"] +
&.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)} +
# => ["バ", "リ", "ュ", "ー"] +
&.*("") +
# => "バリュー"

これは後で気付いたのだが、Ruby は多倍長整数が扱えるので "48746992" のようなデータは単に 48746992 と書けばよかった。kana() 関数が多少長くはなるが、振り仮名データの数 x 2 バイト分サイズが減るのでこちらの方が短くなる。サイズ制限の都合で振り仮名を振るのを諦めた記号もあったのでもったいない。 diff --git a/services/nuldoc/public/blog/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos/index.html b/services/nuldoc/public/blog/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos/index.html index fa50b368..e881f68c 100644 --- a/services/nuldoc/public/blog/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos/index.html +++ b/services/nuldoc/public/blog/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos/index.html @@ -15,7 +15,7 @@ 【Composer】 composer-patches v2 では macOS でも GNU patch のインストールが不要になる (予定)|REPL: Rest-Eat-Program Loop - +

@@ -126,9 +126,8 @@ ワークアラウンドとして、macOS にも GNU patch をインストールしてしまうという方法がある。例:

-
$ brew install gpatch
-$ echo 'PATH="/opt/homebrew/opt/gpatch/libexec/gnubin:$PATH"' >> ~/.zshrc
-
+
$ brew install gpatch +
$ echo 'PATH="/opt/homebrew/opt/gpatch/libexec/gnubin:$PATH"' >> ~/.zshrc

GNU patch を Homebrew などの手段でインストールし、BSD patch よりも優先されるパスに配置すれば問題が解消する。 diff --git a/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html b/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html index 4d828f45..79e31d17 100644 --- a/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html +++ b/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html @@ -15,7 +15,7 @@ セルフホスト可能な C コンパイラを作った|REPL: Rest-Eat-Program Loop - +

@@ -359,10 +359,9 @@ compilerbook では整数一つのパース・コード生成から始めるが、今回は以下のようなソースをパースしてコード生成するところからスタートすることにした。

-
int main() {
-    return 42;
-}
-
+
int main() { +
return 42; +
}

この時点で、struct Tokenstruct Parserstruct AstNodestruct CodeGen といった主要なデータ構造が定義され、この後もほぼ同じソース設計のまま進めている。 @@ -409,24 +408,23 @@ 一日の終わりには、次のようなプログラムのテストが通るようになった。

-
int printf();
-
-int main() {
-    int i;
-    for (i = 1; i <= 100; i = i + 1) {
-        if (i % 15 == 0) {
-            printf("FizzBuzz\n");
-        } else if (i % 3 == 0) {
-            printf("Fizz\n");
-        } else if (i % 5 == 0) {
-            printf("Buzz\n");
-        } else {
-            printf("%d\n", i);
-        }
-    }
-    return 0;
-}
-
+
int printf(); +
+
int main() { +
int i; +
for (i = 1; i <= 100; i = i + 1) { +
if (i % 15 == 0) { +
printf("FizzBuzz\n"); +
} else if (i % 3 == 0) { +
printf("Fizz\n"); +
} else if (i % 5 == 0) { +
printf("Buzz\n"); +
} else { +
printf("%d\n", i); +
} +
} +
return 0; +
}
@@ -515,14 +513,13 @@ 記念すべき (?) 最後のバグはこちら。

-
         gen_expr(g, ast->expr1, GEN_RVAL);
-     } else {
-         gen_expr(g, ast->expr1, GEN_RVAL);
--        gen_lval2rval(ast->expr1->ty);
-+        gen_lval2rval(ast->expr1->ty->to);
-     }
- }
-
+
gen_expr(g, ast->expr1, GEN_RVAL); +
} else { +
gen_expr(g, ast->expr1, GEN_RVAL); +
- gen_lval2rval(ast->expr1->ty); +
+ gen_lval2rval(ast->expr1->ty->to); +
} +
}

メモリアドレスから参照先の値を得る際、その型によってロードする命令の種類を変える必要があるのだが、その切替をポインタ型でおこなっていた。正しくは、そのポインタ型が指す型を元にして切り替えなければならない。 @@ -537,17 +534,16 @@ 一体どこが異なるのか。hexdump の差分がこちら。

-
$ diff -u <(hexdump -C p4dcc2) <(hexdump -C p4dcc3)
-@@ -5090,7 +5090,7 @@
- 00015db0  72 72 61 79 5f 65 6e 74  72 79 00 66 72 61 6d 65  |rray_entry.frame|
- 00015dc0  5f 64 75 6d 6d 79 00 5f  5f 66 72 61 6d 65 5f 64  |_dummy.__frame_d|
- 00015dd0  75 6d 6d 79 5f 69 6e 69  74 5f 61 72 72 61 79 5f  |ummy_init_array_|
--00015de0  65 6e 74 72 79 00 63 63  6d 69 42 49 59 6b 2e 6f  |entry.ccmiBIYk.o|
-+00015de0  65 6e 74 72 79 00 63 63  53 71 64 47 76 57 2e 6f  |entry.ccSqdGvW.o|
- 00015df0  00 66 61 74 61 6c 5f 65  72 72 6f 72 00 72 65 61  |.fatal_error.rea|
- 00015e00  64 5f 61 6c 6c 00 74 6f  6b 65 6e 69 7a 65 00 74  |d_all.tokenize.t|
- 00015e10  79 70 65 5f 6e 65 77 00  74 79 70 65 5f 6e 65 77  |ype_new.type_new|
-
+
$ diff -u <(hexdump -C p4dcc2) <(hexdump -C p4dcc3) +
@@ -5090,7 +5090,7 @@ +
00015db0 72 72 61 79 5f 65 6e 74 72 79 00 66 72 61 6d 65 |rray_entry.frame| +
00015dc0 5f 64 75 6d 6d 79 00 5f 5f 66 72 61 6d 65 5f 64 |_dummy.__frame_d| +
00015dd0 75 6d 6d 79 5f 69 6e 69 74 5f 61 72 72 61 79 5f |ummy_init_array_| +
-00015de0 65 6e 74 72 79 00 63 63 6d 69 42 49 59 6b 2e 6f |entry.ccmiBIYk.o| +
+00015de0 65 6e 74 72 79 00 63 63 53 71 64 47 76 57 2e 6f |entry.ccSqdGvW.o| +
00015df0 00 66 61 74 61 6c 5f 65 72 72 6f 72 00 72 65 61 |.fatal_error.rea| +
00015e00 64 5f 61 6c 6c 00 74 6f 6b 65 6e 69 7a 65 00 74 |d_all.tokenize.t| +
00015e10 79 70 65 5f 6e 65 77 00 74 79 70 65 5f 6e 65 77 |ype_new.type_new|

fatal_errorread_alltokenize type_new はいずれも main.c で定義された関数の名前である。このことから考えると、これは GCC が埋め込んだシンボルテーブルである可能性が高い。わずかに異なっている 6バイトは、ランダム生成された何かのように見える。 diff --git a/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html b/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html index fbeac4b9..1a1cd0c9 100644 --- a/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html +++ b/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html @@ -15,7 +15,7 @@ 最高のパズルゲーム Baba Is You をやれ|REPL: Rest-Eat-Program Loop - +

diff --git a/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html b/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html index eb022a41..897cffa5 100644 --- a/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html +++ b/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html @@ -14,7 +14,7 @@ 電子書籍への部分的降伏|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2025-10-31/representing-single-value-with-half-open-float-interval/index.html b/services/nuldoc/public/blog/posts/2025-10-31/representing-single-value-with-half-open-float-interval/index.html index 2c383346..535e9e49 100644 --- a/services/nuldoc/public/blog/posts/2025-10-31/representing-single-value-with-half-open-float-interval/index.html +++ b/services/nuldoc/public/blog/posts/2025-10-31/representing-single-value-with-half-open-float-interval/index.html @@ -15,7 +15,7 @@ 浮動小数点数の半開区間で単一値を表現する|REPL: Rest-Eat-Program Loop - +
@@ -152,9 +152,8 @@ 1p のビット列での表現を見てみよう。

-
1 = 0011111111110000000000000000000000000000000000000000000000000000
-p = 0011111111110000000000000000000000000000000000000000000000000001
-
+
1 = 0011111111110000000000000000000000000000000000000000000000000000 +
p = 0011111111110000000000000000000000000000000000000000000000000001

p1 よりも一つ分だけ大きいのがわかるだろうか (ここでは binary64 の具体的な表現について言及していないのでそうなる保証はないのだが、あくまで雰囲気として)。 @@ -183,39 +182,37 @@ p = 0011111111110000000000000000000000000000000000000000000000000001 binary64 を 64 bit の整数に変換できるなら、他の言語でもほとんど同じ方法で実装できるはずだ。

-
    public static function nextUp(float $x): float
-    {
-        // NaN (Not a Number) なら NaN を返す。
-        if (is_nan($x)) {
-            return NAN;
-        }
-        // 正の無限大なら正の無限大を返す。
-        if (is_infinite($x) && $x > 0) {
-            return INF;
-        }
-        // 0 なら minValue() を返す (後述)。
-        if ($x === 0.0) {
-            return self::minValue();
-        }
-        // binary64 を 64 bit 整数に変換する。
-        $u = self::floatToInt($x);
-        // 正なら整数に +1 して binary64 に戻す。
-        // 負なら整数に -1 して binary64 に戻す。
-        return $x > 0.0 ? self::intToFloat($u + 1) : self::intToFloat($u - 1);
-    }
-
+
public static function nextUp(float $x): float +
{ +
// NaN (Not a Number) なら NaN を返す。 +
if (is_nan($x)) { +
return NAN; +
} +
// 正の無限大なら正の無限大を返す。 +
if (is_infinite($x) && $x > 0) { +
return INF; +
} +
// 0 なら minValue() を返す (後述)。 +
if ($x === 0.0) { +
return self::minValue(); +
} +
// binary64 を 64 bit 整数に変換する。 +
$u = self::floatToInt($x); +
// 正なら整数に +1 して binary64 に戻す。 +
// 負なら整数に -1 して binary64 に戻す。 +
return $x > 0.0 ? self::intToFloat($u + 1) : self::intToFloat($u - 1); +
}

0 のときに返している minValue() は次のような値である。

-
    public static function minValue(): float
-    {
-        // 整数の 1 を binary64 と解釈した値を返す。
-        // binary64 で表せる最小の正の非正規化数。
-        return self::intToFloat(1);
-    }
-
+
public static function minValue(): float +
{ +
// 整数の 1 を binary64 と解釈した値を返す。 +
// binary64 で表せる最小の正の非正規化数。 +
return self::intToFloat(1); +
}
diff --git a/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html b/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html index 68b470f6..95dd435f 100644 --- a/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html +++ b/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html @@ -15,7 +15,7 @@ ルービックキューブを目隠しで揃えることに初成功した|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2025-11-27/anybatross-writeup/index.html b/services/nuldoc/public/blog/posts/2025-11-27/anybatross-writeup/index.html index c3b577e1..98f66370 100644 --- a/services/nuldoc/public/blog/posts/2025-11-27/anybatross-writeup/index.html +++ b/services/nuldoc/public/blog/posts/2025-11-27/anybatross-writeup/index.html @@ -15,7 +15,7 @@ カヤックさん開催のコードゴルフコンテスト Anybatross に参加して優勝した|REPL: Rest-Eat-Program Loop - +
@@ -132,8 +132,7 @@

回答 (45 byte)

-
print$a+=$\=y/8B/0/+y/0469ADO-R//.$/,","for<>
-
+
print$a+=$\=y/8B/0/+y/0469ADO-R//.$/,","for<>

Hole 1 については同一言語・同一スコアの回答が複数あるので詳細は省略する。 @@ -151,11 +150,10 @@ 最終スコアを見ると 4 位タイ (107 byte) が多く、3 位以上の回答と明確にアルゴリズムの差があるのでここから解説をスタートしようと思う。

-
s=gets
-?A.upto(?Z){(b,),m=s.scan(/(?=(.\B.))/).tally.max_by{_2}
-m>1&&(s.gsub!b,it;$*<<it+?:+b)}
-puts$**?,,s
-
+
s=gets +
?A.upto(?Z){(b,),m=s.scan(/(?=(.\B.))/).tally.max_by{_2} +
m>1&&(s.gsub!b,it;$*<<it+?:+b)} +
puts$**?,,s

変数名などの細かい差異を除けば他の 107 byte 回答と同じだが、 String#scan に渡す正規表現にこれを採用していたのは私だけだったのではないだろうか。 /(?=(\S\S))//(?=(\w\w))/ と比べて短くはならないので意味はない。 @@ -170,10 +168,9 @@ puts$**?,,s Enumerable#max_by で最頻値を取ってきた後は、多重代入を使って必要な値を取り出している。

-
x = [["la"], 3]
-(b,),m = x
-# => b = "la", m = 3
-
+
x = [["la"], 3] +
(b,),m = x +
# => b = "la", m = 3

置換テーブルのデータは $* へと追加しているが、これは Ruby の特殊変数で、本来は Object::ARGV を指す。ここでは単に最初から空配列で初期化されている便利な入れ物として用いている。 @@ -188,11 +185,10 @@ puts$**?,,s 回答 A をぐっと睨むと、m>1&&(...) の括弧を削りたくなる。しかしそれには m>1&& がどうしても邪魔になる。というわけで終了条件を工夫することでなんとか m を排除できないかを考えた。それがこちら。

-
s=gets
-?A.upto(?Z){(b,),=(?_+s).scan(/(?=(.\B.))/).tally.max_by{_2}
-$*<<it+?:+b if s.gsub!b,it}
-puts$**?,,s
-
+
s=gets +
?A.upto(?Z){(b,),=(?_+s).scan(/(?=(.\B.))/).tally.max_by{_2} +
$*<<it+?:+b if s.gsub!b,it} +
puts$**?,,s

s の先頭に番兵 _ を置くことで、bi-gram の出現頻度がすべて 1 になったとき、b へと代入される値が「_ + (s の先頭の文字)」になる。これを String#gsub! で置き換えようとすると、そのような文字列は s 中にないので置換が発生しない。String#gsub! は置換が起きなかったとき nil を返すので、これを使って条件分岐ができる。&& だと優先度の関係から String#gsub! の括弧が省略できないが、後置 if なら省略できる。 @@ -207,11 +203,10 @@ puts$**?,,s Kernel#gets は、入力を特殊変数 $_ へ代入する。これは Perl 由来の挙動で、Ruby にはいくつか $_ を参照するものがある。これを使って変数 s を置き換えると次のようになる。

-
gets
-?A.upto(?Z){(b,),="_#$_".scan(/(?=(.\B.))/).tally.max_by{_2}
-$*<<it+?:+b if$_.gsub!b,it}
-puts$**?,,$_
-
+
gets +
?A.upto(?Z){(b,),="_#$_".scan(/(?=(.\B.))/).tally.max_by{_2} +
$*<<it+?:+b if$_.gsub!b,it} +
puts$**?,,$_

これで 1 byte 縮む。 @@ -223,10 +218,9 @@ puts$**?,,$_ 回答 C を眺めると、b への代入に文字を費やしすぎている。これを String#gsub! の第一引数に直接書いてはどうか。更に、直前のマッチしたパターンを指す特殊変数 $& を使えば、変数 b を排除できる。それがこちら。

-
gets
-?A.upto(?Z){$*<<it+?:+$&if$_.gsub!"_#$_".scan(/(?=(.\B.))/).tally.max_by{_2}[0][0],it}
-puts$**?,,$_
-
+
gets +
?A.upto(?Z){$*<<it+?:+$&if$_.gsub!"_#$_".scan(/(?=(.\B.))/).tally.max_by{_2}[0][0],it} +
puts$**?,,$_

これにより 2 bytes も一気に縮まった。 @@ -238,10 +232,9 @@ puts$**?,,$_ 回答 D を提出したことで tompng 氏のスコアを越え、氏のコードを閲覧できるようになった。そこから少し変更したものが、mame 氏と (変数名などの些事を除いて) 同じ以下のコードである。

-
s=gets
-?A.upto(?Z){s.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or($*<<it+?:+b;s.gsub!b,it)}
-puts$**?,,s
-
+
s=gets +
?A.upto(?Z){s.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or($*<<it+?:+b;s.gsub!b,it)} +
puts$**?,,s

ここまでとは大きく異なる戦略で終了条件を判定している。使われているのはパターンマッチで、in がマッチの有無を true / false で返すことを利用している。or を用いて、最頻値の出現回数が 1 でないなら置換処理を継続する。 @@ -250,11 +243,10 @@ puts$**?,,s パターンマッチの利用については途中何度か検討したが、1 でないときに処理を実行するという方針で実装しようとしてしまい、上手く短縮できなかった。

-
# これは 106 byte
-s=gets
-?A.upto(?Z){s.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],2..and($*<<it+?:+b;s.gsub!b,it)}
-puts$**?,,s
-
+
# これは 106 byte +
s=gets +
?A.upto(?Z){s.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],2..and($*<<it+?:+b;s.gsub!b,it)} +
puts$**?,,s
@@ -274,11 +266,10 @@ puts$**?,,s ruby-p を付けると、以下のようなコードを書いたかのように動作する。

-
while gets
-  ... # 記載したコードの処理
-  puts $_
-end
-
+
while gets +
... # 記載したコードの処理 +
puts $_ +
end

また、Kernel#gsub という $_ = $_.gsub(...) と同様の処理をおこなうメソッドが生えてくる。今回は String#gsub! も使うので、shebang の分を回収できれば短縮になりそうだ。 @@ -287,20 +278,18 @@ puts$**?,,s というわけで、実はこれまでも shebang での短縮は何度か試していた。しかし、いずれも 1 byte 増えたり変化しなかったりで成果を上げられずにいた。回答 E についても同様に、以下のようなコードを作っていた。

-
#!ruby -p
-?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or($*<<it+?:+b;gsub b,it)}
-puts$**?,
-
+
#!ruby -p +
?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or($*<<it+?:+b;gsub b,it)} +
puts$**?,

しかしこれは 103 byte で縮められない。にっくきは gsubb の間のスペースである。せっかく s.gsub!gsub にしたのに、後ろが記号でなくなったことでスペースが生じている。といって、括弧を付けるのも上手くはいかない。

-
# これも同じく 103 byte
-#!ruby -p
-?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or$*<<it+?:+b&&gsub(b,it)}
-puts$**?,
-
+
# これも同じく 103 byte +
#!ruby -p +
?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or$*<<it+?:+b&&gsub(b,it)} +
puts$**?,

外側の括弧を移動させてくれば gsubb の間のスペースを消せるが、;&& にせねばならず失敗する。この問題を解決したのが最終回答の 102 byte コードである。 @@ -309,10 +298,9 @@ puts$**?,

最終回答 (102 byte)

-
#!ruby -p
-?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or$*<<it+?:+b%gsub(b,it)}
-puts$**?,
-
+
#!ruby -p +
?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or$*<<it+?:+b%gsub(b,it)} +
puts$**?,

String#% は文字列のフォーマット処理をおこなう演算子だが、ここでは特にフォーマット目的で呼んでいるわけではない。ここで重要なのは、この演算子が特に副作用を持たず、どんな型でも右辺に取れることである。b の中身にフォーマット指定子はない (% などの記号が入力されないことが問題文から分かる) ので、誤って動作を壊してしまうおそれもない。 diff --git a/services/nuldoc/public/blog/posts/2025-12-06/archive-dynamic-site-with-wget/index.html b/services/nuldoc/public/blog/posts/2025-12-06/archive-dynamic-site-with-wget/index.html index 13a7761e..988abaa5 100644 --- a/services/nuldoc/public/blog/posts/2025-12-06/archive-dynamic-site-with-wget/index.html +++ b/services/nuldoc/public/blog/posts/2025-12-06/archive-dynamic-site-with-wget/index.html @@ -14,7 +14,7 @@ wget を使って動的サイトを静的サイトにアーカイブする|REPL: Rest-Eat-Program Loop - +

@@ -101,42 +101,41 @@ 今回使用したスクリプトはこちら: https://github.com/nsfisis/phperkaigi-2024-albatross-archive/blob/cc837f6d2109555e2392016e8f6820fb5fd46dd6/archive.sh

-
# 指定した URL からスタートしてリンクを辿りながら全ファイルをファイルに書き出す
-#
-# --mirror  リンクを再帰的に辿ってダウンロードする
-# --page-requisites  CSS や画像等も含めて HTML から参照されている全ファイルをダウンロードする
-# --convert-links  リンクを相対リンクへ変換する
-# --adjust-extension  URL に拡張子が無くてもいい感じに推測する
-# --no-parent  親ディレクトリは見に行かない
-# --no-wait=1  リクエスト間で 1 秒待機する
-# -P ./archive/  指定したディレクトリに保存する
-wget \
-    --mirror \
-    --page-requisites \
-    --convert-links \
-    --adjust-extension \
-    --no-parent \
-    --wait=1 \
-    -P ./archive/ \
-    https://t.nil.ninja/phperkaigi/2024/golf/
-
-# ディレクトリ構造を調整する
-mv ./archive/t.nil.ninja/phperkaigi/2024/golf/* ./archive
-rmdir ./archive/t.nil.ninja/phperkaigi/2024/golf/
-rmdir ./archive/t.nil.ninja/phperkaigi/2024/
-rmdir ./archive/t.nil.ninja/phperkaigi/
-rmdir ./archive/t.nil.ninja/
-
-mkdir -p ./archive/api/quizzes/{1,2,3}
-
-# 動的な API エンドポイントを叩いて結果を JSON ファイルとして保存する
-wget -O ./archive/api/quizzes/1/chart.json https://t.nil.ninja/phperkaigi/2024/golf/api/quizzes/1/chart
-wget -O ./archive/api/quizzes/2/chart.json https://t.nil.ninja/phperkaigi/2024/golf/api/quizzes/2/chart
-wget -O ./archive/api/quizzes/3/chart.json https://t.nil.ninja/phperkaigi/2024/golf/api/quizzes/3/chart
-
-# 上記 API を叩いている箇所を、落としてきた静的ファイルを参照するように変更する
-sed -i -e 's#/chart`#/chart.json`#' ./archive/assets/chart.js
-
+
# 指定した URL からスタートしてリンクを辿りながら全ファイルをファイルに書き出す +
# +
# --mirror リンクを再帰的に辿ってダウンロードする +
# --page-requisites CSS や画像等も含めて HTML から参照されている全ファイルをダウンロードする +
# --convert-links リンクを相対リンクへ変換する +
# --adjust-extension URL に拡張子が無くてもいい感じに推測する +
# --no-parent 親ディレクトリは見に行かない +
# --no-wait=1 リクエスト間で 1 秒待機する +
# -P ./archive/ 指定したディレクトリに保存する +
wget \ +
--mirror \ +
--page-requisites \ +
--convert-links \ +
--adjust-extension \ +
--no-parent \ +
--wait=1 \ +
-P ./archive/ \ +
https://t.nil.ninja/phperkaigi/2024/golf/ +
+
# ディレクトリ構造を調整する +
mv ./archive/t.nil.ninja/phperkaigi/2024/golf/* ./archive +
rmdir ./archive/t.nil.ninja/phperkaigi/2024/golf/ +
rmdir ./archive/t.nil.ninja/phperkaigi/2024/ +
rmdir ./archive/t.nil.ninja/phperkaigi/ +
rmdir ./archive/t.nil.ninja/ +
+
mkdir -p ./archive/api/quizzes/{1,2,3} +
+
# 動的な API エンドポイントを叩いて結果を JSON ファイルとして保存する +
wget -O ./archive/api/quizzes/1/chart.json https://t.nil.ninja/phperkaigi/2024/golf/api/quizzes/1/chart +
wget -O ./archive/api/quizzes/2/chart.json https://t.nil.ninja/phperkaigi/2024/golf/api/quizzes/2/chart +
wget -O ./archive/api/quizzes/3/chart.json https://t.nil.ninja/phperkaigi/2024/golf/api/quizzes/3/chart +
+
# 上記 API を叩いている箇所を、落としてきた静的ファイルを参照するように変更する +
sed -i -e 's#/chart`#/chart.json`#' ./archive/assets/chart.js

このように wget に適切なオプションを渡すことで、指定したページから遷移可能なページを再帰的に辿っていき、サイト内の全ページをファイルへ落とすことができる。今回のサイトにはページ遷移では辿り着けないページがあったが (管理画面など)、運用が停止している今そういったページはアーカイブしなくてもよい。 @@ -145,17 +144,16 @@ wget -O ./archive/api/quizzes/3/chart.json h あとはこのファイルを適当にサーブしてやればよい。

-
server {
-    listen 80 default;
-    listen [::]:80;
-
-    charset UTF-8;
-
-    location /phperkaigi/2024/golf/ {
-        alias /archive/;
-    }
-}
-
+
server { +
listen 80 default; +
listen [::]:80; +
+
charset UTF-8; +
+
location /phperkaigi/2024/golf/ { +
alias /archive/; +
} +
}
diff --git a/services/nuldoc/public/blog/posts/2025-12-31/2025-reflections/index.html b/services/nuldoc/public/blog/posts/2025-12-31/2025-reflections/index.html index 9f4a5b7d..75a78bf8 100644 --- a/services/nuldoc/public/blog/posts/2025-12-31/2025-reflections/index.html +++ b/services/nuldoc/public/blog/posts/2025-12-31/2025-reflections/index.html @@ -14,7 +14,7 @@ 2025 年の振り返り|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2026-01-16/development-environment-2026/index.html b/services/nuldoc/public/blog/posts/2026-01-16/development-environment-2026/index.html index 128d5209..06bc03f4 100644 --- a/services/nuldoc/public/blog/posts/2026-01-16/development-environment-2026/index.html +++ b/services/nuldoc/public/blog/posts/2026-01-16/development-environment-2026/index.html @@ -15,7 +15,7 @@ 開発環境現状確認 2026|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/2026-02-01/rewrite-this-site-generator-2026/index.html b/services/nuldoc/public/blog/posts/2026-02-01/rewrite-this-site-generator-2026/index.html index 905cf5b5..65abf235 100644 --- a/services/nuldoc/public/blog/posts/2026-02-01/rewrite-this-site-generator-2026/index.html +++ b/services/nuldoc/public/blog/posts/2026-02-01/rewrite-this-site-generator-2026/index.html @@ -15,7 +15,7 @@ このサイトの静的サイトジェネレータを書き直した (2026)|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/3/index.html b/services/nuldoc/public/blog/posts/3/index.html index ccd30e91..428565cd 100644 --- a/services/nuldoc/public/blog/posts/3/index.html +++ b/services/nuldoc/public/blog/posts/3/index.html @@ -15,7 +15,7 @@ 投稿一覧 (3ページ目)|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/4/index.html b/services/nuldoc/public/blog/posts/4/index.html index 0bab68b4..a0969660 100644 --- a/services/nuldoc/public/blog/posts/4/index.html +++ b/services/nuldoc/public/blog/posts/4/index.html @@ -15,7 +15,7 @@ 投稿一覧 (4ページ目)|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/posts/5/index.html b/services/nuldoc/public/blog/posts/5/index.html index dffcbc52..b70c17cd 100644 --- a/services/nuldoc/public/blog/posts/5/index.html +++ b/services/nuldoc/public/blog/posts/5/index.html @@ -15,7 +15,7 @@ 投稿一覧 (5ページ目)|REPL: Rest-Eat-Program Loop - +
@@ -50,7 +50,7 @@
1
-
+
@@ -287,7 +287,7 @@
1
-
+
diff --git a/services/nuldoc/public/blog/posts/6/index.html b/services/nuldoc/public/blog/posts/6/index.html index 39dcda0d..429ef75e 100644 --- a/services/nuldoc/public/blog/posts/6/index.html +++ b/services/nuldoc/public/blog/posts/6/index.html @@ -15,7 +15,7 @@ 投稿一覧 (6ページ目)|REPL: Rest-Eat-Program Loop - +
@@ -50,7 +50,7 @@
1
-
+
@@ -276,7 +276,7 @@
1
-
+
diff --git a/services/nuldoc/public/blog/posts/index.html b/services/nuldoc/public/blog/posts/index.html index 43aab702..8bd417c7 100644 --- a/services/nuldoc/public/blog/posts/index.html +++ b/services/nuldoc/public/blog/posts/index.html @@ -15,7 +15,7 @@ 投稿一覧 (1ページ目)|REPL: Rest-Eat-Program Loop - +
@@ -52,7 +52,7 @@
2
-
+
@@ -268,7 +268,7 @@
2
-
+
diff --git a/services/nuldoc/public/blog/style.css b/services/nuldoc/public/blog/style.css index eba86a34..4090151a 100644 --- a/services/nuldoc/public/blog/style.css +++ b/services/nuldoc/public/blog/style.css @@ -181,18 +181,18 @@ code { font-size: 0.9rem; } -.shiki code { +.codeblock code { background-color: unset; padding: 0; } /* https://github.com/shikijs/shiki/issues/3 */ -.shiki code { +.codeblock.numbered code { counter-reset: codeblock-line-number; counter-increment: codeblock-line-number 0; } -.numbered .shiki code .line::before { +.codeblock.numbered code .codeblock-line::before { content: counter(codeblock-line-number); counter-increment: codeblock-line-number; width: 2rem; @@ -280,7 +280,7 @@ h1 { .admonition { background-color: #f5f5f5; - border: 1px solid #d1d1d1; + border: 1px solid #ddd; padding: 1rem 1.5rem; margin: 1rem 0; } @@ -335,8 +335,8 @@ img { gap: 1rem; margin: 2rem 0; padding: 1rem 0; - border-top: 1px solid #d1d1d1; - border-bottom: 1px solid #d1d1d1; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } .pagination-page { @@ -368,7 +368,7 @@ img { color: #fff; } -.pagination-elipsis { +.pagination-ellipsis { color: #999; } diff --git a/services/nuldoc/public/blog/tags/c/index.html b/services/nuldoc/public/blog/tags/c/index.html index 4ada2b09..a9838d82 100644 --- a/services/nuldoc/public/blog/tags/c/index.html +++ b/services/nuldoc/public/blog/tags/c/index.html @@ -16,7 +16,7 @@ タグ「C」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/ci-cd/index.html b/services/nuldoc/public/blog/tags/ci-cd/index.html index 64043793..40c97935 100644 --- a/services/nuldoc/public/blog/tags/ci-cd/index.html +++ b/services/nuldoc/public/blog/tags/ci-cd/index.html @@ -16,7 +16,7 @@ タグ「CI/CD」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/code-golf/index.html b/services/nuldoc/public/blog/tags/code-golf/index.html index 2b2f0b71..57675938 100644 --- a/services/nuldoc/public/blog/tags/code-golf/index.html +++ b/services/nuldoc/public/blog/tags/code-golf/index.html @@ -16,7 +16,7 @@ タグ「コードゴルフ」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/cohackpp/index.html b/services/nuldoc/public/blog/tags/cohackpp/index.html index 419d2753..25c0afa7 100644 --- a/services/nuldoc/public/blog/tags/cohackpp/index.html +++ b/services/nuldoc/public/blog/tags/cohackpp/index.html @@ -16,7 +16,7 @@ タグ「紅白ぺぱ合戦」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/composer/index.html b/services/nuldoc/public/blog/tags/composer/index.html index f2714a58..a4bbc409 100644 --- a/services/nuldoc/public/blog/tags/composer/index.html +++ b/services/nuldoc/public/blog/tags/composer/index.html @@ -16,7 +16,7 @@ タグ「Composer」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/conference/index.html b/services/nuldoc/public/blog/tags/conference/index.html index 2d44739f..f9c31b4c 100644 --- a/services/nuldoc/public/blog/tags/conference/index.html +++ b/services/nuldoc/public/blog/tags/conference/index.html @@ -16,7 +16,7 @@ タグ「カンファレンス」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/cpp/index.html b/services/nuldoc/public/blog/tags/cpp/index.html index 8116b0b1..2ea2ccbe 100644 --- a/services/nuldoc/public/blog/tags/cpp/index.html +++ b/services/nuldoc/public/blog/tags/cpp/index.html @@ -16,7 +16,7 @@ タグ「C++」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/float/index.html b/services/nuldoc/public/blog/tags/float/index.html index 4f707cec..73fe0116 100644 --- a/services/nuldoc/public/blog/tags/float/index.html +++ b/services/nuldoc/public/blog/tags/float/index.html @@ -16,7 +16,7 @@ タグ「浮動小数点数」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/game/index.html b/services/nuldoc/public/blog/tags/game/index.html index 074196c4..5db38213 100644 --- a/services/nuldoc/public/blog/tags/game/index.html +++ b/services/nuldoc/public/blog/tags/game/index.html @@ -16,7 +16,7 @@ タグ「ゲーム」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/gitlab/index.html b/services/nuldoc/public/blog/tags/gitlab/index.html index 98d8d0c0..151dadf0 100644 --- a/services/nuldoc/public/blog/tags/gitlab/index.html +++ b/services/nuldoc/public/blog/tags/gitlab/index.html @@ -16,7 +16,7 @@ タグ「GitLab」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/go/index.html b/services/nuldoc/public/blog/tags/go/index.html index dbbfa94e..6b39c553 100644 --- a/services/nuldoc/public/blog/tags/go/index.html +++ b/services/nuldoc/public/blog/tags/go/index.html @@ -16,7 +16,7 @@ タグ「Go」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/http/index.html b/services/nuldoc/public/blog/tags/http/index.html index f73599a8..dbe4e080 100644 --- a/services/nuldoc/public/blog/tags/http/index.html +++ b/services/nuldoc/public/blog/tags/http/index.html @@ -16,7 +16,7 @@ タグ「HTTP」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/index.html b/services/nuldoc/public/blog/tags/index.html index 18c78531..57a7523e 100644 --- a/services/nuldoc/public/blog/tags/index.html +++ b/services/nuldoc/public/blog/tags/index.html @@ -14,7 +14,7 @@ タグ一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/isucon/index.html b/services/nuldoc/public/blog/tags/isucon/index.html index c44aa5cc..46de16b5 100644 --- a/services/nuldoc/public/blog/tags/isucon/index.html +++ b/services/nuldoc/public/blog/tags/isucon/index.html @@ -16,7 +16,7 @@ タグ「ISUCON」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/macos/index.html b/services/nuldoc/public/blog/tags/macos/index.html index 86182028..c58d8dd4 100644 --- a/services/nuldoc/public/blog/tags/macos/index.html +++ b/services/nuldoc/public/blog/tags/macos/index.html @@ -16,7 +16,7 @@ タグ「macOS」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/mncore-challenge/index.html b/services/nuldoc/public/blog/tags/mncore-challenge/index.html index be2dff30..bdf21530 100644 --- a/services/nuldoc/public/blog/tags/mncore-challenge/index.html +++ b/services/nuldoc/public/blog/tags/mncore-challenge/index.html @@ -16,7 +16,7 @@ タグ「MN-Core Challenge」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/neovim/index.html b/services/nuldoc/public/blog/tags/neovim/index.html index 8975379c..1e8513e8 100644 --- a/services/nuldoc/public/blog/tags/neovim/index.html +++ b/services/nuldoc/public/blog/tags/neovim/index.html @@ -16,7 +16,7 @@ タグ「Neovim」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/note-to-self/index.html b/services/nuldoc/public/blog/tags/note-to-self/index.html index eb0b947c..f1651906 100644 --- a/services/nuldoc/public/blog/tags/note-to-self/index.html +++ b/services/nuldoc/public/blog/tags/note-to-self/index.html @@ -16,7 +16,7 @@ タグ「備忘録」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/ouj/index.html b/services/nuldoc/public/blog/tags/ouj/index.html index 8f17ecdd..7b430224 100644 --- a/services/nuldoc/public/blog/tags/ouj/index.html +++ b/services/nuldoc/public/blog/tags/ouj/index.html @@ -16,7 +16,7 @@ タグ「放送大学」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/perl/index.html b/services/nuldoc/public/blog/tags/perl/index.html index 1ef8343d..5f3476cc 100644 --- a/services/nuldoc/public/blog/tags/perl/index.html +++ b/services/nuldoc/public/blog/tags/perl/index.html @@ -16,7 +16,7 @@ タグ「Perl」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/php/index.html b/services/nuldoc/public/blog/tags/php/index.html index f18c30c3..dbc138b3 100644 --- a/services/nuldoc/public/blog/tags/php/index.html +++ b/services/nuldoc/public/blog/tags/php/index.html @@ -16,7 +16,7 @@ タグ「PHP」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phpcon-nagoya/index.html b/services/nuldoc/public/blog/tags/phpcon-nagoya/index.html index c2796bba..843bb31f 100644 --- a/services/nuldoc/public/blog/tags/phpcon-nagoya/index.html +++ b/services/nuldoc/public/blog/tags/phpcon-nagoya/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス名古屋」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phpcon-odawara/index.html b/services/nuldoc/public/blog/tags/phpcon-odawara/index.html index d52f5526..14690029 100644 --- a/services/nuldoc/public/blog/tags/phpcon-odawara/index.html +++ b/services/nuldoc/public/blog/tags/phpcon-odawara/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス小田原」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phpconfuk/index.html b/services/nuldoc/public/blog/tags/phpconfuk/index.html index cf6989a3..73ab67db 100644 --- a/services/nuldoc/public/blog/tags/phpconfuk/index.html +++ b/services/nuldoc/public/blog/tags/phpconfuk/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス福岡」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phpconkagawa/index.html b/services/nuldoc/public/blog/tags/phpconkagawa/index.html index acf6dc85..1d552c60 100644 --- a/services/nuldoc/public/blog/tags/phpconkagawa/index.html +++ b/services/nuldoc/public/blog/tags/phpconkagawa/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス香川」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phpconokinawa/index.html b/services/nuldoc/public/blog/tags/phpconokinawa/index.html index cd11d6b1..a8075022 100644 --- a/services/nuldoc/public/blog/tags/phpconokinawa/index.html +++ b/services/nuldoc/public/blog/tags/phpconokinawa/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス沖縄」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phperkaigi/index.html b/services/nuldoc/public/blog/tags/phperkaigi/index.html index de2fb902..d4d7cff3 100644 --- a/services/nuldoc/public/blog/tags/phperkaigi/index.html +++ b/services/nuldoc/public/blog/tags/phperkaigi/index.html @@ -16,7 +16,7 @@ タグ「PHPerKaigi」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/phpkansai/index.html b/services/nuldoc/public/blog/tags/phpkansai/index.html index 1763034e..090ad836 100644 --- a/services/nuldoc/public/blog/tags/phpkansai/index.html +++ b/services/nuldoc/public/blog/tags/phpkansai/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス関西」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/piet/index.html b/services/nuldoc/public/blog/tags/piet/index.html index 258bf828..0ae85b32 100644 --- a/services/nuldoc/public/blog/tags/piet/index.html +++ b/services/nuldoc/public/blog/tags/piet/index.html @@ -16,7 +16,7 @@ タグ「Piet」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/python/index.html b/services/nuldoc/public/blog/tags/python/index.html index 92fe0b0b..7ed42261 100644 --- a/services/nuldoc/public/blog/tags/python/index.html +++ b/services/nuldoc/public/blog/tags/python/index.html @@ -16,7 +16,7 @@ タグ「Python」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/ruby/index.html b/services/nuldoc/public/blog/tags/ruby/index.html index d25a40b4..fc98ef2e 100644 --- a/services/nuldoc/public/blog/tags/ruby/index.html +++ b/services/nuldoc/public/blog/tags/ruby/index.html @@ -16,7 +16,7 @@ タグ「Ruby」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/rubykaigi/index.html b/services/nuldoc/public/blog/tags/rubykaigi/index.html index d276f893..e08543e9 100644 --- a/services/nuldoc/public/blog/tags/rubykaigi/index.html +++ b/services/nuldoc/public/blog/tags/rubykaigi/index.html @@ -16,7 +16,7 @@ タグ「RubyKaigi」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/rust/index.html b/services/nuldoc/public/blog/tags/rust/index.html index b672ba6c..d4af32ec 100644 --- a/services/nuldoc/public/blog/tags/rust/index.html +++ b/services/nuldoc/public/blog/tags/rust/index.html @@ -16,7 +16,7 @@ タグ「Rust」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/scala/index.html b/services/nuldoc/public/blog/tags/scala/index.html index 4deaa800..e22ab835 100644 --- a/services/nuldoc/public/blog/tags/scala/index.html +++ b/services/nuldoc/public/blog/tags/scala/index.html @@ -16,7 +16,7 @@ タグ「Scala」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/scalamatsuri/index.html b/services/nuldoc/public/blog/tags/scalamatsuri/index.html index 996c01bb..abe68c1d 100644 --- a/services/nuldoc/public/blog/tags/scalamatsuri/index.html +++ b/services/nuldoc/public/blog/tags/scalamatsuri/index.html @@ -16,7 +16,7 @@ タグ「ScalaMatsuri」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/speedcubing/index.html b/services/nuldoc/public/blog/tags/speedcubing/index.html index 87311347..b8c258a6 100644 --- a/services/nuldoc/public/blog/tags/speedcubing/index.html +++ b/services/nuldoc/public/blog/tags/speedcubing/index.html @@ -16,7 +16,7 @@ タグ「ルービックキューブ」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/trick/index.html b/services/nuldoc/public/blog/tags/trick/index.html index 3ca51d83..86e9c550 100644 --- a/services/nuldoc/public/blog/tags/trick/index.html +++ b/services/nuldoc/public/blog/tags/trick/index.html @@ -16,7 +16,7 @@ タグ「TRICK」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/vim/index.html b/services/nuldoc/public/blog/tags/vim/index.html index cdfbdaf8..2a555ba7 100644 --- a/services/nuldoc/public/blog/tags/vim/index.html +++ b/services/nuldoc/public/blog/tags/vim/index.html @@ -16,7 +16,7 @@ タグ「Vim」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/wasm/index.html b/services/nuldoc/public/blog/tags/wasm/index.html index 4aaf7776..12b6bcc2 100644 --- a/services/nuldoc/public/blog/tags/wasm/index.html +++ b/services/nuldoc/public/blog/tags/wasm/index.html @@ -16,7 +16,7 @@ タグ「WebAssembly」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/wireguard/index.html b/services/nuldoc/public/blog/tags/wireguard/index.html index ce3ae1b0..26add2d3 100644 --- a/services/nuldoc/public/blog/tags/wireguard/index.html +++ b/services/nuldoc/public/blog/tags/wireguard/index.html @@ -16,7 +16,7 @@ タグ「WireGuard」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/yaml/index.html b/services/nuldoc/public/blog/tags/yaml/index.html index 4b097421..d936e92c 100644 --- a/services/nuldoc/public/blog/tags/yaml/index.html +++ b/services/nuldoc/public/blog/tags/yaml/index.html @@ -16,7 +16,7 @@ タグ「YAML」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/yapc/index.html b/services/nuldoc/public/blog/tags/yapc/index.html index 35a7e2a7..4f107163 100644 --- a/services/nuldoc/public/blog/tags/yapc/index.html +++ b/services/nuldoc/public/blog/tags/yapc/index.html @@ -16,7 +16,7 @@ タグ「YAPC」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/blog/tags/zsh/index.html b/services/nuldoc/public/blog/tags/zsh/index.html index fbd2cd91..ee2263b2 100644 --- a/services/nuldoc/public/blog/tags/zsh/index.html +++ b/services/nuldoc/public/blog/tags/zsh/index.html @@ -16,7 +16,7 @@ タグ「Zsh」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/services/nuldoc/public/default/404.html b/services/nuldoc/public/default/404.html index 466c34d4..0cc57490 100644 --- a/services/nuldoc/public/default/404.html +++ b/services/nuldoc/public/default/404.html @@ -14,7 +14,7 @@ Page Not Found|nsfisis.dev - +
diff --git a/services/nuldoc/public/default/index.html b/services/nuldoc/public/default/index.html index b5bc01e6..51088ef7 100644 --- a/services/nuldoc/public/default/index.html +++ b/services/nuldoc/public/default/index.html @@ -15,7 +15,7 @@ nsfisis.dev - +
diff --git a/services/nuldoc/public/default/style.css b/services/nuldoc/public/default/style.css index eba86a34..4090151a 100644 --- a/services/nuldoc/public/default/style.css +++ b/services/nuldoc/public/default/style.css @@ -181,18 +181,18 @@ code { font-size: 0.9rem; } -.shiki code { +.codeblock code { background-color: unset; padding: 0; } /* https://github.com/shikijs/shiki/issues/3 */ -.shiki code { +.codeblock.numbered code { counter-reset: codeblock-line-number; counter-increment: codeblock-line-number 0; } -.numbered .shiki code .line::before { +.codeblock.numbered code .codeblock-line::before { content: counter(codeblock-line-number); counter-increment: codeblock-line-number; width: 2rem; @@ -280,7 +280,7 @@ h1 { .admonition { background-color: #f5f5f5; - border: 1px solid #d1d1d1; + border: 1px solid #ddd; padding: 1rem 1.5rem; margin: 1rem 0; } @@ -335,8 +335,8 @@ img { gap: 1rem; margin: 2rem 0; padding: 1rem 0; - border-top: 1px solid #d1d1d1; - border-bottom: 1px solid #d1d1d1; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } .pagination-page { @@ -368,7 +368,7 @@ img { color: #fff; } -.pagination-elipsis { +.pagination-ellipsis { color: #999; } diff --git a/services/nuldoc/public/slides/404.html b/services/nuldoc/public/slides/404.html index 14a425ba..942c3508 100644 --- a/services/nuldoc/public/slides/404.html +++ b/services/nuldoc/public/slides/404.html @@ -14,7 +14,7 @@ Page Not Found|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/slides/2023-01-18/phpstudy-tokyo-148/index.html b/services/nuldoc/public/slides/slides/2023-01-18/phpstudy-tokyo-148/index.html index e854837b..434bc263 100644 --- a/services/nuldoc/public/slides/slides/2023-01-18/phpstudy-tokyo-148/index.html +++ b/services/nuldoc/public/slides/slides/2023-01-18/phpstudy-tokyo-148/index.html @@ -15,7 +15,7 @@ 明日のあなたの役に立たない PHP コーディング技法~polyglot~ (PHP 勉強会@東京 第148 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-02-15/phpstudy-tokyo-149/index.html b/services/nuldoc/public/slides/slides/2023-02-15/phpstudy-tokyo-149/index.html index 508f0688..cfa0c164 100644 --- a/services/nuldoc/public/slides/slides/2023-02-15/phpstudy-tokyo-149/index.html +++ b/services/nuldoc/public/slides/slides/2023-02-15/phpstudy-tokyo-149/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023 のトークン問題でボツにした問題を供養する (PHP 勉強会@東京 第149 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-03-15/phpstudy-tokyo-150/index.html b/services/nuldoc/public/slides/slides/2023-03-15/phpstudy-tokyo-150/index.html index 59736835..700d4b03 100644 --- a/services/nuldoc/public/slides/slides/2023-03-15/phpstudy-tokyo-150/index.html +++ b/services/nuldoc/public/slides/slides/2023-03-15/phpstudy-tokyo-150/index.html @@ -15,7 +15,7 @@ 明日のあなたの役に立たない PHP コーディング技法~細長い FizzBuzz を書く~ (PHP 勉強会@東京 第150 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-03-24/phperkaigi-2023/index.html b/services/nuldoc/public/slides/slides/2023-03-24/phperkaigi-2023/index.html index c813dd2a..5060156d 100644 --- a/services/nuldoc/public/slides/slides/2023-03-24/phperkaigi-2023/index.html +++ b/services/nuldoc/public/slides/slides/2023-03-24/phperkaigi-2023/index.html @@ -15,7 +15,7 @@ 詳説「参照」PHP の参照を完全に理解する (PHPerKaigi 2023)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-03-25/phperkaigi-2023-tokens/index.html b/services/nuldoc/public/slides/slides/2023-03-25/phperkaigi-2023-tokens/index.html index 43f8929c..84eaf51b 100644 --- a/services/nuldoc/public/slides/slides/2023-03-25/phperkaigi-2023-tokens/index.html +++ b/services/nuldoc/public/slides/slides/2023-03-25/phperkaigi-2023-tokens/index.html @@ -15,7 +15,7 @@ PHPer チャレンジ解説 (デジタルサーカス株式会社) (PHPerKaigi 2023)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-04-12/phpstudy-tokyo-151/index.html b/services/nuldoc/public/slides/slides/2023-04-12/phpstudy-tokyo-151/index.html index a7ce2545..8d5b1891 100644 --- a/services/nuldoc/public/slides/slides/2023-04-12/phpstudy-tokyo-151/index.html +++ b/services/nuldoc/public/slides/slides/2023-04-12/phpstudy-tokyo-151/index.html @@ -15,7 +15,7 @@ list でない array の末尾を探す (PHP 勉強会@東京 第151 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-06-21/phpstudy-tokyo-153/index.html b/services/nuldoc/public/slides/slides/2023-06-21/phpstudy-tokyo-153/index.html index 0d33d707..e20b28b6 100644 --- a/services/nuldoc/public/slides/slides/2023-06-21/phpstudy-tokyo-153/index.html +++ b/services/nuldoc/public/slides/slides/2023-06-21/phpstudy-tokyo-153/index.html @@ -15,7 +15,7 @@ テキストファイルの末尾には改行コードを入れよう (PHP 勉強会@東京 第153 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-06-23/phpconfuk-2023-eve/index.html b/services/nuldoc/public/slides/slides/2023-06-23/phpconfuk-2023-eve/index.html index c23fcc3b..a6350f9c 100644 --- a/services/nuldoc/public/slides/slides/2023-06-23/phpconfuk-2023-eve/index.html +++ b/services/nuldoc/public/slides/slides/2023-06-23/phpconfuk-2023-eve/index.html @@ -15,7 +15,7 @@ 巨大なコードベースへ突撃するために (PHP カンファレンス福岡 2023 前夜祭 (非公式))|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-07-26/phpstudy-tokyo-154/index.html b/services/nuldoc/public/slides/slides/2023-07-26/phpstudy-tokyo-154/index.html index a6e55e61..f7550d6d 100644 --- a/services/nuldoc/public/slides/slides/2023-07-26/phpstudy-tokyo-154/index.html +++ b/services/nuldoc/public/slides/slides/2023-07-26/phpstudy-tokyo-154/index.html @@ -15,7 +15,7 @@ 言語間で比較するエラーの通知と処理 (PHP 勉強会@東京 第154 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-08-24/phpstudy-tokyo-155/index.html b/services/nuldoc/public/slides/slides/2023-08-24/phpstudy-tokyo-155/index.html index a7b3ce47..b6443936 100644 --- a/services/nuldoc/public/slides/slides/2023-08-24/phpstudy-tokyo-155/index.html +++ b/services/nuldoc/public/slides/slides/2023-08-24/phpstudy-tokyo-155/index.html @@ -15,7 +15,7 @@ PHP 3.0 の処理系のソースを読んでみる (PHP 勉強会@東京 第155 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2023-10-25/phpstudy-tokyo-157/index.html b/services/nuldoc/public/slides/slides/2023-10-25/phpstudy-tokyo-157/index.html index f39aab18..4ef5bc14 100644 --- a/services/nuldoc/public/slides/slides/2023-10-25/phpstudy-tokyo-157/index.html +++ b/services/nuldoc/public/slides/slides/2023-10-25/phpstudy-tokyo-157/index.html @@ -15,7 +15,7 @@ PHP コードを隔離された環境で安全に動かす (on WebAssembly) (PHP 勉強会@東京 第157 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-01-24/phpstudy-tokyo-160/index.html b/services/nuldoc/public/slides/slides/2024-01-24/phpstudy-tokyo-160/index.html index e2f756c5..b98423a1 100644 --- a/services/nuldoc/public/slides/slides/2024-01-24/phpstudy-tokyo-160/index.html +++ b/services/nuldoc/public/slides/slides/2024-01-24/phpstudy-tokyo-160/index.html @@ -15,7 +15,7 @@ PHPStan の力で Algebraic Data Types を実現する (PHP 勉強会@東京 第160 回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-03-08/phperkaigi-2024/index.html b/services/nuldoc/public/slides/slides/2024-03-08/phperkaigi-2024/index.html index 91bf69e3..5e22750e 100644 --- a/services/nuldoc/public/slides/slides/2024-03-08/phperkaigi-2024/index.html +++ b/services/nuldoc/public/slides/slides/2024-03-08/phperkaigi-2024/index.html @@ -15,7 +15,7 @@ WebAssembly を理解する 〜VM の作成を通して〜 (PHPerKaigi 2024)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-03-15/ya8-2024/index.html b/services/nuldoc/public/slides/slides/2024-03-15/ya8-2024/index.html index 17a01670..2214afb6 100644 --- a/services/nuldoc/public/slides/slides/2024-03-15/ya8-2024/index.html +++ b/services/nuldoc/public/slides/slides/2024-03-15/ya8-2024/index.html @@ -15,7 +15,7 @@ CLI の PHP プログラムを限界まで高速化してみる (Ya8 2024)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-04-13/phpcon-odawara-2024/index.html b/services/nuldoc/public/slides/slides/2024-04-13/phpcon-odawara-2024/index.html index 42c7b62f..a2f125df 100644 --- a/services/nuldoc/public/slides/slides/2024-04-13/phpcon-odawara-2024/index.html +++ b/services/nuldoc/public/slides/slides/2024-04-13/phpcon-odawara-2024/index.html @@ -15,7 +15,7 @@ 来る新 JIT エンジンについて知った気になる (PHP カンファレンス小田原 2024)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-04-25/phpstudy-tokyo-163/index.html b/services/nuldoc/public/slides/slides/2024-04-25/phpstudy-tokyo-163/index.html index 654db9b4..f1ba2d66 100644 --- a/services/nuldoc/public/slides/slides/2024-04-25/phpstudy-tokyo-163/index.html +++ b/services/nuldoc/public/slides/slides/2024-04-25/phpstudy-tokyo-163/index.html @@ -15,7 +15,7 @@ Tracing JIT の発動条件 (PHP 勉強会@東京 第163回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-07-18/phpstudy-tokyo-166/index.html b/services/nuldoc/public/slides/slides/2024-07-18/phpstudy-tokyo-166/index.html index 569f1b97..1532ea37 100644 --- a/services/nuldoc/public/slides/slides/2024-07-18/phpstudy-tokyo-166/index.html +++ b/services/nuldoc/public/slides/slides/2024-07-18/phpstudy-tokyo-166/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2024 で発表した WebAssembly ランタイムのその後 (PHP 勉強会@東京 第166回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-10-30/phpstudy-tokyo-169/index.html b/services/nuldoc/public/slides/slides/2024-10-30/phpstudy-tokyo-169/index.html index 4ec2eeab..bff785ec 100644 --- a/services/nuldoc/public/slides/slides/2024-10-30/phpstudy-tokyo-169/index.html +++ b/services/nuldoc/public/slides/slides/2024-10-30/phpstudy-tokyo-169/index.html @@ -15,7 +15,7 @@ PHP で PHP を作る (縮小版) (PHP 勉強会@東京 第169回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2024-11-30/cohackpp/index.html b/services/nuldoc/public/slides/slides/2024-11-30/cohackpp/index.html index a80a5425..bdf9c0ef 100644 --- a/services/nuldoc/public/slides/slides/2024-11-30/cohackpp/index.html +++ b/services/nuldoc/public/slides/slides/2024-11-30/cohackpp/index.html @@ -15,7 +15,7 @@ プログラミングマナー講座 (紅白ぺぱ合戦)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2025-02-22/phpcon-nagoya-2025/index.html b/services/nuldoc/public/slides/slides/2025-02-22/phpcon-nagoya-2025/index.html index 4cb8d1b2..b741f78c 100644 --- a/services/nuldoc/public/slides/slides/2025-02-22/phpcon-nagoya-2025/index.html +++ b/services/nuldoc/public/slides/slides/2025-02-22/phpcon-nagoya-2025/index.html @@ -15,7 +15,7 @@ PHP 処理系の garbage collection を理解する~メモリはいつ解放されるのか~ (PHP カンファレンス名古屋 2025)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2025-03-23/phperkaigi-2025/index.html b/services/nuldoc/public/slides/slides/2025-03-23/phperkaigi-2025/index.html index 91bf1206..3d44230f 100644 --- a/services/nuldoc/public/slides/slides/2025-03-23/phperkaigi-2025/index.html +++ b/services/nuldoc/public/slides/slides/2025-03-23/phperkaigi-2025/index.html @@ -15,7 +15,7 @@ PHPで作るPHP~セルフホストできる言語処理系を作ろう~ (PHPerKaigi 2025)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2025-04-12/phpcon-odawara-2025/index.html b/services/nuldoc/public/slides/slides/2025-04-12/phpcon-odawara-2025/index.html index ee64611a..e93f60c3 100644 --- a/services/nuldoc/public/slides/slides/2025-04-12/phpcon-odawara-2025/index.html +++ b/services/nuldoc/public/slides/slides/2025-04-12/phpcon-odawara-2025/index.html @@ -15,7 +15,7 @@ PHP 8.x 時代のクラス設計(property promotion から property hooks まで) (PHP カンファレンス小田原 2025)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2025-07-26/techramen-25-conf/index.html b/services/nuldoc/public/slides/slides/2025-07-26/techramen-25-conf/index.html index a0c46ae7..4e4c3233 100644 --- a/services/nuldoc/public/slides/slides/2025-07-26/techramen-25-conf/index.html +++ b/services/nuldoc/public/slides/slides/2025-07-26/techramen-25-conf/index.html @@ -15,7 +15,7 @@ セルフホスト可能なCコンパイラを2000行弱で書く (TechRAMEN 2025 Conference)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2025-10-29/phpstudy-tokyo-180/index.html b/services/nuldoc/public/slides/slides/2025-10-29/phpstudy-tokyo-180/index.html index 20204766..b1f98b9d 100644 --- a/services/nuldoc/public/slides/slides/2025-10-29/phpstudy-tokyo-180/index.html +++ b/services/nuldoc/public/slides/slides/2025-10-29/phpstudy-tokyo-180/index.html @@ -15,7 +15,7 @@ 浮動小数点数の半開区間で単一値を指定する (PHP 勉強会@東京 第180回)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/2025-11-24/phpconkagawa-2025/index.html b/services/nuldoc/public/slides/slides/2025-11-24/phpconkagawa-2025/index.html index 8d5124e8..b61519e2 100644 --- a/services/nuldoc/public/slides/slides/2025-11-24/phpconkagawa-2025/index.html +++ b/services/nuldoc/public/slides/slides/2025-11-24/phpconkagawa-2025/index.html @@ -15,7 +15,7 @@ Pure PHP で作る簡易 HTTP サーバ (PHP カンファレンス香川 2025)|nsfisis’ slides - + diff --git a/services/nuldoc/public/slides/slides/index.html b/services/nuldoc/public/slides/slides/index.html index 868a8da4..8dfae6c1 100644 --- a/services/nuldoc/public/slides/slides/index.html +++ b/services/nuldoc/public/slides/slides/index.html @@ -15,7 +15,7 @@ スライド一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/style.css b/services/nuldoc/public/slides/style.css index eba86a34..4090151a 100644 --- a/services/nuldoc/public/slides/style.css +++ b/services/nuldoc/public/slides/style.css @@ -181,18 +181,18 @@ code { font-size: 0.9rem; } -.shiki code { +.codeblock code { background-color: unset; padding: 0; } /* https://github.com/shikijs/shiki/issues/3 */ -.shiki code { +.codeblock.numbered code { counter-reset: codeblock-line-number; counter-increment: codeblock-line-number 0; } -.numbered .shiki code .line::before { +.codeblock.numbered code .codeblock-line::before { content: counter(codeblock-line-number); counter-increment: codeblock-line-number; width: 2rem; @@ -280,7 +280,7 @@ h1 { .admonition { background-color: #f5f5f5; - border: 1px solid #d1d1d1; + border: 1px solid #ddd; padding: 1rem 1.5rem; margin: 1rem 0; } @@ -335,8 +335,8 @@ img { gap: 1rem; margin: 2rem 0; padding: 1rem 0; - border-top: 1px solid #d1d1d1; - border-bottom: 1px solid #d1d1d1; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } .pagination-page { @@ -368,7 +368,7 @@ img { color: #fff; } -.pagination-elipsis { +.pagination-ellipsis { color: #999; } diff --git a/services/nuldoc/public/slides/tags/c/index.html b/services/nuldoc/public/slides/tags/c/index.html index 82a90137..43946dd0 100644 --- a/services/nuldoc/public/slides/tags/c/index.html +++ b/services/nuldoc/public/slides/tags/c/index.html @@ -16,7 +16,7 @@ タグ「C」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/cohackpp/index.html b/services/nuldoc/public/slides/tags/cohackpp/index.html index 05fc6323..b882a93d 100644 --- a/services/nuldoc/public/slides/tags/cohackpp/index.html +++ b/services/nuldoc/public/slides/tags/cohackpp/index.html @@ -16,7 +16,7 @@ タグ「紅白ぺぱ合戦」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/conference/index.html b/services/nuldoc/public/slides/tags/conference/index.html index f1785903..40197f63 100644 --- a/services/nuldoc/public/slides/tags/conference/index.html +++ b/services/nuldoc/public/slides/tags/conference/index.html @@ -16,7 +16,7 @@ タグ「カンファレンス」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/index.html b/services/nuldoc/public/slides/tags/index.html index 7572f17c..9335a6de 100644 --- a/services/nuldoc/public/slides/tags/index.html +++ b/services/nuldoc/public/slides/tags/index.html @@ -14,7 +14,7 @@ タグ一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/php/index.html b/services/nuldoc/public/slides/tags/php/index.html index 2efade95..a5ab9e5d 100644 --- a/services/nuldoc/public/slides/tags/php/index.html +++ b/services/nuldoc/public/slides/tags/php/index.html @@ -16,7 +16,7 @@ タグ「PHP」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/phpcon-nagoya/index.html b/services/nuldoc/public/slides/tags/phpcon-nagoya/index.html index 4e869002..8ba14ed3 100644 --- a/services/nuldoc/public/slides/tags/phpcon-nagoya/index.html +++ b/services/nuldoc/public/slides/tags/phpcon-nagoya/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス名古屋」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/phpcon-odawara/index.html b/services/nuldoc/public/slides/tags/phpcon-odawara/index.html index 0dca6844..a2df445d 100644 --- a/services/nuldoc/public/slides/tags/phpcon-odawara/index.html +++ b/services/nuldoc/public/slides/tags/phpcon-odawara/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス小田原」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/phpconfuk/index.html b/services/nuldoc/public/slides/tags/phpconfuk/index.html index 3a4eea6b..0b286178 100644 --- a/services/nuldoc/public/slides/tags/phpconfuk/index.html +++ b/services/nuldoc/public/slides/tags/phpconfuk/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス福岡」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/phpconkagawa/index.html b/services/nuldoc/public/slides/tags/phpconkagawa/index.html index e333165b..0c2a87f4 100644 --- a/services/nuldoc/public/slides/tags/phpconkagawa/index.html +++ b/services/nuldoc/public/slides/tags/phpconkagawa/index.html @@ -16,7 +16,7 @@ タグ「PHP カンファレンス香川」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/phperkaigi/index.html b/services/nuldoc/public/slides/tags/phperkaigi/index.html index e8801ce8..54306421 100644 --- a/services/nuldoc/public/slides/tags/phperkaigi/index.html +++ b/services/nuldoc/public/slides/tags/phperkaigi/index.html @@ -16,7 +16,7 @@ タグ「PHPerKaigi」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/phpstudy-tokyo/index.html b/services/nuldoc/public/slides/tags/phpstudy-tokyo/index.html index 116640f0..0d102e79 100644 --- a/services/nuldoc/public/slides/tags/phpstudy-tokyo/index.html +++ b/services/nuldoc/public/slides/tags/phpstudy-tokyo/index.html @@ -16,7 +16,7 @@ タグ「PHP 勉強会@東京」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/techramen/index.html b/services/nuldoc/public/slides/tags/techramen/index.html index 7e5ef492..157484ca 100644 --- a/services/nuldoc/public/slides/tags/techramen/index.html +++ b/services/nuldoc/public/slides/tags/techramen/index.html @@ -16,7 +16,7 @@ タグ「TechRAMEN」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/wasm/index.html b/services/nuldoc/public/slides/tags/wasm/index.html index cf981042..ebc40225 100644 --- a/services/nuldoc/public/slides/tags/wasm/index.html +++ b/services/nuldoc/public/slides/tags/wasm/index.html @@ -16,7 +16,7 @@ タグ「WebAssembly」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/public/slides/tags/ya8/index.html b/services/nuldoc/public/slides/tags/ya8/index.html index 6b71674a..71fb0bc3 100644 --- a/services/nuldoc/public/slides/tags/ya8/index.html +++ b/services/nuldoc/public/slides/tags/ya8/index.html @@ -16,7 +16,7 @@ タグ「Ya8」一覧|nsfisis’ slides - +
diff --git a/services/nuldoc/static/_all/style.css b/services/nuldoc/static/_all/style.css index fff94cac..4090151a 100644 --- a/services/nuldoc/static/_all/style.css +++ b/services/nuldoc/static/_all/style.css @@ -181,18 +181,18 @@ code { font-size: 0.9rem; } -.shiki code { +.codeblock code { background-color: unset; padding: 0; } /* https://github.com/shikijs/shiki/issues/3 */ -.shiki code { +.codeblock.numbered code { counter-reset: codeblock-line-number; counter-increment: codeblock-line-number 0; } -.numbered .shiki code .line::before { +.codeblock.numbered code .codeblock-line::before { content: counter(codeblock-line-number); counter-increment: codeblock-line-number; width: 2rem; -- cgit v1.3-1-g0d28